我文章被简书锁了,因此搬运到掘金。在准备刷题跳槽,这里纯粹是实行费曼方法,打算给我自己讲懂,但没打算给别人看。
LeetCode 1824
方法1:DP
时间复杂度:O(n)
想法:这个DP维护的是到达当前这一个点的三条lane的minimum sideway jumps. 所以说dp数组就三个元素就行。在0这个点,题目说了这青蛙在2道出发,那么初始数组就是{1, 0, 1}. 遍历到每个点,这个点上可能会有一个obstacle,因为不可能达到这个obstacle,所以原则上这种点应该计正无穷,这里会有一个问题,几句话之后再解释。所以真正dp算转移的时候,你到达现在这个点的这个lane,最小的横跳数要么是你压根没横跳,直接从上一个点的同样lane过来的,要么是从现在这个点的其他两条lane横跳过来的,所以dp[i] = Math.min(dp[i], Math.min(dp[(i + 1) % 3], dp[(i + 2) % 3]) + 1);.
有两个问题:
- 直接在for循环里更新dp[i]的值,比方说dp[0]是从dp[1] + 1和dp[2] + 1的最小值来的,比方说是dp[1] + 1,那么在更新dp[1]的时候,我好像包含的需要比较的值又包括dp[0] + 1,也就是说dp[0]的值是刚刚根据dp[1]的值更新的,那现在更新dp[1]又使用刚刚更新的dp[0]的值,这样会不会有问题? 不会有问题,因为如果出现这种情况,dp[1]拿回来dp[0] + 1的值的时候,它会是dp[1] + 1 + 1,这一项绝对不可能比dp[1]小,因此直接for循环更新没有问题。
- 正无穷应该用什么表示?
这个题不能用
Integer.MAX_VALUE. 比如样例obstacles = [0,1,2,3,0],更新到index=2这个地方的时候,obstacles[2] = 2, 所以这里计正无穷,其余两个lane是正常的正整数。下面更新index=3这个地方,首先obstacles[3] = 3,因此dp[2]这里先计正无穷,然后这时候for循环先更新dp[0],此时dp[1]和dp[2]全是正无穷,那么如果采用Integer.MAX_VALUE,Math.min(dp[1], dp[2]) + 1会溢出,成为Integer中的最小值,是一个负数,那么这一次更新dp[0]就会得到负数。为了解决这个问题,我们应该选取合适的“正无穷”,使“正无穷”加1时,不会溢出。题目当中给了数据范围5 * 10^5。在最差情况下,每一次青蛙先横跳到其他lane,然后在下一个点再跳回来,那么dp[i]最大为dp[i - 1] + 2。那么到最后dp数组的值不可能超过10^6。我们这里使用10^6作为正无穷即可解决问题。
class Solution {
public int minSideJumps(int[] obstacles) {
int[] dp = new int[] {1, 0, 1};
for (int a : obstacles) {
if (a > 0) {
dp[a - 1] = 1000000;
}
for (int i = 0; i < 3; i++) {
if (a != i + 1) {
dp[i] = Math.min(dp[i], Math.min(dp[(i + 1) % 3], dp[(i + 2) % 3]) + 1);
}
}
}
return Math.min(dp[0], Math.min(dp[1], dp[2]));
}
}
方法2:贪心
时间复杂度:O(n)
想法:我当时周赛的时候写了个贪心,想法就是说我只维护一个变量position,它代表现在青蛙在哪一道,然后去跳的时候,如果下一个点没有obstacle,或者下一个点的obstacle跟青蛙现在的道不在同一条道上,就直接continue。
如果在同一条道上,那说明青蛙在现在的这一个点得跳一次。如果当前的点有obstacle,那就跳到,跟现在的obstacle和下一个点的obstacle不同的那一条道上。如果当前的点没有obstacle,那相当于现在有两条道可以选,那么这时候,应该跳到下一个obstacle最远的那条道上。
这个是我当时周赛的时候的做法,但后来想想觉得不如第一种写起来简单,所以这里就不贴了,但具体写法就是预处理得到一个数组,我叫它getNextDiffIndex,就是说对于每个点,下一个obstacle跟它不一样的点在哪里,然后根据如上所说的方法各种if else.