今天的题,怎么说呢?有些怪异
1、看题
简洁地不像话。
2、审题
题目本身可能没有什么难度。
很明显,只要嵌套循环,计算每个数之前有多少个比它小的数,当一个数之前有三个比它小的数的话,就返回true即可。
可这样的话,无疑是完成不了进阶要求里*时间复杂度为 O(n)*的限制的。
所以我们不仅要做,还要做得很牛逼。
辣么,这题又有什么重点呢?
真没什么,唯一要注意的是,题目没有限制三元组必须是相邻的。
示例case里都避开了这一点不谈,而如果你不注意的话,思路就会被带偏。
3、思路
好吧,既然要求时间复杂度为O(n),空间复杂度为O(1),辣么我们遍历的次数就必须限制在一次或几次中,至少不能嵌套遍历。其次,也仅能使用常数级的额外空间。
于是,我从仅靠单次循环就可以解决的角度开始思考。
由于题目是需要找出三元组,在确认第三个数的同时就可以跳出循环,于是重点在于确认前两个数i
和j
。
辣么如何确认i
和j
呢?
按照题意,有i<j
且nums[i]<nums[j]
,即i
是三元数中最小的。
于是,运用贪心的思想,在确认j
之前,我们只要把遇到的最小的数记为i
即可。
在确认了i
之后,如果遇到任何t
使nums[t] > nums[i]
,辣么就记 j = t
。
如此,我们就确认了i
和j
,在j
存在后,我们只要能找到任何t
使nums[t] > nums[j]
就可以认为找到三元数了。
但假如之后遇到t
使nums[t] < nums[j]
,而偏偏存在 nums[t] < nums[t + x] < nums[j]
,使i
、t
和 t+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>j
且nums[i]<nums[j]
了,这并不符合题意!
别急,我们再分析之后的情况:
- 后续存在
t
,使nums[i] < nums[t] < nums[j]
,则会之前的逻辑辑,让j = t
即修正回i < j
的情况 - 后续存在
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、总结
嗯……今天的题,严格来说可以算是贪心吗?
感觉更像是分析归类之类的。
但总之,能从紊乱的思绪中厘出正确的思路,我多少也可以骄傲一点点吧