题目地址:
https://leetcode.com/problems/circular-array-loop/
给定一个长 n n n且不含 0 0 0的数组 A A A,每个数 A [ i ] A[i] A[i]表示如果当前位置在 i i i的话,需要做的位移,即如果 A [ i ] > 0 A[i]>0 A[i]>0则要向右移动 A [ i ] A[i] A[i]步,否则要向左移动 A [ i ] A[i] A[i]步。注意要将 A A A视为循环数组,如果走出界了会走到另一边。问是否存在环。环的长度要大于 1 1 1,并且每一步的方向要都相同。要求空间 O ( 1 ) O(1) O(1)。 A [ i ] ≤ 1000 A[i]\le 1000 A[i]≤1000,
从任一点开始出发,它的下一个位置是确定的,由于 A [ i ] ≠ 0 A[i]\ne 0 A[i]=0,那么从这个点出发走下去一定会走到某个之前走过的点。我们只需要排除一些不符合要求的情况就行了。为此我们要开一个变量存当前走的方向,方向只有两个正向和负向,如果中途发现方向不对,则不符合要求;此外因为题目要求环的长度大于 1 1 1,所以还需要存最后一步走的步数,如果这个步数是 n n n的倍数,说明这是自环,也不符合要求。并且,为了知道是否走到了之前走过的地方,我们需要对走过的数打个标记。因为空间要 O ( 1 ) O(1) O(1),我们考虑直接在数组里打标记,但是当某次循环走到打了标记的数的时候,我们需要知道这个标记是前面几轮打上的,还是本轮打上的(前面轮打上的不作数的,只有本轮打上的才有可能产生环),所以每一轮的标记需要不一样。我们可以每轮的标记是 2000 + p 2000+p 2000+p, p p p是出发点下标,这样只有走到了 2000 + p 2000+p 2000+p这个标记才退出循环。代码如下:
class Solution {
public:
bool circularArrayLoop(vector<int>& a) {
int n = a.size();
// BASE是打标记的初始数字
const int BASE = 2000;
for (int i = 0; i < n; i++) a[i] %= n;
for (int i = 0; i < n; i++) {
// 大于等于BASE说明之前的某轮走到过i这个位置而没有发现环,所以直接略过;自环也略过
if (a[i] >= BASE || !a[i]) continue;
int p = i, mark = BASE + i;
bool sign = a[p] > 0;
do {
int ne = (p + a[p] + n) % n;
a[p] = mark;
p = ne;
// 如果走到了自环,或者走到之前走过的路径,或者方向变了,则退出循环
} while (a[p] && a[p] < BASE && sign == a[p] > 0);
// 只有走到了本轮走过的路径,才找到了环
if (a[p] == mark) return true;
}
return false;
}
};
时间复杂度 O ( n ) O(n) O(n)(因为每个数最多只会打一次标记),空间 O ( 1 ) O(1) O(1)。