继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

递归算法:全排列详解【leetcode-46】PHP语言

小铖06
关注TA
已关注
手记 6
粉丝 0
获赞 2

题目概述

有关于递归的一个话题就是全排列。
给定一个没有重复数字的数组,返回其中所有可能的全排列。
比如输入输入的是123。那么就是说把123全排列全球出来的话就是。123,132,213,231,312,321。

PHP代码:


class Solution {

    private $used;
    private $res;
    /**
     * @param Integer[] $nums
     * @return Integer[][]
     */
    function permute($nums) {

        $p = [];
        $this->res = [];
        if(empty($nums)){
            return $this->res;
        }

        foreach ($nums as $k=>$val) {
            $this->used[$k] = false;
        }

        $this->_dfs($nums, 0, $p);

        return $this->res;
    }

    function _dfs($nums, $index, &$p){
        
        $size = count($nums);
        if($index == $size){
            $this->res[] = $p;
            return ;
        }

        for($i=0; $i< $size; $i++){
            if(!$this->used[$i]){
                $p[] = $nums[$i];
                $this->used[$i] = true;
                $this->_dfs($nums, $index+1, $p);
                array_pop($p);
                $this->used[$i] = false;
            }

        }

        return ;
    }
}

$nums = [1,2,3,4];
$res = (new Solution())->permute($nums);

代码详解

思考题目

我们构造一个函数dfs,函数输入的参数是:一个数组,这个数组的名字叫nums。它由123这三个数组成。第二个参数是index,表示排列已经选取了几个数字,第三个参数是一个数组p,表示一个全排列的结果。如果我们想求他的全排列的话,其实就是每个数字取一次,然后把它给全取出来。
这样的话,如果我们用循环的话,我们把这个数组进行循环一遍,从零到小于nums的大小,如果进行循环的话,那就是说把123数循环出来。
所以这个题的这个解题方式应该不是这样简单的一个循环。这个问题需要一个递归的方式才能把它处理完。
递归的过程:从零开始,i = 0 到 i<nums的大小。
我们写一个递归函数,假设叫dfs。它的参数是:第一个参数是:nums数组。第二个参数是index,指排列已经选取了几个数字。第三个参数是一个中间变量p。保存一次全排列的结果。res保存了所有的全排列。

思路和为什么需要used数组:

for循环从i=0到i<nums的大小。然后呢,在这个循环中,再递归调用函数dfs。输入参数,除了整个数组nums。还有index,表示我们考察了几个数字,所以说我们第一次递归调用的时候就考察了一个数字。index参数的值就是index+1。
然后呢,再传入那个中间变量p,我们一直递归下去,我们就会第一次在这个中间变量p存了第0个位置元素1,当进行递归的时候,把当前考察的元素index加1,说明这个index也表示我们已经考察了多少个元素,但我们考察的元素等于这个数组nums的大小的时候,就说明我们已经找到了一个全排列。这样就找到我们的递归终止条件了。

为什么用使用used数组?

现在有一个问题:我们要求的是全排列,所以说我们每次要考察这个数组中所有的元素,每次都是遍历所有的数组元素。我们要需要数组used标记一下这哪一个元素是否已经被使用过了,比如我们第一次调用dfs的时候从第0个位置。没有被用过的话,那我们就把这个元素放到了这个中间数组p。这样的话,当我们再次递归调用dfs函数的时候,他每次还是从第0个位置遍历所有的元素,然后遍历数组所有元素过程中,如果发现了这个位置已经是被考察过了的话。那么就不会考虑这个位置。而继续去考虑下一个位置,循环进行下一个位置。

dfs函数的过程:

现在我们简单的走一遍啊。
当第一次调用这个dfs函数的时候,我们传入了数组nums。第二个参数是我们考察了几个元素的index,然后还有一个中间数组p。当第一次调dfs函数的时候,那就是从零开定个位置开始考察,然后这个位置的used数组对应值是false,那么就把第0个数放出的中间数组p中。然后标记这个used数组对应位置的值是true。说明这个位置的元素已经用过。
然后呢,这里面在递归调用这个DFS函数,第一个参数是nums不变;第二个参数Index加1,说明全排列中已经放一个元素,然后执行dfs函数,进入这个循环中还是从零开始,当从零开始取得时候,我们发现第0个元素用过了,所以说我们就继续循环,然后i=1的时候,然后发现一个used[1]是false,这个位置没用过,就把第1个元素放进去,这时我们已经考察两个元素。
然后在递归调用dfs,进入这个循环中还是从零开始,当从零开始取得时候,我们发现第0个的元素用过了,考察第1个元素也用过了,然后继续循环,当i=2时,第2个元素没有被使用过,我们把第2个元素放入中间变量p。并标记为已使用,
然后继续下面语句调用dfs。这时index等于3,说明已经考察了3个元素,和数组nums大小进行比较相等。
那就说明已经得到了一个全排列,这就是递归的终止条件。然后把这个中间数组p记录到了一个变量res中。

dfs返回的过程:

这时我们已经完成了一个全排列的操作,也就123。当我们找完之后这个dfs函数返回到了上次dfs函数调用的位置,下面还有个对数组p进行的pop操作,也就是说是数组p相当于一个栈,每次找到一个元素的时候呢,就把这个元素推入站,然后标记这个元素,这个位置已经被用过了。
当每次函数dfs返回了之后,说明这个元素已经在考察过了,我们就需要把这个栈p的元素出栈,就123把中的第三个元素出栈,然后把used[2]标为false。然后继续循环,这个i++,i等于3了。for循环结束。然后返回回去。
因为当i等于2的时候,那个是i等于1的时候dfs调的,所以说返回之后,那就到了i等1的那个地方,然后接着那个dfs往下执行的时候。used[1]标为false,然后对栈p进行出栈,这会儿p里就已经就剩最先进栈的1了。index 等于1,循环继续的i等于2的时候,刚才used[2]已经是false,所以说是没有用过的,把i等于2的这个位置,即第2个元素3放入了这个栈p中。现在p里面有1和3。
然后dfs调用,其中1个参数index 等于index+1=2,其他2个参数nums和p照样传入。dfs执行,从i=0到2的循环,然后第0、2位置已经被占了,当i=1的时候,used[1]等于false,第1个元素2进栈p中,这样就得到了一个全排列132,然后,index + 1 等于 3 调用dfs函数,这时index=3,说明已经考察了3个元素,和数组nums大小进行比较相等。那就说明已经得到了一个全排列,这就是递归的终止条件。然后把这个中间数组p记录到了一个变量res中。
返回之后,然后index等于2,i=1。把2弹出栈,这个used[1]设置为false。然后继续进行循环,i++,i等于2,发现used[2]是true,这个for循环就结束了。返回到那个i等于2位置之后,那会儿index等于1,栈p出栈3,这个for循环结束了。返回到了index等于0,i=0的那个地方。就是咱把那个1,即第0个位置放进去栈p,当dfs(nums, 1, p)返回了之后,执行pop语句把栈p的1出栈,然后把那个used[0]设成false。

继续dfs函数最外层的for

这会儿在第一次调用dfs的地方,继续for循环,i++,i=1,这是 used[1] 是false,所以2进栈,继续调用dfs。处理方式就像刚开始i=0一样。再次得到了 213,231。

这会儿在第一次调用dfs的地方,继续for循环,i++,i=2,继续调用dfs ,得到了312,321。

总结

使用了详细说明,细致的讲解了求解全排列时的递归和回溯过程,如果对这个算法不熟悉的话,仔细阅读,绝对可以读懂。写的不好的地方,请大家多包涵。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP