334. 递增的三元子序列
1、题干
给你一个整数数组 nums
,判断这个数组中是否存在长度为 3
的递增子序列。
如果存在这样的三元组下标 (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 * 10^5
-2^31 <= nums[i] <= 2^31 - 1
**进阶:**你能实现时间复杂度为 O(n)
,空间复杂度为 O(1)
的解决方案吗?
2、解法1-求子问题
- 问题是求递增的三元子序列,实际可以先求解子问题,即先确定递增的二元子序列再找第三个元素
- 对于递增的二元子序列一定有这样一个规律:如果数列中存在递增二元子序列,那必定存在连续的递增二元子序列 (用反证法很容易推导)
- 所以找第一对递增二元子序列只需要找到两个相邻且递增的元素即可,这里将他们记为
l1
和l2
- 如果后续能找到大于当前
l2
的元素就可以结束查找返回true
- 如果后续无法找到大于当前
l2
的元素那就无法找到正解,因此需要不断用小于l2
大于l1
的数不断更新l2
- 另外
l1
的值限制了l2
的下限也可能导致无法找到正解,因此需要找到更小的递增二元子序列m1
和m2
,其中m1 < m2 <= l1
(m2
在实际编码中不声明),如果m1
和m2
都存在则更新l1
和l2
的值
3、代码
var increasingTriplet = function (nums) {
let l1, l2, m1;
for (let i = 1; i < nums.length; i++) {
if (isNaN(l2) && nums[i - 1] < nums[i]) l1 = nums[i - 1], l2 = nums[i];
if (!isNaN(l2) && nums[i] > l2) return true;
if (!isNaN(l2) && l1 < nums[i]) l2 = nums[i];
if (!isNaN(m1) && nums[i] <= l1) l1 = m1, l2 = nums[i], m1 = undefined;
if (nums[i] < l1) m1 = nums[i];
}
return false;
};
这里实际也用到了贪心思想,代码进一步简化后会跟官解贪心类似,与官解的不同点在于利用了递增二元子序列的连续性
4、执行结果
执行用时: 84 ms 内存消耗: 51 MB
5、解法2-贪心
3个月前写的代码,跟官解的贪心解法类似,先确定前两个元素并初始化为 nums[0]
和 Infinity
,然后遍历后续元素并不断用更小的数更新前两个元素,最后如果存在第三个更大的数则表示存在递增三元子序列。
这种方式比较巧妙,用两个变量存储了两组递增二元子序列,中间两组子序列切换的过程实际不影响最终求解
6、代码
var increasingTriplet = function (nums) {
let [min, max] = [nums[0], Infinity];
for (let i = 1; i < nums.length; i++) {
if (nums[i] > max) return true;
if (nums[i] < min) min = nums[i];
if (nums[i] > min && nums[i] < max) max = nums[i];
}
return false;
};
7、执行结果
执行用时: 100 ms 内存消耗: 51.2 MB
这个题目还可以用回溯求解,但是时间复杂度比较高大概率会超时;另外题解区有二分的实现方案,这种方案具有通用性可以解决一类问题,非常值得学习。