赞
踩
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
我们先看看官方怎么说的:
回溯,计算机算法,回溯法也称试探法,它的基本思想是:从问题的某一种状态(初始状态)出发,搜索从这种状态出发所能达到的所有“状态”,当一条路走到“尽头”的时候(不能再前进),再后退一步或若干步,从另一种可能“状态”出发,继续搜索,直到所有的“路径”(状态)都试探过。这种不断“前进”、不断“回溯”寻找解的方法,就称作“回溯法”。
而今天这写的是回溯算法-DFS,其实际含义就是递归回溯。在学习回溯的过程中,很多的初学者对于程序本身是怎么调用的,以及为什么要这样去做,还存在些许的困惑,接下来我会列举三道力扣中的经典题目,来记录下我对回溯的理解。
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1] 输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1] 输出:[[1]]
先看图片,这张图是找的代码随想录的一张图,很经典;
对于每个结果,它是一直走到底的,走完之后才会在往回遍历。
回溯三问:
1、当前的操作是什么
枚举path[i]要填入的字母;
2、子问题
构造字符串>=i 的部分;
3、下一个子问题是什么
构造字符串>=i + 1的部分;
那么什么时候就返回答案呢;
当i== 集合的长度是,这就是一个结果。
java版本:
- class Solution {
-
- List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
- LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
- boolean[] used;
- public List<List<Integer>> permute(int[] nums) {
- if (nums.length == 0){
- return result;
- }
- used = new boolean[nums.length];
- permuteHelper(nums);
- return result;
- }
-
- private void permuteHelper(int[] nums){
- if (path.size() == nums.length){
- result.add(new ArrayList<>(path));
- return;
- }
- for (int i = 0; i < nums.length; i++){
- if (used[i]){
- continue;
- }
- used[i] = true;
- path.add(nums[i]);
- permuteHelper(nums);
- path.removeLast();
- used[i] = false;
- }
- }
- }
- 当按照1->2->3这条路线遍历到最底层的时候,path = [1,2,3]确实被存进了res中,但是被忘了存的是path这个指针,在后来的遍历过程中path指向的堆内存的内容会一直修改,因为回溯算法的精髓是通过 path.removeLast()
- 将路径的最后一个节点删除去返回上一个节点,返回一次删一个,最终返回到最高层的时候,path指向的内容就为空了,所以为了保存遍历的结果,要讲path的内容复制一份。
c++ 版本
- class Solution {
- public:
- vector<vector<int>> permute(vector<int>& nums) {
- int n = nums.size();
- vector<vector<int>>ans;
- vector<int>path(n),inpath(n);
- //第一个数组用来记录最后回溯到底的值,第二个数组是用来判断该数组元素是否被用过;
- function<void(int)> dfs = [&](int i){
- if(i == n)
- {
- ans.emplace_back(path);
- return;
- }
- for(int j = 0;j < n;j ++){
- if(!inpath[j]){
- path[i] = nums[j];
- inpath[j] = true;
- dfs(i + 1);
- inpath[j] = false;
- }
- }
- };
- dfs(0);
- return ans;
- }
- };
当你理解了上面的全排列问题后,其实下面两题的思路就和他是一样的了,按照回溯三问,一步步想清楚;
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于 150
个。
示例 1:
输入:candidates = [2,3,6,7],
target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例 2:
输入: candidates = [2,3,5],
target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例 3:
输入: candidates = [2],
target = 1
输出: []
java版本
- class Solution {
- List<List<Integer>>ans = new ArrayList<>();
- List<Integer>path = new ArrayList<>();
- public List<List<Integer>> combinationSum(int[] candidates, int target) {
- int n = candidates.length;
- dfs(candidates,0,target);
- return ans;
- }
- public void dfs(int[] c, int i,int x){
- if(x == 0){
- ans.add(new ArrayList(path));
- return;
- }
- if(x < 0)
- return;
- for(int j = i;j < c.length;j ++){
- if(c[j] <= x){
- path.add(c[j]);
- dfs(c,j,x - c[j]);
- path.remove(path.size() - 1);
- }
- }
- }
- }
c++版本
- class Solution {
- public:
- vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
- //回溯经典问题:
- int n = candidates.size();
- vector<int>path;
- vector<vector<int>>ans;
- sort(candidates.begin(),candidates.end());
- function<void(int,int)>dfs = [&](int i,int c){
- if(c == 0)
- {
- ans.emplace_back(path);
- return;
- }
- if(c < candidates[i])
- return;
- for(int j = i;j < n;j ++){
- path.push_back(candidates[j]);
- dfs(j,c - candidates[j]);
- path.pop_back();//恢复现场;
- }
- };
- dfs(0,target);
- return ans;
- }
- };
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0] 输出:[[],[0]]
- class Solution {
- public:
- vector<vector<int>> subsets(vector<int>& nums) {
- vector<vector<int>>ans;
- vector<int>path;
- int n = nums.size();
- function<void(int)> dfs = [&](int i){
- if(i == n)
- {//终止条件
- ans.push_back(path);
- return;
- }
- dfs(i + 1);//不选nums[i];
- path.push_back(nums[i]);//选择nums[i];
- dfs(i + 1);
- path.pop_back();
- };
- dfs(0);
- return ans;
- }
- };
- void backtracking(参数){
- if(终止条件){
- 收集结果;
- return;
- }
- for(集合元素,层次遍历){
- 处理节点;
- 递归函数;
- 回溯操作;
- }
- }
总体概括为横向for循环遍历,纵向递归搜索。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。