LeetCode算法题详解 128:最长连续序列

目录

  • [1 问题描述](#1 问题描述)
  • 2.问题分析
  • 3.算法设计与实现
    • [3.1 哈希集合法(最优解法)](#3.1 哈希集合法(最优解法))
    • [4.2 哈希映射法](#4.2 哈希映射法)
    • [4.3 排序法(对比参考)](#4.3 排序法(对比参考))
    • [4.4 并查集法](#4.4 并查集法)
    • [4.5 区间DP法](#4.5 区间DP法)
    • [4.6 图+BFS法(图论思路)](#4.6 图+BFS法(图论思路))
  • 4.性能分析对比
    • [4.1 复杂度对比](#4.1 复杂度对比)
    • [4.2 性能分析](#4.2 性能分析)
    • [4.3 选择建议](#4.3 选择建议)
  • 5.边界情况处理
    • [5.1 常见边界情况](#5.1 常见边界情况)
    • [5.2 边界测试用例](#5.2 边界测试用例)
    • [5.3 边界处理技巧](#5.3 边界处理技巧)
  • 6.扩展与变体
    • [6.1 返回最长连续序列本身](#6.1 返回最长连续序列本身)
    • [6.2 允许间隔k的"连续"序列](#6.2 允许间隔k的"连续"序列)
    • [6.3 动态添加元素的最长连续序列](#6.3 动态添加元素的最长连续序列)
    • [6.4 二维矩阵中的最长连续序列](#6.4 二维矩阵中的最长连续序列)
  • 7.总结
    • [7.1 核心知识点总结](#7.1 核心知识点总结)
    • [7.2 算法思维提升](#7.2 算法思维提升)
    • [7.3 实际应用场景](#7.3 实际应用场景)
    • [7.4 面试建议](#7.4 面试建议)

1 问题描述

问题描述

给定一个未排序的整数数组 nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。要求设计并实现时间复杂度为 O(n) 的算法。

示例1

复制代码
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4],长度为 4

示例2

复制代码
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

示例3

复制代码
输入:nums = [1,0,1,2]
输出:3

提示:

  • 0 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9

2.问题分析

理解连续序列

  • 序列中的数字按值递增1排列
  • 序列元素在原数组中可以不连续
  • 序列必须是严格递增的,差值必须为1
  • 序列长度至少为1(单个元素也是连续序列)

关键挑战

  1. 时间复杂度要求O(n):不能使用排序(O(n log n))
  2. 数组可能非常大:最多10^5个元素
  3. 数值范围大 :-109到109
  4. 可能包含重复元素:需要正确处理重复值
  5. 序列不要求在原数组中连续:需要全局视角

核心洞察

要找到最长连续序列,我们需要快速判断一个数字的前驱(num-1)和后继(num+1)是否存在。哈希表/集合提供了O(1)的查找能力。

破题关键

  1. 连续序列的起点特征:数字num是某个连续序列的起点,当且仅当num-1不在数组中
  2. 我们可以从每个起点开始,向后扩展,直到无法继续
  3. 每个数字只属于一个连续序列

3.算法设计与实现

3.1 哈希集合法(最优解法)

核心思想

利用HashSet的O(1)查找能力,识别连续序列的起点,只从起点开始向后扩展统计序列长度,避免重复计算。

算法思路

  1. 去重存储:将所有数字存入HashSet,自动去除重复元素
  2. 识别起点 :遍历HashSet中的每个数字,检查num-1是否在集合中
    • 如果num-1不在集合中,说明num是某个连续序列的起点
    • 如果num-1在集合中,说明num不是起点,跳过处理
  3. 扩展统计:对于每个起点,向后扩展(检查num+1、num+2...),统计连续序列长度
  4. 更新结果:记录并更新遇到的最大长度

时间复杂度分析

  • 每个数字最多被访问两次:
    • 一次在外层循环中检查是否为起点
    • 一次在内层循环中作为连续序列的一部分被扩展
  • 总时间复杂度:O(n),其中n为数组长度

空间复杂度分析

  • 需要HashSet存储所有数字:O(n)

代码实现

java 复制代码
import java.util.HashSet;
import java.util.Set;

public class LongestConsecutiveSequence {
    public int longestConsecutive(int[] nums) {
        // 边界条件处理
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        // 步骤1:将所有数字存入HashSet(自动去重)
        Set<Integer> numSet = new HashSet<>();
        for (int num : nums) {
            numSet.add(num);
        }
        
        int longestStreak = 0;
        
        // 步骤2:遍历集合中的每个数字
        for (int num : numSet) {
            // 步骤3:检查是否为序列起点
            // 关键:只有当num-1不在集合中时,num才是序列起点
            if (!numSet.contains(num - 1)) {
                int currentNum = num;
                int currentStreak = 1;
                
                // 步骤4:从起点向后扩展
                while (numSet.contains(currentNum + 1)) {
                    currentNum++;
                    currentStreak++;
                }
                
                // 步骤5:更新最长序列长度
                longestStreak = Math.max(longestStreak, currentStreak);
            }
        }
        
        return longestStreak;
    }
}

4.2 哈希映射法

核心思想

使用HashMap记录每个数字所在的连续序列长度。当插入新数字时,检查其左右相邻数字,更新序列长度,并同步更新序列边界的长度信息。

算法思路

  1. 初始化映射:创建HashMap,键为数字,值为该数字所在连续序列的长度
  2. 遍历处理 :对于数组中的每个数字num:
    • 如果num已存在Map中,跳过(避免重复处理)
    • 获取左邻居长度left(num-1在Map中的值,默认为0)
    • 获取右邻居长度right(num+1在Map中的值,默认为0)
    • 计算当前序列长度:length = left + right + 1
    • 更新当前数字的映射:map.put(num, length)
    • 更新左边界:如果left>0,则map.put(num-left, length)
    • 更新右边界:如果right>0,则map.put(num+right, length)
    • 更新最大长度
  3. 返回结果:遍历完成后返回记录的最大长度

时间复杂度分析

  • 每个数字只被处理一次:O(n)
  • HashMap的插入和查询操作平均为O(1)
  • 总时间复杂度:O(n)

空间复杂度分析

  • 需要HashMap存储所有数字及其序列长度:O(n)

代码实现

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class LongestConsecutiveSequenceMap {
    public int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        // Map存储:数字 -> 该数字所在连续序列的长度
        Map<Integer, Integer> map = new HashMap<>();
        int maxLength = 0;
        
        for (int num : nums) {
            // 跳过已处理的数字(去重)
            if (map.containsKey(num)) {
                continue;
            }
            
            // 获取左右相邻数字的序列长度
            int left = map.getOrDefault(num - 1, 0);
            int right = map.getOrDefault(num + 1, 0);
            
            // 计算当前序列的总长度
            int currentLength = left + right + 1;
            maxLength = Math.max(maxLength, currentLength);
            
            // 更新当前数字(值可以任意,主要标记已处理)
            map.put(num, currentLength);
            
            // 更新左边界(如果存在左邻居)
            if (left > 0) {
                // num-left是左边界的起点
                map.put(num - left, currentLength);
            }
            
            // 更新右边界(如果存在右邻居)
            if (right > 0) {
                // num+right是右边界的终点
                map.put(num + right, currentLength);
            }
        }
        
        return maxLength;
    }
}

4.3 排序法(对比参考)

核心思想

对数组排序后,遍历统计连续序列的长度。这种方法虽然简单直观,但不满足O(n)时间复杂度要求。

算法思路

  1. 排序数组:使用Arrays.sort()对数组进行排序
  2. 遍历统计
    • 初始化最长长度和当前长度都为1
    • 遍历排序后的数组(从第二个元素开始)
    • 如果当前元素等于前一个元素,跳过(处理重复)
    • 如果当前元素等于前一个元素加1,当前长度加1
    • 否则,更新最长长度,重置当前长度为1
  3. 返回结果:返回最长长度和当前长度的最大值

时间复杂度分析

  • 排序操作:O(n log n)
  • 遍历操作:O(n)
  • 总时间复杂度:O(n log n)

空间复杂度分析

  • Arrays.sort()在Java中对于基本类型使用双轴快速排序,空间复杂度为O(log n)
  • 如果排序需要额外空间,则为O(n)

代码实现

java 复制代码
import java.util.Arrays;

public class LongestConsecutiveSequenceSorting {
    public int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        // 步骤1:排序
        Arrays.sort(nums);
        
        int longestStreak = 1;
        int currentStreak = 1;
        
        // 步骤2:遍历统计
        for (int i = 1; i < nums.length; i++) {
            // 处理重复元素:跳过但不重置序列
            if (nums[i] == nums[i - 1]) {
                continue;
            }
            
            // 如果连续,增加当前长度
            if (nums[i] == nums[i - 1] + 1) {
                currentStreak++;
            } else {
                // 序列中断,重置当前长度
                longestStreak = Math.max(longestStreak, currentStreak);
                currentStreak = 1;
            }
        }
        
        // 步骤3:返回最大值(处理以最后一个元素结尾的序列)
        return Math.max(longestStreak, currentStreak);
    }
}

4.4 并查集法

核心思想

将每个数字看作一个节点,如果两个数字连续(差值为1),则将它们所在的集合合并。最后找到最大的连通分量大小即为最长连续序列长度。

算法思路

  1. 初始化并查集:为每个数字创建独立的集合
  2. 构建连接关系:对于每个数字num,检查num-1和num+1是否存在,如果存在则合并对应集合
  3. 查找最大集合:遍历所有集合,找到大小最大的集合
  4. 返回结果:最大集合的大小即为最长连续序列长度

时间复杂度分析

  • 并查集操作(查找、合并)接近O(1)(使用路径压缩和按秩合并)
  • 需要处理每个数字和它的邻居:O(n)
  • 总时间复杂度:O(nα(n)),其中α(n)是反阿克曼函数,增长极慢

空间复杂度分析

  • 需要存储每个数字的父节点和集合大小:O(n)

代码实现

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class LongestConsecutiveSequenceUnionFind {
    
    // 并查集类
    class UnionFind {
        private Map<Integer, Integer> parent;
        private Map<Integer, Integer> size;
        
        public UnionFind(int[] nums) {
            parent = new HashMap<>();
            size = new HashMap<>();
            
            // 初始化,每个元素自成一组
            for (int num : nums) {
                parent.put(num, num);
                size.put(num, 1);
            }
        }
        
        // 查找根节点(带路径压缩)
        public int find(int x) {
            if (parent.get(x) != x) {
                parent.put(x, find(parent.get(x)));
            }
            return parent.get(x);
        }
        
        // 合并两个集合
        public void union(int x, int y) {
            if (!parent.containsKey(x) || !parent.containsKey(y)) {
                return;
            }
            
            int rootX = find(x);
            int rootY = find(y);
            
            if (rootX == rootY) {
                return;
            }
            
            // 按大小合并(小树接在大树下)
            if (size.get(rootX) < size.get(rootY)) {
                parent.put(rootX, rootY);
                size.put(rootY, size.get(rootY) + size.get(rootX));
            } else {
                parent.put(rootY, rootX);
                size.put(rootX, size.get(rootX) + size.get(rootY));
            }
        }
        
        // 获取最大集合大小
        public int getMaxSize() {
            int max = 0;
            for (int s : size.values()) {
                max = Math.max(max, s);
            }
            return max;
        }
    }
    
    public int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        UnionFind uf = new UnionFind(nums);
        
        // 合并连续的数字
        for (int num : nums) {
            // 如果num-1存在,合并num和num-1
            if (uf.parent.containsKey(num - 1)) {
                uf.union(num, num - 1);
            }
            // 如果num+1存在,合并num和num+1
            if (uf.parent.containsKey(num + 1)) {
                uf.union(num, num + 1);
            }
        }
        
        return uf.getMaxSize();
    }
}

4.5 区间DP法

核心思想

将问题转化为区间动态规划问题。虽然这不是最优解,但它提供了不同的解题视角。我们使用动态规划记录每个数字所在的连续区间边界。

算法思路

  1. 数据预处理:使用HashSet去重并快速查找
  2. DP状态定义
    • leftMap:记录以某个数字为左端点的区间长度
    • rightMap:记录以某个数字为右端点的区间长度
  3. 状态转移 :遍历每个数字,尝试将其与左右邻居连接
    • 如果num-1存在,则num可以接在num-1的右侧
    • 如果num+1存在,则num可以接在num+1的左侧
    • 更新合并后区间的左右边界
  4. 结果统计:记录过程中遇到的最大区间长度

时间复杂度分析

  • 每个数字处理一次:O(n)
  • 每次更新操作O(1)
  • 总时间复杂度:O(n)

空间复杂度分析

  • 需要两个HashMap存储区间边界信息:O(n)

代码实现

java 复制代码
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class LongestConsecutiveSequenceDP {
    public int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        // 去重
        Set<Integer> numSet = new HashSet<>();
        for (int num : nums) {
            numSet.add(num);
        }
        
        // DP状态:记录区间边界
        Map<Integer, Integer> leftMap = new HashMap<>();  // 右边界 -> 区间长度
        Map<Integer, Integer> rightMap = new HashMap<>(); // 左边界 -> 区间长度
        
        int maxLength = 0;
        
        for (int num : numSet) {
            // 尝试将num与左右区间合并
            int leftLen = rightMap.getOrDefault(num - 1, 0);
            int rightLen = leftMap.getOrDefault(num + 1, 0);
            
            // 新区间长度
            int newLen = leftLen + rightLen + 1;
            maxLength = Math.max(maxLength, newLen);
            
            // 更新区间边界
            // 左边界
            int leftBound = num - leftLen;
            // 右边界
            int rightBound = num + rightLen;
            
            // 更新左右边界的映射
            leftMap.put(leftBound, newLen);
            rightMap.put(rightBound, newLen);
            
            // 如果左右区间存在,还需要更新中间点(如果存在)
            if (leftLen > 0) {
                rightMap.put(num - 1, newLen); // 原来左区间的右边界
            }
            if (rightLen > 0) {
                leftMap.put(num + 1, newLen); // 原来右区间的左边界
            }
        }
        
        return maxLength;
    }
}

优化版本

java 复制代码
public int longestConsecutiveDPOptimized(int[] nums) {
    if (nums == null || nums.length == 0) return 0;
    
    Set<Integer> set = new HashSet<>();
    for (int num : nums) set.add(num);
    
    // 只需要一个map,记录每个数字所在的区间长度
    Map<Integer, Integer> map = new HashMap<>();
    int maxLen = 0;
    
    for (int num : set) {
        if (map.containsKey(num)) continue;
        
        // 找到左边界的连续数字
        int left = num;
        while (set.contains(left - 1) && !map.containsKey(left - 1)) {
            left--;
        }
        
        // 找到右边界的连续数字
        int right = num;
        while (set.contains(right + 1) && !map.containsKey(right + 1)) {
            right++;
        }
        
        // 计算区间长度
        int len = right - left + 1;
        maxLen = Math.max(maxLen, len);
        
        // 标记区间内的所有数字
        for (int i = left; i <= right; i++) {
            map.put(i, len);
        }
    }
    
    return maxLen;
}

4.6 图+BFS法(图论思路)

核心思想

将问题建模为图论问题:每个数字是一个节点,如果两个数字相差1,则在它们之间添加一条边。问题转化为在无向图中寻找最大的连通分量。

算法思路

  1. 建图
    • 使用HashSet存储所有数字(去重)
    • 对于每个数字,它的邻居是num-1和num+1(如果存在)
  2. BFS遍历
    • 使用visited集合记录已访问的节点
    • 对于每个未访问的数字,执行BFS搜索其连通分量
    • 在BFS过程中,统计连通分量的大小
  3. 结果更新:记录最大的连通分量大小

时间复杂度分析

  • 每个节点被访问一次:O(n)
  • 每个节点的邻居查找O(1)
  • 总时间复杂度:O(n)

空间复杂度分析

  • 需要HashSet存储数字:O(n)
  • 需要队列和visited集合:O(n)
  • 总空间复杂度:O(n)

代码实现

java 复制代码
import java.util.*;

public class LongestConsecutiveSequenceGraph {
    public int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        // 步骤1:构建数字集合(去重)
        Set<Integer> numSet = new HashSet<>();
        for (int num : nums) {
            numSet.add(num);
        }
        
        // 步骤2:BFS遍历寻找最大连通分量
        Set<Integer> visited = new HashSet<>();
        int maxLength = 0;
        
        for (int num : numSet) {
            // 如果已经访问过,跳过
            if (visited.contains(num)) {
                continue;
            }
            
            // BFS搜索当前连通分量
            int currentLength = 0;
            Queue<Integer> queue = new LinkedList<>();
            queue.offer(num);
            visited.add(num);
            
            while (!queue.isEmpty()) {
                int current = queue.poll();
                currentLength++;
                
                // 检查左邻居
                if (numSet.contains(current - 1) && !visited.contains(current - 1)) {
                    queue.offer(current - 1);
                    visited.add(current - 1);
                }
                
                // 检查右邻居
                if (numSet.contains(current + 1) && !visited.contains(current + 1)) {
                    queue.offer(current + 1);
                    visited.add(current + 1);
                }
            }
            
            // 更新最大长度
            maxLength = Math.max(maxLength, currentLength);
        }
        
        return maxLength;
    }
}

DFS版本

java 复制代码
public class LongestConsecutiveSequenceDFS {
    public int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        
        Set<Integer> set = new HashSet<>();
        for (int num : nums) set.add(num);
        
        Set<Integer> visited = new HashSet<>();
        int maxLen = 0;
        
        for (int num : set) {
            if (visited.contains(num)) continue;
            
            int len = dfs(set, visited, num);
            maxLen = Math.max(maxLen, len);
        }
        
        return maxLen;
    }
    
    private int dfs(Set<Integer> set, Set<Integer> visited, int num) {
        if (!set.contains(num) || visited.contains(num)) {
            return 0;
        }
        
        visited.add(num);
        int len = 1;
        
        // 向左右两个方向递归搜索
        len += dfs(set, visited, num - 1);
        len += dfs(set, visited, num + 1);
        
        return len;
    }
}

图邻接表版本

java 复制代码
import java.util.*;

public class LongestConsecutiveSequenceAdjList {
    public int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        
        // 构建邻接表
        Map<Integer, List<Integer>> graph = new HashMap<>();
        Set<Integer> nodeSet = new HashSet<>();
        
        // 添加所有节点
        for (int num : nums) {
            nodeSet.add(num);
            graph.putIfAbsent(num, new ArrayList<>());
        }
        
        // 添加边(与相邻数字的连接)
        for (int num : nodeSet) {
            if (nodeSet.contains(num - 1)) {
                graph.get(num).add(num - 1);
                graph.get(num - 1).add(num);
            }
            if (nodeSet.contains(num + 1)) {
                graph.get(num).add(num + 1);
                graph.get(num + 1).add(num);
            }
        }
        
        // BFS遍历所有连通分量
        Set<Integer> visited = new HashSet<>();
        int maxLen = 0;
        
        for (int num : nodeSet) {
            if (visited.contains(num)) continue;
            
            // BFS搜索当前连通分量
            Queue<Integer> queue = new LinkedList<>();
            queue.offer(num);
            visited.add(num);
            int len = 0;
            
            while (!queue.isEmpty()) {
                int current = queue.poll();
                len++;
                
                for (int neighbor : graph.get(current)) {
                    if (!visited.contains(neighbor)) {
                        queue.offer(neighbor);
                        visited.add(neighbor);
                    }
                }
            }
            
            maxLen = Math.max(maxLen, len);
        }
        
        return maxLen;
    }
}

4.性能分析对比

4.1 复杂度对比

方法 时间复杂度 空间复杂度 实现难度 优点 缺点
哈希集合法 O(n) O(n) ★★☆☆☆ 思路清晰,通用最优解 需要遍历集合多次
哈希映射法 O(n) O(n) ★★★☆☆ 只需一次遍历 边界更新逻辑复杂
排序法 O(n log n) O(log n)或O(n) ★☆☆☆☆ 实现简单直观 不满足O(n)要求
并查集法 O(nα(n)) O(n) ★★★★☆ 支持动态扩展 实现复杂,常数大
区间DP法 O(n) O(n) ★★★☆☆ DP思想,易于理解 边界更新易出错
图+BFS法 O(n) O(n) ★★★☆☆ 图论视角,通用性强 需要额外存储图结构

4.2 性能分析

  1. 哈希集合法:实际运行时间接近2n,每个元素最多被访问两次
  2. 哈希映射法:每个元素只被处理一次,但更新边界操作增加常数时间
  3. 排序法:对于大规模数据,排序开销显著
  4. 并查集法:虽然理论复杂度低,但常数因子较大
  5. 区间DP法:与哈希映射法类似,但更直观体现了区间合并思想
  6. 图+BFS法:需要构建图结构,内存开销略大

4.3 选择建议

  • 面试场景:推荐哈希集合法,容易解释和实现
  • 竞赛场景:哈希集合法或哈希映射法
  • 学习场景
    • 理解哈希思想:哈希集合法
    • 学习动态规划:区间DP法
    • 掌握图论算法:图+BFS法
    • 理解并查集:并查集法
  • 生产环境
    • 数据规模大且内存充足:哈希集合法
    • 需要一次遍历:哈希映射法
    • 需要支持动态操作:并查集法

5.边界情况处理

5.1 常见边界情况

  1. 空数组:返回0
  2. 单个元素:返回1
  3. 所有元素相同:返回1(重复元素不增加序列长度)
  4. 包含负数:正常处理,连续序列可包含负数
  5. 大数值边界 :注意整数溢出,但题目范围-109到109在int范围内
  6. 无序但连续:算法不依赖原始顺序
  7. 最大值相邻:正常处理,但需注意Integer.MAX_VALUE+1会溢出

5.2 边界测试用例

java 复制代码
// 测试各种边界情况
int[][] testCases = {
    {},                    // 空数组,期望0
    {1},                   // 单个元素,期望1
    {1, 1, 1},             // 全部重复,期望1
    {-2, -1, 0, 1, 2},     // 包含负数,期望5
    {Integer.MAX_VALUE-1, Integer.MAX_VALUE}, // 大数边界,期望2
    {5, 4, 3, 2, 1, 0},    // 逆序连续,期望6
    {1, 3, 5, 2, 4},       // 无序连续,期望5
    {1, 2, 0, 1},          // 包含重复,期望3
};

5.3 边界处理技巧

  1. 空数组和null检查:方法开头进行防御性检查
  2. 重复元素处理:使用Set自动去重,或在排序法中跳过重复元素
  3. 整数溢出:对于通用解法,考虑使用long类型,但本题范围在int内
  4. 连续判断:严格检查差值是否为1,不是小于等于1

6.扩展与变体

6.1 返回最长连续序列本身

java 复制代码
public List<Integer> findLongestConsecutiveSequence(int[] nums) {
    if (nums == null || nums.length == 0) {
        return new ArrayList<>();
    }
    
    Set<Integer> set = new HashSet<>();
    for (int num : nums) set.add(num);
    
    List<Integer> longestSequence = new ArrayList<>();
    
    for (int num : set) {
        // 只从序列起点开始
        if (!set.contains(num - 1)) {
            List<Integer> currentSequence = new ArrayList<>();
            int current = num;
            
            while (set.contains(current)) {
                currentSequence.add(current);
                current++;
            }
            
            if (currentSequence.size() > longestSequence.size()) {
                longestSequence = currentSequence;
            }
        }
    }
    
    return longestSequence;
}

6.2 允许间隔k的"连续"序列

java 复制代码
public int longestConsecutiveWithGap(int[] nums, int k) {
    if (nums == null || nums.length == 0) return 0;
    
    // 先排序
    Arrays.sort(nums);
    
    int maxLen = 1;
    int currLen = 1;
    
    for (int i = 1; i < nums.length; i++) {
        // 跳过重复元素
        if (nums[i] == nums[i - 1]) continue;
        
        // 允许最大间隔为k
        if (nums[i] - nums[i - 1] <= k) {
            currLen++;
        } else {
            maxLen = Math.max(maxLen, currLen);
            currLen = 1;
        }
    }
    
    return Math.max(maxLen, currLen);
}

6.3 动态添加元素的最长连续序列

java 复制代码
class LongestConsecutiveDynamic {
    private Set<Integer> set = new HashSet<>();
    private Map<Integer, Integer> leftBound = new HashMap<>();
    private Map<Integer, Integer> rightBound = new HashMap<>();
    private int maxLen = 0;
    
    // 添加元素并更新最长连续序列
    public void add(int num) {
        if (set.contains(num)) return;
        
        set.add(num);
        
        // 检查左右邻居
        boolean hasLeft = set.contains(num - 1);
        boolean hasRight = set.contains(num + 1);
        
        if (hasLeft && hasRight) {
            // 连接左右两个序列
            int left = leftBound.get(num - 1);
            int right = rightBound.get(num + 1);
            int newLen = left + right + 1;
            
            // 更新边界
            leftBound.put(num - left, newLen);
            rightBound.put(num + right, newLen);
            
            maxLen = Math.max(maxLen, newLen);
        } else if (hasLeft) {
            // 只连接左边序列
            int left = leftBound.get(num - 1);
            int newLen = left + 1;
            
            leftBound.put(num - left, newLen);
            rightBound.put(num, newLen);
            
            maxLen = Math.max(maxLen, newLen);
        } else if (hasRight) {
            // 只连接右边序列
            int right = rightBound.get(num + 1);
            int newLen = right + 1;
            
            rightBound.put(num + right, newLen);
            leftBound.put(num, newLen);
            
            maxLen = Math.max(maxLen, newLen);
        } else {
            // 孤立元素
            leftBound.put(num, 1);
            rightBound.put(num, 1);
            maxLen = Math.max(maxLen, 1);
        }
    }
    
    public int getLongestConsecutive() {
        return maxLen;
    }
}

6.4 二维矩阵中的最长连续序列

java 复制代码
public int longestConsecutiveInMatrix(int[][] matrix) {
    if (matrix == null || matrix.length == 0) return 0;
    
    int rows = matrix.length, cols = matrix[0].length;
    int maxLen = 0;
    
    // 记录每个位置是否访问过
    boolean[][] visited = new boolean[rows][cols];
    
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            // 从每个位置开始DFS搜索
            int len = dfs(matrix, i, j, visited, matrix[i][j] - 1);
            maxLen = Math.max(maxLen, len);
        }
    }
    
    return maxLen;
}

private int dfs(int[][] matrix, int i, int j, boolean[][] visited, int prev) {
    if (i < 0 || i >= matrix.length || j < 0 || j >= matrix[0].length 
        || visited[i][j] || matrix[i][j] != prev + 1) {
        return 0;
    }
    
    visited[i][j] = true;
    int maxLen = 1;
    
    // 四个方向
    int[][] dirs = {{0,1}, {1,0}, {0,-1}, {-1,0}};
    for (int[] dir : dirs) {
        int len = 1 + dfs(matrix, i + dir[0], j + dir[1], visited, matrix[i][j]);
        maxLen = Math.max(maxLen, len);
    }
    
    visited[i][j] = false;
    return maxLen;
}

7.总结

7.1 核心知识点总结

  1. 哈希集合的应用:利用O(1)查找能力判断数字是否存在
  2. 序列起点识别:数字num是序列起点当且仅当num-1不存在
  3. 时间复杂度分析:每个数字最多被访问两次,总体O(n)
  4. 空间换时间:使用O(n)额外空间实现O(n)时间复杂度
  5. 多种解法对比:根据不同场景选择合适算法
  6. 算法思维拓展:从不同角度(哈希、DP、图论)理解同一问题

7.2 算法思维提升

  1. 问题转化能力:将"寻找最长连续序列"转化为"寻找序列起点并扩展"
  2. 避免重复计算:只从起点扩展,确保每个数字只被处理一次
  3. 边界处理思维:考虑各种极端情况,编写健壮代码
  4. 复杂度分析能力:准确分析时间、空间复杂度
  5. 多视角解题:从不同算法范式思考问题

7.3 实际应用场景

  1. 数据分析:识别连续事件或连续时间点
  2. 社交网络:寻找连续的用户ID序列或连续活动记录
  3. 游戏开发:连续登录奖励或连续成就系统
  4. 数据库优化:连续范围查询的预处理
  5. 系统监控:连续错误或异常检测

7.4 面试建议

  1. 首选解法:哈希集合法,容易解释和实现
  2. 解题步骤
    • 理解问题,确认要求和约束
    • 分析难点,强调O(n)挑战
    • 提出思路,基于序列起点观察
    • 详细实现,逐步解释算法
    • 复杂度分析,证明O(n)时间复杂度
    • 测试验证,举例说明正确性
    • 扩展讨论,提及其他解法
  3. 代码质量:注重可读性、健壮性和边界处理
  4. 沟通能力:清晰表达思路,主动解释关键点

学习建议

  1. 首先掌握哈希集合法,理解核心思想
  2. 尝试实现哈希映射法,理解边界更新机制
  3. 学习区间DP法,掌握动态规划思想
  4. 了解图+BFS法,拓展图论思维
  5. 研究并查集法,理解并查集数据结构
相关推荐
性感博主在线瞎搞1 天前
【算法】KMP算法的next数组的数学原理以及推导过程
数据结构·算法·kmp算法
一起努力啊~1 天前
算法刷题--移除元素
算法
ballball~~1 天前
正态(高斯)分布(Gaussian distribution)
算法·概率论
Blossom.1181 天前
强化学习推荐系统实战:从DQN到PPO的演进与落地
人工智能·python·深度学习·算法·机器学习·chatgpt·自动化
AI科技星1 天前
引力场与磁场的几何统一:磁矢势方程的第一性原理推导、验证与诠释
数据结构·人工智能·经验分享·线性代数·算法·计算机视觉·概率论
Frank_refuel1 天前
C++日期类实现
开发语言·c++·算法
Jeremy爱编码1 天前
电话号码的字母组合
java·算法·leetcode
YuTaoShao1 天前
【LeetCode 每日一题】1339. 分裂二叉树的最大乘积
算法·leetcode·职场和发展
Neil今天也要学习1 天前
永磁同步电机控制算法--基于增量式模型的鲁棒无差拍电流预测控制
单片机·嵌入式硬件·算法