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

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--

相关推荐
AitTech1 小时前
掌握C#中的动态规划技术
算法·c#·动态规划
新知图书1 小时前
Rust 运算符快速了解
算法
笨笨D幸福1 小时前
Rust 全局变量的最佳实践 lazy_static/OnceLock/Mutex/RwLock
开发语言·算法·rust
deardeer72 小时前
Leetcode算法基础篇-分治算法
数据结构·算法·leetcode
乘风破浪的咸鱼君2 小时前
2024/9/21 leetcode 19题 24题
数据结构·算法·leetcode
小松学前端3 小时前
第四章 4.2 时间复杂度
java·数据结构·算法·排序算法
Long long 523 小时前
常见排序详解
数据结构·算法·排序算法
小周的C语言学习笔记3 小时前
鹏哥C语言49---第5次作业:选择语句 if 和 switch
c语言·开发语言·c++·算法
李妖妖的拯救者3 小时前
C语言小tip之动态内存常见错误
c语言·c++·算法
深鱼~4 小时前
【二分算法】模板总结
算法