代码随想录-回溯算法(组合问题)|ACM模式

目录

前言:

77.组合

题目描述:

输入输出示例:

思路和想法:

216. 组合总和 III

题目描述:

输入输出示例:

思路和想法:

17. 电话号码的字母组合

题目描述:

输入输出描述:

思路和想法:

40. 组合总和 II

题目描述:

输入输出描述:

思路和想法:


前言:

这里对于组合问题,进行了深入的探讨(ACM模式):

  • 数组集合里元素不重复,元素只能取一次,组合不许重复
  • 引入总和统计
  • 引入map哈希
  • 引入去重的概念---(数组集合里元素有重复,元素只能取一次,但组合不许重复)

77.组合

题目描述:

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

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

输入输出示例:

示例1:

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

示例2:

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

提示:

  • 1 <= n <= 20
  • 1 <= k <= n

思路和想法:

回溯法的问题,都可以抽象为树形结构问题。

回溯法解决的是在集合中递归查找子集,集合的大小构成树的宽度,递归的深度构成树的深度。

这道题目属于组合问题的模板题。

#include <bits/stdc++.h>

using namespace std;
/*
* 作者:希希雾里
* 77.组合
* */

/*
* 这里我们能够比较清晰的,要使用回溯。
* */
vector<vector<int>> result;
vector<int> path;
//这里对于回溯要传入的参数:元素数量,限定条件以及要开始遍历的元素
//这里能够比较清晰的知道n即为回溯的宽度,k即为回溯的深度。
void backtracing(int n, int k, int startIndex){
    /*终止条件*/
    if(path.size() == k){
        result.push_back(path);
        return;
    }
    /*处理节点 + 回溯撤销*/
    for (int i = startIndex; i <= n; ++i) {
        path.push_back(i);
        backtracing(n, k, i+1);
        path.pop_back();
    }
};

int main() {
    int n, k;
    cin>> n >> k;
    result.clear();
    path.clear();
    backtracing(n, k,1);

    //结果输出
    for (int i = 0; i < result.size(); ++i) {
        for (int j = 0; j < result[i].size(); ++j) {
            cout << result[i][j];
            if(j != result[i].size() - 1) cout << " ";
        }
        //注意最后输出不需要换行。
        if(i == result.size() - 1) return 0;
        cout << endl;
    }
    return 0;
}

/*  测试样例
4 2

1 1
*
* */

216. 组合总和 III

题目描述:

找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

输入输出示例:

示例1:

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

示例2:

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

示例3:

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

提示:

  • 2 <= k <= 9
  • 1 <= n <= 60

思路和想法:

这道题目和上一道题基本一致,在原有基础上,还需要统计path里元素的总和,终止条件发生了改变, path.size() == k && sum == n 。

#include <bits/stdc++.h>

using namespace std;
/*
* 作者:希希雾里
* 216.组合总和III
* */
vector<vector<int>> result;
vector<int> path;
int sum = 0;
//这里对于回溯要传入的参数,集合个数,目标总和以及要遍历的元素下标
void backtracing(int k, int n, int startIndex){
    /*终止条件*/
    if(path.size() == k && sum == n){
        result.push_back(path);
        return;
    }
    /*处理节点 + 回溯撤销*/
    for (int i = startIndex; i <= 9; ++i) {
        path.push_back(i);
        sum += i;
        backtracing(n, k, i + 1);
        sum -= path.back();
        path.pop_back();
    }
};

int main() {
    int n, k;
    cin>> n >> k;
    result.clear();
    path.clear();
    backtracing(k, n,1);

    //结果输出
    for (int i = 0; i < result.size(); ++i) {
        for (int j = 0; j < result[i].size(); ++j) {
            cout << result[i][j];
            if(j != result[i].size() - 1) cout << " ";
        }
        //注意最后输出不需要换行。
        if(i == result.size() - 1) return 0;
        cout << endl;
    }
    return 0;
}

17. 电话号码的字母组合

题目描述:

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

输入输出描述:

示例1:

输入:digits = "23" 输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例2:

输入:digits = "" 输出:[]

示例3:

输入:digits = "2" 输出:["a","b","c"]

提示:

  • 0 <= digits.length <= 4
  • digits[i] 是范围 ['2', '9'] 的一个数字

思路和想法:

这里首先构建数字和字符之间的映射,后续就是组合问题,采用回溯方法就可以解决了。

#include <bits/stdc++.h>

using namespace std;
/*
* 作者:希希雾里
* 17.电话号码的字母组合
* */
vector<string> result;
string s;
//构建map,建立映射
const string map_letter[10] = {
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz",
};

/*
* 函数:          backtracing()
* 输入参数:       digits:输入的字符串    index:字符串元素下标 
* */
void backtracing(const string & digits, int index){
    /*终止条件*/
    if(index == digits.length()){
        result.push_back(s);
        return;
    }
    int digit = digits[index] - '0';
    string letters = map_letter[digit];
    /*处理节点 + 回溯过程*/
    for (int i = 0; i < letters.length(); ++i) {
        s.push_back(letters[i]);
        backtracing(digits, index + 1);
        s.pop_back();
    }
};

int main() {
    result.clear();
    s.clear();
    //字符串输入
    string str;
    getline(cin,str);

    backtracing(str, 0);
    //结果输出
    for (int i = 0; i < result.size(); ++i) {
        //注意最后输出不需要换行。
        cout << result[i];
        if(i == result.size() - 1) return 0;
        cout << endl;
    }
    return 0;
}

/*  测试样例
23

2
*
* */

40. 组合总和 II

题目描述:

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次

注意:解集不能包含重复的组合。

输入输出描述:

示例1:

输入:candidates = [10,1,2,7,6,1,5], target = 8 输出:[ [1,1,6], [1,2,5], [1,7], [2,6] ]

示例2:

输入:candidates = [2,5,2,1,2], target = 5, 输出:[ [1,2,2], [5] ]

提示:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

思路和想法:

这道题目和之前不一样的地方在于,要取的数组里有数值相等的元素并且要求解集不能包含重复的组合。所以这道题目涉及到了去重。

一个组合里可以有重复的元素,即不需要树枝去重(纵向)。不能有重复的组合,即要进行树层去重(横向)。

树层去重和树枝去重,是根据树形结构,提出的去重概念。

那么转换到具体实现上,需要解决两个问题:

  • 如何实现去重?这里先对数组进行排序,将重复的元素紧挨在一起,之后遍历时判断与前面的元素是否相等即可。
  • 如何分辨树层去重和树枝去重?这里采用一个bool数组used,used[i - 1] == true,说明同一树枝candidates[i - 1]使用过,used[i - 1] == false,说明同一树层candidates[i - 1]使用过。
#include <bits/stdc++.h>

using namespace std;
/*
* 作者:希希雾里
* 40. 组合总和 II
* */
vector<vector<int>> result;
vector<int> path;
int sum = 0;

/*
* 函数:          backtracing()
* 输入参数:       candidates:输入的数组    target: 目标数值     index:字符串元素下标       used:标志数组
* */
void backtracing(vector<int>& candidates, int target, int index, vector<bool>& used){
    if(sum == target){
        result.push_back(path);
        return;
    }
    for(int i = index; i < candidates.size();++i){
        if(i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false){
            continue;
        }
        path.push_back(candidates[i]);
        sum += candidates[i];
        used[i] = true;
        backtracing(candidates, target, i + 1, used);
        used[i] = false;
        sum -= candidates[i];
        path.pop_back();
    }
};

int main() {
    result.clear();
    path.clear();

    vector<int> candidates;
    int n;
    while(cin >> n){
        candidates.push_back(n);
        if(getchar() == '\n'){
            break;
        }
    }
    int target;
    cin >> target;
    vector<bool> used(candidates.size(),false);

    //默认升序排序
    sort(candidates.begin(),candidates.end());

    backtracing(candidates, target, 0, used);
    //结果输出
    for (int i = 0; i < result.size(); ++i) {
        for (int j = 0; j < result[i].size(); ++j) {
            cout << result[i][j];
            if(j != result[i].size() - 1) cout << " ";
        }
        //注意最后输出不需要换行。
        if(i == result.size() - 1) return 0;
        cout << endl;
    }
    return 0;
}

/*  测试样例
10 1 2 7 6 1 5
8

2 5 2 1 2
5
*
* */

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

希希雾里

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

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

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

打赏作者

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

抵扣说明:

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

余额充值