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. Java 并发编程实践基础 读书笔记: 第一章 JAVA并发编程实践基础

    1.创建线程的方式: /** * StudySjms * <p> * Created by haozb on 2018/2/28. */ public class ThreadDemo e ...

  2. SpringBoot12 QueryDSL01之QueryDSL介绍、springBoot项目中集成QueryDSL

    1 QueryDSL介绍 1.1 背景 QueryDSL的诞生解决了HQL查询类型安全方面的缺陷:HQL查询的扩展需要用字符串拼接的方式进行,这往往会导致代码的阅读困难:通过字符串对域类型和属性的不安 ...

  3. [BZOJ 1079][SCOI 2008]着色方案

    1079: [SCOI2008]着色方案 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2237  Solved: 1361[Submit][Stat ...

  4. Leetcode 14——Longest Common Prefix

    题目:Write a function to find the longest common prefix string amongst an array of strings. 很简单的一个描述,最 ...

  5. JavaScript(第二十九天)【js处理XML】

    随着互联网的发展,Web应用程序的丰富,开发人员越来越希望能够使用客户端来操作XML技术.而XML技术一度成为存储和传输结构化数据的标准.所以,本章就详细探讨一下JavaScript中使用XML的技术 ...

  6. 听翁恺老师mooc笔记(7)--字符串1

    C语言中字符串的定义 如果定义一个字符数组word,并使用大括号对其初始化,如下图所示: 但是这个不是C语言的字符串,只是字符数组,不是字符串,因为不能使用字符串的方式进行计算.那么C语言的字符串长什 ...

  7. bug终结者 团队作业第六、七周

    bug终结者 团队作业第六.七周 作业要求:团队作业第六.七周 博客编辑:20162322 朱娅霖 一.修改<需求规格说明书> <需求规格说明书>2.0版(即初稿) <需 ...

  8. Build to win

    UPDATE:看到周筠老师的评论里的链接,那版式真的非常舒服.我想想模仿模仿他的布局来看看,虽然感觉做的也不是太好.另外对博客内容稍作修改. 一.获得小黄衫的感受 很幸运能够获得"领骑衫&q ...

  9. VS2013 重装 无法打开项目

    今天遇到的奇葩BUG,耗时我一下午,现在跟大家说道说道. 今天重装系统,让各种开发环境开发工具自然要重装一次,最后装完VS2013,然后刚好客户打电话要改点东西,然后我就双击项目准备打开改,然后奇葩来 ...

  10. hadoop2.6.0实践:引入开发依赖的jar包

    hadoop-2.5.0\share\hadoop\common  所有jar,hadoop-2.5.0\share\hadoop\common\lib  所有jar,hadoop-2.5.0\sha ...