【PHP解法==LeetCode(回溯递归3-组合)】组合(77.39.40.216) && 子集(78.90) && 401.二进制手表

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010365335/article/details/87950070

目录

组合(77.39.40.216)

77.组合

39.组合总和

40.组合总和 II

216.组合总和 III

子集(78.90)

78.子集

90.子集II

401.二进制手表


递归回溯通常解法:

(1)初始化成员变量:为了减少递归中的参数传递,传递大参数比较耗时

(2)初始化判断:对于特殊的结果,直接返回结果,无需进入递归

(3)递归回溯:记录下标,保存一个暂存路径,再递归完成后,将路径回复到保存前的状态,进行下一次递归操作


组合(77.39.40.216)

77.组合

给定两个整数 n 和 k,返回 1 ... 中所有可能的 k 个数的组合。

示例:

输入: n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

解法:递归回溯,如下图。依次取出一个数,在从剩余的元素中取出另外一个数,递归回溯

未优化前,如上图,每次都要遍历到4那个元素

优化后,进行剪枝,可以不需要考虑取4的元素,如果数据量大,可以剪枝(不去搜索的数)的数量是很多的。

优化方法:每次执行后,暂存的结果数组还有$k - $lenp 个空位,所以[i……n]中至少要有$k - $lenp 个元素空间,因此$i 最多为 $n - ($k-$lenp) + 1

class Solution {
    private $res = [];  //初始化结果数组
    private $n,$k;      //初始化n和k
    /**
     * @param Integer $n
     * @param Integer $k
     * @return Integer[][]
     */
    function combine($n, $k) {
        if($n < 1 || $k <=0 || $k > $n) //初始化判断,直接返回空数组
            return $this->res;
        $this->n = $n;
        $this->k = $k;
        $this->findCombine(1,[]);       //递归回溯寻找组合
        return $this->res;
    }
    /**
     * [递归回溯寻找满足条件的组合]
     * @param  [type] $start [开始下标]
     * @param  [type] $path  [暂存的结果数组]
     */
    private function findCombine($start,$path){
        $lenp = count($path);
        if($lenp == $this->k){            //递归终止条件:暂存的结果数组长度 = 题目要求长度
            $this->res[] = $path;         //将结果存在结果数组中,并返回
            return ;
        }
        //for($i = $start;$i<=$n;++$i){         //未优化,一直遍历到数组末尾
        //还有$k - $lenp 个空位,所以[i……n]中至少要有$k - $lenp 个元素
        //所以 $i 最多为 $n - ($k-$lenp) + 1
        $end = $this->n - ($this->k - $lenp) +1;//优化简直,只要剩余长度不满足条件,直接返回,不用继续进行
        for($i = $start;$i<=$end;++$i){
            $path[] = $i;                       //压入暂存结果数组
            $this->findCombine($i+1,$path);     //每次循环遍历都在从i+1后进行,因为之前肯定已经递归过了
            array_pop($path);                   //回溯递归
        }
    }
}

39.组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。 

示例 1:

输入: candidates =[2,3,6,7],target =7,
所求解集为:
[
  [7],
  [2,2,3]
]

示例 2:

输入: candidates = [2,3,5],target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

注意:(1)该题需要先排序,有利于后续for循环时的判断;

            (2)该题进入下一次循环时,下标不+1,因为元素可以重复使用

class Solution {
    private $res = [];
    private $len,$can;
    /**
     * @param Integer[] $candidates
     * @param Integer $target
     * @return Integer[][]
     */
    function combinationSum($candidates, $target) {
        sort($candidates);                      //初始化排序组合数组
        $this->can = $candidates;               //使其成为成员变量
        $this->len = count($candidates);        
        if($this->len == 0) return $this->res;  //当数组长度为0时,肯定无法找到结果,返回空数组
        $this->findSum(0,$target,[]);           //递归回溯寻找结果数组
        return $this->res;
    }
    /**
     * [递归回溯寻找结果数组]
     * @param  [type] $index  [下标]
     * @param  [type] $target [目标值]
     * @param  [type] $path   [暂存的结果数组]
     */
    private function findSum($index,$target,$path){
        if($target == 0){                   //当目标值 = 0时,已找到结果数组,记录结果数组并返回
            $this->res[] = $path;
            return ;
        }
        if($target < $this->can[$index]){   //当目标值 < 当前下标下的值时,计算后target的值肯定 < 0,不满足条件,直接返回
            return;
        }
        //递归回溯遍历,直到超出数组长度或者目标值小于待减值
        for($i = $index;$i<$this->len && $this->can[$i] <= $target;++$i){
            $num = $this->can[$i];                  //取出待减值
            $path[] = $num;                         //将该值压入暂存结果数组
            $this->findSum($i,$target-$num,$path);  //下标不变,继续递归遍历,目标值减去待减值进入函数
            array_pop($path);                       //推出暂存结果数组,回溯进行下一次循环
        }
    }
}

40.组合总和 II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

  • 所有数字(包括目标数)都是正整数。
  • 解集不能包含重复的组合。 

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
  [1,2,2],
  [5]
]

区别:元素不可重复调用,且数组中有重复的元素

区别解法:

(1)递归循环时,下标要+1

(2)去重:类似【PHP解法==LeetCode(回溯递归2-排列)】46.全排列 && 47.全排列 II中的47.全排列II。在开始下标之后,出现与上一次遍历的元素一样时,直接跳过,因为剩余的元素一样,得到的结果也一样

class Solution {
    private $res = [];
    private $len,$can;
    /**
     * @param Integer[] $candidates
     * @param Integer $target
     * @return Integer[][]
     */
    function combinationSum2($candidates, $target) {
        sort($candidates);                      //初始化排序组合数组
        //print_r($candidates);
        //[1,1,2,5,6,7,10]
        $this->can = $candidates;               //使其成为成员变量
        $this->len = count($candidates);
        if($this->len == 0) return $this->res;  //当数组长度为0时,肯定无法找到结果,返回空数组
        $this->findSum(0,$target,[]);           //递归回溯寻找结果数组
        return $this->res;
    }
    /**
     * [递归回溯寻找结果数组]
     * @param  [type] $index  [下标]
     * @param  [type] $target [目标值]
     * @param  [type] $path   [暂存的结果数组]
     */
    private function findSum($index,$target,$path){
        if($target < 0) return ;       //当目标值 < 0时,不满足条件,直接返回
        if($target == 0){              //当目标值 = 0时,已找到结果数组,记录结果数组并返回
            $this->res[] = $path;
            return ;
        }
        //递归回溯遍历,直到超出数组长度或者目标值小于待减值
        for($i = $index;$i<$this->len && $this->can[$i] <= $target;++$i){
            //在开始下标之后,出现与上一次遍历的元素一样时,直接跳过,因为剩余的元素一样,得到的结果也一样
            if($i>$index && $this->can[$i] == $this->can[$i-1]){
                continue;
            }
            $num = $this->can[$i];                  //取出待减值
            $path[] = $num;                         //将该值压入暂存结果数组
            $this->findSum($i+1,$target-$num,$path);//递归遍历,目标值减去待减值进入函数
            array_pop($path);                       //推出暂存结果数组,回溯进行下一次循环
        }
    }
}

216.组合总和 III

找出所有相加之和为 n 的 个数的组合组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

  • 所有数字都是正整数。
  • 解集不能包含重复的组合。 

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]

示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

与39的区别,(1)有规定结果的长度 (2)用数字代替检索数组,其实就是替换成该数组[1,2,3,4,5,6,7,8,9]

区别解法:必须满足长度且结果都满足条件的情况下再记录结果,但满足长度时不满足目标值时,也必须返回

class Solution {
    private $res = [];      //初始化结果数组

    /**
     * @param Integer $k
     * @param Integer $n
     * @return Integer[][]
     */
    function combinationSum3($k, $n) {
        if($n == 0 || $k == 0) return $this->res; //初始化判断:不满足条件,直接返回空数组
        $this->findSum($k,$n,1,[]);               //递归回溯寻找结果数组
        return $this->res;
    }

    /**
     * [递归回溯寻找结果数组]
     * @param  [type] $remain [剩余可以填充的长度]
     * @param  [type] $target [目标]
     * @param  [type] $start  [开始下标]
     * @param  [type] $path   [暂存的结果长度]
     */
    private function findSum($remain,$target,$start,$path){
        if($target < 0) return;         //当目标 < 0 时,不满足条件,直接返回
        if($remain == 0){               //当数组长度满足条件时,判断目标,一定要返回
            if($target == 0){           //并且目标 = 0,满足条件,记录结果数组
                $this->res[] = $path;
            }
            return ;                
        }
        //从开始下标遍历,一直遍历到 9 或者 已经大于目标值 则停止
        for($i = $start;$i<=9 && $i <= $target;++$i){
            $path[] = $i;                                   //压入暂存结果数组
            $this->findSum($remain-1,$target-$i,$i+1,$path);//剩余长度-1,目标-待减数,开始下标为当前下标+1
            array_pop($path);                               //回溯,进行下次遍历
        }
    }
}

子集(78.90)

78.子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: nums = [1,2,3]
输出:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

该题是比较简单的递归回溯解法,没有终止条件,遍历完即可,每一个路径都是一个新的子集

class Solution {
    private $res = [];
    private $len,$nums;

    /**
     * @param Integer[] $nums
     * @return Integer[][]
     */
    function subsets($nums) {
        $this->len = count($nums);              //使其成为成员变量
        $this->nums = $nums;
        if($this->len == 0) return $this->res;  //初始化判断
        $this->findSub(0,[]);                   //递归回溯记录结果数组
        return $this->res;
    }

    /**
     * [递归回溯记录结果数组]
     * @param  [type] $start [开始下标]
     * @param  [type] $path  [暂存的结果数组]
     */
    private function findSub($start,$path){
        $this->res[] = $path;                   //记录子集,每次递归都是一个新的子集
        for($i = $start;$i<$this->len;++$i){
            $path[] = $this->nums[$i];          //回溯递归过程
            $this->findSub($i+1,$path);
            array_pop($path);
        }
    }
}

90.子集II

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: [1,2,2]
输出:
[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]

与78.区别:数组中有重复的元素,且不能得到重复的子集

区别解法:

(1)先排序,有利于后续for循环时的判断

(2)去重:类似【PHP解法==LeetCode(回溯递归2-排列)】46.全排列 && 47.全排列 II中的47.全排列II。在开始下标之后,出现与上一次遍历的元素一样时,直接跳过,因为剩余的元素一样,得到的结果也一样

class Solution {
    private $res = [];
    private $len,$nums;

    /**
     * @param Integer[] $nums
     * @return Integer[][]
     */
    function subsetsWithDup($nums) {
        $this->len = count($nums);              //使其成员变量
        sort($nums);
        $this->nums = $nums;
        if($this->len == 0) return $this->res;  //初始化条件变量
        $this->findSub(0,[]);                   //递归回溯获得子集
        return $this->res;
    }

    /**
     * [递归回溯获得子集]
     * @param  [type] $start [开始下标]
     * @param  [type] $path  [暂存的结果路径]
     */
    private function findSub($start,$path){
        $this->res[] = $path;                   //每次进入该函数都可以得到一个子集
        for($i = $start;$i<$this->len;++$i){    //遍历到数组结束
            //在开始下标之后,出现与上一次遍历的元素一样时,直接跳过,因为剩余的元素一样,得到的结果也一样
            if($i>$start && $this->nums[$i] == $this->nums[$i-1])
                continue;
            $path[] = $this->nums[$i];          //回溯递归过程
            $this->findSub($i+1,$path);
            array_pop($path);
        }
    }
}

401.二进制手表

二进制手表顶部有 4 个 LED 代表小时(0-11),底部的 6 个 LED 代表分钟(0-59)

每个 LED 代表一个 0 或 1,最低位在右侧。

例如,上面的二进制手表读取 “3:25”。

给定一个非负整数 代表当前 LED 亮着的数量,返回所有可能的时间。

案例:

输入: n = 1
返回: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"]

注意事项:

  • 输出的顺序没有要求。
  • 小时不会以零开头,比如 “01:00” 是不允许的,应为 “1:00”。
  • 分钟必须由两位数组成,可能会以零开头,比如 “10:2” 是无效的,应为 “10:02”。

PS:博主小小作弊一下,该题本来是也是属于递归回溯的解法,但是感觉暴力法更加直接明白

class Solution {

    /**
     * @param Integer $num
     * @return String[]
     */
    function readBinaryWatch($num) {
        $res = [];
        if($num < 0 || $num > 10) return $res;
        $map = [];
        for($i = 0;$i<=60;++$i){            //记录每一个数字对应的二进制中的1的个数
            $map[$i] = $this->findBin($i);
        }
        for($i=0;$i<12;++$i){                       //遍历时 分
            for($j = 0;$j<60;$j++){
                if($map[$i] + $map[$j] == $num){    //但时分对应的二进制中1的个数满足条件时,记录结果数组
                    $j = $j<10?'0'.$j:$j;
                    $res[] = $i.':'.$j;
                }
            }
        }
        return $res;
    }

    private function findBin($num){
        $str = (string)decbin($num);
        $len = strlen($str);
        $count = 0;
        for($i = 0;$i<$len;++$i){
            if($str[$i] == 1) $count++;
        }
        return $count;
    }
}

猜你喜欢

转载自blog.csdn.net/u010365335/article/details/87950070