1、了解全排列和回溯

所谓全排列就是从n个元素中取出n个元素按照一定的顺序进行排列,所有的排列情况叫做全排列

这n个元素又分为两种情况,一种是n个元素存在重复元素,一种是n个元素不存在重复元素。不存在重复元素的好办,关键是存在重复元素的,我们在求解过程中需要进行处理。

回溯法,名字很高大上,其实本质就是穷举。这里我们结合三道题来理解如何使用回溯法解决全排列问题。

(1)46. 全排列
(2)47. 全排列 II
(3)剑指 Offer 38. 字符串的排列

2、全排列问题分析

比如给定数组[1, 2, 3],求所有可能的全排列。

如果让我们在纸上写的话,很容易可以写出来[1, 2, 3],[1, 3, 2],[2, 1, 3],[2, 3, 1],[3, 1, 2],[3, 2, 1]

不妨抽象成下面这棵树


那么只需要从根节点开始遍历,记录路径上的数字,到叶子节点就得到了一个排序,遍历完这棵树,就得到了全排列。我们可以定义下面几个概念:

  • 已选择列表:就是已经选择的元素。
  • 可选择列表:就是可以选择的元素。


那么在这里,到叶子节点其实就是可选择列表为空的时候,此时就得到一个排列。就跟二叉树的遍历一样,到了叶子节点后,我们需要回到它的父节点,去走它的同胞节点。所以我们在得到一个全排列之后,再把已选择列表的元素一个个弹出来放到未选择列表,重新进行选择。


那么可以总结出回溯法的伪代码如下

if (已选择列表的长度 == 元素列表长度)得到一个全排列
for 元素 in 元素列表判断元素是否在可选列表# 做选择已选列表.add(元素)backTrace(元素列表, 已选择列表)# 撤销选择已选列表.remove(元素)

3、实例分析

3.1 不含重复元素的全排列

首先看看不含重复元素的全排列。46. 全排列

根据上面的思路,其实很快就可以写出来

class Solution {List<List<Integer>> res = new LinkedList<>();public List<List<Integer>> permute(int[] nums) {LinkedList<Integer> track = new LinkedList<>();backTrace(nums, track);return res;}private void backTrace(int[] nums, LinkedList<Integer> track) {// 相等的时候,说明得到了一个全排列if (track.size() == nums.length) {res.add(new LinkedList(track));return;}for (int i = 0; i < nums.length; i++) {// 如果已经存在该元素,就不添加if (track.contains(nums[i])) {continue;}// 选择元素track.add(nums[i]);backTrace(nums, track);// 撤销选择track.removeLast();}}
}

是不是和模板大差不差。

3.2 含重复元素的全排列

47. 全排列 II 和 剑指 Offer 38. 字符串的排列 。

一道是数字,一道是字符串。

先看数字的。有了上面这题的基础,这道题其实也不难了,在求解的过程中,有两个点需要注意:

  • 不能用contains方法去判断list中是否存在重复元素了,这样势必会得不到一个排列。因为我们需要的是不重复的排列而不是不重复的元素,所以我们需要一个boolean数组通过下标去判断某个元素是否已经被加入已选择列表。
  • 得到全部排列后,我们需要去掉重复的排序,这里可以把数据结构换成Set进行去重,然后再把Set换成List。不过这种方式是十分低效的,因为有很多无效的状态会被计算。最好的方法就是在回溯过程中进行剪枝,无效的状态直接跳过不计算。

Set去重

class Solution {Set<List<Integer>> temp = new HashSet<>();public List<List<Integer>> permuteUnique(int[] nums) {LinkedList<Integer> list = new LinkedList<>();// 记录已经访问过的元素boolean[] visited = new boolean[nums.length];backTrace(nums, list, visited);List<List<Integer>> res = new LinkedList<>(temp);return res;}private void backTrace(int[] nums, LinkedList<Integer> list, boolean[] visited) {if (list.size() == nums.length) {temp.add(new LinkedList(list));return;}for (int i = 0; i < nums.length; i++) {if (visited[i]) {continue;}// 下标为i的元素已经访问过visited[i] = true;list.add(nums[i]);backTrace(nums, list, visited);// 移除list的元素同时将下标为i的元素置为未访问状态list.removeLast();visited[i] = false;}}
}

可以发现和上一题不一样的地方,就是使用了一个boolean[] visited数组去记录哪些元素被访问哪些没有被访问,而不是通过contains方法去判断。另外就是使用了Set去存储排列结果,这样就能去掉重复结果,但效率不太行。

可以发现用Set去重效率十分的低。需要考虑在回溯过程中进行剪枝,去掉一些无效的中间状态。可以参考题解:https://leetcode-cn.com/problems/permutations-ii/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liwe-2/。图文并茂,这里直接上代码

class Solution {Set<List<Integer>> temp = new HashSet<>();public List<List<Integer>> permuteUnique(int[] nums) {LinkedList<Integer> list = new LinkedList<>();// 记录已经访问过的元素boolean[] visited = new boolean[nums.length];// 排序,方便剪枝Arrays.sort(nums);backTrace(nums, list, visited);List<List<Integer>> res = new LinkedList<>(temp);return res;}private void backTrace(int[] nums, LinkedList<Integer> list, boolean[] visited) {if (list.size() == nums.length) {temp.add(new LinkedList(list));return;}for (int i = 0; i < nums.length; i++) {if (visited[i]) {continue;}// 剪枝if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {continue;}// 下标为i的元素已经访问过visited[i] = true;list.add(nums[i]);backTrace(nums, list, visited);// 移除list的元素同时将下标为i的元素置为未访问状态list.removeLast();visited[i] = false;}}
}


可以发现时间从90ms -> 4ms,但这个击败率。。。


接下来看看重复字符串的, 剑指 Offer 38. 字符串的排列

其实这道和重复数字的大差不差,只是数据类型不一样罢了。。不信看代码

class Solution {public String[] permutation(String s) {// Set去重Set<String> set = new HashSet<>();char[] chs = s.toCharArray();// 记录已经访问过的字符boolean[] visited = new boolean[s.length()];char[] temp = new char[s.length()];backTrace(0, chs, set, visited, temp);StringBuilder sb = new StringBuilder();set.stream().forEach(str -> {sb.append(str + ",");});return sb.substring(0, sb.length() - 1).toString().split(",");}private void backTrace(int index, char[] chs, Set<String> set, boolean[] visited, char[] con) {if (index == chs.length) {set.add(new String(con));return;}for (int i = 0; i < chs.length; i++) {if (!visited[i]) {visited[i] = true;con[index] = chs[i];backTrace(index + 1, chs, set, visited, con);visited[i] = false;}}}
}

4、总结

全排列问题,其实只要记住了这个思路和套路,基本上要写出来都没问题,万变不离其宗,多刷几道题就可以了。

回溯法解决全排列问题总结相关推荐

  1. 回溯法解决tsp问题 matlab,回溯法求解tsp问题

    回溯法以这种工作方式递归地在解空间中搜索, 直至找到所 要求的解或解 空间中已无活结点时为止. 回溯法求解 TSP 问题,首先把所有的顶点的访问标志初始化为 0,...... 回溯法求解 TSP 问题 ...

  2. 动态规划,分治,回溯法,全排列,切片

    全排列问题,可以从动态规划状态方程考虑,也可以从回溯法考虑,二者代码递归形式的代码是一致的,但是理解的角度不同 动态规划: # 基于动态规划,状态方程考虑,f[n] = 首位为所有元素 + f[n-1 ...

  3. 回溯算法背包问题迭代c语言,回溯法解决0_1背包问题(迭代和递归)

    问题:0/1背包问题 例子:weight数组代表物品重量,value数组代表物品价值,M代表背包容量.背包是按单位价值递减的顺序排列的,即value[i]/weight[i]>value[i-1 ...

  4. 利用回溯法解决1-9之间添加+或-或使得运算结果为100的问题

    问题描述 编写一个在1,2,-,9(顺序不能变)数字之间插入+或-或什么都不插入,使得计算结果总是100的程序,并输出所有的可能性.例如:1 + 2 + 34 – 5 + 67 – 8 + 9 = 1 ...

  5. 回溯法解决0-1背包问题

    回溯法解决0-1背包问题 参考文章: (1)回溯法解决0-1背包问题 (2)https://www.cnblogs.com/womendouyiyang/p/10957527.html (3)http ...

  6. 编程解决素数环问题Java_回溯法解决素数环问题java实现

    素数环问题: 输入正整数n,把整数1,2,3--,n组成一个环,使得相邻两个整数之和均为素数,输出所有方案,注意同一个环应恰好输出一次.n<==16 样例输入: 6 样例输出: 1 4 3 2  ...

  7. 回溯法解决部落冲突问题

    回溯法解决部落冲突问题 实验内容 问题描述 思路分析 方法步骤 实验代码 实验内容 原始部落byteland中的居民为了争抢有限的资源,经常发生冲突.几乎每个居民都有它的仇敌.部落酋长为了组织一支保卫 ...

  8. 回溯法解决n皇后问题

    回溯法解决n皇后问题 题目要求: 在n×n格的棋盘上放置彼此不受攻击的n个皇后.按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子.n后问题等价于在n×n格的棋盘上放置n个皇后, ...

  9. 回溯法解决力扣79题单词搜索

    回溯法解决力扣79题单词搜索 给定一个 m x n 二维字符网格 board 和一个字符串单词 word .如果 word 存在于网格中,返回 true :否则,返回 false . 单词必须按照字母 ...

最新文章

  1. harris角点检测与ncc匹配
  2. 重磅!GitHub 全部源代码被泄露?
  3. 【EventBus】事件通信框架 ( 订阅方法注册 | 检查订阅方法缓存 | 反射获取订阅类中的订阅方法 )
  4. html5动态圆,HTML5 很有创意的圆形导航动画
  5. JSON文件学习(jsonc、json-c)(不要学这个,去学cJSON)
  6. UGUI的text赋值问题-速度
  7. 意料之外,情理之中,Spring.NET 3.0 版本发布-
  8. android 使用 audiorecord 和 audiotrack 实现实时录音播放
  9. Editplus 的配色方案
  10. TortoiseSVN的安装与使用
  11. asp页面实现301重定向方法
  12. 从携程事件给我们警示
  13. WEB应用程序--概述
  14. 单片机c语言自学视频教程下载,郭天祥 十天学会单片机和C语言编程视频教程
  15. VC知识库的离线包整合
  16. 《测绘综合能力》——地籍测绘
  17. 转载防丢-caffe训练集搭建
  18. 如何将git上项目在本地跑起来
  19. 上网本丢失F盘怎么恢复
  20. NetLink机制使用

热门文章

  1. FIFO的FPGA实现
  2. 修改IDEA启动时控制台出现的图标
  3. 哪些文字生成图片的软件好用?分享这三款好用的软件
  4. 360急速浏览器和Google浏览器还是有差别的
  5. 解决texstudio编译IEEE官方文件时找不到字体问题Font shape `TU/ptm/m/n‘ undefined(Font) using `TU/lmr/m/n‘ instead
  6. HTML基础总结 02
  7. int double float大小范围
  8. 在Linux上如何快速搭建MongoDB集群
  9. TLB的作用及工作过程
  10. 如何在机器人工程培养方案中实现全栈ROS机器人工程师课程规划总结篇(2021之1024)