代码随想录-高级篇之回溯算法

发布于:2025-03-30 ⋅ 阅读:(36) ⋅ 点赞:(0)

回溯

当你举例子,需要不断用到循环时,这时候就要想到回溯,首先应该举例子,画出树形图,

  • 递归函数 + for循环
    在这里插入图片描述

  • 回溯方法—纯暴力方法

  • 模板

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }
 
    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}
  • 回溯三部曲
    • 递归函数返回值
    • 终止条件
    • 单层递归逻辑

组合问题

组合

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    void backtracing(int n, int k,int startIndex)
    {
        // 终止条件
        if (path.size() == k)
        {
            res.push_back(path);
            return;
        }
        //单层递归逻辑
        for (int i = startIndex; i <= n; i++)
        {
            path.push_back(i);
            backtracing(n,k,i+1);
            //这里是递归逻辑,
            path.pop_back();
        }
    }
    vector<vector<int>> combine(int n, int k) {
        // 返回1-n之间,k个说的组合
        backtracing(n,k,1);
        return res;
    }
};

在这里插入图片描述

组合总和III

在这里插入图片描述

错误版本
/* 找出总和为n的k个数的组合,且这k个数只能是1~9,且每个数只能出现一次
        */
class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    void backtracing(int n, int k, int startIndex,int curSum)
    {
        // 终止条件
        if (path.size() == k && curSum==n)  // 注意这里不能用&&,两个满足其一就应该结束递归了
        {
            res.push_back(path);
            return;
        }
        //单层递归逻辑
        for (int i = startIndex; i <= 9; i++)
        {
            path.push_back(i);
            curSum += i;
            if (curSum <= n)
            {
                backtracing(n, k, i + 1, curSum);
                path.pop_back();   // 这里回溯了
                //curSum  也应该回溯
            }
            else {
                return;
            }
        }
    }
    //vector<vector<int>> combinationSum3(int k, int n) 
    vector<vector<int>> combinationSum3(int k, int n) {
        // 找出总和为n的k个数的组合,且这k个数只能是1~9,且每个数只能出现一次
        backtracing(n, k, 1,0);
        return res;
    }
};
正确版本
class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;

    void backtracing(int n, int k, int startIndex, int curSum) {
        // 终止条件:终止条件有两个,但是这个两个条件不是&&
        if (path.size() == k) {
            if (curSum == n) 
                res.push_back(path);
            return;
        }

        // 单层递归逻辑
        for (int i = startIndex; i <= 9; i++) {
            path.push_back(i);
            curSum += i;

            backtracing(n, k, i + 1, curSum);

            // 回溯
            path.pop_back();
            curSum -= i; // 注意这里也应该回溯
        }
    }

    vector<vector<int>> combinationSum3(int k, int n) {
        res.clear();
        path.clear();
        backtracing(n, k, 1, 0);
        return res;
    }
};

求电话号码的字母组合

在这里插入图片描述

在这里插入图片描述


#include<vector>
#include<iostream>
#include<string>

using namespace std;

class Solution {
public:
    string letterMap[10] = {
        "","","abc","def","ghi","jki","mno","pqrs","tuv","wxyz"
    };
    vector<string> res;
    string path;
    void backtracing(string digits, int index)
    {
        if (digits.size() == index)  // 将数字对于的字符串遍历完
        {
            res.push_back(path);
            return;
        }
        int number = digits[index] - '0'; // 取出对应的数字,char的数字-‘0’,转为int类型的数字
        string letter = letterMap[number]; // 取出对应的数字
        for (int i = 0; i < letter.size(); i++)
        {
            path.push_back(letter[i]);      
            backtracing(digits, index + 1); // 递归递归添加下一个数字。
            path.pop_back();
        }
    }
    // 电话号码的字母组合
    vector<string> letterCombinations(string digits) {
        if (digits.size() == 0) {
            return res;
        }
        backtracing(digits, 0);
        // 返回所有的电话号码的字母组合
        return res;
    }
};

组合总和

在这里插入图片描述


class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracing(vector<int>& candidates, int target, int curSum,int index)
    {
        if (curSum == target)
        {
            result.push_back(path);
            return;
        }else if (curSum > target)
            return;

        for ( ; index < candidates.size();)
        {
            curSum += candidates[index];
            path.push_back(candidates[index]);
            backtracing(candidates, target, curSum,index);
            path.pop_back();
            curSum -= candidates[index];
             index++;

        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracing(candidates,target,0,0);

        return result;
    }
};

在这里插入图片描述

标准
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracing(vector<int>& candidates, int target, int curSum,int index)
    {
        if (curSum == target)
        {
            result.push_back(path);
            return;
        }else if (curSum > target)
            return;

        for ( int i = index; index < candidates.size(); i++)
        {
            curSum += candidates[i];
            path.push_back(candidates[i]);
            backtracing(candidates, target, curSum, i);
            path.pop_back();
            curSum -= candidates[i];
             index++;
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracing(candidates,target,0,0);

        return result;
    }
};

组合总和II

在这里插入图片描述
与组合总和III的区别:组合总和III是从1~9中选择,候选数组中本身没有相同的数字,但是组合总和II有相同的数字

结果去重
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    /*
        思路:想按照组合问题III的走,然后再加入是剔除掉相同的组合即可
    */
    void backtracking(vector<int>& candidates, int target, int curSum, int startIndex) {
        if (curSum == target) {
            // 在这里对 path 进行排序并检查是否存在
            sort(path.begin(), path.end());
            auto it = find(result.begin(), result.end(), path);
            // 注意:返回end表示没有找到
            if (it == result.end()) {  // 如果没有找到相同的 path,则添加到 result 中
                result.push_back(path);
            }
            return;
        }
        if (curSum > target)
            result;

        for (int i = startIndex; i < candidates.size(); ++i) {
            path.push_back(candidates[i]);
            curSum += candidates[i];
            backtracking(candidates, target, curSum, i + 1);  // 递归调用,传递新的 curSum 和索引
            path.pop_back();  // 回溯
            curSum -= candidates[i];
        }
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());  // 对输入数组进行排序
        backtracking(candidates, target, 0, 0);
        return result;
    }
};
过程中去重
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    /*
        思路:想按照组合问题III的走,然后再加入是剔除掉相同的组合即可
    */
    void backtracking(vector<int>& candidates, int target, int curSum, int startIndex,int used[]) {
        if (curSum > target)
            return;
        if (curSum == target) {
            result.push_back(path);
            return;
        }

        for (int i = startIndex; i < candidates.size(); ++i) {
            if (i > 0 && candidates[i - 1] == candidates[i] && used[i - 1] == 0)
                continue;
            path.push_back(candidates[i]);
            used[i] = 1;
            curSum += candidates[i];
            backtracking(candidates, target, curSum, i + 1,used);  // 递归调用,传递新的 curSum 和索引
            path.pop_back();  // 回溯
            curSum -= candidates[i];
            used[i] = 0;
        }
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());  // 对输入数组进行排序
        int* used = new int[candidates.size()];
        backtracking(candidates, target, 0, 0,used);
        return result;
    }
};

在这里插入图片描述

切割问题

分割回文串

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

复原IP地址

在这里插入图片描述

直接思路
#include <vector>
#include <string>
using namespace std;

class Solution {
public:
    vector<string> res;
    string path;
    int count = 0;
    vector<string> restoreIpAddresses(string s) {
      
        backtracking(s, 0);
        return res;
    }

private:
    void backtracking(const string& s, int start) {
        // 如果已经找到了4段(IP地址应该由4段组成),并且遍历完了整个字符串,则添加到结果中
        if (count == 4 && start == s.size()) { // 当四个数字都加入的时候才会进入此
            path.erase(path.end() - 1); // 移除最后一个多余的点
            res.push_back(path);
            path += '.';            // 恢复path状态  ???????
            return;
        }
        // 如果还没有遍历完字符串,但是已经分了4段,提前返回
        if (count == 4) return;

        for (int i = start; i < s.size() && i < start + 3; ++i) { // 最多取3个字符
            string segment = s.substr(start, i - start + 1);
            int num = stoi(segment);

            // 判断数字是否在0-255之间,并且处理前导0的情况
            if (num <= 255 && (segment == "0" || segment[0] != '0')) {
                // 追加当前段到路径
                path += segment + '.'; // 这里没事,当为path为四个的时候去除最后的小数点就ok了
                count++;
                backtracking(s, i + 1);
                count--;
                // 回溯,移除最后添加的段
                path.erase(path.length() - segment.length() - 1);
            }
        }
    }
};
通过计数点的数量

在这里插入图片描述


#include <vector>
#include <string>
using namespace std;

class Solution {
private:
    vector<string> result;// 记录结果
    // startIndex: 搜索的起始位置,pointNum:添加逗点的数量
    void backtracking(string& s, int startIndex, int pointNum) {
        if (pointNum == 3) { // 逗点数量为3时,分隔结束
            // 判断第四段子字符串是否合法,如果合法就放进result中
            if (isValid(s, startIndex, s.size() - 1)) {
                result.push_back(s);
            }
            return;
        }
        for (int i = startIndex; i < s.size(); i++) {
            if (isValid(s, startIndex, i)) { // 判断 [startIndex,i] 这个区间的子串是否合法
                s.insert(s.begin() + i + 1, '.');  // 在i的后面插入一个逗点
                pointNum++;
                backtracking(s, i + 2, pointNum);   // 插入逗点之后下一个子串的起始位置为i+2
                pointNum--;                         // 回溯
                s.erase(s.begin() + i + 1);         // 回溯删掉逗点
            }
            else break; // 不合法,直接结束本层循环
        }
    }
    // 判断字符串s在左闭右闭区间[start, end]所组成的数字是否合法
    bool isValid(const string& s, int start, int end) {
        if (start > end) {
            return false;
        }
        if (s[start] == '0' && start != end) { // 0开头的数字不合法
            return false;
        }
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
                return false;
            }
            num = num * 10 + (s[i] - '0');
            if (num > 255) { // 如果大于255了不合法
                return false;
            }
        }
        return true;
    }
public:
    vector<string> restoreIpAddresses(string s) {
        result.clear();
        if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了
        backtracking(s, 0, 0);
        return result;
    }
};


子集问题

子集

  • 与组合问题的区别,收获结果时是从每一个节点中去取。
    在这里插入图片描述在这里插入图片描述

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        result.push_back(path);
        if (startIndex >= nums.size()) {
            return;
        }

        for (int i = startIndex; i < nums.size(); i++) {
            path.push_back(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {

        backtracking(nums,0);
        return result;
    }
};

子集II

在这里插入图片描述

结果去重
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;

    void backtracking(vector<int>& nums, int startIndex) {
        vector<int> tmp = path;
        sort(path.begin(), path.end());
        if (find(result.begin(), result.end(), path) == result.end())  // 标识没有找到
        {
            result.push_back(path);
        }
        path = tmp;
        if (startIndex >= nums.size()) {
            return;
        }

        for (int i = startIndex; i < nums.size(); i++) {
            path.push_back(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {

        backtracking(nums,0);
        return result;
    }
};
过程去重
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;

    void backtracking(vector<int>& nums, int startIndex, int used[]) {

        result.push_back(path);
        if (startIndex >= nums.size()) {
            return;
        }

        for (int i = startIndex; i < nums.size(); i++) {
            //树层去重:前后两个原始相等,而且已经遍历到相等的第二个元素
            if (i > 0 && nums[i - 1] == nums[i] && used[i - 1] == 0)
                continue;
            path.push_back(nums[i]);
            used[i] = 1;
            backtracking(nums,i+1, used);
            path.pop_back();
            used[i] = 0;
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(), nums.end());  // 注意一定需要排序
        int* used = new int[nums.size()];
        backtracking(nums,0, used);
        return result;
    }
};

非递减子序列

在这里插入图片描述

直接的思路
/*
    这里无非就是:子集问题,要求子集内的元素个数>1,子集内元素按从小到大
*/
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;

    void backtracking(vector<int>& nums, int startIndex, int used[]) {

        if (path.size() > 1)
        {
            result.push_back(path);
        }
        if (startIndex >= nums.size()) {
            return;
        }

        for (int i = startIndex; i < nums.size(); i++) {
            //树层去重:前后两个原始相等,而且已经遍历到相等的第二个元素
            if (i > 0 && nums[i - 1] == nums[i] && used[i - 1] == 0)
                continue;
            if (!path.empty() && path.back() > nums[i])
                continue;
            path.push_back(nums[i]);
            used[i] = 1;
            backtracking(nums,i+1, used);
            path.pop_back();
            used[i] = 0;
        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        int* used = new int[nums.size()];
        backtracking(nums,0, used);
        return result;
    }
};

在这里插入图片描述

树层去重
// 版本一
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        if (path.size() > 1) {
            result.push_back(path);
            // 注意这里不要加return,要取树上的节点
        }
        //有两种情况不要处理 
            // 1、非递增不处理 
             // 2、递增但是会产生重复结果 例如 4、7、7 第一个7要选 第二个7不要选 否则会产生重复结果
            //用数组去重 一个数组在一层中利用 所以在for循环外面定义
        unordered_set<int> uset; // 使用set对本层元素进行去重
        for (int i = startIndex; i < nums.size(); i++) {
            if ((!path.empty() && nums[i] < path.back())
                || uset.find(nums[i]) != uset.end()) {
                continue;
            }
            uset.insert(nums[i]); // 记录这个元素在本层用过了,本层后面不能再用了
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        result.clear();
        path.clear();
        backtracking(nums, 0);
        return result;
    }
};

排列问题

全排列

在这里插入图片描述
在这里插入图片描述

class Solution
{
private:
    vector<int> path;
    vector<vector<int>> result;

public:
    void backtracking(vector<int> &nums, vector<int> used)
    {
        if (path.size() == nums.size())
        {
            result.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++)
        {
            if (used[i] == 1)
                continue;
            path.push_back(nums[i]);
            used[i] = 1;
            backtracking(nums, used);
            path.pop_back();
            used[i] = 0;
        }
    }
    vector<vector<int>> permute(vector<int> &nums)
    {
        vector<int> used(nums.size(), 0);
        backtracking(nums, used);
    }
};

全排列II

在这里插入图片描述

结果去重


class Solution
{
private:
    vector<int> path;
    vector<vector<int>> result;

public:
    void backtracking(vector<int> &nums, vector<int> used)
    {
        if (path.size() == nums.size())
        {
            if (find(result.begin(), result.end(), path) == result.end())
                result.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++)
        {
            if (used[i] == 1)
                continue;
            path.push_back(nums[i]);
            used[i] = 1;
            backtracking(nums, used);
            path.pop_back();
            used[i] = 0;
        }
    }
    vector<vector<int>> permuteUnique(vector<int> &nums)
    {
        vector<int> used(nums.size(), 0);
        backtracking(nums, used);
        return result;
    }
};

在回溯过程中去重

在这里插入图片描述



class Solution
{
private:
    vector<int> path;
    vector<vector<int>> result;

public:
    void backtracking(vector<int> &nums, vector<int> used)
    {
        if (path.size() == nums.size())
        {
            result.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++)
        {
            if ( i>0 && nums[i] == nums[i - 1]&&used[i-1] == 0) 
            //  树层去重:注意前提是数组已经经过排序了,相同元素相邻.
            //used[i-1] == 0 保证是输层上去重
                continue;
            if (used[i] == 1)  // 保证一个数字不会被重复选择
              continue; 
            path.push_back(nums[i]);
            used[i] = 1;
            backtracking(nums, used);
            path.pop_back();
            used[i] = 0;
        }
    }
    vector<vector<int>> permuteUnique(vector<int> &nums)
    {
        sort(nums.begin(), nums.end());
        vector<int> used(nums.size(), 0);
        backtracking(nums, used);
        return result;
    }
};

棋盘问题

N皇后

在这里插入图片描述


class Solution {
public:
    vector<vector<string>> res;
    vector<string> target;
    vector<vector<string>> solveNQueens(int n) {
        target = vector<string>(n, string(n, '.'));
        rb(n, 0);
        return res;
    }

    void rb(int n, int row) {
        if (n == row) {
            res.push_back(target);
            return;
        }

        for (int i = 0; i < n; i++) {
            if (!isValid(i, row, n)) {
                continue;
            }
            target[i][row] = 'Q';
            rb(n, row + 1);
            target[i][row] = '.';
        }
    }

    bool isValid(int x, int y, int n) {
        for (int i = 0; i < y; i++) {
            if (target[x][i] == 'Q')
                return false;
        }

        for (int i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j--) {
            if (target[i][j] == 'Q')
                return false;
        }

        for (int i = x + 1, j = y - 1; i <n && j >= 0; i++, j--) {
            if (target[i][j] == 'Q')
                return false;
        }

        return true;
    }
};

解数独问题

在这里插入图片描述


class Solution
{
public:
    bool backtracking(vector<vector<char>> &board)
    {
        for (int i = 0; i < board.size(); i++)
        {
            for (int j = 0; j < board[0].size(); j++)
            {
                if (board[i][j] == '.')
                {
                    for (char k = '1'; k <= '9'; k++)
                    {
                        if (isvalid(board, k, i, j))
                        {
                            board[i][j] = k;
                            if (backtracking(board))
                                return true;
                            board[i][j] = '.';
                        }
                    }
                    return false;
                }
            }
        }
        return true;
    }
    bool isvalid(vector<vector<char>> &board, char val, int row, int col)
    {
        for (int i = 0; i < 9; i++)
        {
            if (board[i][col] == val)
                return false;
        }
        for (int j = 0; j < 9; j++)
        {
            if (board[row][j] == val)
                return false;
        }
        int startrow = (row / 3) * 3;
        int startcol = (col / 3) * 3;
        for (int i = startrow; i < startrow + 3; i++)
        {
            for (int j = startcol; j < startcol + 3; j++)
            {
                if (board[i][j] == val)
                    return false;
            }
        }
        return true;
    }
    void solveSudoku(vector<vector<char>> &board)
    {
        backtracking(board);
    }
};

网站公告

今日签到

点亮在社区的每一天
去签到