leetcode--递增的三元子序列

题目

给你一个整数数组 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 * 105
  • -2^31 <= nums[i] <= 2^31 - 1

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

贪心

若想要找到一个递增的三元子序列,则要找到 a , b , c a,b,c a,b,c 满足 a < b < c a < b < c a<b<c。若我们能保证 a < b a < b a<b,且 a a a b b b 前面,则当寻找到一个数大于 b b b 时,我们就找了了一个递增的三元子序列。

那维持 a < b a < b a<b,怎么才最容易找到 c c c 呢?当然是 b b b 最小的时候。 b b b 想要小,那么也就要求 a a a 也尽量小。

因此我们的任务转化为

  • 维持 a < b a < b a<b,尝试找 c c c
  • a , b a,b a,b 都尽量小
  • a , b , c a,b,c a,b,c 从左向右排列

我们使用两个变量firstsecond分别存储三元子序列中的第一个数和第二个数。i为当前遍历到的位置的索引

  • nums[i] <= first,这时我们找到了迄今为止最小的数。我们令first = nums[i]
  • 否则,若nums[i] <= second,我们令second = nums[i]
    • 先更新first,再更新second能够保证first < second,从而不错过任何可能的第三个数
  • 否则,若nums[i] <= first,则first = nums[i]
    • 这可能会导致,更新完之后firstsecond右边
    • 更新完之后,下一步可能会有三种情况
      • nums[i] > second,这时可以返回true,因为second之前有一个刚刚的first
      • nums[i] <= second && nums[i] > first,这时我们更新second,更新完之后second又在first右边了,且两个数都比更新之前更小了
      • nums[i] <= first,这时我们又更新first。下一步仍有三种情况,和上述三种情况相同。操作循环(又回到上面的判断中)
    • 我们可以看到,这种临时的fisrtsecond 右边的情况并不影响结果
class Solution {
public:
    bool increasingTriplet(vector<int>& nums) {
        int first = INT_MAX, second = INT_MAX;
        for(int& i: nums){
            if(i <= first) first = i; // 更新 first
            else if(i <= second) second = i; // 更新 second
            else return true; // 找到第三个数
        }
        return false;
    }
};
  • 时间复杂度 O ( n ) O(n) O(n)
  • 空间复杂度 O ( 1 ) O(1) O(1)

贪心操作的正确性说明:

我们上述操作实际是在尝试,nums[i]能否作为三元子序列的第三个数

  • 我们在遍历到一个nums[i]时,已经保证了其前面的first,second都是最小的。若此时,nums[i]都不能作为第三个数,则其前面的数无论怎样组合,nums[i]都不可能作为第三个数。

  • nums[i]不能作为第三个数之后,我们将fisrt,second更新到最小,为nums[i + 1]作为第三个数提供最好的条件

  • 我们遍历了整个数组,因此能够考察到每个数,若有一个数能够作为第三个数,则返回true。不会出现遗漏的情况

动态规划

我们判断一个数是否能够作为三元子序列中的第二个数。若nums[i]左边有小于它的数,右边有大于他的数,则找到一个三元子序列。

我们可以额外使用两个数组,一个是LeftMinLeftMin[i]代表nums[i]左边最小的数。一个是RightMaxRightMax[i]代表nums[i]右边最大的数。

上述两个数组的功能可以通过两遍扫描来实现

  • 从左向右扫描LeftMin[i] = min(LeftMin[i-1], nums[i])
  • 从右向左扫描RightMax[i] = max(RightMax[i+1], nums[i])

之后遍历nums[i],若LeftMin[i] < nums[i] < RigthMax[i],则返回true

  • 时间复杂度 O ( n ) O(n) O(n)
  • 空间复杂度 O ( n ) O(n) O(n)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长命百岁️

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值