并查集思想解决最长连续序列

leetcode 128. 最长连续序列

常规qsort暴力解法

  • 常规想法就是使用先排序,然后遍历的思想。时间复杂度为 nlogn + n
int MyCmp(const void *a, const void *b)
{
    int left = *(int *)a;
    int right = *(int *)b;
    return left < right ? -1 : 1;
}

int longestConsecutive(int* nums, int numsSize)
{
    if (numsSize == 0) {
        return 0;
    }

    int maxlen = 1;
    qsort(nums, numsSize, sizeof(int), MyCmp);

    int pre = nums[0];
    int cnt = 1;
    for (int i = 1; i < numsSize; i++) {
        if (nums[i] == pre) {
            continue;
        } else if (nums[i] == pre + 1) {
            cnt++;
        } else {
            cnt = 1;
        }
        maxlen = fmax(maxlen, cnt);
        pre = nums[i];
    }
    return maxlen;
}

使用hash表,精简O(n)算法

  • 题目要求O(n)时间复杂度,所以需要想办法优化,用空间换时间
  • 那么就按照hash表保存数组中所有元素,然后逐个遍历所有的元素。且从序列的最小的数字开始数有多少个连续
typedef struct {
    int num;
    int index;
    UT_hash_handle hh;
} MyHash;

MyHash* g_head = NULL;

int GetNumsIndex(int num)
{
    MyHash* tmp = NULL;
    HASH_FIND_INT(g_head, &num, tmp);
    if (tmp == NULL) {
        return -1;
    } else {
        return tmp->index;
    }
}

void AddNumIndex(int num, int index) 
{
    if (GetNumsIndex(num) != -1) return;

    MyHash* tmp = (MyHash *)malloc(sizeof(MyHash));
    tmp->num = num;
    tmp->index = index;
    HASH_ADD_INT(g_head, num, tmp);
}

int longestConsecutive(int* nums, int numsSize)
{
    g_head = NULL;
    if (numsSize == 0) {
        return 0;
    }
    for (int i = 0; i < numsSize; i++) {
        AddNumIndex(nums[i], i);
    }

    int maxlen = 1;
    // 时间复杂度O(n)
    for (int i = 0; i < numsSize; i++) {
        if (GetNumsIndex(nums[i] - 1) != -1) {
            continue;
        }
        int tmp = nums[i];
        int len = 0;
        while (GetNumsIndex(tmp) != -1) {
            tmp++;
            len++;
        }
        maxlen = fmax(maxlen, len);
    }

    return maxlen;
}

并查集算法

  • 使用常规的解法后,再尝试一下使用并查集的方式,这种方式一开始是怎么也想不起来的。
  • 也算是拓宽了自己的思路
  • 大概的思路就是,遍历数组中每一个数值nums[i],然后尝试将nums[i] 和 nums[i] + 1合并(前提是nums[i] + 1也在数组中)
  • 一开始每一个数字的parent就是自己。合并的时候将根据两个数值的parent的子节点个数的多少来裁决怎么合并,由谁来做新的parent根节点。
  • 还有一个非常重要的数据结构,保存了每一个数字作为parent根时,该根下面有多少个子节点,非常关键。也是本题使用并查集的重要思路。
  • 这样在合并的时候,就可以把根节点的子节点个数累加。在累加的过程中。比较记录最大的子节点个数。
  • 本题使用c语言,无奈没有map,只能使用ut hash来处理了。代码稍显繁琐。
typedef struct {
    int num;
    int parent;
    UT_hash_handle hh;
} Parent;

typedef struct {
    int parent;
    int sonNum;
    UT_hash_handle hh;
} Son;


Parent* g_parent = NULL;
Son* g_sonNums = NULL;

int g_maxlen = 1;

Son* GetParentSon(int parent)
{
    Son* tmp = NULL;
    HASH_FIND_INT(g_sonNums, &parent, tmp);
    return tmp;
}
void InitParentSon(int num) 
{
    Son* tmp = (Son *)malloc(sizeof(Son));
    tmp->parent = num;
    tmp->sonNum = 1;
    HASH_ADD_INT(g_sonNums, parent, tmp);
}


bool NumIsExist(num) {
    Parent* tmp = NULL;
    HASH_FIND_INT(g_parent, &num, tmp);
    if (tmp == NULL) {
        return false;
    } else {
        return true;
    }
}

int GetNumParent(int num)
{
    Parent* tmp = NULL;
    HASH_FIND_INT(g_parent, &num, tmp);
    return tmp->parent;
}

void InitNumParent(int num) 
{
    Parent* tmp = (Parent *)malloc(sizeof(Parent));
    tmp->num = num;
    tmp->parent = num;
    HASH_ADD_INT(g_parent, num, tmp);
}

int Find(int num)
{
    int parent = GetNumParent(num);
    if (parent == num) {
        return num;
    }
    Parent* tmp = NULL;
    HASH_FIND_INT(g_parent, &num, tmp);
    tmp->parent = Find(parent);
    return tmp->parent;
}

void Union(int a, int b)
{
    int parent_a = Find(a);
    int parent_b = Find(b);
    if (parent_a == parent_b) {
        return;
    }
    Son* son_a = GetParentSon(parent_a);
    Son* son_b = GetParentSon(parent_b);

    if (son_a->sonNum > son_b->sonNum) {
        son_a->sonNum += son_b->sonNum;
        Parent* ParentB = NULL;
        HASH_FIND_INT(g_parent, &parent_b, ParentB);
        ParentB->parent = parent_a;
        g_maxlen = fmax(g_maxlen, son_a->sonNum);
    } else {
        son_b->sonNum += son_a->sonNum;
        Parent* ParentA = NULL;
        HASH_FIND_INT(g_parent, &parent_a, ParentA);
        ParentA->parent = parent_b;
        g_maxlen = fmax(g_maxlen, son_b->sonNum);
    }
}


int longestConsecutive(int* nums, int numsSize)
{
    g_parent = NULL;
    g_sonNums = NULL;
    if (numsSize == 0) {
        return 0;
    }
    g_maxlen = 1;
    for (int i = 0; i < numsSize; i++) {
        if (NumIsExist(nums[i]) == true) continue;
        InitNumParent(nums[i]); // 初始化各个元素的parent是自己
        InitParentSon(nums[i]);
    }

    for (int i = 0; i < numsSize; i++) {
        if (NumIsExist(nums[i] + 1) == false) continue;
        // 开始合并,并在合并中更新最大的连续序列的长度
        Union(nums[i], nums[i] + 1);
    }

    // 清理ut hash 表
    return g_maxlen;
}
posted @ 2021-10-06 23:37  匠人小魏  阅读(168)  评论(0)    收藏  举报