1. 题目
读题
考查点
2. 解法
两种解法
- 使用hashSet
- 使用并查集
解法一:使用hashSet
思路
这个解法的核心思想是
- 找出数组中的所有连续序列的起点,
- 然后从每个起点开始向后延伸,计算出每个序列的长度,并更新最长长度。
- 为了找出所有的起点,我们需要判断一个元素是否有前驱,即它的前一个元素是否在数组中。
- 如果没有前驱,说明它是一个起点,
- 否则它已经被之前的序列包含了,不需要再考虑。
- 为了快速判断一个元素是否有前驱,我们使用了HashSet来存储数组中的所有元素,并利用它的O(1)的查找操作。
- 为了从每个起点开始向后延伸,我们需要判断一个元素是否有后继,即它的后一个元素是否在数组中。
- 如果有后继,说明序列还可以继续,否则序列结束了。
- 我们同样使用HashSet来判断一个元素是否有后继,并用一个变量来记录当前序列的长度。
- 每次找到一个新的起点,我们就重置这个变量为1,并开始向后延伸,每次延伸时将这个变量加1,并与最长长度比较,
- 如果更大就更新最长长度。
- 这样,我们就可以在一次遍历数组的过程中,找出最长连续元素序列的长度。
代码逻辑
- 第1-4行,我们创建了一个HashSet,并将数组中的所有元素添加到其中,这样我们就可以在O(1)的时间内判断一个元素是否在数组中。
- 第6行,我们初始化了一个变量longest,用来记录最长连续元素序列的长度,初始值为0。
- 第8-18行,我们遍历了数组中的每个元素,对于每个元素num,我们执行以下操作:
- 第9-10行,我们检查num是否是一个连续序列的起点,即它的前一个元素num-1是否不在HashSet中。如果是,我们就进入第11-17行的逻辑,否则我们就跳过这个元素,因为它已经被之前的序列包含了。
- 第11-16行,我们从num开始向后寻找连续的元素,并记录当前序列的长度。我们用一个变量current来表示当前访问的元素,初始值为num,用一个变量length来表示当前序列的长度,初始值为1。然后我们进入一个循环,每次循环中,我们检查current+1是否在HashSet中,如果是,说明序列还可以继续,我们就将current和length都加1,并继续循环;如果不是,说明序列结束了,我们就跳出循环。
- 第17行,我们将当前序列的长度和最长长度进行比较,如果当前序列更长,就更新最长长度。
- 第19行,我们返回最长长度作为答案。
具体实现
class Solution { public int longestConsecutive(int[] nums) { // 创建一个HashSet存储数组中的所有元素 Set<Integer> set = new HashSet<>(); for (int num : nums) { set.add(num); } // 初始化最长长度为0 int longest = 0; // 遍历数组 for (int num : nums) { // 检查num是否是一个连续序列的起点 if (!set.contains(num - 1)) { // 从num开始向后寻找连续的元素 int current = num; int length = 1; while (set.contains(current + 1)) { current++; length++; } // 更新最长长度 longest = Math.max(longest, length); } } // 返回最长长度 return longest; } }
解法二:使用并查集
思路
这个问题是要找到一个无序数组中最长的连续序列的长度。
你可以使用并查集(union find)的数据结构来解决这个问题。并查集是一种能够快速合并和查询集合的数据结构。
- 你可以用一个数组来表示每个元素所属的集合,
- 初始时每个元素都是自己的集合。
- 然后你可以遍历数组,对于每个元素,
- 如果它的前一个或后一个元素也在数组中,就把它们所属的集合合并起来。
- 合并的时候,你需要找到每个集合的根节点,然后把其中一个根节点指向另一个根节点。
- 同时,你还需要维护一个哈希表,记录每个集合的大小。
- 最后,你只需要遍历哈希表,找到最大的集合大小,就是答案了。
具体实现
class Solution { public int longestConsecutive(int[] nums) { // 如果数组为空,返回0 if (nums == null || nums.length == 0) return 0; // 初始化并查集数组和哈希表 int n = nums.length; int[] parent = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; } HashMap<Integer, Integer> map = new HashMap<>(); // key: 数组元素,value: 元素在并查集数组中的索引 HashMap<Integer, Integer> size = new HashMap<>(); // key: 集合根节点在并查集数组中的索引,value: 集合大小 // 遍历数组 for (int i = 0; i < n; i++) { int num = nums[i]; // 如果元素已经出现过,跳过 if (map.containsKey(num)) continue; // 把元素和它在并查集数组中的索引放入哈希表 map.put(num, i); // 初始化该元素所属集合的大小为1 size.put(i, 1); // 如果元素的前一个或后一个元素也在数组中,就把它们所属的集合合并起来 if (map.containsKey(num - 1)) { union(parent, size, map.get(num - 1), i); } if (map.containsKey(num + 1)) { union(parent, size, map.get(num + 1), i); } } // 遍历哈希表,找到最大的集合大小 int max = 0; for (int s : size.values()) { max = Math.max(max, s); } return max; } // 合并两个集合的函数 private void union(int[] parent, HashMap<Integer, Integer> size, int x, int y) { // 找到两个集合的根节点 int rootX = find(parent, x); int rootY = find(parent, y); // 如果根节点相同,说明已经在同一个集合中,不需要合并 if (rootX == rootY) return; // 把其中一个根节点指向另一个根节点 parent[rootX] = rootY; // 更新合并后的集合大小 size.put(rootY, size.get(rootX) + size.get(rootY)); } // 找到一个元素所属集合的根节点的函数 private int find(int[] parent, int x) { // 如果元素不是自己的父节点,就沿着父节点向上找,直到找到根节点 while (x != parent[x]) { // 顺便把沿途的元素都指向根节点,压缩路径,加速查找 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } }