likes
comments
collection
share

334. 递增的三元子序列 : 最长上升子序列(LIS)问题的贪心解运用题

作者站长头像
站长
· 阅读数 14

题目描述

这是 LeetCode 上的 334. 递增的三元子序列 ,难度为 中等

Tag : 「LIS」、「最长上升子序列」、「贪心」、「二分」

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

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

示例 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

提示:

  • 1<=nums.length<=5∗1051 <= nums.length <= 5 * 10^51<=nums.length<=5105
  • −231<=nums[i]<=231−1-2^{31} <= nums[i] <= 2^{31} - 1231<=nums[i]<=2311

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

最长上升子序列(贪心 + 二分)

题目要我们判断是否存在长度为 333 的上升子序列,问题可以转换为求 numsnumsnums 的最长上升子序列(LIS 问题)的长度。

如果 numsnumsnums 的最长上升子序列长度大于等于 333,那么原问题答案为 True,否则为 False

而求最长上升子序列的最优解是有基于「贪心 + 二分」的 O(nlog⁡n)O(n\log{n})O(nlogn) 做法,对此不了解的同学,可以先看前置 🧀 :LCS 问题与 LIS 问题的相互关系,以及 LIS 问题的最优解证明,里面详细讲解了 LIS 的贪心解证明,以及与最长公共子序列(LCS 问题)的相互关系,本次不再赘述。

简单来说,就是在遍历每个数 nums[i]nums[i]nums[i] 的同时,维护一个具有单调性的 f[]f[]f[] 数组,其中 f[len]=xf[len] = xf[len]=x 代表长度为 lenlenlen 的最长上升子序列最小结尾元素为 xxx,可以证明 fff 数组具有单调性(看 前置🧀),因此每次可以通过二分找到小于 nums[i]nums[i]nums[i] 的最大下标,来作为 nums[i]nums[i]nums[i] 的前一个数。

综上,我们使用 LIS 的「贪心 + 二分」做法求得最长上升子序列的最大长度,然后和 333 比较即可得出答案。

代码(感谢 @🍭可乐可乐吗QAQ 同学提供的其他语言版本):

class Solution {
    public boolean increasingTriplet(int[] nums) {
        int n = nums.length, ans = 1;
        int[] f = new int[n + 1];
        Arrays.fill(f, 0x3f3f3f3f);
        for (int i = 0; i < n; i++) {
            int t = nums[i];
            int l = 1, r = i + 1;
            while (l < r) {
                int mid = l + r >> 1;
                if (f[mid] >= t) r = mid;
                else l = mid + 1;
            }
            f[r] = t;
            ans = Math.max(ans, r);
        }
        return ans >= 3;
    }
}
class Solution {
public:
    bool increasingTriplet(vector<int>& nums) {
        int n = nums.size(), ans = 1;
        vector<int> f(n + 1, INT_MAX);
        for(int i = 0; i < n; i++){
            int t = nums[i];
            int L = 1, R = i + 1;
            while(L < R){
                int mid = L + R >> 1;
                if(f[mid] >= t) R = mid;
                else L = mid + 1;
            }
            f[R] = t;
            ans = max(ans, R);
        }
        return ans >= 3;
    }
};
  • 时间复杂度:O(nlog⁡n)O(n\log{n})O(nlogn)
  • 空间复杂度:O(n)O(n)O(n)

优化 : 定长上升子序列(贪心)

利用本题只需要我们判定是否存在长度为 333 的上升子序列,而不需要回答 LIS 最大长度。

我们可以对 fff 数组进行优化:使用有限变量进行替换(将 fff 数组的长度压缩为 222),数组含义不变,f[1]=xf[1] = xf[1]=x 代表长度为 111 的上升子序列最小结尾元素为 xxxf[2]=yf[2] = yf[2]=y 代表长度为 222 的上升子序列的最小结尾元素为 yyy

从前往后扫描每个 nums[i]nums[i]nums[i],每次将 nums[i]nums[i]nums[i] 分别与 f[1]f[1]f[1]f[2]f[2]f[2] 进行比较,如果能够满足 nums[i]>f[2]nums[i] > f[2]nums[i]>f[2],代表 nums[i]nums[i]nums[i] 能够接在长度为 222 的上升子序列的后面,直接返回 True,否则尝试使用 nums[i]nums[i]nums[i] 来更新 f[1]f[1]f[1]f[2]。f[2]。f[2]

这样,我们只使用了有限变量,每次处理 nums[i]nums[i]nums[i] 只需要和有限变量进行比较,时间复杂度为 O(n)O(n)O(n),空间复杂度为 O(1)O(1)O(1)

代码(感谢 @🍭可乐可乐吗QAQ@Benhao 同学提供的其他语言版本):

class Solution {
    public boolean increasingTriplet(int[] nums) {
        int n = nums.length;
        long[] f = new long[3];
        f[1] = f[2] = (int)1e19;
        for (int i = 0; i < n; i++) {
            int t = nums[i];
            if (f[2] < t) return true;
            else if (f[1] < t && t < f[2]) f[2] = t;
            else if (f[1] > t) f[1] = t;
        }
        return false;
    }
}
class Solution:
    def increasingTriplet(self, nums: List[int]) -> bool:
        n = len(nums)
        f = [inf] * 3
        for i in range(n):
            t = nums[i]
            if f[2] < t:
                return True
            elif f[1] < t < f[2]:
                f[2] = t
            elif f[1] > t:
                f[1] = t
        return False
func increasingTriplet(nums []int) bool {
    n := len(nums)
    f := []int{math.MaxInt32,math.MaxInt32,math.MaxInt32}
    for i := 0; i < n; i++ {
        t := nums[i]
        if f[2] < t{
            return true
        } else if f[1] < t && t < f[2]{
            f[2] = t
        } else if f[1] > t {
            f[1] = t
        }
    }
    return false
}
class Solution {
public:
    bool increasingTriplet(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(3, INT_MAX);
        for(int i = 0; i < n; i++){
            int t = nums[i];
            if(f[2] < t) return true;
            else if(f[1] < t and t < f[2]) f[2] = t;
            else if(f[1] > t) f[1] = t;
        }
        return false;
    }
};
  • 时间复杂度:O(n)O(n)O(n)
  • 空间复杂度:O(1)O(1)O(1)

最后

这是我们「刷穿 LeetCode」系列文章的第 No.334 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。