回溯法理论基础

回溯法是一种搜索算法,从本质上来说,回溯法是一种穷举法,穷尽其所有可能而举其可行解;尽管回溯法有剪枝等操作,但也只是去除一些明显不可行的部分,仍改变不了回溯法暴力搜索的本质。

虽然回溯法是一种暴力求解算法,但很多时候我们也只能选择这种算法。

回溯法是以深度优先的方式系统地搜索问题的解,它适用于解一些组合数较大的问题。

回溯法可以解决的问题

  • 组合问题:从n个数的集合中选出k个数的组合问题
  • 排列问题:对n个数进行排列有多少种排列方法
  • 子集问题:一个集合种有多少符合条件的子集问题
  • 棋盘问题:典型的有n皇后问题、解数独问题

回溯法解题模板

void backtrack(参数) {if (终止条件) {收集结果;return;}for (选择:本层集合中元素(集合大小为本层节点的数量)) {处理节点;backtrack(路径,选择列表,其他参数); // 递归回溯,撤销处理操作;}
}

回溯法之组合问题

组合问题基础
力扣第39题39. 组合总和 - 力扣(LeetCode) (leetcode-cn.com)

求一个数组中使数字和为目标值的所有组合,属于典型的组合问题,接下来套用模板进行解决。

首先我们需要一个总的链表来存放已经找到的组合,还需要一个存放当前组合的链表

List<List<Integer>> res=new ArrayList<>();   //存放可行解的链表
List<Integer> l=new ArrayList<>();   //存放当前结果的链表

接下来定义我们的回溯函数backtrack(),这也是回溯法的重中之重,由于回溯法的参数一般比较复杂,我们可以先不填写参数,需要用到的时候再加入参数。

为了判断当前组合是否满足条件,我们需要设置一个整型变量sum,用于记录当前组合的数字和,回溯终止条件是sum==target即当前数字之和等于给定的目标值,并将其加入到结果集中;

if(sum==target){       //收集结果res.add(new ArrayList<Integer>(l));return;
}

如果sum值小于目标值,继续进行深度搜索;在本层集合中依次选择元素都尝试将其加入当前组合中,在for循环中先将其加入组合中,并修改sum值,然后回溯递归进行下一层操作,回溯完成后,不要忘了撤销刚才的操作。

for(int i=startindex;i<candidates.length;i++){sum+=candidates[i];l.add(candidates[i]);backtrack(res,candidates,target,l,sum,i);l.remove(l.size()-1);    //回溯sum=sum-candidates[i];
}

我们还可以加入剪枝操作,当sum值已经大于目标值target时,就不需要进行接下来的操作了,直接break。加入剪枝操作之后的代码为:

for(int i=startindex;i<candidates.length;i++){int tmp=sum+candidates[i];if(tmp<=target){sum+=candidates[i];l.add(candidates[i]);backtrack(res,candidates,target,l,sum,i);l.remove(l.size()-1);      //回溯sum=sum-candidates[i];}else break;                //剪枝操作
}

在主函数中我们需要对数组进行排序,可以减少重复的回溯组合,然后调用回溯函数进行求解。

总的代码如下:

class Solution {public List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> res=new ArrayList<>();List<Integer> l=new ArrayList<>();Arrays.sort(candidates);backtrack(res,candidates,target,l,0,0);return res;}public void backtrack(List<List<Integer>> res,int[] candidates,int target,List<Integer> l,int sum,int startindex){if(sum==target){       //收集结果res.add(new ArrayList<Integer>(l));return;}for(int i=startindex;i<candidates.length;i++){int tmp=sum+candidates[i];if(tmp<=target){sum+=candidates[i];l.add(candidates[i]);backtrack(res,candidates,target,l,sum,i);l.remove(l.size()-1);    //回溯sum=sum-candidates[i];}else break;}}
}
组合问题(去重)

力扣第40题40. 组合总和 II - 力扣(LeetCode) (leetcode-cn.com)

此题和上题的描述大致相同,但有两个区别,这也导致了此题的答案与上题有两处不同

  1. 数组中的每个数字只能使用一次
  2. 解集不能包含重复的组合

这是两个不同的限制要求

  • 对于第一个要求,每个数字只能使用一次,我们考虑上题的backtrack函数中的startIndex参数,由于上题中没有要求每个数字只能使用一次,我们传给下一层的startIndex参数是i,这代表下一层还可以继续使用自己,所以在此题中,i这一个数字在下层肯定不能继续使用,于是传给下一层的参数是i+1

    backtrack(res, list, candidates, sum, target, i+1);
    
  • 对于第二个要求,是结果不能包含重复的组合,其实重复的组合是由于数组中有重复的元素造成的,比如此题中有两个1,第一个1和数字7组成一个组合得到target数字8,第二个1同样可以和7组成一个组合也可以得到target数字8,这两个组合都是可行解,且每个数字只使用一次,但是却是重复的组合。

    我们在回溯开始之前已经对数组进行了排序,在for循环中遇到第二个1、第三个1或是更多的1时可以直接跳过此循环,因为此时产生的组合都已经加入到结果集中了(第一个1产生的)。

    if(i>startIndex && candidates[i]==candidates[i-1]) continue;
    

完整的代码如下:

public List<List<Integer>> combinationSum2(int[] candidates, int target) {List<List<Integer>> res=new ArrayList<>();List<Integer> list=new ArrayList<>();Arrays.sort(candidates);backtrack(res,list,candidates,0,target,0);return res;}void backtrack(List<List<Integer>> res,List<Integer> list,int[] candidates,int sum,int target,int startIndex){if(sum==target){res.add(new ArrayList<Integer>(list));return;}for(int i=startIndex;i<candidates.length;i++){if(i>startIndex && candidates[i]==candidates[i-1]) continue ; //去重int temp=sum+candidates[i];if(temp<=target){list.add(candidates[i]);sum+=candidates[i];backtrack(res, list, candidates, sum, target, i+1);   //数组中每个数字只使用一次sum-=candidates[i];list.remove(list.size()-1);}else break;}}
组合问题练习

力扣第77题77. 组合 - 力扣(LeetCode) (leetcode-cn.com)

做完上面两个题之后相信对于回溯法做组合问题已经有了大致思路,不妨做一下此题,和上述题目异曲同工。

public List<List<Integer>> combine(int n, int k) {List<List<Integer>> res=new ArrayList<>();List<Integer> list=new ArrayList<>();backtrack1(res,list,n,k,0,1);return res;
}
void backtrack1(List<List<Integer>> res,List<Integer> list,int n,int k,int count,int startIndex){if(count==k){res.add(new ArrayList<>(list));return;}for(int i=startIndex;i<n+1;i++){list.add(i);count++;backtrack1(res,list,n,k,count,i+1);count--;list.remove(list.size()-1);}
}

算法笔记之回溯法(一)——溯洄从之,道阻且长;溯游从之,宛在水中央。相关推荐

  1. 0027算法笔记——【回溯法】回溯法与装载问题

    1.回溯法 (1)描述:回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标.但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法.  (2)原 ...

  2. 算法笔记之回溯法(2)

    着色问题 问题分析 假设地图共有7个区域,分别是A/B/C/D/E/F/G,对上面顺序进行编号,每个区域用一个结点表示,相邻的区域有连线,那么地图就转化成一个无向连接图. 算法设计 定义问题的解空间. ...

  3. 0x08算法设计与分析复习(二):算法设计策略-回溯法2

    参考书籍:算法设计与分析--C++语言描述(第二版) 算法设计策略-回溯法 子集和数 问题描述 已知n个不同的正数wi(0≤i≤n−1)的集合,求该集合的所有满足条件的子集,使得每个子集中的正数之和等 ...

  4. 【算法思想:回溯法】回溯算法入门级详解

    回溯法是一种非常重要的算法思想,在大厂面试中频繁出现,所以做了一个笔记,记录了一下. 回溯算法与深度优先遍历 以下是维基百科中「回溯算法」和「深度优先遍历」的定义. 回溯法 采用试错的思想,它尝试分步 ...

  5. 回溯 皇后 算法笔记_回溯算法:N皇后问题

    给「代码随想录」一个星标吧! ❝ 通知:我将公众号文章和学习相关的资料整理到了Github :https://github.com/youngyangyang04/leetcode-master,方便 ...

  6. 4.Python算法之试探算法思想(回溯法)

    1.什么是试探算法? 2.试探算法的解题的基本步骤 3.试探算法的思想 4.试探算法适合的问题 5.试探算法解决"八皇后"的问题 1.什么是试探算法?   试探算法也叫回溯法,试探 ...

  7. 五大常用算法之四:回溯法

    http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741376.html 1.概念 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试 ...

  8. 回溯法采用的搜索策略_五大常用算法之四:回溯法

    1.概念 回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就"回溯"返回,尝试别的路径.回溯法是一种选优搜索法,按选优条件向 ...

  9. java 符号三角形_算法java实现--回溯法--符号三角形问题

    符号三角形问题的java实现(回溯法) 具体问题描述以及C/C++实现参见网址 http://blog.csdn.net/liufeng_king/article/details/8764319 /* ...

最新文章

  1. c# 操作文件_小练习(音乐播放器)
  2. 【转】错误: ORA-01591: 锁被未决分布式事务处理 7.2.428982 持有--解决方案
  3. 在中国使用pip更新或安装库的配置
  4. 《VMware vSphere设计(原书第2版)》——1.1 什么是设计
  5. .Net Core 自定义配置源从配置中心读取配置
  6. 如何管理kvm虚拟机的时钟源
  7. Objc Runtime在项目中该怎么用
  8. linux zfs raid,ZFS-自我恢复RAID
  9. 从时分复用、频分复用到码分复用(CDMA)
  10. good nice fine well区别
  11. Spring Cloud 应用篇 之 Spring Cloud Stream(消息驱动)
  12. pdfobject屏蔽头部工具栏如何兼容ie
  13. 川西云南行散记之五《稻城-亚丁-洛绒牛场》
  14. spring使用之旅 ---- bean的装配
  15. 什么是好用的身份证实名认证api接口?其应用场景有哪些?
  16. 武汉大学计算机学院朱晓薇,基于SDN的TDMA体制星间网络架构设计
  17. Node-RED中建立Websocket客户端连接
  18. C# Web页面打印网页
  19. 移动应用安全之沙盒技术
  20. Qt--模拟按下按键(键盘)

热门文章

  1. 【JavaSE】继承 你拿下继承了吗?一篇让你轻松拿下,简明扼要,超详解
  2. 苹果应用分身_你喜欢用苹果手机拍照!不学会这4个功能,怪不得拍不出好照片...
  3. 数据库中间件Mycat诞生记1
  4. 几个cve漏洞库查询网站
  5. css画钟表_利用css+原生js制作简单的钟表
  6. 联系书商出译著的流程
  7. SV--随机化笔记总结
  8. A Hybrid ℓ1-ℓ0 Layer Decomposition Model for Tone Mapping
  9. CAD文件低版本怎么转换高版本
  10. Java正则表达式email