目录
递归回溯通常解法:
(1)初始化成员变量:为了减少递归中的参数传递,传递大参数比较耗时
(2)初始化判断:对于特殊的结果,直接返回结果,无需进入递归
(3)递归回溯:记录下标,保存一个暂存路径,再递归完成后,将路径回复到保存前的状态,进行下一次递归操作
组合(77.39.40.216)
77.组合
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 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 的 k 个数的组合。组合中只允许含有 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”。
给定一个非负整数 n 代表当前 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;
}
}