【LIS&二分】Leetcode334. 递增的三元子序列

95 阅读1分钟

题目描述

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

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

算法示例

输入:nums = [1,2,3,4,5]
输出:true
解释:任何 i < j < k 的三元组都满足题意
输入:nums = [5,4,3,2,1]
输出:false
解释:不存在满足题意的三元组
输入:nums = [2,1,5,0,4,6]
输出:true
解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6

提示:

  • 1 <= nums.length <= 5 * 105
  • -231 <= nums[i] <= 231 - 1

方法一:暴力【超时】

  • 看数据范围O(n^2)会超时
class Solution {
    public boolean increasingTriplet(int[] nums) {
        int n = nums.length;
        int[] f = new int[n];
        Arrays.fill(f, 1);
        for(int i = 1; i < n; i++) {
            for(int j = 0; j < i; j++) {
                if(nums[i] > nums[j]) {
                    f[i] = Math.max(f[j] + 1, f[i]);
                    if(f[i] >= 3) return true;
                }
            }
        }
        return false;
    }
}

image-20230216225453090.png

方法二:LIS&二分

  • 我们考虑维护一个列表 f,其中每个元素 f[k]的值代表 长度为 k+1 的子序列尾部元素的值

  • 举例:7,8,9,1,2,3,4,5序列,遍历前3个元素的时候,f=[7,8,9]

    而遍历到1的时候,需要在f进行二分找到适合1的位置,得到f=[1,8,9],此时说明[7,8,9,1]长度为1的子序列中最小结尾元素为1

    依此类推,得到最终的f=[1,2,3,4,5],该结果就是最长递增子序列(LIS)

  • f列表一定是递增的,即当尽可能使每个子序列尾部元素值最小的前提下,子序列越长,其序列尾部元素值一定更大。

class Solution {
    public boolean increasingTriplet(int[] nums) {
        int n = nums.length;
        // 表示长度为k + 1的最长上升子序列的最小结尾元素
        int[] f = new int[n + 1];
        Arrays.fill(f, Integer.MAX_VALUE);
        int ans = 0;
        for(int i = 0; i < n; i++) {
            int l = 1, r = i + 1;
            while (l <= r) {
                int mid = l + (r - l) /2;
                if (f[mid] >= nums[i]) r = mid - 1;
                else l = mid + 1;
            }
            // l左侧全部小于nums[i],r右侧全部大于等于nums[i]
            // 因此直接覆盖掉大于等于nums[i]的第一个元素即可
            f[l] = nums[i];
            // 此时l下标就代表递增子序列的长度
            ans = Math.max(ans, l);
        }
        System.out.println(Arrays.toString(f));
        return ans >= 3;
    }
}