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

总结

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

相关推荐
CryptoRzz10 分钟前
日本股票 API 对接实战指南(实时行情与 IPO 专题)
java·开发语言·python·区块链·maven
yugi98783811 分钟前
基于M序列的直扩信号扩频码生成方法及周期长码直扩信号的MATLAB实现方案
开发语言·matlab
旧梦吟15 分钟前
脚本网页 三人四字棋
前端·数据库·算法·css3·html5
乾元18 分钟前
基于时序数据的异常预测——短期容量与拥塞的提前感知
运维·开发语言·网络·人工智能·python·自动化·运维开发
江上清风山间明月18 分钟前
使用python将markdown文件生成pdf文件
开发语言·python·pdf
凯_kyle19 分钟前
Python 算法竞赛 —— 基础篇(更新ing)
笔记·python·算法
j_xxx404_21 分钟前
C++算法入门:二分查找合集(二分查找|在排序数组中查找元素的第一个和最后一个位置)
开发语言·c++
ss27324 分钟前
阻塞队列:ArrayBlockingQueue如何用Lock与Condition实现高效并发控制
开发语言·python
lizz3127 分钟前
C++操作符重载深度解析
java·c++·算法
CodeCraft Studio28 分钟前
Vaadin 25 正式发布:回归标准Java Web,让企业级开发更简单、更高效
java·开发语言·前端·vaadin·java web 框架·纯java前端框架·企业级java ui框架