C++算法 —— 回溯(一)

一、全排列

1.链接

46. 全排列 - 力扣(LeetCode)

2.描述

3.思路

在做回溯相关的题目时,一般最优先的是根据题意我画出决策树,然后再去对细节进行设计

4.参考代码

cpp 复制代码
class Solution 
{
public:
    vector<int> path;
    vector<vector<int>> ret;
    bool cheak[7] = {false};
    vector<vector<int>> permute(vector<int>& nums) 
    {
        dfs(nums);
        return ret;
    }
    void dfs(vector<int>& nums)
    {
        if(path.size() == nums.size())
        {
            ret.push_back(path);
            return;
        }
        for(int i =0;i<nums.size();i++)
        {
            if(!cheak[i])
            {
                path.push_back(nums[i]);
                cheak[i] = true;
                dfs(nums);
                //回溯
                path.pop_back();
                cheak[i] = false;
            }
        }
    }
};

5.代码分析

二、子集

1.链接

78. 子集 - 力扣(LeetCode)

2.描述

3.思路

思路一:以每一个数字选或者不选的视角去看

思路二:以每次选几个的视角去看

4.参考代码

思路一:

cpp 复制代码
class Solution {
    vector<vector<int>> ret;
    vector<int> path; 
public:
    vector<vector<int>> subsets(vector<int>& nums) 
    {
        dfs(nums,0);
        return ret;
    }

    void dfs(vector<int>& nums,int pos)
    {
        if(pos == nums.size())
        {
            ret.push_back(path);
            return;
        }
        //选
        path.push_back(nums[pos]);
        dfs(nums,pos+1);
        path.pop_back();
        //不选
        dfs(nums,pos+1);
    }
};

思路二:

cpp 复制代码
class Solution {
    vector<vector<int>> ret;
    vector<int> path; 
public:
    vector<vector<int>> subsets(vector<int>& nums) 
    {
        dfs(nums,0);
        return ret;
    }

    void dfs(vector<int>& nums,int pos)
    {
        ret.push_back(path);
        for(int i = pos;i<nums.size();i++)
        {
            path.push_back(nums[i]);
            dfs(nums,i+1);
            path.pop_back();
        }
    }
};

三、找到所有子集的异或总和再求和

1.链接

1863. 找出所有子集的异或总和再求和 - 力扣(LeetCode)

2.描述

3.思路

和上一题的找子集是一样的,题目放这里是为了让我们在学习完上面的两题后,尝试自己动手去做出这题

4.参考代码

cpp 复制代码
class Solution {
    int ret = 0;
    int path = 0;
public:
    int subsetXORSum(vector<int>& nums) 
    {
        dfs(nums,0);
        return ret;
    }
    void dfs(vector<int>& nums,int pos)
    {
        ret += path;
        for(int i = pos;i<nums.size();i++)
        {
            path^=nums[i];
            dfs(nums,i+1);
            path^=nums[i];
        }
    }

};

四、全排列||

1.链接

47. 全排列 II - 力扣(LeetCode)

2.描述

3.思路

这题的整体思路和第一题全排列一是一样的,不同在于剪枝的不同,因为多了重复的数字,所以需要对如何剪枝进行分析,同样是先画决策树

4.参考代码

cpp 复制代码
class Solution {
    vector<vector<int>> ret;
    vector<int> path;
    bool cheak[9] = {false};
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) 
    {
        sort(nums.begin(),nums.end());
        dfs(nums);
        return ret;
    }
    void dfs(vector<int>& nums)
    {
        if(path.size() == nums.size())
        {
            ret.push_back(path);
            return;
        }

        for(int i = 0;i<nums.size();i++)
        {
            if(cheak[i] == false && (i==0 || nums[i-1] != nums[i] || cheak[i-1] == true))
            {
                path.push_back(nums[i]);
                cheak[i] = true;
                dfs(nums);
                path.pop_back();
                cheak[i] = false;
            }
        }
    }
};

五、电话号码的字母组合

1.链接

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

2.描述

3.思路

4.参考代码

cpp 复制代码
class Solution {
    vector<string> num = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    vector<string> ret;
    string path;
public:
    vector<string> letterCombinations(string digits) 
    {
        if(digits.size() == 0) return {};
        dfs(digits,0);
        return ret;
    }
    void dfs(string digits,int pos)
    {
        if(path.size() == digits.size())
        {
            ret.push_back(path);
            return;
        }

        string tmp = num[digits[pos]-'0'];
        for(int i = 0 ;i<tmp.size();i++)
        {
            path.push_back(tmp[i]);
            dfs(digits,pos+1);
            path.pop_back();
        }
    }
};

六、括号生成

1.链接

22. 括号生成 - 力扣(LeetCode)

2.描述

3.思路

4.参考代码

cpp 复制代码
class Solution {
    int left = 0;
    int right = 0;
    vector<string> ret;
    string path;

public:
    vector<string> generateParenthesis(int n) 
    {
        dfs(n);
        return ret;
    }
    void dfs(int n)
    {
        if(path.size() == 2*n)
        {
            ret.push_back(path);
            return;
        }
        if(left < n)
        {
            path += '(';
            left++;
            dfs(n);
            path.pop_back();
            left--;
        }
        if(right < left)
        {
            path += ')';
            right++;
            dfs(n);
            path.pop_back();
            right--;
        }
    }
};

七、组合

1.链接

77. 组合 - 力扣(LeetCode)

2.描述

3.思路

这是个典型的不重复选择的全排列,思路和前面的题目中基本一致,这里是考察对全排列的控制,当k作为一个参数去限定排列的格子时,我们通过递归出口处去控制格子的数量,不重复因此设计一个pos参数,决策树和代码设计,可以参考上面不重复选择的全排列

4.参考代码

cpp 复制代码
class Solution {
    vector<vector<int>> ret;
    vector<int> path;
public:
    vector<vector<int>> combine(int n, int k) 
    {
        dfs(n,k,1);
        return ret;
    }
    void dfs(int n,int k,int pos)
    {
        if(path.size() == k)
        {
            ret.push_back(path);
            return;
        }

        for(int i = pos;i<=n;i++)
        {
            path.push_back(i);
            dfs(n,k,i+1);
            path.pop_back();
        }
    }
};

八、目标和

1.链接

494. 目标和 - 力扣(LeetCode)

2.描述

3.思路

每个数字前面,我们可以添加一个正号或者符号,也就是说这个数组中每个数,我们可以选择加上这个数或者减去这个数,最终求得的结果要等于目标值,我们可以将所有情况穷举出来,因此同样是用到回溯算法,每一次可以选择加或者减这个数,当所有数字选完后,判断是否满足情况

代码设计:这是一个典型的不重复选择的全排列穷举,没法剪枝,但是在参数设计上可以有一定的优化,path参数可以选择用整形记录,此时可以将path放到dfs中,利用递归回溯

4.参考代码

cpp 复制代码
class Solution {
    int count = 0;
    int aim = 0;
public:
    int findTargetSumWays(vector<int>& nums, int target) 
    {
        aim = target;
        dfs(nums,0,0);
        return count;
    }
    void dfs(vector<int>& nums,int pos,int path)
    {
        if(pos == nums.size())
        {
            if(path == aim)
            {
                count++;
            }
            return;
        }
        //加号
        dfs(nums,pos+1,path+nums[pos]);
        //减号
        dfs(nums,pos+1,path-nums[pos]);
    }
};

九、组合总和

1.链接

39. 组合总和 - 力扣(LeetCode)

2.描述

3.思路

4.参考代码

cpp 复制代码
class Solution {
    vector<vector<int>> ret;
    vector<int> path;
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) 
    {
        dfs(candidates,target,0);
        return ret;
    }
    void dfs(vector<int>& candidates,int target,int pos)
    {
        int sum = 0;
        for(auto num:path)
        {
            sum+=num;
        }
        if(sum > target) return;
        if(sum == target)
        {   
            ret.push_back(path);
            return;
        }

        for(int i = pos;i<candidates.size();i++)
        {
            path.push_back(candidates[i]);
            dfs(candidates,target,i);
            path.pop_back();
        }
    }

};

5.代码分析

总结

本篇从最基础常见的两个回溯全排列的问题去切入,后续整理了大量相关的变形,提供练习,核心就是分析决策树去实现回溯、剪枝和递归出口三个地方的设计,下一篇逐步会再整理大约八九题与回溯相关的题目

相关推荐
艾莉丝努力练剑1 小时前
【C++:C++11】C++11新特性深度解析:从可变参数模板到Lambda表达式
c++·stl·c++11·lambda·可变模版参数
同学小张4 小时前
【端侧AI 与 C++】1. llama.cpp源码编译与本地运行
开发语言·c++·aigc·llama·agi·ai-native
踢球的打工仔4 小时前
PHP面向对象(7)
android·开发语言·php
轻抚酸~5 小时前
KNN(K近邻算法)-python实现
python·算法·近邻算法
汤姆yu7 小时前
基于python的外卖配送及数据分析系统
开发语言·python·外卖分析
Yue丶越7 小时前
【C语言】字符函数和字符串函数
c语言·开发语言·算法
翔云 OCR API7 小时前
人脸识别API开发者对接代码示例
开发语言·人工智能·python·计算机视觉·ocr
小白程序员成长日记7 小时前
2025.11.24 力扣每日一题
算法·leetcode·职场和发展
有一个好名字7 小时前
LeetCode跳跃游戏:思路与题解全解析
算法·leetcode·游戏
V***u4537 小时前
MS SQL Server partition by 函数实战二 编排考场人员
java·服务器·开发语言