红黑树的应用与力扣456号算法题132模式详解,go语言解题

红黑树

力扣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
    }
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值