代码随想录笔记--回溯算法篇

1--回溯算法理论基础

回溯算法本质上是一个暴力搜索 的过程,其常用于解决组合切割子集排列 等问题,其一般模板如下:

cpp 复制代码
void backTracking(参数){
    if(终止条件){
    // 1. 收获结果;
    // 2. return;
    }

    for(..遍历){
        // 1. 处理节点
        // 2. 递归搜索
        // 3. 回溯 // 即撤销对节点的处理
    }
    return;
}

2--组合问题

主要思路:

基于回溯法,暴力枚举 k 个数,需注意回溯弹出元素的操作;

cpp 复制代码
#include <iostream>
#include <vector>

class Solution {
public:
    std::vector<std::vector<int>> combine(int n, int k) {
        std::vector<int> path;
        backTracking(n, k, path, 1); // 从第 1 个数开始
        return res;
    }

    void backTracking(int n, int k, std::vector<int> path, int start){
        if(path.size() == k){
            res.push_back(path);
            return;
        }

        for(int i = start; i <= n; i++){
            path.push_back(i);
            backTracking(n, k, path, i + 1); // 递归暴力搜索下一个数
            path.pop_back(); // 回溯
        }
    }

private:
    std::vector<std::vector<int>> res;
};

int main(int argc, char* argv[]){
    int n = 4, k = 2;
    Solution S1;
    std::vector<std::vector<int>> res = S1.combine(n, k);
    for(auto v : res){
        for(int item : v) std::cout << item << " ";
        std::cout << std::endl;
    }
    return 0;
}

3--组合问题的剪枝操作

上题的组合问题中,对于进入循环体 for(int i = start; i <= n; i++):

已选择的元素数量为:path.size()

仍然所需的元素数量为:k - path.size()

剩余的元素集合为:n - i + 1

则为了满足要求,必须满足:k-path.size() <= n - i + 1,即 i <= n - k + path.size() + 1

因此,可以通过以下条件完成剪枝操作:

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

cpp 复制代码
#include <iostream>
#include <vector>

class Solution {
public:
    std::vector<std::vector<int>> combine(int n, int k) {
        std::vector<int> path;
        backTracking(n, k, path, 1); // 从第 1 个数开始
        return res;
    }

    void backTracking(int n, int k, std::vector<int> path, int start){
        if(path.size() == k){
            res.push_back(path);
            return;
        }

        for(int i = start; i <= n - k + path.size() + 1; i++){
            path.push_back(i);
            backTracking(n, k, path, i + 1); // 暴力下一个数
            path.pop_back(); // 回溯
        }
    }

private:
    std::vector<std::vector<int>> res;
};

int main(int argc, char* argv[]){
    int n = 4, k = 2;
    Solution S1;
    std::vector<std::vector<int>> res = S1.combine(n, k);
    for(auto v : res){
        for(int item : v) std::cout << item << " ";
        std::cout << std::endl;
    }
    return 0;
}

4--组合总和III

主要思路:

类似于上面的组合问题,基于回溯来暴力枚举每一个数,需要注意剪枝操作;

cpp 复制代码
#include <iostream>
#include <vector>

class Solution {
public:
    std::vector<std::vector<int>> combinationSum3(int k, int n) {
        std::vector<int> path;
        backTracking(k, n, 0, path, 1);
        return res;
    }

    void backTracking(int k, int n, int sum, std::vector<int>path, int start){
        if(sum > n) return; // 剪枝
        if(path.size() == k){ // 递归终止
            if(sum == n){
                res.push_back(path);
            }
            return;
        }

        for(int i = start; i <= 9 + path.size() - k + 1; i++){ // 剪枝
            path.push_back(i);
            sum += i;
            backTracking(k, n, sum, path, i + 1); // 递归枚举下一个数
            // 回溯
            sum -= i;
            path.pop_back();
        }

    }
private:
    std::vector<std::vector<int>> res;
};

int main(int argc, char* argv[]){
    int k = 3, n = 7;
    Solution S1;
    std::vector<std::vector<int>> res = S1.combinationSum3(k, n);
    for(auto v : res){
        for(int item : v) std::cout << item << " ";
        std::cout << std::endl;
    }
    return 0;
}

5--电话号码的字母组合

主要思路:

根据传入的字符串,遍历每一个数字字符,基于回溯法,递归遍历每一个数字字符对应的字母;

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>

class Solution {
public:
    std::vector<std::string> letterCombinations(std::string digits) {
        if(digits.length() == 0) return res;
        std::vector<std::string> letter = {"", " ", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        backTracking(digits, letter, 0);
        return res;
    }

    void backTracking(std::string digits, std::vector<std::string> letter, int cur){
        if(cur == digits.size()){
            res.push_back(tmp);
            return;
        }
        int letter_idx = digits[cur] - '0';
        int letter_size = letter[letter_idx].size();
        for(int i = 0; i < letter_size; i++){
            tmp.push_back(letter[letter_idx][i]);
            backTracking(digits, letter, cur+1);
            // 回溯
            tmp.pop_back();
        }
    }

private:
    std::vector<std::string> res;
    std::string tmp;
};

int main(int argc, char* argv[]){
    std::string test = "23";
    Solution S1;
    std::vector<std::string> res = S1.letterCombinations(test);
    for(std::string str : res) std::cout << str << " ";
    std::cout << std::endl;
    return 0;
}

6--组合总和

主要思路:

经典组合问题,通过回溯法暴力递归遍历;

cpp 复制代码
#include <iostream>
#include <vector>

class Solution {
public:
    std::vector<std::vector<int>> combinationSum(std::vector<int>& candidates, int target) {
        if(candidates.size() == 0) return res;
        backTracking(candidates, target, 0, 0, path);
        return res;
    }

    void backTracking(std::vector<int>& candidates, int target, int sum, int start, std::vector<int>path){
        if(sum > target) return; // 剪枝
        if(sum == target) {
            res.push_back(path);
            return;
        }

        for(int i = start; i < candidates.size(); i++){
            sum += candidates[i];
            path.push_back(candidates[i]);
            backTracking(candidates, target, sum, i, path);
            // 回溯
            sum -= candidates[i];
            path.pop_back();
        }
    }

private:
    std::vector<std::vector<int>> res;
    std::vector<int> path;
};

int main(int argc, char* argv[]){
    // candidates = [2,3,6,7], target = 7
    std::vector<int> test = {2, 3, 6, 7};
    int target = 7;
    Solution S1;
    std::vector<std::vector<int>> res = S1.combinationSum(test, target);

    for(auto vec : res){
        for(int val : vec) std::cout << val << " ";
        std::cout << std::endl; 
    }

    return 0;
}

7--组合总和II

主要思路:

基于回溯法,暴力递归遍历数组;

本题不能包含重复的组合,但由于有重复的数字,因此需要进行树层上的去重

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

class Solution {
public:
    std::vector<std::vector<int>> combinationSum2(std::vector<int>& candidates, int target) {
        if(candidates.size() == 0) return res;
        std::sort(candidates.begin(), candidates.end());
        std::vector<bool> visited(candidates.size(), false);
        backTracking(candidates, target, 0, 0, visited);
        return res;
    }

    void backTracking(std::vector<int>& candidates, int target, int sum, int start, std::vector<bool>& visited){
        if(sum > target) return; // 剪枝
        if(sum == target){
            res.push_back(path);
            return;
        }
        for(int i = start; i < candidates.size(); i++){
            // 树层剪枝去重
            if(i > 0 && candidates[i-1] == candidates[i]){
                if(visited[i-1] == false) continue;
            }
            sum += candidates[i];
            path.push_back(candidates[i]);
            visited[i] = true;
            backTracking(candidates, target, sum, i+1, visited);
            // 回溯
            sum -= candidates[i];
            path.pop_back();
            visited[i] = false;
        }
    }

private:
    std::vector<std::vector<int>> res;
    std::vector<int> path;
};

int main(int argc, char* argv[]){
    // candidates = [10,1,2,7,6,1,5], target = 8
    std::vector<int> test = {10, 1, 2, 7, 6, 1, 5};
    int target = 8;
    Solution S1;
    std::vector<std::vector<int>> res = S1.combinationSum2(test, target);

    for(auto vec : res){
        for(int val : vec) std::cout << val << " ";
        std::cout << std::endl; 
    }

    return 0;
}

8--

相关推荐
pianmian12 分钟前
python数据结构基础(7)
数据结构·算法
好奇龙猫2 小时前
【学习AI-相关路程-mnist手写数字分类-win-硬件:windows-自我学习AI-实验步骤-全连接神经网络(BPnetwork)-操作流程(3) 】
人工智能·算法
sp_fyf_20243 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01
人工智能·深度学习·神经网络·算法·机器学习·语言模型·数据挖掘
ChoSeitaku3 小时前
链表交集相关算法题|AB链表公共元素生成链表C|AB链表交集存放于A|连续子序列|相交链表求交点位置(C)
数据结构·考研·链表
偷心编程3 小时前
双向链表专题
数据结构
香菜大丸3 小时前
链表的归并排序
数据结构·算法·链表
jrrz08283 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
oliveira-time3 小时前
golang学习2
算法
@小博的博客3 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
南宫生4 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法