1. 题目来源
2. 题目解析
拖了这么久才补题…
思路:
- 将位置分成
[0,n]
共n+1
个,赛道分成[0,2]
三个。 f[i][j]
表示从[0][1]
即 0 号位置,1 赛道,跳到[i][j]
位置的最小侧跳步数。f[i][j]
由f[i-1][k]
进行状态转移。但是需要注意的是,这里的[i-1,k]
都是先往前跳一步,再在同赛道中进行侧跳。
第一个代码一开始并没有考虑直接向前跳一步时前面是否为石头的情况,写完连样例都没过,也是打表后才发现这个问题的。
第二个代码能更加清楚一点,每次直接统一先往前跳一步,然后考虑同一列上的侧跳情况及状态更新。如果我这列上本来位置是石头,那就不能进行状态更新,就 continue
,否则就会导致我本来石头这个位置是 INT_MAX
,但是由于我的统一往前一步跳将这个 INT_MAX
冲掉了,造成状态的错误更新。在这呢就是完全和题意等价的状态转移,即先侧跳和先直跳的顺序可以颠倒,但是枚举顺序一定要合理合法。
- 时间复杂度: O ( 9 n ) O(9n) O(9n)。
- 空间复杂度: O ( 3 n ) O(3n) O(3n)
代码:
自己写的繁琐的模拟代码
const int N = 5e5+5;
int f[N][3];
class Solution {
public:
int minSideJumps(vector<int>& b) {
int n = b.size() - 1;
memset(f, 0x3f, sizeof f);
f[0][0] = f[0][2] = 1, f[0][1] = 0;
for (int i = 1; i <= n; i ++ ) {
for (int j = 0; j < 3; j ++ ) {
if (b[i] == j + 1) continue;
for (int k = 0; k < 3; k ++ ) {
if (b[i] == k + 1) continue; // 先直跳再侧跳,直跳到达的位置不能是石头
int t = 0;
if (j != k) t = 1;
f[i][j] = min(f[i][j], f[i - 1][k] + t);
}
}
}
return min({f[n][0], f[n][1], f[n][2]});
}
};
等价的写法,更加清楚、明朗:
const int N = 5e5+5;
int f[N][3];
class Solution {
public:
int minSideJumps(vector<int>& b) {
int n = b.size() - 1;
memset(f, 0x3f, sizeof f);
f[0][0] = f[0][2] = 1, f[0][1] = 0;
for (int i = 1; i <= n; i ++ ) {
for (int j = 0; j < 3; j ++ ) {
if (b[i] == j + 1) continue;
f[i][j] = min(f[i][j], f[i - 1][j]); // 先统一往前跳一步,石头状态也会更新
for (int k = 0; k < 3; k ++ ) {
// if(b[i] == k + 1) continue; // 也可以直接这样写,当 j==k 时,两者处于同一位置
if (j == k || b[i] == k + 1) continue; // 如果同赛道或者当前位置是石头则不能侧跳更新
f[i][k] = min(f[i][k], f[i][j] + 1);
}
}
}
return min({f[n][0], f[n][1], f[n][2]});
}
};