代码随想录算法训练营第二十四天|回溯算法基础、77.组合、216.组合总和III、17.电话号码的字母组合

回溯算法基础

回溯算法与递归如影随形,有递归的地方就有回溯。通常在递归函数的下面是回溯的逻辑,人们通常说的回溯函数其实是递归函数,因为没有专门用于回溯的一个函数。回溯搜索也是一种纯暴力的搜索方法。

回溯法,一般可以解决如下几种问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独等等

回溯算法模板:

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

回溯三步曲:

1.确定递归函数参数返回值。2.确定终止条件。3.确定单层搜索逻辑。

77.组合问题

77. 组合 - 力扣(LeetCode)

for循环暴力解决:

#include<iostream>
using namespace std;
int main(){
	int n,k;//在这里K的作用是规定使用几轮for循环来解决 
	cin >> n;
	for(int i = 1;i <= n;i++){
		for(int j = i + 1;j <= n;j++){
			cout << i << j <<' ';
		}
		printf("\n");
	}
	return 0;
} 

回溯算法的大体思路:

运用两个动态数组,一个一维动态数组用来存放长度为K的组合,一个二维动态数组用来存放一维数组,本层搜索逻辑是找到开始下标startindex到n的数字区间中长度为k的组合,本层搜索的终止条件是一维数组长度等于k。

 代码:

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    void backingTracking(int n,int k,int startindex){
        if(path.size() == k){//当一维数组中存在长度为k的组合,就将此组合放入二维数组
            result.push_back(path);
            return;
        }
        for(int i = startindex;i <= n;i++){
            path.push_back(i);
            backingTracking(n,k,i+1);//进入下轮递归,本轮第一个数字已经固定住,然后将开始下标加1,去遍历可以组合的第二个数字
            path.pop_back();
        }
    }
    vector<vector<int>> combine(int n, int k) {
        path.clear();
        result.clear();
        backingTracking(n,k,1);
        return result;
    }
};

优化:本题可以优化的地方就是,每层for循环的起始位置,如果for循环的起始位置之后的数字已经不足k,后面就没必要再遍历

 for(int i = startindex;i <= n - (k - path.size()) + 1;i++)

216.组合总和III 

216. 组合总和 III - 力扣(LeetCode)

解题思路:

本题解题思路与上题类似,只是增加了一个计算和的过程,我们可以直接套模板,终止条件就是一维数组长度达到k且和与目标和相等,回溯的过程要将出初始下标数字以外的数字减掉并弹出。

具体代码如下:

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    void backTracking(int targetsum,int k,int sum,int startindex){
        if(path.size() == k){
            if(targetsum == sum){
                result.push_back(path);
                return;
            }
        }
        for(int i = startindex;i <= 9;i++){
            sum += i;
            path.push_back(i);
            backTracking(targetsum,k,sum,i + 1);
            //回溯过程
            sum -= i;//比如说path中现在有[1,2],只有将2减掉,弹出,再将3推入,才有新的组合
            path.pop_back(); 
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        path.clear();
        result.clear();
        backTracking(n,k,0,1);
        return result;
    }
};

 17.电话号码的字母组合

17. 电话号码的字母组合 - 力扣(LeetCode)

解题思路:

我们首先要对数字对应的字符串做一个映射,可以选择用一个二维数组或者Map。回溯函数的参数是字符串digits以及记录当前字符串下标的index。终止条件就是当digits字符串中的字符长度等于index,就将此组合存入result中,本级递归的目标是找到符合条件的集合,从下标为0开始,因为每个数字代表的是一组不同的字符串。

具体代码如下:

class Solution {
private:
    const string letterMap[10]{
        "",
        "",
        "abc",
        "def",
        "ghi",
        "jkl",
        "mno",
        "pqrs",
        "tuv",
        "wxyz",
    };
public:
    string s;
    vector<string> result;
    void backTracking(const string& digits,int index){//index记录到第几个数字
        if(digits.size() == index){
            result.push_back(s);
            return;
        }
        int digit = digits[index] - '0';//将index指向的字母转换为数字
        string letters = letterMap[digit];//找到数字对应的字符串
        for(int i = 0;i < letters.size();i++){//i从0开始,因为每个数字代表的是不同的集合,所以目标数字的集合都需要遍历
            s.push_back(letters[i]);
            backTracking(digits,index + 1);
            s.pop_back();//回溯的过程
        }
    }
    vector<string> letterCombinations(string digits) {
        s.clear();
        result.clear();
        if(digits.size() == 0){
            return result;
        }
        backTracking(digits,0);
        return result;
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值