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 分钟前
【modbus学习】
java·c语言·c++·学习·算法·c#
索迪迈科技3 分钟前
STL库——map/set(类函数学习)
开发语言·c++·学习
Dfreedom.9 分钟前
在Windows上搭建GPU版本PyTorch运行环境的详细步骤
c++·人工智能·pytorch·python·深度学习
ForteScarlet25 分钟前
Kotlin 2.2.20 现已发布!下个版本的特性抢先看!
android·开发语言·kotlin·jetbrains
兴科Sinco27 分钟前
[leetcode 1]给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值 target 的那两个整数[力扣]
python·算法·leetcode
沐怡旸28 分钟前
【算法--链表】138.随机链表的复制--通俗讲解
算法·面试
anlogic34 分钟前
Java基础 9.10
java·开发语言·算法
薛定谔的算法37 分钟前
JavaScript单链表实现详解:从基础到实践
数据结构·算法·leetcode
yongche_shi39 分钟前
第二篇:Python“装包”与“拆包”的艺术:可迭代对象、迭代器、生成器
开发语言·python·面试·面试宝典·生成器·拆包·装包
CoovallyAIHub1 小时前
CostFilter-AD:用“匹配代价过滤”刷新工业质检异常检测新高度! (附论文和源码)
深度学习·算法·计算机视觉