力扣128:最长连续序列

45 阅读3分钟

1暴力解法

1.1我的代码

题目要求时间复杂度为O(n),因此我已经尽可能向哈希法上靠,还是被ds判断为暴力解法

public static int longestConsecutive(int[] nums) {
        int result = 0;
        //将nums放入集合
        Set<Integer> set = new TreeSet<>();
        for (int num : nums) {
            set.add(num);
        }
        //添加到treeset后元素已经变为有序
        for (Integer i : set) {
            //从最小的元素开始遍历
            int temp = 1;
            for (int j = 1; j < set.size(); j++) {
                if(!set.contains(i+j)) {
                    break;
                }
                temp++;
            }
            if(result<temp)
                result = temp;
            if(result == set.size())
                return result;
        }

        return result;
    }

这段代码如何体现暴力解法特性:

  1. 每个元素都被视为起点
  • 即使某个元素是连续序列的中间元素(如[1,2,3]中的2),代码仍会以它为起点重新检测后续连续序列

for (Integer i : set) {  // 每个元素都被作为起点
    int temp = 1;
    for (int j = 1; j < set.size(); j++) {
        if(!set.contains(i+j)) break;
        temp++;
    }
    // ...
}

  1. 重复检测序列
  • 对于连续序列[1,2,3,4]

  • 起点1:检测2,3,4(3次)

    • 起点4:检测5(1次)
    • 起点3:检测4(1次)
    • 起点2:检测3,4(2次)
  • 共计算3+2+1+1=7次,而实际序列长度只有4

  1. 无谓的计算浪费
    • [1,2,3]已被检测后,起点23的检测都是多余的
    • 最坏情况时间复杂度达到O(n²)
  1. 未利用有序性优化
    • 虽然使用了TreeSet排序,但未能利用"连续序列起点"的特性(起点前无相邻元素)

1.2ds优化代码,摆脱暴力解法

通过优化,可摆脱暴力解法

for (Integer i : set) {
    if (!set.contains(i - 1)) {  // 确保 i 是起点
        int temp = 1;
        while (set.contains(i + temp)) {
            temp++;
        }
        result = Math.max(result, temp);
    }
}

优化思路:如果集合中存在i-1元素,说明当前元素不是连续序列的起点,没有处理的必要,优化后时间复杂度为O(n)

1.3总结:

暴力解法的特征:

  1. 穷举所有可能性:对每个可能起点都尝试计算整个连续序列
  2. 重复计算:多次处理相同数据段
  3. 高时间复杂度:通常有较高的多项式时间或指数时间复杂度
  4. 未利用优化条件:没有跳过不必要的计算

本题的核心在于:当前数字只有是连续片段的起点才需对其进行处理,判断是否为起点只需判断是否存在i-1

2.最优解法

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);
        }
        
        int longestStreak = 0;
        
        for (int num : numSet) {
            // 只考虑连续序列的起点(当前数字-1不存在)
            if (!numSet.contains(num - 1)) {
                int currentNum = num;
                int currentStreak = 1;
                
                // 探索当前连续序列的长度
                while (numSet.contains(currentNum + 1)) {
                    currentNum++;
                    currentStreak++;
                }
                
                // 更新最长序列长度
                longestStreak = Math.max(longestStreak, currentStreak);
            }
        }
        
        return longestStreak;
    }

该解法与上文优化思路相同,不再赘述