力扣-排列组合问题(递归回溯)——77. 组合、46. 全排列、47. 全排列 II

一、组合

1. 问题描述

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

示例 2:

输入:n = 1, k = 1
输出:[[1]]

2. 代码实现

(1)c++实现代码

class Solution {
private:
    vector<vector<int>> result; // 存放符合条件结果的集合
    int stack[999], top = -1; // 用来存放符合条件结果
    void backtracking(int n, int k, int startIndex) {
        // 当结果的长度符合时,将结果存放到result结果集合中
        if (top+1 == k) {
            vector<int> ans;
            for(int i=0;i<=top;i++){
                ans.push_back(stack[i]);
            }
            result.push_back(ans);
            return;
        }
        for (int i = startIndex; i <= n; i++) {
            stack[++top] = i;
            backtracking(n, k, i + 1); // 递归
            top--;// 回溯,撤销处理的节点
        }
    }

public:
    vector<vector<int>> combine(int n, int k) {
        result.clear();
        backtracking(n, k, 1);
        return result;
    }
};

 (2)c语言实现代码

struct Combination {
    // 嵌套数组结果
    int **result;
    // 中间数组
    int *path;
    // 中间数组的长度
    int pathSize;
    // 结果数组的总数
    int returnSize;
};

void backtrack(struct Combination *com, int n, int k, int startIndex) {
    // 当组合的大小达到k时,将其添加到结果中
    if (com->pathSize == k) {
        // 在result的数组returnSize(也就是第几个结果集的位置)的位置提前申请一个空间
        com->result[com->returnSize] = (int *)malloc(k * sizeof(int));
        // 将中间数组存入result数组中
        for (int i = 0; i < k; i++) {
            com->result[com->returnSize][i] = com->path[i];
        }
        // 将结果集+1
        com->returnSize++;
        return;
    }
    // 横向遍历(加入了剪枝操作,即最后组成的数无法到要求直接剪掉即可)
    for (int i = startIndex; i <= n - (k - com->pathSize) + 1; i++) {
        // 处理当前节点
        com->path[com->pathSize] = i; 
        // 中间数组长度+1
        com->pathSize++;
        // 纵向遍历(选择之后不能继续再选,即i+1)
        backtrack(com, n, k, i + 1);
        // 回溯,撤销处理的节点
        com->pathSize--; 
    }
}

int** combine(int n, int k, int* returnSize, int** returnColumnSizes) {
    // 定义暂存结构体
    struct Combination com;
    com.returnSize = 0;
    com.pathSize = 0;

    // 初始条件是否符号条件
    if (n <= 0 || k <= 0 || k > n) {
        return NULL;
    }

    // 计算组合的总数
    int totalCombinations = 1;
    for (int i = 1; i <= k; i++) {
        totalCombinations *= (n - i + 1);
        totalCombinations /= i;
    }

    // 分配结果数组的空间
    com.result = (int **)malloc(totalCombinations * sizeof(int *));
    com.path = (int *)malloc(k * sizeof(int));

    // 使用回溯法生成组合
    backtrack(&com, n, k, 1);

    // 返回每一个子数组的大小
    *returnColumnSizes = (int *)malloc(com.returnSize * sizeof(int));
    for (int i = 0; i < com.returnSize; i++) {
        (*returnColumnSizes)[i] = k;
    }

    // 返回结果数组的大小
    *returnSize = com.returnSize;
    return com.result;
}

二、全排列

1. 问题描述

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

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

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

2. 代码实现

 c++代码实现

class Solution {
private:
    vector<vector<int>> result;
    int stack[999], top=-1;
    void backtracking (vector<int>& nums, vector<bool>& used) {
        // 此时说明找到了一组
        if (top+1 == nums.size()) {
            vector<int> ans;
            for(int i=0;i<=top;i++){
                ans.push_back(stack[i]);
            }
            result.push_back(ans);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {
            // 已经使用过直接跳过即可
            if(used[i] == true){
                continue;
            }
            // used[i - 1] == true,说明同一树枝nums[i - 1]使用过
            // used[i - 1] == false,说明同一树层nums[i - 1]使用过 
            // 如果同一树层nums[i - 1]使用过则直接跳过
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            used[i] = true;
            stack[++top] = nums[i];
            backtracking(nums, used);
            top--;
            used[i] = false;
        }
    }
public:
    vector<vector<int>> permute(vector<int>& nums) {
        result.clear();
        sort(nums.begin(), nums.end()); // 排序
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};

c语言实现

struct Combination{
    // 结果数组
    int **result;
    int resultSize;
    // 中间数组
    int *path;
    int pathSize;
};

void backtrace(struct Combination *com, int nums[], int n, int used[])
{
    // 当组合的大小达到k时,将其添加到结果中
    if(com->pathSize == n){
        com->result[com->resultSize] = (int*)malloc(n*sizeof(int));
        for(int i=0;i<n;i++){
            com->result[com->resultSize][i] = com->path[i];
        }
        com->resultSize++;
        return;
    }
    
    // 横向遍历
    for(int i=0; i<n; i++){
        // 如果已经使用过就不再次使用
        if(used[i] == 1){
            continue;
        }
        used[i] = 1;
        com->path[com->pathSize] = nums[i];
        com->pathSize++;
        // 纵向遍历
        backtrace(com, nums, n, used);
        com->pathSize--;
        used[i] = 0;
    }
}

int** permute(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
   struct Combination com;
   com.resultSize = 0;
   com.pathSize = 0;

   // 结果集总数
   int total = 1;
   for(int i=1;i<=numsSize;i++){
       total *= i;
   }

   // malloc申请空间
   com.result = (int**)malloc(total*sizeof(int*));   // 嵌套数组
   com.path = (int*)malloc(numsSize*sizeof(int));  // 数组

   // 状态数组
   int used[numsSize];
   for(int i=0;i<numsSize;i++){
       used[i] = 0;
   }

   // 开始回溯
   backtrace(&com, nums, numsSize, used);

   // 返回每一个子数组的大小
    *returnColumnSizes = (int *)malloc(com.resultSize * sizeof(int));
    for (int i = 0; i < com.resultSize; i++) {
        (*returnColumnSizes)[i] = numsSize;
    }

    // 返回结果数组的大小
    *returnSize = com.resultSize;
    return com.result;
}

三、 全排列 II

1. 问题描述

给定一个可包含重复数字的序列 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]]

2. 代码实现

c++代码实现 

class Solution {
private:
    vector<vector<int>> result;
    int stack[999], top=-1;
    void backtracking (vector<int>& nums, vector<bool>& used) {
        // 此时说明找到了一组
        if (top+1 == nums.size()) {
            vector<int> ans;
            for(int i=0;i<=top;i++){
                ans.push_back(stack[i]);
            }
            result.push_back(ans);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {
            // 已经使用过直接跳过即可
            if(used[i] == true){
                continue;
            }
            // used[i - 1] == true,说明同一树枝nums[i - 1]使用过
            // used[i - 1] == false,说明同一树层nums[i - 1]使用过 
            // 如果同一树层nums[i - 1]使用过则直接跳过
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            used[i] = true;
            stack[++top] = nums[i];
            backtracking(nums, used);
            top--;
            used[i] = false;
        }
    }
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        result.clear();
        sort(nums.begin(), nums.end()); // 排序
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};

        从上面三个题目的代码可以看到其实这三个题目出自同一个模板,稍微总结一下就可以得出,也可以从上面三个题目很好的学习回溯的方法

        上面两个组合和全排列都给出来C++和C语言的代码,C++主要借助vector容器的实现,而C则要复杂一些,我选择使用的是结构的方法实现。 

        解题思路和代码模板均来自代码随想录 - 力扣(LeetCode)

 

77. 组合

​​​​​​46. 全排列

47. 全排列 II

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值