leetcode
leetcode 401 ~ 450
环形数组是否存在循环

环形数组是否存在循环

难度:

标签:

题目描述

存在一个不含 0 环形 数组 nums ,每个 nums[i] 都表示位于下标 i 的角色应该向前或向后移动的下标个数:

  • 如果 nums[i] 是正数,向前(下标递增方向)移动 |nums[i]|
  • 如果 nums[i] 是负数,向后(下标递减方向)移动 |nums[i]|

因为数组是 环形 的,所以可以假设从最后一个元素向前移动一步会到达第一个元素,而第一个元素向后移动一步会到达最后一个元素。

数组中的 循环 由长度为 k 的下标序列 seq 标识:

  • 遵循上述移动规则将导致一组重复下标序列 seq[0] -> seq[1] -> ... -> seq[k - 1] -> seq[0] -> ...
  • 所有 nums[seq[j]] 应当不是 全正 就是 全负
  • k > 1

如果 nums 中存在循环,返回 true ;否则,返回 false

 

示例 1:

输入:nums = [2,-1,1,2,2]
输出:true
解释:存在循环,按下标 0 -> 2 -> 3 -> 0 。循环长度为 3 。

示例 2:

输入:nums = [-1,2]
输出:false
解释:按下标 1 -> 1 -> 1 ... 的运动无法构成循环,因为循环的长度为 1 。根据定义,循环的长度必须大于 1 。

示例 3:

输入:nums = [-2,1,-1,-2,-2]
输出:false
解释:按下标 1 -> 2 -> 1 -> ... 的运动无法构成循环,因为 nums[1] 是正数,而 nums[2] 是负数。
所有 nums[seq[j]] 应当不是全正就是全负。

 

提示:

  • 1 <= nums.length <= 5000
  • -1000 <= nums[i] <= 1000
  • nums[i] != 0

 

进阶:你能设计一个时间复杂度为 O(n) 且额外空间复杂度为 O(1) 的算法吗?

代码结果

运行时间: 23 ms, 内存: 16.1 MB


// Java Stream solution
// 思路:
// 1. 使用Stream API,处理数组元素。
// 2. 计算每个元素的移动方向和步数。
// 3. 利用Stream进行循环检测,返回结果。
// 由于Stream API在处理复杂逻辑时并不十分直观,因此这里示例代码主要展示Java流的简单使用。
 
import java.util.stream.IntStream;
 
public class StreamSolution {
    public boolean circularArrayLoop(int[] nums) {
        return IntStream.range(0, nums.length).anyMatch(i -> {
            int slow = i, fast = i;
            boolean isForward = nums[i] > 0;
 
            while (true) {
                slow = nextIndex(nums, slow, isForward);
                fast = nextIndex(nums, fast, isForward);
                if (fast != -1) {
                    fast = nextIndex(nums, fast, isForward);
                }
 
                if (slow == -1 || fast == -1 || slow == fast) break;
            }
 
            return slow != -1 && slow == fast;
        });
    }
 
    private int nextIndex(int[] nums, int current, boolean isForward) {
        if ((nums[current] > 0) != isForward) return -1;
        int n = nums.length;
        int next = ((current + nums[current]) % n + n) % n;
        if (next == current) return -1;
        return next;
    }
}
 

解释

方法:

本题的解决方案采用了快慢指针的思想。通过快慢两个指针在环形数组中移动,如果存在循环,它们最终会相遇。同时,在移动过程中也要检查移动的方向是否一致,只有方向一致的情况下才可能形成循环。对于非循环的元素,将其标记为0,避免重复访问。最后,如果遍历完整个数组都没有发现循环,则返回 false。

时间复杂度:

O(n^2)

空间复杂度:

O(1)

代码细节讲解

🦆
为什么在检查快慢指针相遇时需要检查循环长度是否大于1?循环长度为1的情况具体是如何处理的?
在检查快慢指针相遇时需要检查循环长度是否大于1,因为循环长度为1意味着某个元素指向自己,形成一个自环,而这不符合题目要求的循环定义,题目要求的循环必须包含多个元素。当检测到快慢指针相遇时,我们需要进一步检查是否slow_pointer等于get_next_index(slow_pointer),如果相等,说明循环长度为1,这种情况下我们会跳出循环并将该元素标记为0,避免后续算法再次访问到它。
🦆
在算法中,你是如何确保快慢指针在移动时始终保持方向一致的?
在算法中,为了确保快慢指针在移动时始终保持方向一致,我们通过检查当前元素与其下一个索引位置的元素的乘积是否大于0来进行判断。具体地,只有当nums[slow_pointer] * nums[fast_pointer] > 0 且 nums[slow_pointer] * nums[get_next_index(fast_pointer)] > 0时,快慢指针才会继续移动。这个条件确保了快慢指针访问的元素始终有相同的正负符号,从而保持了移动的方向一致性。
🦆
标记元素为0的策略是如何帮助提高算法效率的?是否存在可能误标记某些情况导致未能正确识别循环的风险?
标记元素为0的策略能有效地避免算法重复访问已确定不会形成有效循环的元素,从而提高整体算法的效率。一旦确定某元素所在路径不可能形成题目要求的循环,我们将此路径上的所有元素标记为0。这个策略假设一旦元素被访问过并确定不会形成循环,其后续再被遍历到的可能性也不会形成循环。在实际操作中,只有在确认一条路径不可能形成循环后才会进行标记,因此不存在因误标记而未能正确识别循环的风险。
🦆
算法中使用了`get_next_index`函数来处理环形数组的索引,能详细说明其计算逻辑和边界处理吗?
在环形数组中,`get_next_index`函数用于计算当前索引的下一个位置,考虑到数组的环形特性。该函数通过(current_index + nums[current_index]) % length来计算。这里current_index是当前索引,nums[current_index]是当前索引位置的值,表示要向前或向后移动的步数,length是数组的长度。使用取模运算确保得到的结果始终落在数组的有效索引范围内,即使计算结果为负数,取模后也能正确反映环形结构的性质。这种方式有效处理了数组边界的问题,确保索引的环形逻辑。

相关问题