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.代码分析

总结

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

相关推荐
虾球xz4 分钟前
CppCon 2015 学习:Reactive Stream Processing in Industrial IoT using DDS and Rx
开发语言·c++·物联网·学习
aischang28 分钟前
统信桌面专业版如何使用python开发平台jupyter
开发语言·python·jupyter·统信uos
狐凄1 小时前
Python实例题:Python计算概率论
开发语言·python·概率论
liujing102329291 小时前
Day09_刷题niuke20250609
java·c++·算法
不7夜宵1 小时前
力扣热题100 k个一组反转链表题解
算法·leetcode·链表
Bardb1 小时前
02__C++的基本语法
c++·qt
q567315231 小时前
分布式增量爬虫实现方案
开发语言·分布式·爬虫·python
勤奋的知更鸟1 小时前
LLaMA-Factory和python版本的兼容性问题解决
开发语言·python·llama-factory
CIb0la1 小时前
Ai自动补全编程工具:llama vscode
运维·开发语言·学习·测试工具·程序人生
1candobetter2 小时前
JAVA后端开发——多租户
java·开发语言