力扣(LeetCode)78. 子集(C语言)

本文详细介绍了如何使用C语言解决LeetCode第78题——子集问题。通过两种方法实现:01编码和回溯法,分析了代码逻辑并提供了复杂度分析。01编码利用集合性质,通过二重循环构造子集;回溯法则是在递归过程中尝试所有可能,遇到无法满足条件时回溯。文章鼓励读者通过实践加深对这两种算法的理解。

一、环境说明

  1. 本文是 LeetCode 78题 : 子集,使用c语言实现
  2. 一题双解。
  3. 测试环境:Visual Studio 2019

二、代码展示

2.1 01编码

// 两种思路:
// 递归//深度优先//回溯
// 01编码,迭代求解。
int** subsets(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
    //长度n的集合,子集个数为2的n次方-1.
    int row = 1<<numsSize;
    int **ans = (int**)calloc(row,sizeof(int*));
    *returnSize = row;
    *returnColumnSizes = (int*)calloc(row,sizeof(int));//一共2的n次方行
    int *path=(int*)calloc(numsSize,sizeof(int));//子序列
    //01编码,迭代求解。
    for(int i = 0;i<(1<<numsSize);i++){
        int column=0;//列数
        for(int j =0;j<numsSize;j++){
            if(i&(1<<j)){//对i的每一位尝试构造子集
                path[column++]=nums[j];//如果i的第j个二进制位是1,则nums[j]元素入路径path。
            }
        }
        int *temp = (int*)calloc(column,sizeof(int));
        memcpy(temp,path,column*sizeof(int));
        returnColumnSizes[0][i]=column;
        ans[i]=temp;
    }
    return ans;
}

2.2 回溯

int **ans;
int *ansColumnSize;
int ansSize;

int *path;
int pathSize;
void dfs(int cur,int nums[],int numsSize){
    if(cur == numsSize){//构成了一个子集//保存,弹栈
        int *temp = (int*)calloc(pathSize,sizeof(int));
        memcpy(temp,path,pathSize*sizeof(int));
        ansColumnSize[ansSize] = pathSize;//路径大小入答案
        ans[ansSize++]=temp;//ans[ansSize]指向当前路径,答案规模++
        return;
    }
    //递归体编写
    path[pathSize++] = nums[cur];//尝试所有可能
    dfs(cur+1,nums,numsSize);//一路向前
    pathSize--;//回溯
    dfs(cur+1,nums,numsSize);
}
int** subsets(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
    ans = malloc(sizeof(int*) * (1 << numsSize));
    ansColumnSize = malloc(sizeof(int) * (1 << numsSize));
    path = malloc(sizeof(int) * numsSize);
    *returnSize = 1 << numsSize;
    ansSize = pathSize = 0;
    dfs(0, nums, numsSize);
    *returnColumnSizes = ansColumnSize;
    return ans;
}

三、思路分析

3.1 01编码思路

  • 01编码与集合性质说明:
  1. 对于一个集合,元素个数为n。我们可以把它的每个位置采用01编码,集合本身所有位是1,说明它包含集合的全部元素。让集合某些位置为0,构成缺少这些位置的子集。
  2. 利用了集合的性质,一个集合,大小为n,对应子集的数量是Cn-1+Cn-2+…+Cn-n=2的n次方-1。任何大小为n个集合都有这个性质。
  • 利用上述性质,我们只需要二重循环:
  • ①遍历2的n次方-1
  • ②对每个数遍历它的每个二进制位,一个数二进制位是n位。
  • 加入if(i&(1<<j))判断,执行操作,就能得到每一种子集的构造。

3.2 回溯法思路

  • 回溯法,就是在一路向前的递归基础上,在每一步递归后,回退一步,选择下一个元素,再次递归。
  • 在集合,排列的问题里,回溯法用的特别多。还不熟练的读者,多刷排列组合题目,思路会理解的,模板也会背会的。

四、代码分析

  • 理解思路很重要!
  • 博主欢迎读者在评论区留言,作为日更博主,看到就会回复的。

五、AC

01编码AC
回溯AC

六、复杂度分析

6.1 01编码复杂度

  1. 时间复杂度:O(n×2n)O(n\times2^n)O(n×2n) ,n是nums数组的大小。遍历2n2^n2n个数,同时对一个数的每一位尝试构造子集的时间复杂度是O(n×2n)O(n\times2^n)O(n×2n)
  2. 空间复杂度:O(n)O(n)O(n),使用了path,用于暂存子集。

6.2 回溯复杂度

  1. 时间复杂度:O(n×2n)O(n\times2^n)O(n×2n),一共2n2^n2n种可能,对每一种可能构造子集的时间开销是O(n)O(n)O(n),回溯的时间复杂度是O(n×2n)O(n\times2^n)O(n×2n)
  2. 空间复杂度:O(n)O(n)O(n),使用了path,用于暂存子集。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

清墨韵染

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值