【LC75】No334. 递增的三元子序列

题目描述

给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。

如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false

进阶:你能实现时间复杂度为 O(n) ,空间复杂度为 O(1) 的解决方案吗?

提示:

  • 1 <= nums.length <= 5 * 105

  • -231 <= nums[i] <= 231 - 1

示例

示例 1:

输入:nums = [1,2,3,4,5]
输出:true
解释:任何 i < j < k 的三元组都满足题意

示例 2:

输入:nums = [5,4,3,2,1]
输出:false
解释:不存在满足题意的三元组

示例 3:

输入:nums = [2,1,5,0,4,6]
输出:true
解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6

链接

https://leetcode.cn/problems/increasing-triplet-subsequence/description/?envType=study-plan-v2&envId=leetcode-75

思路

依旧是很好理解的题目,没有背景设置,所以中译中环节省略。

又是不会做题的绝望的一天orz

人类一思考,上帝就发笑.jpg

我们可以看到提示中,数组的长度非常大,所以如果直接使用暴力寻找的嵌套循环,结果必然超时。

因此,我们需要分解问题进行处理。

解法一:前缀最小数组和后缀最大数组

题目给定了三元子序列,这是一个非常好的简化解题的设定。

既然从三元子序列的最小和最大元素查找解题都会超时,那么我们不妨从中间元素入手。

对于每个 nums[i] 来说,找到合适的三元子序列,就是从 0...i-1 中找到小于 nums[i] 的元素,从 i+1...len 中找到大于 nums[i] 的元素,找到则证明存在题意的三元子序列,返回 true,找不到则返回 false。

借鉴前缀数组的概念,我们做三次遍历。

  1. 第一次从 nums[0] 向右遍历,创建一个 leftMin 数组,记录从第一个元素到 i 为止的所有元素的最小值,即 nums[0]...nums[i] 的最小值

  2. 第二次从 nums[len-1] 向左遍历,创建一个 rightMax 数组,记录从 i 到最后一个元素为止的所有元素的最大值,即 nums[i]...nums[len-1] 的最大值

  3. 第三次从头至尾遍历,对于每个 nums[i] 判断是否存在 i 的左侧最小比 nums[i] 小且右侧最大比 nums[i] 大,即 leftMin[i-1] < nums[i] < rightMax[i+1] ,存在则返回true;

代码

class Solution {
    public boolean increasingTriplet(int[] nums) {
        int len = nums.length;
        if (len < 3)
            return false;
        // 记录nums[0]...nums[i]的最小值
        int[] leftMin = new int[len];
        leftMin[0] = nums[0];
        for (int i = 1; i < len; i++) {
            leftMin[i] = Math.min(leftMin[i - 1], nums[i]);
        }
        // 记录nums[i]...nums[len-1]的最大值
        int[] rightMax = new int[len];
        rightMax[len - 1] = nums[len - 1];
        for (int i = len - 2; i >= 0; i--) {
            rightMax[i] = Math.max(rightMax[i + 1], nums[i]);
        }
        // 如果存在左侧最小比nums[i]小,右侧最大比nums[i]大,则找到了
        for (int i = 1; i < len - 1; i++) {
            if (leftMin[i - 1] < nums[i] && nums[i] < rightMax[i + 1])
                return true;
        }
        return false;
    }
}
  • 时间复杂度:O(n)

  • 空间复杂度:O(n)

解法二:贪心

非常优秀的想不到的想法。

本题的贪心思想是:为了找到递增的三元子序列,前两个元素应该尽可能地小,此时找到递增的三元子序列的可能性更大。

贪心证明

假设存在递增序列 (first, second, num)

如果还存在一个 second',值介于第一小的数 first 和 第二小的数 second 之间,即 first < second' < second,且 second' 下标也介于 first 和 second 的下标之间,则有 first < second' < num,second' 也可以作为第二小的数,(first, second', num) 也是一个递增序列。

但反过来,当 (first, second', num) 是递增序列时,对于 (first, second, num) ,由于 num 不一定比 second 大,则 (first, second, num) 不一定是递增序列

由此可见,为了使找到递增的三元子序列的可能性更大,所以第二个元素需要尽可能地小。

同理可得,第一个元素也需要尽可能地小。

解法

我们可以初始化 first 和 second 的值,并使 first < second,在遍历中寻找是否存在 num 满足 first < second < num。

令 first 的值为第一个元素 nums[0],为了确保初始时 first < second,设 second 为可取值的最大值 Integer.MAX_VALUE。

之后从 nums[1] 开始遍历:

  • 如果 nums[i] > second,则找到了递增三元子序列,直接返回true。

  • 如果 nums[i] < second,但比 first 大,则代表当前值更小,此时更新 second,让第二小的数更小,增加找到第三个数的可能性。

  • 如果 nums[i] < second,但比 first 还小,则更新 first,让最小的数更小,增加找到第三个数的可能性。

在第三种情况中,更新 first 之后的当下时刻,first 的下标会比 second 的下标大。

然后继续向后寻找,当找到比 second 大的值 num 时,在原数组中,当前的 first 指向的元素在 second 的后面,意味着当前的first、second、num 的下标不满足递增。

但是 second 前面还存在一个之前的 first 比 second 的下标小,所以还是依旧存在一个递增三元序列(之前的first, second, num)。

代码

class Solution {
    public boolean increasingTriplet(int[] nums) {
        int len = nums.length;
        if (len < 3)
            return false;
        int first = nums[0], second = Integer.MAX_VALUE;
        for (int i = 1; i < len; i++) {
            if (nums[i] > second) {
                return true;
            } else if (nums[i] > first) {
                second = nums[i];
            } else {
                first = nums[i];
            }
        }
        return false;
    }
}
  • 时间复杂度:O(n)

  • 空间复杂度:O(1)


【LC75】No334. 递增的三元子序列
https://tiamo495.com//archives/lc75-no334.-di-zeng-de-san-yuan-zi-xu-lie
作者
tiamo495
发布于
2025年03月21日
许可协议