如何求数组的最长连续序列?暴力算法、HashMap、HashSet、TreeSet四种解题思路

题目

给定一个未排序的整数数组 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

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109

暴力方法

代码思路:

  1. 排序数组:首先对数组进行排序,默认是升序排序。

  2. 遍历数组:从数组的第二个元素开始遍历(因为第一个元素无法与前一个比较)。

  3. 判断连续性

    • 如果当前元素等于前一个元素,则跳过当前元素,因为重复元素不会增加连续序列的长度。
    • 如果当前元素等于前一个元素加一,则当前连续序列的长度加一(currentLen++)。
    • 否则,当前连续序列结束,更新最大长度(maxLen = Math.max(maxLen, currentLen)),然后重置当前序列的长度为1(因为当前元素自身构成一个序列)。
  4. 循环结束后的最终比较:在循环结束后,需要再次比较更新一次最大长度,以确保考虑到数组末尾可能存在的最长连续序列。

  5. 返回最大长度:最后返回计算得到的最长连续序列长度。

代码实现: 

import java.util.Arrays;

public class LongestConsecutiveSequence {

    public static int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        Arrays.sort(nums);  // 先排序数组
        
        int maxLen = 1;  // 初始化最长连续序列的长度为1,至少包括自身
        int currentLen = 1;  // 当前连续序列的长度,默认为1
        
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] != nums[i - 1]) {  // 如果当前数字不等于前一个数字,说明不是重复数字
                if (nums[i] == nums[i - 1] + 1) {  // 如果是连续的数字
                    currentLen++;  // 增加当前连续序列的长度
                } else {  // 不连续,更新最大长度并重置当前长度
                    maxLen = Math.max(maxLen, currentLen);  // 更新最长连续序列的长度
                    currentLen = 1;  // 重置当前连续序列的长度为1
                }
            }
        }
        
        return Math.max(maxLen, currentLen);  // 返回最终的最长连续序列长度
    }

    public static void main(String[] args) {
        int[] nums1 = {100, 4, 200, 1, 3, 2};
        System.out.println("Example 1: " + longestConsecutive(nums1));  // Output should be 4
        
        int[] nums2 = {0, 3, 7, 2, 5, 8, 4, 6, 0, 1};
        System.out.println("Example 2: " + longestConsecutive(nums2));  // Output should be 9
    }
}

分析复杂度:

  • 空间复杂度

    • 排序操作 Arrays.sort(nums) 的空间复杂度为 O(n log n),其中 n 是数组 nums 的长度。
    • 额外使用的空间为常数级别,主要是几个整型变量和方法的参数变量,因此空间复杂度为 O(1)。
  • 时间复杂度

    • 排序操作 Arrays.sort(nums) 的时间复杂度为 O(n log n)。
    • 遍历一次排序后的数组的时间复杂度为 O(n)。
    • 总体时间复杂度为 O(n log n),主要由排序操作决定。

使用HashMap优化算法

代码思路:

  • 创建一个HashMap map,用于存储连续序列的边界及其对应的序列长度。键为序列的边界值,值为该边界值所在连续序列的长度。
  • 遍历数组中的每个元素 num。对于每个元素 num,检查其左右相邻的元素是否存在于HashMap中,从而判断是否可以扩展当前连续序列。
  • 如果 num 左右两侧的边界值已经在HashMap中,则更新当前序列的长度为两侧序列长度之和,并更新边界值对应的序列长度。如果 num 是新的边界值,则将 num 加入HashMap,以及以 num 为边界的序列长度。
  • 每次更新HashMap后,都更新 maxLen,记录当前最大的连续序列长度。
  • 遍历完成后,maxLen 即为最长连续序列的长度。

代码实现:

import java.util.HashMap;

public class LongestConsecutiveSequence {

    public static int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        HashMap<Integer, Integer> map = new HashMap<>();
        int maxLen = 0;
        
        for (int num : nums) {
            if (!map.containsKey(num)) {  // 避免重复处理相同的数字
                int leftLen = map.getOrDefault(num - 1, 0);   // 左侧连续序列的长度
                int rightLen = map.getOrDefault(num + 1, 0);  // 右侧连续序列的长度
                
                int currentLen = leftLen + rightLen + 1;  // 当前连续序列的长度
                
                // 更新当前num所在连续序列的长度
                map.put(num, currentLen);
                // 更新左侧边界所在连续序列的长度
                map.put(num - leftLen, currentLen);
                // 更新右侧边界所在连续序列的长度
                map.put(num + rightLen, currentLen);
                
                // 更新最大长度
                maxLen = Math.max(maxLen, currentLen);
            }
        }
        
        return maxLen;
    }

    public static void main(String[] args) {
        int[] nums1 = {100, 4, 200, 1, 3, 2};
        System.out.println("Example 1: " + longestConsecutive(nums1));  // Output should be 4
        
        int[] nums2 = {0, 3, 7, 2, 5, 8, 4, 6, 0, 1};
        System.out.println("Example 2: " + longestConsecutive(nums2));  // Output should be 9
    }
}

分析复杂度:

  • 时间复杂度:由于HashMap的插入和查找操作的平均时间复杂度为O(1),遍历数组的时间复杂度为O(n),因此总体时间复杂度为O(n)。

  • 空间复杂度:额外使用了HashMap来存储连续序列的信息,最坏情况下需要存储整个数组的元素和边界,因此空间复杂度为O(n)。

通过使用HashMap,我们能够在时间复杂度和空间复杂度上都得到优化,使得算法更加高效。

使用HashSet优化算法

代码思路:

  1. HashSet存储元素:首先将数组中所有元素存入 HashSet 中,这样可以实现平均 O(1) 的时间复杂度进行查找。

  2. 遍历数组:对于数组中的每一个元素,检查是否是一个连续序列的起点。即检查当前元素是否是一个序列中的最小元素,即 nums[i] - 1 不在 HashSet 中。

  3. 计算连续序列长度:一旦确定一个连续序列的起点,通过循环逐步增加当前值,并检查 HashSet 中是否存在下一个值,直到找到连续序列的末尾。

  4. 更新最大长度:在检查连续序列过程中,不断更新最长连续序列的长度。

代码实现: 

import java.util.HashSet;

public class LongestConsecutiveSequence {

    public static int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        HashSet<Integer> numSet = new HashSet<>();
        for (int num : nums) {
            numSet.add(num);  // 将数组中所有元素加入 HashSet
        }
        
        int maxLength = 0;
        
        for (int num : numSet) {
            // 只处理连续序列的起点,即当前元素的前一个不存在于 HashSet 中
            if (!numSet.contains(num - 1)) {
                int currentNum = num;
                int currentLength = 1;
                
                // 计算连续序列的长度
                while (numSet.contains(currentNum + 1)) {
                    currentNum++;
                    currentLength++;
                }
                
                // 更新最大长度
                maxLength = Math.max(maxLength, currentLength);
            }
        }
        
        return maxLength;
    }

    public static void main(String[] args) {
        int[] nums1 = {100, 4, 200, 1, 3, 2};
        System.out.println("Example 1: " + longestConsecutive(nums1));  // Output should be 4
        
        int[] nums2 = {0, 3, 7, 2, 5, 8, 4, 6, 0, 1};
        System.out.println("Example 2: " + longestConsecutive(nums2));  // Output should be 9
    }
}

分析复杂度:

时间复杂度

  • 遍历数组并将元素加入 HashSet:这一步的时间复杂度是 O(n),其中 n 是数组的长度。
  • 寻找连续序列
    • 对于每个起点元素,内部的 while 循环最多执行 n 次(当所有元素都是连续的时候)。
    • 因此,总体来说,内部的 while 循环总共执行的次数不会超过 n 次。
  • 总体时间复杂度:O(n) + O(n) = O(n),因此整体时间复杂度为 O(n)。

空间复杂度

  • HashSet 的额外空间:需要使用 O(n) 的额外空间来存储数组中的所有元素。
  • 其他空间开销:除了 HashSet 外,只有几个整型变量用于迭代和计算,因此额外空间开销是常量级的 O(1)。

使用TreeSet优化算法

代码思路:

  1. 利用 TreeSet 的有序性

    • 将数组中的所有元素存入 TreeSet 中。TreeSet 会自动将元素按照大小进行排序。
    • 一旦我们有了有序的元素集合,就可以更方便地寻找连续的序列。
  2. 寻找最长连续序列

    • 遍历 TreeSet 中的每个元素,对于每个元素 num,检查是否存在 num - 1。如果不存在,说明 num 是一个连续序列的起点。
    • 从 num 开始向上递增,直到找不到连续的下一个数为止。这时,计算当前连续序列的长度,并更新最大长度 maxLength
  3. 返回结果

    • 最终返回 maxLength,即找到的最长连续序列的长度。

代码实现:

import java.util.TreeSet;

public class LongestConsecutiveSequence {

    public static int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        TreeSet<Integer> numSet = new TreeSet<>();
        for (int num : nums) {
            numSet.add(num);  // 将数组中所有元素加入 TreeSet
        }
        
        int maxLength = 0;
        
        for (int num : numSet) {
            // 只处理连续序列的起点,即当前元素的前一个不存在于 TreeSet 中
            if (!numSet.contains(num - 1)) {
                int currentNum = num;
                int currentLength = 1;
                
                // 计算连续序列的长度
                while (numSet.contains(currentNum + 1)) {
                    currentNum++;
                    currentLength++;
                }
                
                // 更新最大长度
                maxLength = Math.max(maxLength, currentLength);
            }
        }
        
        return maxLength;
    }

    public static void main(String[] args) {
        int[] nums1 = {100, 4, 200, 1, 3, 2};
        System.out.println("Example 1: " + longestConsecutive(nums1));  // Output should be 4
        
        int[] nums2 = {0, 3, 7, 2, 5, 8, 4, 6, 0, 1};
        System.out.println("Example 2: " + longestConsecutive(nums2));  // Output should be 9
    }
}

 分析复杂度:

  • 时间复杂度

    • 将所有元素插入 TreeSet 的时间复杂度为 O(n log n),因为每次插入操作都可能涉及对红黑树的调整,平均情况下是 O(log n) 的时间复杂度。
    • 遍历 TreeSet 并寻找连续序列的过程,每个元素最多被访问两次(一次作为起点,一次作为连续序列的一部分),因此时间复杂度为 O(n log n)。
    • 综合起来,总体时间复杂度是 O(n log n)。
  • 空间复杂度

    • TreeSet 的额外空间开销是 O(n),用于存储数组中的所有元素。
    • 其他空间开销是常量级的 O(1)。

通过 TreeSet 的有序性,我们可以在保持较高时间复杂度效率的同时,解决寻找最长连续序列的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无知、508

你的鼓励实我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值