题目描述
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
涉及tag
并查集;动态规划
算法思路
方法1:
遍历数组中的元素,设置一个cur,保存nums[i]持续+1的数字(保证连续),去查找cur是否在数组中(判断cur == nums[j]可以设置boolean变量flags辅助判断),如果一直加到x都还在,返回结果为x + 1(算上一开始的nums[i]本身)
方法2:并查集
方法3:并查集优化
方法4:对方法1的优化,避免同一个结合中的后面数字进入while循环
示例代码1
class Solution {
public int longestConsecutive(int[] nums) {
int n = nums.length;
int ans = 0;
// 遍历数组中的每个元素num
for (int i = 0; i < n; i++) {
// 以num为起点,每次+1向后遍历num+1,num+2,num+3...
int cur = nums[i] + 1;
while (true) {
// 标识是否在数组中找到了cur
boolean flag = false;
// 在数组中找cur
for (int j = 0; j < n; j++) {
if (nums[j] == cur) {
flag = true;
break;
}
}
if (!flag) {
break;
}
cur += 1;
}
ans = Math.max(ans, cur - nums[i]);
}
return ans;
}
}
示例代码2
class UnionFind {
// 记录每个节点的父节点
private Map<Integer, Integer> parent;
public UnionFind(int[] nums) {
parent = new HashMap<>();
// 初始化父节点为自身
for (int num : nums) {
parent.put(num, num);
}
}
// 寻找x的父节点,实际上也就是x的最远连续右边界,这点类似于方法2
public Integer find(int x) {
// nums不包含x
if (!parent.containsKey(x)) {
return null;
}
// 遍历找到x的父节点
while (x != parent.get(x)) {
// 进行路径压缩,不写下面这行也可以,但是时间会慢些
parent.put(x, parent.get(parent.get(x)));
x = parent.get(x);
}
return x;
}
// 合并两个连通分量,在本题中只用来将num并入到num+1的连续区间中
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) {
return;
}
parent.put(rootX, rootY);
}
}
class Solution {
public int longestConsecutive(int[] nums) {
UnionFind uf = new UnionFind(nums);
int ans = 0;
for (int num : nums) {
// 当num+1存在,将num合并到num+1所在集合中
if (uf.find(num + 1) != null) {
uf.union(num, num + 1);
}
}
for (int num : nums) {
// 找到num的最远连续右边界
int right = uf.find(num);
ans = Math.max(ans, right - num + 1);
}
return ans;
}
}
示例代码3(并查集哈希优化)
class UnionFind {
// 记录每个节点的父节点
private Map<Integer, Integer> parent;
// 记录节点所在连通分量的节点个数
private Map<Integer, Integer> count;
public UnionFind(int[] nums) {
parent = new HashMap<>();
count = new HashMap<>();
// 初始化父节点为自身
for (int num : nums) {
parent.put(num, num);
count.put(num, 1);
}
}
// 寻找x的父节点,实际上也就是x的最远连续右边界
public Integer find(int x) {
if (!parent.containsKey(x)) {
return null;
}
// 遍历找到x的父节点
while (x != parent.get(x)) {
// 进行路径压缩
parent.put(x, parent.get(parent.get(x)));
x = parent.get(x);
}
return x;
}
// 合并两个连通分量,用来将num并入到num+1的连续区间中
// 返回值为x所在连通分量的节点个数
public int union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) {
return count.get(rootX);
}
parent.put(rootX, rootY);
// 更新该根结点连通分量的节点个数
count.put(rootY, count.get(rootX) + count.get(rootY));
return count.get(rootY);
}
}
class Solution {
public int longestConsecutive(int[] nums) {
// 去除nums为空的特例
if (nums == null || nums.length == 0) {
return 0;
}
UnionFind uf = new UnionFind(nums);
int ans = 1;
// 一次遍历即可
for (int num : nums) {
if (uf.find(num + 1) != null) {
// union会返回num所在连通分量的节点个数
ans = Math.max(ans, uf.union(num, num + 1));
}
}
return ans;
}
}
示例代码4
class Solution {
public int longestConsecutive(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, num);
}
int ans = 0;
for (int num : nums) {
if (map.containsKey(num)) {
int right = map.get(num);
while (map.containsKey(right + 1)) {
map.remove(right);
right = map.get(right + 1);
}
map.put(num, right);
ans = Math.max(ans, right - num + 1);
}
}
return ans;
}
}
示例代码5
哈希集合,可能有多个相同最小数存在
class Solution {
public int longestConsecutive(int[] nums) {
// 建立一个存储所有数的哈希表,同时起到去重功能
Set<Integer> set = new HashSet<>();
for (int num : nums) {
set.add(num);
}
int ans = 0;
// 遍历去重后的所有数字
for (int num : set) {
int cur = num;
// 只有当num-1不存在时,才开始向后遍历num+1,num+2,num+3......
if (!set.contains(cur - 1)) {
while (set.contains(cur + 1)) {
cur++;
}
}
// [num, cur]之间是连续的,数字有cur - num + 1个
ans = Math.max(ans, cur - num + 1);
}
return ans;
}
}