LeetCode 第 565 题,又称数组嵌套,是一道考察对数组元素之间关系的理解和算法应用能力的中等难度题目。本文将带你深入剖析这道题的题意,探讨使用深度优先搜索 (DFS) 算法解决该问题的原理,并提供 Java 和 Python 的详细代码示例。此外,我们还将分析该算法的时间复杂度和空间复杂度,并探讨如何优化代码,使其更加高效。通过本文的学习,你将不仅掌握 LeetCode 565 题的解法,更能够提升对数组和 DFS 算法的理解和应用能力。
关键点
理解数组嵌套的含义,即将数组元素作为索引,形成链式关系。
掌握深度优先搜索 (DFS) 算法,并能将其应用于解决图或树的遍历问题。
能够分析算法的时间复杂度和空间复杂度,并根据实际情况进行优化。
熟悉 Java 和 Python 等编程语言,并能够编写清晰、高效的代码。
数组嵌套问题详解
什么是数组嵌套?
数组嵌套问题,给定一个包含 n 个整数的数组 nums,其中 nums[i] 的取值范围在 [0, n-1] 之间,且不存在重复的数字。可以将数组元素 nums[i] 作为索引,构成一个链式关系:i -> nums[i] -> nums[nums[i]] -> ... 。这个链式关系最终会形成一个环。我们的目标是找到最长的环的长度。
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

举例说明: 假设 nums = [5, 4, 0, 3, 1, 6, 2],从索引 0 开始,我们可以得到如下的链式关系: 0 -> nums[0] = 5 5 -> nums[5] = 6 6 -> nums[6] = 2 2 -> nums[2] = 0
最终形成一个环:0 -> 5 -> 6 -> 2 -> 0,该环的长度为 4。
我们需要遍历整个数组,找到所有可能的环,并返回最长环的长度。
问题分析与解题思路
解决数组嵌套问题的关键在于理解数组元素之间的索引关系,并将问题转化为寻找最长环的问题。我们可以采用以下几种解题思路:
-
暴力搜索:遍历数组的每个元素,以该元素为起点,沿着链式关系进行搜索,直到遇到重复的元素或超出数组范围。记录每个环的长度,并返回最长环的长度。该方法的时间复杂度较高,为 O(n^2)。
-
深度优先搜索 (DFS):从数组的每个元素开始,进行深度优先搜索,记录搜索路径上的元素。如果搜索过程中遇到重复的元素,则表示找到了一个环,计算环的长度。为了避免重复搜索,可以使用一个 visited 数组来标记已经访问过的元素。
-
并查集:将数组中的每个元素看作一个节点,如果 i -> nums[i],则将节点 i 和节点 nums[i] 合并到同一个集合中。最终,每个集合代表一个环,计算最大集合的元素个数即可。

深度优先搜索 (DFS) 算法详解
DFS 算法原理
深度优先搜索 (DFS) 是一种用于遍历或搜索树或图的算法。该算法沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点 v 的所有边都己被探寻过,搜索将回溯到发现节点 v 的起始节点的那些未被访问的节点。该算法在图形搜索和人工智能等领域都有广泛的应用。

DFS 算法步骤:
- 从起始节点开始,访问该节点。
- 标记该节点为已访问。
- 选择该节点的一个未被访问的邻接节点,并从该邻接节点开始递归执行 DFS 算法。
- 如果当前节点的所有邻接节点都己被访问,则回溯到该节点的父节点,继续搜索其他未被访问的邻接节点。
- 重复以上步骤,直到所有节点都被访问。
使用 DFS 解决数组嵌套问题
在数组嵌套问题中,我们可以将数组看作一个图,数组的索引作为图的节点,数组元素之间的索引关系作为图的边。然后,我们可以使用 DFS 算法来遍历图,寻找最长的环。
使用 DFS 算法解决数组嵌套问题的步骤:
- 创建一个 visited 数组,用于标记已经访问过的节点。
- 遍历数组的每个元素,如果该元素未被访问,则从该元素开始进行 DFS 搜索。
- 在 DFS 搜索过程中,记录搜索路径上的元素。如果搜索过程中遇到重复的元素,则表示找到了一个环,计算环的长度。
- 更新最长环的长度。
- 返回最长环的长度。

DFS 算法能够系统性地探索数组中隐含的图结构,沿着每一条链尽可能深入地探索,然后回溯,有效地检测到所有可能的环。
Java 代码示例

class Solution {
public int arrayNesting(int[] nums) {
int n = nums.length;
boolean[] visited = new boolean[n];
int maxLength = 0;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
int start = i;
int count = 0;
do {
visited[start] = true;
start = nums[start];
count++;
} while (start != i);
maxLength = Math.max(maxLength, count);
}
}
return maxLength;
}
}
代码解释:
-
arrayNesting(int[] nums): 这是解决数组嵌套问题的函数,它接受一个整数数组nums作为输入,并返回嵌套数组的最大长度。 -
int n = nums.length;: 这行代码获取输入数组nums的长度,并将该长度存储在变量n中。这用于设置循环的边界和访问数组元素。 -
boolean[] visited = new boolean[n];: 在此创建了一个名为visited的布尔数组。此数组的大小与输入数组nums相同,用于跟踪在遍历期间已访问的数组索引。最初,所有元素都设置为false,表明没有任何索引被访问过。 -
int maxLength = 0;: 初始化变量maxLength为 0。此变量用于跟踪在数组中找到的最大嵌套数组的长度。当算法遍历nums数组时,此变量会更新。 -
for (int i = 0; i : 此for循环从索引 0 迭代到索引n - 1,有效地遍历输入数组nums中的每个索引。变量i表示当前索引。 -
if (!visited[i]): 此条件检查当前索引i是否已在先前迭代中访问过。如果visited[i]为false,则表示该索引尚未被访问,并且该算法将继续探索从该索引开始的嵌套数组。 -
int start = i;声明一个名为start的整数变量并将i的值赋给它。
-
int count = 0;: 初始化count变量为 0。此变量用于跟踪当前嵌套数组的长度。 -
do { ... } while (start != i);:这是一个do-while循环,它遵循嵌套数组,直到返回到起始索引i,表明已经完成了循环。 -
visited[start] = true;: 在do-while循环内,此行代码将visited[start]设置为true,表明已访问当前索引start。这可以防止算法无限循环并确保每个索引只访问一次。 -
start = nums[start];: 这行代码将start变量更新为nums[start]处的值。这会沿着嵌套数组移动到下一个索引。 -
count++;: 在每次迭代中,此行代码将count变量增加 1,从而跟踪当前嵌套数组的长度。 -
maxLength = Math.max(maxLength, count);: 在do-while循环结束后,此行代码使用Math.max()函数将maxLength更新为当前maxLength值和变量count之间的较大值。这样可以确保maxLength始终包含算法找到的最大嵌套数组的长度。 -
return maxLength;: 在for循环结束后,此行代码返回maxLength,即在数组中找到的最大嵌套数组的长度。 总而言之,该算法使用嵌套数组来找到最大嵌套数组的长度。为了避免多次访问相同的数组,使用了辅助的visited[]数组。
Python 代码示例
class Solution:
def arrayNesting(self, nums: List[int]) -> int:
n = len(nums)
visited = [False] * n
max_length = 0
for i in range(n):
if not visited[i]:
start = i
count = 0
while not visited[start]:
visited[start] = True
start = nums[start]
count += 1
max_length = max(max_length, count)
return max_length
代码解释:
-
def arrayNesting(self, nums: List[int]) -> int:: 这是解决数组嵌套问题的 Python 函数。它接受一个整数列表nums作为输入,并返回嵌套数组的最大长度。 -
n = len(nums): 这行代码获取输入列表nums的长度,并将该长度存储在变量n中。 -
visited = [False] * n: 创建了一个名为visited的布尔列表。此列表的大小与输入列表nums相同,用于跟踪在遍历期间已访问的列表索引。最初,所有元素都设置为False,表示没有任何索引被访问过。 -
max_length = 0: 初始化变量max_length为 0。此变量用于跟踪在数组中找到的最大嵌套数组的长度。当算法遍历nums数组时,此变量会更新。 -
for i in range(n):: 此for循环从索引 0 迭代到索引n - 1,有效地遍历输入列表nums中的每个索引。变量i表示当前索引。 -
if not visited[i]:: 此条件检查当前索引i是否已在先前迭代中访问过。如果visited[i]为False,则表示该索引尚未被访问,并且该算法将继续探索从该索引开始的嵌套数组。 -
start = i: 声明一个名为 start 的整数变量并将 i 的值赋给它。 -
count = 0: 初始化count变量为 0。此变量用于跟踪当前嵌套数组的长度。 -
while not visited[start]:: 这是一个while循环,它遵循嵌套数组,直到访问了已经访问过的值。 -
visited[start] = True: 在while循环内,此行代码将visited[start]设置为true,表明已访问当前索引start。这可以防止算法无限循环并确保每个索引只访问一次。 -
start = nums[start]:这行代码将start变量更新为nums[start]处的值。这会沿着嵌套数组移动到下一个索引。 -
count += 1: 在每次迭代中,此行代码将count变量增加 1,从而跟踪当前嵌套数组的长度。 -
max_length = max(max_length, count): 在while循环结束后,此行代码使用max()函数将max_length更新为当前max_length值和变量count之间的较大值。这样可以确保max_length始终包含算法找到的最大嵌套数组的长度。 -
return max_length: 在for循环结束后,此行代码返回max_length,即在数组中找到的最大嵌套数组的长度。 与 Java 示例类似,此 Python 代码遵循 DFS 方法来确定输入数组中最长的嵌套数组的长度。通过使用 visited[] 数组,此实现避免了再次检查相同的索引,从而优化了性能。
代码总结
无论是 Java 还是 Python 代码,都采用了深度优先搜索 (DFS) 算法来解决数组嵌套问题。它们的核心思想是:从数组的每个元素开始,沿着链式关系进行搜索,直到找到一个环。为了避免重复搜索,都使用了 visited 数组来标记已经访问过的元素。
两种语言的代码实现逻辑基本一致,只是在语法上有所差异。Java 代码使用了 do-while 循环,而 Python 代码使用了 while 循环。此外,Python 代码使用了列表来表示 visited 数组,而 Java 代码使用了布尔数组。
如何应用 DFS 解决实际问题
DFS 在其他场景的应用
深度优先搜索 (DFS) 算法不仅可以用于解决数组嵌套问题,还可以应用于解决许多其他实际问题,例如:
- 图的遍历:DFS 可以用于遍历图的所有节点,例如查找图中是否存在环、寻找两个节点之间的路径等。
- 树的遍历:DFS 可以用于遍历树的所有节点,例如计算树的深度、寻找树的叶子节点等。
- 迷宫求解:DFS 可以用于求解迷宫问题,例如寻找从起点到终点的路径。
- 游戏 AI:DFS 可以用于游戏 AI 中,例如搜索最佳的游戏策略。
DFS 的优势与劣势:
优势:
- 实现简单,代码量较少。
- 空间复杂度较低,只需要维护搜索路径上的节点。
- 能够快速找到解,尤其是在解位于搜索树的较深层时。
劣势:
- 可能陷入无限循环,需要使用 visited 数组来避免。
- 不一定能找到最优解,例如在寻找最短路径时,DFS 找到的路径可能不是最短的。
- 容易受到搜索顺序的影响,不同的搜索顺序可能导致不同的结果。
DFS 算法的优缺点分析
? Pros实现简单,代码量较少
空间复杂度较低,只需要维护搜索路径上的节点
能够快速找到解,尤其是在解位于搜索树的较深层时
? Cons可能陷入无限循环,需要使用 visited 数组来避免
不一定能找到最优解,例如在寻找最短路径时,DFS 找到的路径可能不是最短的
容易受到搜索顺序的影响,不同的搜索顺序可能导致不同的结果
常见问题解答
为什么需要使用 visited 数组?
使用 visited 数组是为了避免重复搜索,防止算法陷入无限循环。在 DFS 搜索过程中,如果遇到重复的元素,则表示找到了一个环。如果没有 visited 数组,算法将无法判断是否已经访问过该元素,从而陷入无限循环。
可以使用其他算法来解决数组嵌套问题吗?
是的,除了 DFS 算法,还可以使用暴力搜索和并查集等算法来解决数组嵌套问题。但是,DFS 算法通常具有更好的时间复杂度,因此是解决该问题的首选方法。
如何优化 DFS 算法?
可以使用以下几种方法来优化 DFS 算法: 使用 visited 数组来避免重复搜索。 使用迭代版本的 DFS 算法,可以避免递归调用带来的开销。 使用启发式搜索,例如 A* 算法,可以更快地找到解。
相关问题
除了 LeetCode 565 题,还有哪些 LeetCode 题目可以使用 DFS 算法解决?
LeetCode 上有许多题目可以使用 DFS 算法解决,以下是一些例子: 200. 岛屿数量 130. 被围绕的区域 695. 岛屿的最大面积 417. 太平洋大西洋水流问题 733. 图像渲染 547. 省份数量 这些题目都涉及到图或树的遍历,可以使用 DFS 算法来解决。










