算法编程题-可能的二分法、删除给定值的叶子节点、设计一个支持增量操作的栈
摘要:本文将对三道LeetCode原题进行介绍,分别是可能的二分法、删除给定值的叶子节点和设计一个支持增量操作的栈。首先会简单描述每道题,然后是给出一个思路,接着给出golang语言的实现,最后进行相应的复杂度分析。
关键词:LeetCode,算法,数据结构,golang,复杂度分析
可能的二分法
原题描述
有一群人,现在要分为两组,但是由于有些人相互之间有过节,所以不能分在同一个组,给出所有有过节的人,求是否能将这群人正常分为两组。
思路简述
其实这道题目和之前介绍过的二分图是一模一样的题目,只需要将有过节的人整理成图的形式,在图中两个节点有边相连,则表示这两个相互之间看不顺眼。
代码实现
func possibleBipartition(n int, dislikes [][]int) bool {
graph := make([][]int, n)
for i := 0; i < n; i++ {
graph[i] = make([]int, 0, n)
}
for _, dislike := range dislikes {
a, b := dislike[0], dislike[1]
graph[a - 1] = append(graph[a-1], b - 1)
graph[b - 1] = append(graph[b-1], a - 1)
}
return isBipartite(graph)
}
type NodeColor int
const (
NodeNoColor NodeColor = iota - 1
NodeRed
NodeGreen
)
func isBipartite(graph [][]int) bool {
n := len(graph)
colors := make([]NodeColor, n)
for i := 0; i < n; i++ {
colors[i] = NodeNoColor
}
ans := true
var dfs func(cur, parent int, color NodeColor)
dfs = func(cur, parent int, color NodeColor) {
if colors[cur] != NodeNoColor {
if colors[cur] != color {
ans = false
}
return
}
colors[cur] = color
for _, neightbor := range graph[cur] {
if neightbor == parent {
continue
}
dfs(neightbor, cur, color ^ NodeGreen) // 通过异或操作取相反的颜色
if !ans {
return
}
}
}
for i := 0; i < n; i++ {
if colors[i] == NodeNoColor {
dfs(i, -1, NodeRed)
}
}
return ans
}
LeetCode运行截图如下:

复杂度分析
- 时间复杂度: O ( m + n ) O(m+n) O(m+n),其中m为相互之间看不顺眼的数量,n为所有人的数量
- 空间复杂度: O ( m + n ) O(m+n) O(m+n)
删除给定值的叶子节点
原题描述
在一颗二叉树中,删除所有值为给定值的叶子节点,注意一个原本不是叶子节点的树也有可能在删除叶子节点的过程中变成叶子节点。
思路简述
考虑到刚开始的非叶子节点可能会在删除叶子节点的过程中变成叶子节点,并且这些节点一定是在所有子节点被删除后才有可能发生变化,所以可能对树进行后序遍历,保证父节点一定是在遍历完所有子节点后才会被处理。
在实现上,可以构造一个伪根节点,简化代码实现。
代码实现
func removeLeafNodes(root *TreeNode, target int) *TreeNode {
// 用后序遍历, 保证父节点最后在所有子节点被遍历完后才被遍历
// 实现上使用一个伪根节点,简化代码实现
var pseudoRoot TreeNode
pseudoRoot.Left = root
var postOrder func(curNode, parent *TreeNode)
postOrder = func(curNode, parent *TreeNode) {
if curNode.Left != nil {
postOrder(curNode.Left, curNode)
}
if curNode.Right != nil {
postOrder(curNode.Right, curNode)
}
if curNode.Left == nil && curNode.Right == nil && curNode.Val == target {
if parent.Left == curNode {
parent.Left = nil
} else if parent.Right == curNode {
parent.Right = nil
}
}
}
postOrder(root, &pseudoRoot)
return pseudoRoot.Left
}
LeetCode运行截图如下:

复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),n为树中节点的数量,因为在后序遍历过程中,每一个节点只会被遍历一次
- 空间复杂度: O ( 1 ) O(1) O(1)
设计一个支持增量操作的栈
原题描述
要实现一种支持增量操作的栈,即在常规的栈的两种操作(push,pop)之外,还要增加一种incr操作,参数为k和v,表示从栈底的k个元素的值都要增加v。
思路简述
当然可以在常规栈的实现上,在需要增量操作的时候,栈底在k个元素之中的每一个元素都去增加,但这样无疑时间复杂度是比较恶劣的。考虑到每一次只会在栈顶方向上push或者pop,那么可以考虑为栈的每一个位置增加一个状态,表示从这里一直到栈底的每一个元素的真实值是在表面值的基础上加上这个状态,这样增量操作的时间复杂度就变成 O ( 1 ) O(1) O(1)了。然后在pop的时候,需要将被pop的位置的这个状态传递给栈中的下一个元素。
代码实现
type CustomStack struct {
elems [][]int // [0]位置保存值,[1]保存从这里一直到栈底的变化量
index int // 栈顶指针
maxSize int
}
func Constructor(maxSize int) CustomStack {
elems := make([][]int, maxSize)
for i := 0; i < maxSize; i++ {
elems[i] = make([]int, 2)
}
return CustomStack{index: -1, elems: elems, maxSize: maxSize}
}
func (this *CustomStack) Push(x int) {
if this.index + 1 == this.maxSize {
return
}
this.index++
this.elems[this.index][0] = x
}
func (this *CustomStack) Pop() int {
if this.index == -1 {
return -1
}
delta := this.elems[this.index][1]
this.elems[this.index][1] = 0
ret := this.elems[this.index][0] + delta
this.index--
if delta != 0 && this.index != -1 {
this.elems[this.index][1] += delta
}
return ret
}
func (this *CustomStack) Increment(k int, val int) {
if this.index == -1 {
return
}
size := this.index + 1
this.elems[min(k - 1, size - 1)][1] += val
}
LeetCode运行截图如下:

复杂度分析
- 时间复杂度:增量栈的每一种操作的时间复杂度都是 O ( 1 ) O(1) O(1)
685

被折叠的 条评论
为什么被折叠?



