给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。
如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。
进阶:你能实现时间复杂度为 O(n) ,空间复杂度为 O(1) 的解决方案吗?
题解
法一 动态规划 O(n^2)+O(n)
法二 双向遍历 (O(n)+O(n))
题解转载自Leetcode官方
如果数组nums 中存在一个下标 i 满足 1≤i<n−1,使得在 nums[i] 的左边存在一个元素小于
nums[i] 且在 nums[i] 的右边存在一个元素大于 nums[i],则数组 nums 中存在递增的三元子序列。
创建两个长度为 n 的数组 leftMin 和 rightMax,对于 0≤i<n,leftMin[i] 表示 nums[0] 到 nums[i] 中的最小值,rightMax[i] 表示 nums[i] 到 nums[n−1] 中的最大值。
法三 贪心法(O(n)+O(1))
可以使用贪心的方法将空间复杂度降到 O(1)。从左到右遍历数组 nums,遍历过程中维护两个变量 first 和 second,分别表示递增的三元子序列中的第一个数和第二个数,任何时候都有 first<second。
初始时,second=+∞。 对于 1≤i<n,当遍历到下标 i 时,令 num=nums[i],进行如下操作:
- 如果 num>second,则找到了一个递增的三元子序列,返回 true;
- 否则,如果 num>first,则将 second 的值更新为 num;
- 否则,将 first 的值更新为 num。
- 如果遍历结束时没有找到递增的三元子序列,返回 false。
上述做法的贪心思想是:为了找到递增的三元子序列,first 和 second 应该尽可能地小,此时找到递增的三元子序列的可能性更大。
假设 (first,second,num) 是一个递增的三元子序列,如果存在 second’ 满足first<second’<second 且 second’ 的下标位于 first 的下标和 num 的下标之间,则 (first,second’,num) 也是一个递增的三元子序列。但是当 (first,second’,num) 是递增的三元子序列时,由于 num 不一定大于 second,因此 (first,second,num) 未必是递增的三元子序列。由此可见,为了使找到递增的三元子序列的可能性更大,三元子序列的第二个数应该尽可能地小,将 second’ 作为三元子序列的第二个数优于将 second 作为三元子序列的第二个数。
同理可得,三元子序列的第一个数也应该尽可能地小。
如果遍历过程中遇到的所有元素都大于 first,则当遇到 num>second 时,first 一定出现在 second 的前面,second 一定出现在num 的前面,(first,second,num) 即为递增的三元子序列。
上面的求解过程中有个问题:当已经找到了长度为 2 的递增序列,这时又来了一个比 small 还小的数字,为什么可以直接替换first呢,这样 first和 mid 在原数组中并不是按照索引递增的关系呀?
Trick 就在这里了!假如当前的 first和second为 [3, 5],这时又来了个 1。假如我们不将first替换为 1,那么,当下一个数字是 2,后面再接上一个 3 的时候,我们就没有办法发现这个 [1,2,3] 的递增数组了!也就是说,我们替换最小值,是为了后续能够更好地更新中间值!
另外,即使我们更新了first ,这个first在 second 后面,没有严格遵守递增顺序,但它隐含着的真相是,有一个比 first大比second小的前·最小值出现在second之前。因此,当后续出现比 second大的值的时候,我们一样可以通过当前 first和second推断的确存在着长度为 3 的递增序列。 所以,这样的替换并不会干扰我们后续的计算!
根据上述分析可知,当遇到 num>second 时,一定存在一个递增的三元子序列,该三元子序列的第二个数和第三个数分别是 second 和 num,因此返回 true。
class Solution {
public boolean increasingTriplet(int[] nums) {
int len = nums.length;
int first = nums[0],second=Integer.MAX_VALUE;
//从1开始!
for(int i=1;i<len;i++){
//构成递增三原子序列
if(nums[i]>second) return true;
//为什么first=i?这样不会使得first在second后面吗?如果不替换,后面出现nums[i],不就可以直接接在second后面了吗?
//不会.有一个比first大比second小的前·最小值出现在second之前。因此,当后续出现比second大的值的时候,我们一样可以通过当前first和second推断的确存在着长度为 3 的递增序列
else if(nums[i]<first) first=nums[i];
//写清楚,不要直接else,这样可以过滤掉nums[i]>=first的情况
else if(nums[i]<second&&nums[i]>first) second=nums[i];
}
return false;
}
}