[LeetCode] 128. Longest Consecutive Sequence

[LeetCode] 128. Longest Consecutive Sequence

题目

Given an unsorted array of integers, find the length of the longest consecutive elements sequence.Your algorithm should run in O(n) complexity.

样例

Input: [100, 4, 200, 1, 3, 2]
Output: 4
Explanation: The longest consecutive elements sequence is [1, 2, 3, 4]. Therefore its length is 4.

思路一:

本题要求时间复杂度为 (O(n))。如果进行排序的话,时间复杂度为 (O(n)) 的排序算法有:计数排序,桶排序和基数排序。其中:

  • 计数排序要求数据比较集中,最大值和最小值的差较小。而本题中没有明确数值范围,所以当数值范围为 ([-2^{31} sim 2^{31} - 1]) 时,开辟的计数数组长度为 (2^{32}),复杂度会不一定满足要求。
  • 桶排序要求数据分布比较均匀,从而落在每个桶中的数据个数差不多。题目没没有给出这样的信息。
  • 基数排序要求数据能进行较好的划分,例如对于整数数据而言,它的位数在有限范围内。从而,可以从低位往高位,进行多轮排序。本题中,整数的位数最大为10位,所以最多只需进行10轮排序即可。具体代码如下:
class Solution {
    public int longestConsecutive(int[] nums) {
        int n = nums.length, index = 0, pos;
        long min = 0;
        Node[] arr = new Node[n];
        Node[] base = new Node[10];

        for(int i = 0; i < n; i++){
            if(min > nums[i]){
                min = nums[i];
            }
        }
        for(int i = 0; i < n; i++){
            arr[i] = new Node(nums[i] - min);
        }
        while(index < n - 1){
            //把每个元素放入桶中,桶中以链表形式存放,以后进先出的形式存放
            for(int i = n - 1; i >= index; i--){
                pos = (int)(arr[i].cur % 10);
                arr[i].cur /= 10;
                arr[i].next = base[pos];
                base[pos] = arr[i];
            }
            pos = index;
            //从最小值开始回收结点,
            for(int i = 0; i < 10; i++){
                for(Node cur = base[i]; cur != null; cur = cur.next){
                    arr[pos++] = cur;
                }
                //回收结束时,将桶重置为空。便于下一轮使用
                base[i] = null;
            }
            //回收完成后,更新index
            while(index < n && arr[index].cur == 0){
                index++;
            }
        }
        //计算连续序列的最大长度
        int max = 0, count = n == 0? 0 : 1;
        for(int i = 1; i < n; i++){
            if(arr[i].val == arr[i - 1].val + 1){
                count++;
            }
            else if(arr[i].val > arr[i - 1].val){
                if(max < count){
                    max = count;
                }
                count = 1;
            }
        }
        if(max < count){
            max = count;
        }
        return max;
    }
}
class Node{
    long cur;
    long val;
    Node next;
    public Node(long val){
        this.cur = val;
        this.val = val;
    }
}

思路是:

  1. 新建一个 Node 对象数组。将每个数据放入一个 Node 对象中,每个 Node 对象中有三个成员变量:cur, val, next。val 保存数据值。cur 记录每轮记录排序前,数据的当前值,初始为 val。next 保证位于同一个桶中的Node 以链表形式存放。

  2. 每轮排序时,按照上一轮排序结果从大到小的顺序依次取出每个 Node,按照 (Node.cur \% 10) 进行基数排序,同时,将 cur 更新为 (cur / 10),用于下一轮排序。

  3. 对于在相同桶中的节点,后放入的结点放入链表头部,即以后进先出的形式存放。保证同一个桶内的结点,数值大的位于下方。

  4. 按照从小到大的顺序,回收每一个桶。将每个结点放回数组中,从而保证回收后各结点按照前 k 位值递增排列,k 为当前排序的轮数。

  5. 在回收完结点后,从最小值往右遍历 Node,如果 (Node.cur = 0),说明对应数据最多为 k 位整数,那么它的次序已经确定,无须参与后面的基数排序。用 index 记录已确定的元素下标。初始时,index 为0,每轮回收完结点后,当前 index 下标的 Node 处往右遍历,直至 (Node.cur != 0)。那么下一轮排序只需要考虑 ([index ~ n -1]) 中的 Node。

时间复杂度为:(Theta(n))

算法实现时的一些细节问题:

  • 基数排序算法的前提是所有数据均为非负数。所以,在代码中先找出最小数据,然后将 (Node.cur) 更新为 (Node.cur - min)。同时,由于 (Node.cur - min) 可能溢出,所以,需要将 (Node.cur) 设置为 long 类型。
  • 输入数据可能为空。需要进行判空处理。
  • 计算连续串最大长度时,输入数据中可能存在相同值,如:2,1,0,1。此时,正确答案是 3,即 0 - 1 - 2.所以相同元素只考虑一次。
  • 计算连续串最大长度时,若 ([i, n - 1]) 是连续串,那么,循环中不会考虑此连续串的长度。需要在循环结束后考虑。
原文地址:https://www.cnblogs.com/echie/p/9565487.html