【力扣时间】【334】【中等】递增的三元子序列

今天的题,怎么说呢?有些怪异

1、看题

点我

简洁地不像话。

2、审题

题目本身可能没有什么难度。
很明显,只要嵌套循环,计算每个数之前有多少个比它小的数,当一个数之前有三个比它小的数的话,就返回true即可。

可这样的话,无疑是完成不了进阶要求里*时间复杂度为 O(n)*的限制的。
所以我们不仅要做,还要做得很牛逼。

辣么,这题又有什么重点呢?
真没什么,唯一要注意的是,题目没有限制三元组必须是相邻的。
示例case里都避开了这一点不谈,而如果你不注意的话,思路就会被带偏。

3、思路

好吧,既然要求时间复杂度为O(n),空间复杂度为O(1),辣么我们遍历的次数就必须限制在一次或几次中,至少不能嵌套遍历。其次,也仅能使用常数级的额外空间。

于是,我从仅靠单次循环就可以解决的角度开始思考。

由于题目是需要找出三元组,在确认第三个数的同时就可以跳出循环,于是重点在于确认前两个数ij
辣么如何确认ij呢?
按照题意,有i<jnums[i]<nums[j],即i是三元数中最小的。
于是,运用贪心的思想,在确认j之前,我们只要把遇到的最小的数记为i即可。
在确认了i之后,如果遇到任何t使nums[t] > nums[i],辣么就记 j = t
如此,我们就确认了ij,在j存在后,我们只要能找到任何t使nums[t] > nums[j]就可以认为找到三元数了。
但假如之后遇到t使nums[t] < nums[j],而偏偏存在 nums[t] < nums[t + x] < nums[j],使itt+x成为三元数,且是唯一解的话,该怎么办呢?

于是顺着思路,当遇到t使nums[t] < nums[j]时,再判断nums[t]nums[i]

如果nums[t] > nums[i]时,我们完全可以让j = t,毕竟只要能找到了k,让i,j,k组成递增三元组,而 nums[i] < nums[t] < nums[j]``,则i,t,k`完全也能组成递增三元组。

如果nums[t] < nums[i]时,让i = t即可。此时你会发现,i>jnums[i]<nums[j]了,这并不符合题意!
别急,我们再分析之后的情况:

  1. 后续存在t,使nums[i] < nums[t] < nums[j],则会之前的逻辑辑,让j = t即修正回i < j的情况
  2. 后续存在t,使nums[t]>nums[j],而由于j此前必有一个比它小的数存在,故可直接凑成三元组。

所以,此时的替换并不会影响最后的结局。

以上思路都是我一边码代码一边成形。

4、实现

public boolean increasingTriplet(int[] nums) {
        int i = 0;
        int j = -1;
        for (int t = 1; t < nums.length; t++) {
            if (j != -1) {
                //当j存在时,即存在i<j且nums[i]<nums[j]
                if (nums[t] > nums[j]) {
                    //大于j时,即为i,j,t三个递增
                    return true;
                } else {
                    //小于j时
                    if (nums[t] > nums[i]) {
                        //但大于i时,替换j为t
                        //如果此后遇到了k,能够让i,j,k组成递增三元组,而t<j,则i,t,k也能组成递增三元组,故可以替换
                        j = t;
                    } else {
                        //小于i时,替换i为t
                        //此时替换会使i>j但nums[i]<nums[j],后续有以下两种情况:
                        // 1、后续存在t,使nums[t]<nums[j]但nums[t]>nums[i],则会走上面的逻辑,替换j为t,修正为j>i,故替换不影响
                        // 2、后续存在t,使nums[t]>nums[j],而由于j此前必有一个比它小的数存在,故可直接凑成三元组,替换不影响
                        i = t;
                    }
                }
            } else {
                //当j不存在时
                if (nums[t] > nums[i]) {
                    //当前数字大于i,则记j=t
                    j = t;
                } else {
                    //当前数字小于i,且暂未发现j使j>i且nums[j]>nums[i]时,故可替换i为t
                    i = t;
                }
            }
        }
        return false;
    }

这是我提交过的第一版,由于是逐条分析的情况,虽然思路很清晰,注释也很明确,但很明显代码有冗余的情况。即无论j是否存在,只要nums[t] > nums[i],就让j=t,反之则i=t

于是,我有优化出了下面的版本。

public boolean increasingTriplet(int[] nums) {
        if (nums.length < 3) {
            return false;
        }
        int i = 0;
        int j = -1;
        for (int t = 1; t < nums.length; t++) {
            if (j != -1 && nums[t] > nums[j]) {
                return true;
            }
            if (nums[t] > nums[i]) {
                j = t;
            } else {
                i = t;
            }
        }
        return false;
    }

之后不再多解读了,我想思路中已经把代码的点点滴滴都说明了一遍,如果还有疑问,可以结合代码和注释再思考一下。

6、提交

在这里插入图片描述

7、咀嚼

当然,时间复杂度O(n),仅遍历了一遍。空间复杂度O(1),没有使用到额外的空间。

8、学习大牛

我的思路仅遍历了数组一遍,但耗时排名却现实仍有更优的解法。
我带着疑问扫了下大牛们的题解,最优的逻辑就是我这种的,并没有人给出java耗时排名100%的解法。

是这题还有纯数学的做法吗?可即使这样,也不可能一次遍历都不需要才对。

总之,这里放出官解来,以供参考。

如果有大牛知道更优的解法,欢迎指点
如果将来的我也找到了,记得回来看看

9、总结

嗯……今天的题,严格来说可以算是贪心吗?
感觉更像是分析归类之类的。

但总之,能从紊乱的思绪中厘出正确的思路,我多少也可以骄傲一点点吧

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值