全排列

153 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 21 天,点击查看活动详情

基础知识递归和回溯

关于全排列,我们最常用的是递归方式。递归是有关函数之间调用自己的过程,他可以把有关很大的问题,分解成一些小的子问题,这是使用递归的关键。

比如我们对二叉树进行遍历,就抓住了每一个节点都可以作为根,调用遍历方法。因为方法是(这里以先序遍历为例子)在list里面先加入根,再是左子树调用函数,然后右子树调用函数。在左子树里面,当前加入的左可以当作左树的根,继续上面'在list里面先加入根,再是左子树调用函数,然后右子树调用函数'的过程。这个就是递归。

那么什么是回溯?

回溯也叫试探,其实就是假设从当前状态出发,一条路走到底,如果不能再前进了,就回退一步或者若干步。比如左数据的排列,我们可以先ABC然后回退一步得到ACB,继续回退得到BAC,回退一步得到BCA继续回退回退编程CAB,CBA然后不能再回退,输出结果。这就是回溯。

思路

通过上面基础,我们可以得到遍历的方式来完成全排列。对整个ABC数字来说,先A或者B或者C,对剩下的AC或者BC或者AB来说也同样是选出一个树,这很像递归,使用本题我们需要结婚递归和回溯来完成。

图示如下:

image.png

绿色表示前进路线,粉色表示回溯路线。

我们首先考虑终止条件 外层数组遍历到末尾>

怎么回溯:我们可以通过定义集合,满足的路径加入集合,回溯就是从集合删除值。

回溯函数:如果当前集合长度等于数组长度,表示遍历完成了,或者是已经得到了一个结果了,所以返回。如果没有遍历完成,就出现遍历数组,往集合里面添加没有的值。再次调用函数。并且撤销最后一个。

image.png

关于有重复值的全排列。

对于数组里面如果有重复数,但是我们没有考虑,那么最后答案会比实际答案多一些。如图

image.png

那么你可能会想,那我就把重复数去掉再进行无重复的全排列,这种做法听上去可行,但是实际是不对的,比如上图,如果去掉重复值,就变成1,3,全排列结果为2,但是我们图中发现的全排列是3种。

你也许会发现,没有重复理论来说有六种情况,但是有一个数重复,结果就变成了3种,也没有可能重复一个数,变小1/2?嗯,很有道理,我们继续看看

image.png 1234 不同排列有 4x3x2x1=24种

1134排列有:12种

image.png

1114:全排列四种

image.png

1111 全排列 1种

一个重复:除2x1,两个重复除3x2x1,三个重复除4x3x2x1

1144

image.png

好像只有一个数重复才能有规律。那么我们需要另外考虑方法。

我们可以对于做一个标记数组,默认false,假设1访问过需要回溯,但是回溯后发现还是1,那么就可以跳过整个1,继续回溯

image.png

  • 终止条件:  临时数组中选取了n个元素,已经形成了一种排列情况了,可以将其加入输出数组中。
  • 返回值:  递归到末尾的时候就能添加全部元素。
  • 本级任务:  每一级都需要选择一个元素加入到临时数组末尾(遍历数组选择)。首先已经加入的元素不能再次加入了,因此我们需要使用额外的数组用于记录哪些位置的数字被加入了。同时为了去除重复元素的影响,如果当前的元素num[i]与同一层的前一个元素num[i-1]相同且num[i-1]已经用过了,也不需要将其纳入。