递增的三元子序列 图解题解
这道题到底在问什么
- 输入
- nums=[2,1,5,0,4,6]
- 输出
- true (如 0<4<6,按顺序能挑出递增三个)
- 输入
- nums=[5,4,3,2,1]
- 输出
- false (一路递减,连两个递增都凑不出)
先想最直接的笨办法
例 1(存在) 开始。蓝色光标 cur 停在下标 0。first 先记成 ∞(还没见过最小值),second 也记 ∞。下面一个一个数往右扫,盯着 first / second 怎么被更新。(动画第 4 步)
最优解:为什么这么做
一句话答案:LeetCode 334 递增的三元子序列:贪心维护 first、second 两个尽量小的值,一旦冒出比 second 还大的数,递增三元组必成立,一遍扫定答案,时间 O(n)、空间 O(1)。
存在三个下标让数值一路递增吗
给一个整数数组 nums,问能不能找出三个下标 i<j<k,让 nums[i]<nums[j]<nums[k],即挑出严格递增的三个数,能返 true、不能返 false。三个数不必挨在一起。题面 nums=[2,1,5,0,4,6] 返回 true,因 0、4、6 递增;nums=[5,4,3,2,1] 递减,凑不出两个递增,返回 false。
三层枚举 i、j、k 慢在哪
三个下标要凑成 i<j<k,一个写法是三层循环枚举 i、j、k,撞上 nums[i]<nums[j]<nums[k] 就返回 true。可数组有 n 个数,三层嵌套约 n³ 种搭配,n 到一千就十亿次比较,超时。只枚举中间的 j、往左找更小往右找更大,仍是 O(n²) 拖不动。
只记两个最小值,怎么就不漏
与其反复枚举,不如换一种贪心的记法——每个数只扫一遍、当场决定它更新谁:first 记「目前见过的最小值」、second 记「某个更小的数之后凑成递增二元组的第二个数」——二元组就是按前后顺序排好的一对递增数——两者都尽量往小压。遍历每个 x:x≤first 就把 first 换成更小的 x;否则 x≤second 就把 second 换成 x;再否则 x>second,直接返回 true。
最反直觉的是:first 压小后,下标可能排到 second 后面,逻辑不就乱了?但并不会。second 被撑起来那一刻,就锁死一个事实——它左边出现过比它小的数。这是过去时,之后把 first 换更小只给第三个数留余地,动不了已成立的历史。所以只要冒出比 second 还大的 x,递增链「更小的 first < second < x」一定存在。
更新为什么用小于等于,哨兵怎么设
更新到底用 x≤first、x≤second 还是严格 <,直接决定成败:题目要严格递增,相等的值若用 < 就不压小、会被当成能接续而误判,用 ≤ 才不会把重复值算成递增。初值则把 first、second 设成正无穷当哨兵——哨兵就是起手放的占位,设正无穷好让第一个数一定能压进来;Python 写 float('inf'),C++、Java 别用 INT_MAX、遇接近上限的大数会溢出,改 long。一旦 x>second 返回 true,扫完没触发就 false。
跟着 [2,1,5,0,4,6] 走一遍
拿题面 nums=[2,1,5,0,4,6],first、second 从正无穷起步。读 2→first=2;读 1→first=1;读 5→second=5(凑出 1<5);读 0→first=0(second 不动、1<5 仍算数);读 4→second=4(成 0<4);读 6>second=4,返回 true——0、4、6 正是下标与值都递增的一组。
换成 nums=[5,4,3,2,1],都更小,first→1,second 始终没被撑起来、一直是正无穷,等不到更大的数,返回 false。整趟扫一遍,时间 O(n);只留两个变量,空间 O(1)。
▶ 动画逐步走查(共 23 步)——想跟着动画一帧帧对照就展开
- 3思路一句话:贪心维护 first=见过的最小值、second=次小值。冒出比 second 还大的数,三元组必真实存在。下面一步步演给你看。
- 4例 1(存在) 开始。蓝色光标 cur 停在下标 0。first 先记成 ∞(还没见过最小值),second 也记 ∞。下面一个一个数往右扫,盯着 first / second 怎么被更新。
- 5下标 0(值 9)不大于当前 first=∞,所以把 first 压小到 9——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 6下标 1(值 8)不大于当前 first=9,所以把 first 压小到 8——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 7下标 2(值 3)不大于当前 first=8,所以把 first 压小到 3——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 8下标 3(值 7)比 first=3 大、又不超过当前 second=∞,于是把 second 压小到 7。这意味着已经凑出一个递增二元组 3<7;只要之后再来个比 7 大的数,三元组就成了。
- 9下标 4(值 2)不大于当前 first=3,所以把 first 压小到 2——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 10下标 5(值 6)比 first=2 大、又不超过当前 second=7,于是把 second 压小到 6。这意味着已经凑出一个递增二元组 2<6;只要之后再来个比 6 大的数,三元组就成了。
- 11下标 6(值 1)不大于当前 first=2,所以把 first 压小到 1——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 12下标 7(值 5)比 first=1 大、又不超过当前 second=6,于是把 second 压小到 5。这意味着已经凑出一个递增二元组 1<5;只要之后再来个比 5 大的数,三元组就成了。
- 13下标 8(值 0)不大于当前 first=1,所以把 first 压小到 0——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 14下标 9(值 4)比 first=0 大、又不超过当前 second=5,于是把 second 压小到 4。这意味着已经凑出一个递增二元组 0<4;只要之后再来个比 4 大的数,三元组就成了。
- 15走到下标 10(值 12):它比当前 second=4 还大。而 second 当初是被一个更小的 first=0 撑起来的——所以前面真有一个比 4 小的数。三个数 0 < 4 < 12 按顺序排好,递增三元组找到了,true!
- 16例 1(存在) 扫完:高亮的三格正是一组合法的递增三元子序列(前后顺序对、值严格递增)。答案 true。
- 17例 2(不存在) 开始。蓝色光标 cur 停在下标 0。first 先记成 ∞(还没见过最小值),second 也记 ∞。下面一个一个数往右扫,盯着 first / second 怎么被更新。
- 18下标 0(值 7)不大于当前 first=∞,所以把 first 压小到 7——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 19下标 1(值 6)不大于当前 first=7,所以把 first 压小到 6——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 20下标 2(值 5)不大于当前 first=6,所以把 first 压小到 5——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 21下标 3(值 4)不大于当前 first=5,所以把 first 压小到 4——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 22下标 4(值 3)不大于当前 first=4,所以把 first 压小到 3——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 23下标 5(值 2)不大于当前 first=3,所以把 first 压小到 2——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 24下标 6(值 1)不大于当前 first=2,所以把 first 压小到 1——起点越小,后面越容易接出更大的数。注意:压小 first 不会破坏已立起来的 second,因为 second 记的是「曾经存在过更小 first」这件事。
- 25例 2(不存在) 扫完:数列一路递减,first 被一路压小、second 始终没被撑起来(一直是 ∞),自然不可能冒出比 second 大的数。挑不出递增的三个,答案 false。
⚠️ 容易写错的地方
✗ 错:用 < 而不是 ≤ 更新 first/second
✓ 对:更新用 x≤first / x≤second
遇到相等值时用 ≤ 才能正确压小,避免把相等当成递增(要求严格递增)
✗ 错:担心 first 被压到 second 后面会出错
✓ 对:不会错
second 记的是「曾存在更小 first」这一事实;事后压小 first 不影响这个既成结论
✗ 错:first/second 用 int 存 INT_MAX 比较
✓ 对:用 long 或更大上限
nums 里若有接近 INT_MAX 的值,与 INT_MAX 比较可能溢出,用 long 更稳
完整代码(Python / C++ / Java)
Python
def increasingTriplet(nums):
first = second = float("inf") # 见过的最小、次小
for x in nums:
if x <= first:
first = x # 更小的起点
elif x <= second:
second = x # 凑出递增二元组
else:
return True # x>second 三元递增成立
return FalseC++
bool increasingTriplet(vector<int>& nums) {
long first = LONG_MAX, second = LONG_MAX;
for (int x : nums) {
if (x <= first) first = x; // 更小的起点
else if (x <= second) second = x; // 递增二元组
else return true; // 三元递增成立
}
return false;
}Java
public boolean increasingTriplet(int[] nums) {
long first = Long.MAX_VALUE, second = Long.MAX_VALUE;
for (int x : nums) {
if (x <= first) first = x; // 更小的起点
else if (x <= second) second = x; // 递增二元组
else return true; // 三元递增成立
}
return false;
}复杂度
时间
O(n)
从左到右扫一遍,每个数只看一次
空间
O(1)
只用 first / second 两个变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 递增的三元子序列 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
first 被压到比 second 还靠后的位置,之前那个递增二元组不就废了吗?+
不会废。second 被更新的那一刻,就永久记下了「在它之前存在一个比它小的数」这个事实,这跟 first 现在指向哪个下标无关。之后把 first 压得更小,只是为后面接更大的数腾地方,并不会让那个已经成立的递增二元组失效。所以一旦扫到 x>second,「某个更小的数 < second < x」这三个数按顺序、按大小都对得上,三元组必真实存在。
要是把 3 改成找递增 k 元子序列,这套 first/second 还够用吗?+
不够,得推广成一个长度 k−1 的贪心末尾数组 tails。对每个 x,用二分找 tails 里第一个 ≥x(严格递增用这个界)的位置替换,x 比所有元素都大就追加;tails 长度一旦达到 k 就返回 true。这其实就是最长递增子序列 LIS 的 O(n log k) 贪心加二分写法,k 取 3 时 tails 只有两格,正好退化成这里的 first 和 second。
更新条件为什么非得用 x≤first,写成 x<first 会错在哪?+
会把相等值当成能接续,破坏严格递增。举个例子 nums=[2,2,2],若用 x<first,第二个 2 不小于 first=2、就落进更新 second 的分支,second 被压成 2,看起来凑出了「二元组」,可 2 和 2 并不递增。用 x≤first 时,后面的 2 会一路压 first、始终进不了 second,扫完 false,才符合题目要严格递增的要求。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 递增的三元子序列 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。