Leetcode.456 132模式

题目

给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。

如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。

示例 1:
输入:nums = [1,2,3,4]
输出:false
解释:序列中不存在 132 模式的子序列。
示例 2:

输入:nums = [3,1,4,2]
输出:true
解释:序列中有 1 个 132 模式的子序列: [1, 4, 2] 。
示例 3:

输入:nums = [-1,3,2,0]
输出:true
解释:序列中有 3 个 132 模式的的子序列:[-1, 3, 2]、[-1, 3, 0] 和 [-1, 2, 0] 。

枚举 “3”模式

枚举 3是容易想到并且也是最容易实现的。由于 3 是模式中的最大值,并且其出现在 1 和 2的中间,因此我们只需要从左到右枚举 3的下标 j,那么:

由于 1 是模式中的最小值,因此我们在枚举 j的同时,维护数组 a 中左侧元素 a [ 0.. j − 1 ] a[0..j-1] a[0..j1]的最小值,即为 1 对应的元素 a [ i ] a[i] a[i]。需要注意的是,只有 a [ i ] < a [ j ] a[i] < a[j] a[i]<a[j]时, a [ i ] a[i] a[i]才能作为 11 对应的元素;

由于 2 是模式中的次小值,因此我们可以使用一个有序集合(例如平衡树)维护数组 a 中右侧元素 a [ j + 1.. n − 1 ] a[j+1..n-1] a[j+1..n1]中的所有值。当我们确定了 a [ i ] a[i] a[i] a [ j ] a[j] a[j]之后,只需要在有序集合中查询严格比 a [ i ] a[i] a[i]大的那个最小的元素,即为 a [ k ] a[k] a[k]。需要注意的是,只有 a [ k ] < a [ j ] a[k] < a[j] a[k]<a[j]时, a [ k ] a[k] a[k]才能作为 3 对应的元素。

枚举策略

  1. 建立树状数组(升序)的哈希存储,结构为(value, count)。设置第0为元素为 m i n min min
  2. 从数组第1位(默认第0位)开始循环,
  • 建立树状数组(升序)的哈希存储,结构为(value, count)。
  • 从数组第1位(起始第0位)开始循环,
    • 如果当前元素 c u r cur cur m i n min min
      • 去树状数组找比 c u r cur cur大的最小元素 c u r + cur+ cur+,如果存在,则证明 m i n min min c u r cur cur c u r + cur+ cur+正好构成132模式
    • 否则,比较 c u r cur cur进行更新 m i n min min
    • 将哈希计数中的 c u r cur cur次数进行减一
      • 如果 c u r cur cur被到0说明右侧不再出现 c u r cur cur值的数字,将它从树状哈希删除。

代码

class Solution {
    public boolean find132pattern(int[] nums) {
        int n = nums.length;
        if (n < 3) {
            return false;
        }

        // 左侧最小值
        int leftMin = nums[0];
        // 右侧所有元素
        TreeMap<Integer, Integer> rightAll = new TreeMap<Integer, Integer>();

        for (int k = 2; k < n; ++k) {
            rightAll.put(nums[k], rightAll.getOrDefault(nums[k], 0) + 1);
        }
        // Iterator iterator = rightAll.entrySet().iterator();   迭代查看树状数组结构为 val : count
        // while (iterator.hasNext()) {
        //     System.out.println(iterator.next());
        // }
        for (int j = 1; j < n - 1; ++j) {
            if (leftMin < nums[j]) {
                Integer next = rightAll.ceilingKey(leftMin + 1);
                if (next != null && next < nums[j]) {
                    return true;
                }
            }
            leftMin = Math.min(leftMin, nums[j]);
            rightAll.put(nums[j + 1], rightAll.get(nums[j + 1]) - 1);
            if (rightAll.get(nums[j + 1]) == 0) {
                rightAll.remove(nums[j + 1]);
            }
        }

        return false;
    }
}

方法二:枚举 1

思路与算法

我们使用一种数据结构维护所有遍历过的元素,它们作为 2 的候选元素。每当我们遍历到一个新的元素时,就将其加入数据结构中;

在遍历到一个新的元素的同时,我们可以考虑其是否可以作为 3。如果它作为 3,那么数据结构中所有严格小于它的元素都可以作为 2,我们将这些元素全部从数据结构中移除,并且使用一个变量维护所有被移除的元素的最大值。这些被移除的元素都是可以真正作为 2 的,并且元素的值越大,那么我们之后找到 1 的机会也就越大。

那么这个「数据结构」是什么样的数据结构呢?我们尝试提取出它进行的操作:

  • 它需要支持添加一个元素

  • 它需要支持移除所有严格小于给定阈值的所有元素

上面两步操作是「依次进行」的,即我们先用给定的阈值移除元素,再将该阈值加入数据结构中。
这就是「单调栈」。在单调栈中,从栈底到栈顶的元素是严格单调递减的。当给定阈值 x 时,我们只需要不断地弹出栈顶的元素,直到栈为空或者 x 严格小于栈顶元素。此时我们再将 xx 入栈,这样就维护了栈的单调性。

每一次比较与弹栈,其实是旧的3和新的3的对比。如果栈中元素依次是2345当遇见新元素7时,2345会依次被弹出,作为7的备选值,形成3-2模式。栈里维护的是最高点3,用更大的3来更替旧的3,并把它最大的次于栈顶3的元素作为 m a x k max_k maxk,它也是最后一个被弹出的元素。前例中的5会作为 m a x k max_k maxk,当下一次遇见比5小的值时候,可以直接判断存在1-3-2模式

因此,我们可以使用单调栈作为维护 2 的数据结构,并给出下面的算法:

我们用单调栈维护所有可以作为 2 的候选元素。初始时,单调栈中只有唯一的元素 a [ n − 1 ] a[n−1] a[n1]。我们还需要使用一个变量 m a x k max_k maxk记录所有可以真正作为 2 的元素的最大值;

随后我们从 n − 2 n-2 n2开始从右到左枚举元素 a [ i ] a[i] a[i]

  • 首先我们判断 a [ i ] a[i] a[i] 是否可以作为 1。如果 a[i] < m a x k max_k maxk,那么它就可以作为 1,我们就找到了一组满足 132模式的三元组;

  • 随后我们判断 a [ i ] a[i] a[i]是否可以作为 3,以此找出哪些可以真正作为 2 的元素。我们将 a [ i ] a[i] a[i]不断地与单调栈栈顶的元素进行比较,如果 a [ i ] a[i] a[i]较大,那么栈顶元素可以真正作为 2,将其弹出并更新 m a x k max_k maxk

最后我们将 a [ i ] a[i] a[i]作为 2 的候选元素放入单调栈中。这里可以进行一个优化,即如果 a [ i ] ≤ m a x k a[i]≤max_k a[i]maxk,那么我们也没有必要将 a [ i ] a[i] a[i]放入栈中,因为即使它在未来被弹出,也不会将 m a x k max_k maxk更新为更大的值。

在枚举完所有的元素后,如果仍未找到满足 132 模式的三元组,那就说明其不存在。

class Solution {
    public boolean find132pattern(int[] nums) {
        int n = nums.length;
        Deque<Integer> candidateK = new LinkedList<Integer>();
        candidateK.push(nums[n - 1]);
        int maxK = Integer.MIN_VALUE;

        for (int i = n - 2; i >= 0; --i) {
            if (nums[i] < maxK) {
                return true;
            }
            while (!candidateK.isEmpty() && nums[i] > candidateK.peek()) {
                maxK = candidateK.pop();
            }
            if (nums[i] > maxK) {
                candidateK.push(nums[i]);
            }
        }

        return false;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值