代码随想录算法训练营day25:回溯04:491.递增子序列;46.全排列

491.递增子序列

力扣题目链接(opens new window)

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

  • 输入: [4, 6, 7, 7]
  • 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

说明:

  • 给定数组的长度不会超过15。
  • 数组中的整数范围是 [-100,100]。
  • 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

分析:

使序列合法的办法非常简单,即给「选择」做一个限定条件,只有当前的元素大于等于上一个选择的元素的时候才能选择这个元素,这样枚举出来的所有元素都是合法的

那如何保证没有重复呢?我们需要给「不选择」做一个限定条件,只有当当前的元素不等于上一个选择的元素的时候,才考虑不选择当前元素,直接递归后面的元素。因为如果有两个相同的元素,我们会考虑这样四种情况:——考虑怎么样会重复?需要避免的情况是什么样的?

x前者被选择,后者被选择
前者被选择,后者不被选择
前者不被选择,后者被选择
前者不被选择,后者不被选择
其中第二种情况和第三种情况其实是等价的,我们这样限制之后,舍弃了第二种,保留了第三种,于是达到了去重的目的。——也就是前者被选择的时候,后者一定会被选择

作者:力扣官方题解

终止情况不仅要考虑合法的、输出的情况,更需要考虑遍历完了 但是非法的情况
 

void bp(int *nums,int numsSize,int *returnSize,int *path,int p,int **returnColumnSizes,int start,int **ans){
    if(start>=numsSize && p>1){//至少有两个元素 
        ans[*returnSize]=malloc(sizeof(int)*p);
        memcpy(ans[*returnSize], path, sizeof(int)*p);
        (*returnColumnSizes)[(*returnSize)++]=p;
        return;
    }
    else if(start>=numsSize) return;

    int j=start;
    if(p>=1 && nums[j]==path[p-1]){//和前一个数字相同必须放入
        path[p]=nums[j];
        bp(nums, numsSize, returnSize,path,  p+1, returnColumnSizes, j+1,ans);
    }
    else if(p==0 || ( p>=1 && nums[j]>=path[p-1] ))
    {//放入第一个数,或者和前一个放入的数字不同 且是递增关系的时候,可以任选放入和不放入
        //不放
        bp(nums, numsSize, returnSize,path,  p, returnColumnSizes, j+1,ans);

        //放
        path[p]=nums[j];
        bp(nums, numsSize, returnSize,path,  p+1, returnColumnSizes, j+1,ans);

    }
    else bp(nums, numsSize, returnSize,path,  p, returnColumnSizes, j+1,ans);//不放
    

}

int** findSubsequences(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
    int **ans=(int **)malloc(sizeof(int *)*(pow(2,numsSize)-numsSize-1));
    *returnSize=0;

    int *path=(int *)malloc(sizeof(int)*numsSize);
    
    *returnColumnSizes=(int *)malloc(sizeof(int)*(pow(2,numsSize)-numsSize-1));
    bp(nums, numsSize, returnSize, path, 0, returnColumnSizes, 0,ans);
    return ans;
}

46.全排列

力扣题目链接(opens new window)

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

  • 输入: [1,2,3]
  • 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

分析:

已经很像dfs搜索了,需要用used数组来判断这个数据是否被轮到过了,然后再bp之后 回溯的部分 再把used修改回来

代码:

void bp (int *nums,int numsSize,int *returnSize,int **returnColumnSizes,int *path,int p,int **ans,int *used){
    if(p==numsSize){
        ans[*returnSize]=malloc(sizeof(int)*p);
        memcpy(ans[*returnSize], path, sizeof(int)*p);
        (*returnColumnSizes)[(*returnSize)++]=p;

        return;
    }
    for (int i=0;i<numsSize;i++){
        //不放
        if(used[i]==1) continue;
        
        //放入
        path[p]=nums[i];
        used[i]=1;
        bp(nums, numsSize, returnSize, returnColumnSizes, path,p+1, ans, used);
        used[i]=0;
    }

}


int** permute(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
    int **ans =malloc(sizeof(int *)*6*5*4*3*2);
    *returnSize=0;

    int *path=(int *)malloc(sizeof(int)*numsSize);
    int *used=(int *)malloc(sizeof(int)*numsSize);
    

    *returnColumnSizes=(int *)malloc(sizeof(int)*6*5*4*3*2);
    for (int i=0;i<numsSize;i++){
        used[i]=0;
    }
    bp(nums, numsSize, returnSize, returnColumnSizes, path,  0, ans, used);
    return ans;
}

47.全排列 II

力扣题目链接(opens new window)

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

示例 1:

  • 输入:nums = [1,1,2]
  • 输出: [[1,1,2], [1,2,1], [2,1,1]]

示例 2:

  • 输入:nums = [1,2,3]
  • 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

分析:

这是为什么呢,就是上面我刚说的,如果要对树层中前一位去重,就用used[i - 1] == false,如果要对树枝前一位去重用used[i - 1] == true

对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!

这么说是不是有点抽象?

来来来,我就用输入: [1,1,1] 来举一个例子。

树层上去重(used[i - 1] == false),的树形结构如下:

47.全排列II2

树枝上去重(used[i - 1] == true)的树型结构如下:

47.全排列II3

大家应该很清晰的看到,树层上对前一位去重非常彻底,效率很高,树枝上对前一位去重虽然最后可以得到答案,但是做了很多无用搜索。

代码:

int cmp(const void *a,const void *b) {
	return *(int*)a-*(int*)b;
}

void bp (int *nums,int numsSize,int *returnSize,int **returnColumnSizes,int *path,int p,int **ans,int *used){
    if(p==numsSize){
        ans[*returnSize]=malloc(sizeof(int)*p);
        memcpy(ans[*returnSize], path, sizeof(int)*p);
        (*returnColumnSizes)[(*returnSize)++]=p;
        return;
    }
    int prior;
    for (int i=0;i<numsSize;i++){
        //不放
        if(used[i]==1) continue;
        if(i<numsSize-1  && nums[i]==nums[i+1]  && used[i+1]==0) continue;
        
        //放入
        path[p]=nums[i];
        used[i]=1;
        bp(nums, numsSize, returnSize, returnColumnSizes, path,p+1, ans, used);
        used[i]=0;
    }

}

int** permuteUnique(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
    int len=1;
    for (int x=1;x<=numsSize;x++){
        len=len*x;
    }
    int **ans =malloc(sizeof(int *)*len);
    *returnSize=0;

    qsort(nums, numsSize, sizeof(int), cmp);

    int *path=(int *)malloc(sizeof(int)*numsSize);
    int *used=(int *)malloc(sizeof(int)*numsSize);

    *returnColumnSizes=(int *)malloc(sizeof(int)*len);
    for (int i=0;i<numsSize;i++){
        used[i]=0;
    }
    bp(nums, numsSize, returnSize, returnColumnSizes, path,  0, ans, used);
    return ans;
}

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值