|
| 1 | +### 题目描述 |
| 2 | + |
| 3 | +这是 LeetCode 上的 **[334. 递增的三元子序列](https://leetcode-cn.com/problems/increasing-triplet-subsequence/solution/gong-shui-san-xie-zui-chang-shang-sheng-xa08h/)** ,难度为 **中等**。 |
| 4 | + |
| 5 | +Tag : 「LIS」、「最长上升子序列」、「贪心」、「二分」 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +给你一个整数数组 `nums`,判断这个数组中是否存在长度为 3ドル$ 的递增子序列。 |
| 10 | + |
| 11 | +如果存在这样的三元组下标 `(i, j, k)` 且满足 `i < j < k` ,使得 `nums[i] < nums[j] < nums[k]` ,返回 `true` ;否则,返回 `false`。 |
| 12 | + |
| 13 | +示例 1: |
| 14 | +``` |
| 15 | +输入:nums = [1,2,3,4,5] |
| 16 | + |
| 17 | +输出:true |
| 18 | + |
| 19 | +解释:任何 i < j < k 的三元组都满足题意 |
| 20 | +``` |
| 21 | +示例 2: |
| 22 | +``` |
| 23 | +输入:nums = [5,4,3,2,1] |
| 24 | + |
| 25 | +输出:false |
| 26 | + |
| 27 | +解释:不存在满足题意的三元组 |
| 28 | +``` |
| 29 | +示例 3: |
| 30 | +``` |
| 31 | +输入:nums = [2,1,5,0,4,6] |
| 32 | + |
| 33 | +输出:true |
| 34 | + |
| 35 | +解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6 |
| 36 | +``` |
| 37 | + |
| 38 | +提示: |
| 39 | +* 1ドル <= nums.length <= 5 * 10^5$ |
| 40 | +* $-2^{31} <= nums[i] <= 2^{31} - 1$ |
| 41 | + |
| 42 | +进阶:你能实现时间复杂度为 $O(n$) ,空间复杂度为 $O(1)$ 的解决方案吗? |
| 43 | + |
| 44 | +--- |
| 45 | + |
| 46 | +### 最长上升子序列(贪心 + 二分) |
| 47 | + |
| 48 | +题目要我们判断是否存在长度为 3ドル$ 的上升子序列,问题可以转换为求 $nums$ 的最长上升子序列(`LIS` 问题)的长度。 |
| 49 | + |
| 50 | +如果 $nums$ 的最长上升子序列长度大于等于 3ドル,ドル那么原问题答案为 `True`,否则为 `False`。 |
| 51 | + |
| 52 | +而求最长上升子序列的最优解是有基于「贪心 + 二分」的 $O(n\log{n})$ 做法,对此不了解的同学,可以先看前置 🧀 :[LCS 问题与 LIS 问题的相互关系,以及 LIS 问题的最优解证明](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247487814&idx=1&sn=e33023c2d474ff75af83eda1c4d01892&chksm=fd9cba59caeb334f1fbfa1aefd3d9b2ab6abfccfcab8cb1dbff93191ae9b787e1b4681bbbde3&token=252055586&lang=zh_CN#rd),里面详细讲解了 `LIS` 的贪心解证明,以及与最长公共子序列(`LCS` 问题)的相互关系,本次不再赘述。 |
| 53 | + |
| 54 | +**简单来说,就是在遍历每个数 $nums[i]$ 的同时,维护一个具有单调性的 $f[]$ 数组,其中 $f[len] = x$ 代表长度为 $len$ 的最长上升子序列最小结尾元素为 $x,ドル可以证明 $f$ 数组具有单调性(看 [前置🧀](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247487814&idx=1&sn=e33023c2d474ff75af83eda1c4d01892&chksm=fd9cba59caeb334f1fbfa1aefd3d9b2ab6abfccfcab8cb1dbff93191ae9b787e1b4681bbbde3&token=252055586&lang=zh_CN#rd)),因此每次可以通过二分找到小于 $nums[i]$ 的最大下标,来作为 $nums[i]$ 的前一个数。** |
| 55 | + |
| 56 | +综上,我们使用 `LIS` 的「贪心 + 二分」做法求得最长上升子序列的最大长度,然后和 3ドル$ 比较即可得出答案。 |
| 57 | + |
| 58 | +**代码(感谢 [@🍭可乐可乐吗QAQ](/u/littletime_cc/) 同学提供的其他语言版本):** |
| 59 | +```Java |
| 60 | +class Solution { |
| 61 | + public boolean increasingTriplet(int[] nums) { |
| 62 | + int n = nums.length, ans = 1; |
| 63 | + int[] f = new int[n + 1]; |
| 64 | + Arrays.fill(f, 0x3f3f3f3f); |
| 65 | + for (int i = 0; i < n; i++) { |
| 66 | + int t = nums[i]; |
| 67 | + int l = 1, r = i + 1; |
| 68 | + while (l < r) { |
| 69 | + int mid = l + r >> 1; |
| 70 | + if (f[mid] >= t) r = mid; |
| 71 | + else l = mid + 1; |
| 72 | + } |
| 73 | + f[r] = t; |
| 74 | + ans = Math.max(ans, r); |
| 75 | + } |
| 76 | + return ans >= 3; |
| 77 | + } |
| 78 | +} |
| 79 | +``` |
| 80 | +- |
| 81 | +```C++ |
| 82 | +class Solution { |
| 83 | +public: |
| 84 | + bool increasingTriplet(vector<int>& nums) { |
| 85 | + int n = nums.size(), ans = 1; |
| 86 | + vector<int> f(n + 1, INT_MAX); |
| 87 | + for(int i = 0; i < n; i++){ |
| 88 | + int t = nums[i]; |
| 89 | + int L = 1, R = i + 1; |
| 90 | + while(L < R){ |
| 91 | + int mid = L + R >> 1; |
| 92 | + if(f[mid] >= t) R = mid; |
| 93 | + else L = mid + 1; |
| 94 | + } |
| 95 | + f[R] = t; |
| 96 | + ans = max(ans, R); |
| 97 | + } |
| 98 | + return ans >= 3; |
| 99 | + } |
| 100 | +}; |
| 101 | +``` |
| 102 | +* 时间复杂度:$O(n\log{n})$ |
| 103 | +* 空间复杂度:$O(n)$ |
| 104 | + |
| 105 | +--- |
| 106 | + |
| 107 | +### 优化 : 定长上升子序列(贪心) |
| 108 | + |
| 109 | +利用本题只需要我们判定是否存在长度为 3ドル$ 的上升子序列,而不需要回答 `LIS` 最大长度。 |
| 110 | + |
| 111 | +我们可以对 $f$ 数组进行优化:**使用有限变量进行替换(将 $f$ 数组的长度压缩为 2ドル$),数组含义不变,$f[1] = x$ 代表长度为 1ドル$ 的上升子序列最小结尾元素为 $x,ドル$f[2] = y$ 代表长度为 2ドル$ 的上升子序列的最小结尾元素为 $y$。** |
| 112 | + |
| 113 | +**从前往后扫描每个 $nums[i],ドル每次将 $nums[i]$ 分别与 $f[1]$ 和 $f[2]$ 进行比较,如果能够满足 $nums[i] > f[2],ドル代表 $nums[i]$ 能够接在长度为 2ドル$ 的上升子序列的后面,直接返回 `True`,否则尝试使用 $nums[i]$ 来更新 $f[1]$ 和 $f[2]。$** |
| 114 | + |
| 115 | +这样,我们只使用了有限变量,每次处理 $nums[i]$ 只需要和有限变量进行比较,时间复杂度为 $O(n),ドル空间复杂度为 $O(1)$。 |
| 116 | + |
| 117 | +**代码(感谢 [@🍭可乐可乐吗QAQ](/u/littletime_cc/) 和 [@Benhao](/u/himymben/) 同学提供的其他语言版本):** |
| 118 | +```Java |
| 119 | +class Solution { |
| 120 | + public boolean increasingTriplet(int[] nums) { |
| 121 | + int n = nums.length; |
| 122 | + long[] f = new long[3]; |
| 123 | + f[1] = f[2] = (int)1e19; |
| 124 | + for (int i = 0; i < n; i++) { |
| 125 | + int t = nums[i]; |
| 126 | + if (f[2] < t) return true; |
| 127 | + else if (f[1] < t && t < f[2]) f[2] = t; |
| 128 | + else if (f[1] > t) f[1] = t; |
| 129 | + } |
| 130 | + return false; |
| 131 | + } |
| 132 | +} |
| 133 | +``` |
| 134 | +- |
| 135 | +```Python |
| 136 | +class Solution: |
| 137 | + def increasingTriplet(self, nums: List[int]) -> bool: |
| 138 | + n = len(nums) |
| 139 | + f = [inf] * 3 |
| 140 | + for i in range(n): |
| 141 | + t = nums[i] |
| 142 | + if f[2] < t: |
| 143 | + return True |
| 144 | + elif f[1] < t < f[2]: |
| 145 | + f[2] = t |
| 146 | + elif f[1] > t: |
| 147 | + f[1] = t |
| 148 | + return False |
| 149 | +``` |
| 150 | +- |
| 151 | +```Go |
| 152 | +func increasingTriplet(nums []int) bool { |
| 153 | + n := len(nums) |
| 154 | + f := []int{math.MaxInt32,math.MaxInt32,math.MaxInt32} |
| 155 | + for i := 0; i < n; i++ { |
| 156 | + t := nums[i] |
| 157 | + if f[2] < t{ |
| 158 | + return true |
| 159 | + } else if f[1] < t && t < f[2]{ |
| 160 | + f[2] = t |
| 161 | + } else if f[1] > t { |
| 162 | + f[1] = t |
| 163 | + } |
| 164 | + } |
| 165 | + return false |
| 166 | +} |
| 167 | +``` |
| 168 | +- |
| 169 | +```C++ |
| 170 | +class Solution { |
| 171 | +public: |
| 172 | + bool increasingTriplet(vector<int>& nums) { |
| 173 | + int n = nums.size(); |
| 174 | + vector<int> f(3, INT_MAX); |
| 175 | + for(int i = 0; i < n; i++){ |
| 176 | + int t = nums[i]; |
| 177 | + if(f[2] < t) return true; |
| 178 | + else if(f[1] < t and t < f[2]) f[2] = t; |
| 179 | + else if(f[1] > t) f[1] = t; |
| 180 | + } |
| 181 | + return false; |
| 182 | + } |
| 183 | +}; |
| 184 | +``` |
| 185 | +* 时间复杂度:$O(n)$ |
| 186 | +* 空间复杂度:$O(1)$ |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +### 最后 |
| 191 | + |
| 192 | +这是我们「刷穿 LeetCode」系列文章的第 `No.334` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 |
| 193 | + |
| 194 | +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 |
| 195 | + |
| 196 | +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 |
| 197 | + |
| 198 | +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 |
| 199 | + |
0 commit comments