红黑树
- 这里偷个懒,没有去自己实现一个红黑树。 具体可以参考这个视频,讲的概念很清晰。
- https://www.bilibili.com/video/BV1mU4y1w7XM?from=search&seid=7284699508084696342&spm_id_from=333.337.0.0
- 下面的红黑树解法中,官方的 代码帮我们 手撕了一个红黑树。 可以参考
- (看完这道题后,可以看下这道题,这道题难度稍微低些,理解下过程):https://gitee.com/douma_edu/douma_algo_training_camp/issues/I4KWJK
力扣456号算法题132模式
- 上问题:
解法一: 暴力破解
-
通过三层for循环遍历,找到满足条件的三个数。返回true,否则返回false
-
时间复杂度 O(n^3)
// 暴力破解, 找到i j 找 k func find132pattern(nums []int) bool { n := len(nums) for i := 0; i < n; i++ { for j := i + 1; j < n; j++ { if nums[i] >= nums[j] { continue } for k := j + 1; k < n; k++ { if nums[i] < nums[j] && nums[j] > nums[k] && nums[k] > nums[i] { fmt.Println(nums[i], nums[j], nums[k]) return true } } } } return false }
解法二:暴力破解优化
-
枚举j,只要当前位置前面有小于该值的,就可以作为 j,那么通过一个 变量 numI,记录当前位置之前最小的数字,用来判断,当前位置能不能作为 j,
-
找到 j 后,再线性遍历后序节点,查找合适的 k
-
时间复杂度O(n^2)
// 暴力破解优化, 枚举 j // 因为只要找到一次,就能确定true。 所以,我们选择 当前 j位置前面最小的 一个值 作为 i // 那么 满足,132模式的可能性最大。时间复杂度降为O(n^2) func find132pattern(nums []int) bool { n := len(nums) // 记录 j 位置前面最小的值,当做i // 因为 j 不可能是 第一个或者最后一个元素,numI初始化为 nums[0] numI := nums[0] for j := 1; j < n - 1; j++ { // i 为 当前 j 前面最小的元素 numI = min(numI, nums[j - 1]) // 循环找 合适的k for k := j + 1; k < n; k++ { if nums[j] > nums[k] && nums[k] > numI { fmt.Println(numI, nums[j], nums[k]) return true } } } return false } func min(a, b int) int { if a > b { return b } return a }
解法三:红黑树优化查找速度
-
在二的基础上,优化查找 k的速度。 上面的性能慢在了,查找k的时候还是使用的线性遍历! 我们可以使用红黑树进行优化, logN 的速度,快速查找出 后面是否存在 k。
-
python代码: 比较简洁看清过程,优化了查找k的部分:(也可以去看java实现,treemap)
from sortedcontainers import SortedList class Solution: def find132pattern(self, nums: list) -> bool: """ # 对上面的优化,因为上面对 k的查找还是线性的一个一个找,那么我们需要改变,利用平衡树,或者红黑树 # 进而快速进行查找j 后面位置满足条件的 k """ n = len(nums) if n < 3: return False # 左侧最小值 left_min = nums[0] # 右侧所有元素 right_all = SortedList(nums[2:]) for j in range(1, n - 1): if left_min < nums[j]: index = right_all.bisect_right(left_min) if index < len(right_all) and right_all[index] < nums[j]: return True left_min = min(left_min, nums[j]) # 要删除掉, 因为 当前位置 为下一次的 j, 那么他就不可能再作为 k,不能继续放到查找树中查找了 right_all.remove(nums[j + 1]) return False
-
下面是go语言代码,超级长!! 因为官方代码中实现了下红黑树! go数据结构中,是没有有序的 map结构的!太牛了
import "math/rand" type node struct { ch [2]*node priority int val int cnt int } func (o *node) cmp(b int) int { switch { case b < o.val: return 0 case b > o.val: return 1 default: return -1 } } func (o *node) rotate(d int) *node { x := o.ch[d^1] o.ch[d^1] = x.ch[d] x.ch[d] = o return x } type treap struct { root *node } func (t *treap) _put(o *node, val int) *node { if o == nil { return &node{priority: rand.Int(), val: val, cnt: 1} } if d := o.cmp(val); d >= 0 { o.ch[d] = t._put(o.ch[d], val) if o.ch[d].priority > o.priority { o = o.rotate(d ^ 1) } } else { o.cnt++ } return o } func (t *treap) put(val int) { t.root = t._put(t.root, val) } func (t *treap) _delete(o *node, val int) *node { if o == nil { return nil } if d := o.cmp(val); d >= 0 { o.ch[d] = t._delete(o.ch[d], val) return o } if o.cnt > 1 { o.cnt-- return o } if o.ch[1] == nil { return o.ch[0] } if o.ch[0] == nil { return o.ch[1] } d := 0 if o.ch[0].priority > o.ch[1].priority { d = 1 } o = o.rotate(d) o.ch[d] = t._delete(o.ch[d], val) return o } func (t *treap) delete(val int) { t.root = t._delete(t.root, val) } func (t *treap) upperBound(val int) (ub *node) { for o := t.root; o != nil; { if o.cmp(val) == 0 { ub = o o = o.ch[0] } else { o = o.ch[1] } } return } func find132pattern(nums []int) bool { n := len(nums) if n < 3 { return false } leftMin := nums[0] rights := &treap{} for _, v := range nums[2:] { rights.put(v) } for j := 1; j < n-1; j++ { if nums[j] > leftMin { ub := rights.upperBound(leftMin) if ub != nil && ub.val < nums[j] { return true } } else { leftMin = nums[j] } rights.delete(nums[j+1]) } return false }
解法四,单调栈优化,并应用于流式
-
时间复杂度O(n)
-
我自己的写法。从左向右进行遍历。解析都在代码上方注释清晰。
package greedy // 这道题有从暴力,到暴力优化再到 红黑树优化的过程 // 这里我直接给我我写了好久的栈优化的 // 使用一个 “形式的单调递减栈”。从头开始遍历判断,可以用于流试结构!O(n)的时间复杂度。 // 其中贪心的地方在于, 判断谁更可能称为 j 时,优先选择后面比栈顶更大的 K, 进行pop栈顶元素。 // 还有就是找 k 的时候,使用前面最小的 numI /* 已知 132 模式, nums[i] < nums[k] < nums[j], 从前到后,顺序遍历每一元素 1. 能满足是 k 的,就 "可能" 满足它也是个 j 2. 所以我们需要先确定一个位置是否是 k 这个很好做,维护当前位置之前最小的 i 值(numI),只要大于这个就可以是 k 3. 如果当前位置是 k, 那么它可能是 j: 维护一个 "有条件的递减栈"! (1)如果栈空,直接将当前k压入栈中(它可能是j) (2)如果栈非空,那当前的 k对应的 nums[k] 如果小于栈顶的 k,就可能满足132: 如果 它们对应的 i 值是相等的,一定满足 132 ,i j k 顺序对。return true。栈顶的k能当做 j 不相等,一定有 栈顶的 i 值 大于 当前k的i值, 比较当前 k值 和栈顶的k 对应的i值 如果 k > 栈顶的i 值,满足 132 ,return true 否则,说明 当前的位置是 [栈顶, 当前k的i值, 当前k] 变成了这个样子,j i k 了不满足。 此时 满足递减栈的条件: 当前k小于栈顶元素, 且 i值不相等。进栈。 (3)如果栈非空,且当前k 大于 栈顶元素 那么栈顶的 i 一定等于 或者小于 当前k的i,循环判断,依次剔除掉栈顶的值,最后当前k入栈 因为进栈的 k越大,后续才更容易出现比它小的,当前k,才能以变为 j (4)最后没有结果,就返回 false了 */ func find132pattern(nums []int) bool { n := len(nums) // 定义栈,[值, numsI] 栈中元素表示的是,当前的元素 和 对应的nums[i]值 stack := make([][2]int, 0) // 定义当前位置最小 nums[i] numI := nums[0] // 循环开始找满足条件的 nums[k], 或nums[j],因为下标1-n, 满足j 和 k的位置 for k := 1; k < n; k++ { numI = min(numI, nums[k-1]) // 1. 当前元素小于 numI 不能作为 k,直接退出 if nums[k] < numI { continue } // 4. 如果大于, pop栈顶元素: 这里使用大于等于:[40,50,25,35,15,35,20] // 这个例子中的 35, 后买的35满足条件吗,但是如果没有等于的话,不会删除掉栈中前面的35,就得不到正确结果 // 这个例子,[80,84,70,80,60,70,50,60,82,85], 最终后面的82 要淘汰删除多组栈顶,才能和 84 进行比较,需要用 for。 for len(stack) > 0 && nums[k] >= stack[len(stack)-1][0] { stack = stack[:len(stack)-1] } // 3. nums[k] 小于当前的栈顶元素,可能满足 132 if len(stack) > 0 && nums[k] < stack[len(stack)-1][0] { // nums[k] 小于栈顶, 大于栈顶的 numI,满足 if nums[k] > stack[len(stack)-1][1] { fmt.Println(stack[len(stack)-1][1],stack[len(stack)-1][0], nums[k]) return true // 否则说明 它俩的 numsI 值不相同,进栈 } else { stack = append(stack, [2]int{nums[k], numI}) } } // 2. 栈空直接 k值进栈 if len(stack) == 0 { stack = append(stack, [2]int{nums[k], numI}) continue } } return false } func min(a, b int) int { if a > b { return b } return a } // 优化上面的代码结构,更简洁 func find132pattern(nums []int) bool { n := len(nums) // 定义栈,[值, numsI] 栈中元素表示的是,当前的元素 和 对应的nums[i]值 stack := make([][2]int, 0) // 定义当前位置最小 nums[i] numI := nums[0] // 循环开始找满足条件的 nums[k], 或nums[j],因为下标1-n, 满足j 和 k的位置 for k := 1; k < n; k++ { numI = min(numI, nums[k-1]) // 1. 当前元素小于 numI 不能作为 k,直接退出 if nums[k] < numI { continue } // 4. 如果大于, pop栈顶元素: 这里使用大于等于:[40,50,25,35,15,35,20] // 这个例子中的 35, 后买的35满足条件吗,但是如果没有等于的话,不会删除掉栈中前面的35,就得不到正确结果 // 这个例子,[80,84,70,80,60,70,50,60,82,85], 最终后面的82 要淘汰删除多组栈顶,才能和 84 进行比较,需要用 for。 for len(stack) > 0 && nums[k] >= stack[len(stack)-1][0] { stack = stack[:len(stack)-1] } // 3. nums[k] 小于当前的栈顶元素,可能满足 132 if len(stack) > 0 && nums[k] < stack[len(stack)-1][0] { // nums[k] 小于栈顶, 大于栈顶的 numI,满足 if nums[k] > stack[len(stack)-1][1] { fmt.Println(stack[len(stack)-1][1],stack[len(stack)-1][0], nums[k]) return true } } // 其他情况,全部进栈 stack = append(stack, [2]int{nums[k], numI}) } return false } func min(a, b int) int { if a > b { return b } return a }