You are given a circular array nums of positive and negative integers. If a number k at an index is positive, then move forward k steps. Conversely, if it's negative (-k), move backward k steps. Since the array is circular, you may assume that the last element's next element is the first element, and the first element's previous element is the last element.

Determine if there is a loop (or a cycle) in nums. A cycle must start and end at the same index and the cycle's length > 1. Furthermore, movements in a cycle must all follow a single direction. In other words, a cycle must not consist of both forward and backward movements.

Example 1:

Input: [2,-1,1,2,2]
Output: true
Explanation: There is a cycle, from index 0 -> 2 -> 3 -> 0. The cycle's length is 3.

Example 2:

Input: [-1,2]
Output: false
Explanation: The movement from index 1 -> 1 -> 1 ... is not a cycle, because the cycle's length is 1. By definition the cycle's length must be greater than 1.

Example 3:

Input: [-2,1,-1,-2,-2]
Output: false
Explanation: The movement from index 1 -> 2 -> 1 -> ... is not a cycle, because movement from index 1 -> 2 is a forward movement, but movement from index 2 -> 1 is a backward movement. All movements in a cycle must follow a single direction.

Note:

  1. -1000 ≤ nums[i] ≤ 1000
  2. nums[i] ≠ 0
  3. 1 ≤ nums.length ≤ 5000

Follow up:

Could you solve it in O(n) time complexity and O(1) extra space complexity?

说实话,这道题描述的并不是很清楚,比如题目中有句话说循环必须是 forward 或是 backward 的,如果不给例子说明,不太容易能 get 到 point。所谓的循环必须是一个方向的就是说不能跳到一个数,再反方向跳回来,这不算一个 loop。比如 [1, -1] 就不是一个 loop,而 [1, 1] 是一个正确的 loop。看到论坛中一半的帖子都是各种需要 clarify 和不理解 test case 就感觉很好笑~ 当然博主也成功踩坑了。弄清楚了题意后来考虑如何做,由于从一个位置只能跳到一个别的位置,而不是像图那样一个点可以到多个位置,所以这里我们就可以用个 HashMap 根据坐标建立一对一的映射,一旦某个达到的坐标已经有映射了,说明环存在,当然我们还需要进行一系列条件判断。首先我们需要一个 visited 数组,来记录访问过的数字,然后我们遍历原数组,如果当前数字已经访问过了,直接跳过,否则就以当前位置坐标为起始点开始查找,进行 while 循环,计算下一个位置,计算方法是当前位置坐标加上对应的数字,由于是循环数组,所以结果可能会超出数组的长度,所以我们要对数组长度取余。当然上面的数字也可能是负数,加完以后可能也是负数,所以在取余之前还得再补上一个n,使其变为正数,但是 若这个负数远大于n的话,取余之前只加上一个n,可能是不够的,所以正确的方法是应该先对n取余,再加上n。为了同时把正数的情况也包含进来,最终我们的处理方法是先对n取余,再加上n,再对n取余,这样不管正数还是负数,大小如何,都可以成功的旋转跳跃了。此时我们判断,如果 next 和 cur 相等,说明此时是一个数字的循环,不符合题意,再有就是检查二者的方向,数字是正数表示 forward,若是负数表示 backward,在一个 loop 中必须同正或同负,我们只要让二者相乘,如果结果是负数的话,说明方向不同,直接 break 掉。此时如果 next 已经有映射了,说明我们找到了合法的 loop,返回 true,否则建立一个这样的映射,将 next 位置在 visited 数组中标记 true,继续循环,参见代码如下:

解法一:

class Solution {
public:
bool circularArrayLoop(vector<int>& nums) {
int n = nums.size();
vector<bool> visited(n);
for (int i = ; i < n; ++i) {
if (visited[i]) continue;
visited[i] = true;
unordered_map<int, int> m;
int cur = i;
while (true) {
int next = ((cur + nums[cur]) % n + n) % n;
if (next == cur || nums[next] * nums[cur] < ) break;
if (m.count(next)) return true;
m[cur] = next;
cur = next;
visited[next] = true;
}
}
return false;
}
};

我们也可以不用 visited 数组,直接在 nums 中标记,由于题目中说了 nums 中不会有0,所以可以把访问过的位置标记为0。然后计算 next 位置,对于会超出数组的长度的正数,我们可以通过对n取余,但是对于负数,若这个负数远大于n的话,取余之前只加上一个n,可能是不够的,所以正确的方法是应该先对n取余,再加上n。为了同时把正数的情况也包含进来,最终我们的处理方法是先对n取余,再加上n,再对n取余,这样不管正数还是负数,大小如何,都可以成功的旋转跳跃了。接下来看,如果 next 和i相等,直接跳过,因为这表示循环只有一个数字,不合题意。然后开始循环,当 cur 和 nums[next] 的乘积为正时,说明此时是一个方向的,我们将 cur 赋值为 nums[next],将 nums[next] 赋值为0,表示已经访问过,然后再计算新的 next 位置。直到循环被打破,若此时 next 和i相等,说明有大于1个数字的环存在,返回 true。最终 for 循环退出后,返回 false 即可,代码参见评论区11楼。想法倒是不错,但是存在一个逻辑上的错误。

对于 [1, 1, 2] 这个例子,当 i=0 时,最后跳出 while 循环时 next 是等于1的,和 i 不相等,所以没法返回 true。但这个例子其实是有正确的循环的,是后两个数字1和2可以循环,那为什么我们没 catch 到呢?因为我们前面的思路默认为循环开始的位置就是i,但明显不一定成立的,就像在链表中找环一样,环的起点可以是任意位置啊,不一定是表头结点啊。那为啥上面的解法就可以呢?这是因为上面使用的是 HashMap,而且对于每个i,都使用了一个新的 HashMap,跟之前的并没有联系,而且把以i位置为起点,经过的位置都存到了 HashMap 中,这样是可以找出环的。而这里我们直接跟i相比肯定是不对的。那么既然说到了链表中找环,刷题老司机们大声告诉我该用什么?对了,就是快慢指针了。那么对于每个i位置,慢指针指向i,快指针指向下一个位置,这里调用子函数来计算下一个位置。此时慢指针指向的数要和快指针指向的数正负相同,这个不难理解。并且慢指针指向的数还要跟快指针的下一个位置上的数符号相同,这个原因后面再讲。上面这两个就是 while 循环的条件,我们直到当快慢指针相遇的时候,就是环出现的时候,但是这里有个坑,即便快慢指针相遇了,也不同立马返回 true,因为题目中说了了环的长度必须大于1,所以我们要用慢指针指向的数和慢指针下一个位置上的数比较,若相同,则说明环的长度为1,此时并不返回 false,而且 break 掉 while 循环。因为这只能说以i位置开始的链表无符合要求的环而已,后面可能还会出现符合要求的环。但是若二者不相同的话,则已经找到了符合要求的环,直接返回 true。若快慢指针还不相同的,则分别更新,慢指针走一步,快指针走两步。当 while 循环退出后,我们需要标记已经走过的结点,从而提高运算效率,方法就是将慢指针重置为i,再用一个 while 循环,条件是 nums[i] 和 慢指针指的数正负相同,然后计算下一个位置,并且 nums[slow] 标记为0,并且慢指针移动到 next 位置。最终 for 循环退出后,返回 false 即可,参见代码如下:

解法二:

class Solution {
public:
bool circularArrayLoop(vector<int>& nums) {
int n = nums.size();
for (int i = ; i < n; ++i) {
if (nums[i] == ) continue;
int slow = i, fast = getNext(nums, i), val = nums[i];
while (val * nums[fast] > && val * nums[getNext(nums, fast)] > ) {
if (slow == fast) {
if (slow == getNext(nums, slow)) break;
return true;
}
slow = getNext(nums, slow);
fast = getNext(nums, getNext(nums, fast));
}
slow = i;
while (val * nums[slow] > ) {
int next = getNext(nums, slow);
nums[slow] = ;
slow = next;
}
}
return false;
}
int getNext(vector<int>& nums, int i) {
int n = nums.size();
return (((nums[i] + i) % n) + n) % n;
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/457

参考资料:

https://leetcode.com/problems/circular-array-loop/

https://leetcode.com/problems/circular-array-loop/discuss/94148/Java-SlowFast-Pointer-Solution

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Circular Array Loop 环形数组循环的更多相关文章

  1. [LeetCode] 457. Circular Array Loop 环形数组循环

    You are given a circular array nums of positive and negative integers. If a number k at an index is ...

  2. 【LeetCode】457. Circular Array Loop 环形数组是否存在循环 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题思路 快慢指针 代码 日期 题目地址:https://le ...

  3. Leetcode: Circular Array Loop

    You are given an array of positive and negative integers. If a number n at an index is positive, the ...

  4. Leetcode 457.环形数组循环

    环形数组循环 给定一组含有正整数和负整数的数组.如果某个索引中的 n 是正数的,则向前移动 n 个索引.相反,如果是负数(-n),则向后移动 n 个索引. 假设数组首尾相接.判断数组中是否有环.环中至 ...

  5. Java实现 LeetCode 457 环形数组循环

    457. 环形数组循环 给定一个含有正整数和负整数的环形数组 nums. 如果某个索引中的数 k 为正数,则向前移动 k 个索引.相反,如果是负数 (-k),则向后移动 k 个索引.因为数组是环形的, ...

  6. LeetCode 457. Circular Array Loop

    原题链接在这里:https://leetcode.com/problems/circular-array-loop/ 题目: You are given a circular array nums o ...

  7. [Swift]LeetCode457. 环形数组循环 | Circular Array Loop

    You are given an array of positive and negative integers. If a number n at an index is positive, the ...

  8. [LeetCode] Design Circular Deque 设计环形双向队列

    Design your implementation of the circular double-ended queue (deque). Your implementation should su ...

  9. [LeetCode] Design Circular Queue 设计环形队列

    Design your implementation of the circular queue. The circular queue is a linear data structure in w ...

随机推荐

  1. Android layout属性之gravity和layout_gravity

    1. gravity用来描述当前view的内容在view中的位置. gravity是控制其内容或者包含的views在该view(或view group)中的位置 2. layout_gravity是表 ...

  2. 基于python的接口自动化测试+ddt数据驱动

    在测试接口时,一个接口会先写好测试用例,这个用例主要针对功能,传入参时考虑到各种场景,正常的,异常的,如:参数缺省,参数传一个六位数字写用例时考虑边界情况等. 一个接口设计用例时有可能会十几条到几十条 ...

  3. R语言-逻辑回归建模

    案例1:使用逻辑回归模型,预测客户的信用评级 数据集中采用defect为因变量,其余变量为自变量 1.加载包和数据集 library(pROC) library(DMwR)model.df <- ...

  4. C语言的第一次博客作业

    题目1:7-3 温度转换 1.代码 int fahr, celsius; fahr=150; celsius=5*(fahr-32)/9; printf("fahr = %d, celsiu ...

  5. 《Language Implementation Patterns》之访问&重写语法树

    每个编程的人都学习过树遍历算法,但是AST的遍历并不是开始想象的那么简单.有几个因素会影响遍历算法:1)是否拥有节点的源码:2)是否子节点的访问方式是统一的:3)ast是homogeneous或het ...

  6. RxSwift:ReactiveX for Swift 翻译

    RxSwift:ReactiveX for Swift 翻译 字数1787 阅读269 评论3 喜欢3 图片发自简书App RxSwift | |-LICENSE.md |-README.md |-R ...

  7. python 面向对象之继承与派生

    一:初识继承 1,什么是继承? 继承指的是类与类之间的关系,是一种什么"是"什么的关系,继承的功能之一就是用来解决代码重用问题 继承是一种创建新类的方式,在python中,新建的类 ...

  8. crontab的mysqldump备份任务未能完全正确执行的故障处理

    crontab是每个运维一线人员必须掌握的技术,熟练运用crontab可以自动帮助我们执行重复性的工作,提高运维的工作效率.它就像一个闹钟,在特定的时间,准时响应并执行相应的任务.如果你的工作经常与L ...

  9. 多台linux主机之间建立免密通信

    多台linux主机之间设置免密通信 例:A 向 B设置免密通信 第一步 A生成密钥对 ssh-keygen -t rsa -t 指定秘钥的类型 rsa 秘钥类型 密钥目录:/root/.ssh/(如果 ...

  10. 新概念英语(1-a)句子集锦