一、力扣题39. 组合总和
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于 150
个。
示例 1:
输入: candidates =[2,3,6,7], target = 7 输出:[[2,2,3],[7]] 解释: 2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 7 也是一个候选, 7 = 7 。 仅有这两种组合。
示例 2:
输入: candidates = [2,3,5], target = 8 输出: [[2,2,2,2],[2,3,3],[3,5]]
示例 3:
输入: candidates = [2], target = 1 输出: []
提示:
1 <= candidates.length <= 30
2 <= candidates[i] <= 40
candidates
的所有元素 互不相同1 <= target <= 40
看到本题时,我一开始还在想既然元素可以无限选取,那怎么避免重复的组合,看了讲解后才发现简单变一下cur就不用担心这个问题了。
之前的题目每次选取后,剩下的可选取值都是cur 之后的值。但本题目因为元素可重复选取,所以剩下的可选取值是包括cur以及cur之后的值。
这样就控制了同一元素重复的范围,每个树枝上同一元素重复的次数肯定不会完全一样。
剪枝操作:当数组总和大于目标值时,直接中断。代码中是在for循环中加入判断,当 当前数组总和 + 下一个数 > 目标值时,直接跳过下一个数。
class Solution {
private $result = [];
/**
* @param Integer[] $candidates
* @param Integer $target
* @return Integer[][]
*/
function combinationSum($candidates, $target) {
sort($candidates);
$this->backtracking($candidates, $target, [], 0, 0);
return $this->result;
}
function backtracking($candidates, $target, $path, $sum, $cur) {
if($sum == $target) {
$this->result[] = $path;
return;
}
for($i = $cur; $i < count($candidates) && $sum + $candidates[$i] <= $target; $i++) {
$path[] = $candidates[$i];
$sum += $candidates[$i];
$this->backtracking($candidates, $target, $path, $sum, $i);
$sum -= $candidates[$i];
array_pop($path);
}
}
}
二、力扣题40. 组合总和 II
给定一个候选人编号的集合 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8, 输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5, 输出: [ [1,2,2], [5] ]
提示:
1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30
本题与上一题的区别在于,上一题是给的集合中没有相同元素,但元素可以重复使用。本题是,给的集合有重复元素,但一个元素只能使用一次。
两个题的去重本质其实一样,就是要保证同一树层中不能取重复的元素,但同一树枝上可以取重复元素,这样解集中就不会有重复的元素。
所以本题只要保证同一树层不取重复元素就ok 了。
class Solution {
private $result = [];
/**
* @param Integer[] $candidates
* @param Integer $target
* @return Integer[][]
*/
function combinationSum2($candidates, $target) {
sort($candidates);
$this->backtracking($candidates, $target, [], 0, 0);
return $this->result;
}
function backtracking($candidates, $target, $path, $sum, $cur) {
if($sum == $target) {
$this->result[] = $path;
return;
}
for($i = $cur; $i < count($candidates) && $sum + $candidates[$i] <= $target; $i++) {
if($i != $cur && $candidates[$i] == $candidates[$i - 1]) {
continue;
}
$path[] = $candidates[$i];
$sum += $candidates[$i];
$this->backtracking($candidates, $target, $path, $sum, $i + 1);
$sum -= $candidates[$i];
array_pop($path);
}
}
}
代码随想录还写了另一种方法,就是使用 used 数组,例如当一个树枝上取了第 i 个元素入集合,那么 used[ i ] 为 true,而未被取用的元素在 used 中对应的位置则为 false。
这样当遍历到元素 i 时, 若used[ i - 1] = true,说明在上一层中取用了第 i - 1 个元素,若 used[ i - 1] = false,说明第 i - 1 个元素与 i 元素在同层被选择。
所以将数组从小到大排序后,若第 i 元素与第 i - 1 元素相同,则可通过 used[ i - 1]来确定 i - 1 元素与 i 元素在同树层还是在同树枝,从而进行不同的操作。
class Solution {
private $result = [];
/**
* @param Integer[] $candidates
* @param Integer $target
* @return Integer[][]
*/
function combinationSum2($candidates, $target) {
sort($candidates);
$this->backtracking($candidates, $target, [], 0, 0, []);
return $this->result;
}
function backtracking($candidates, $target, $path, $sum, $cur, $used) {
if($sum == $target) {
$this->result[] = $path;
return;
}
for($i = $cur; $i < count($candidates) && $sum + $candidates[$i] <= $target; $i++) {
if($i != 0 && $candidates[$i] == $candidates[$i - 1] && $used[$i - 1] == false) {
continue;
}
$path[] = $candidates[$i];
$sum += $candidates[$i];
$used[$i] = true;
$this->backtracking($candidates, $target, $path, $sum, $i + 1, $used);
$used[$i] = false;
$sum -= $candidates[$i];
array_pop($path);
}
}
}
第二个方法在本题会显得略繁琐且没有必要,但是在后面的题目中会有更好的效果。
三、力扣题131. 分割回文串
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a" 输出:[["a"]]
提示:
1 <= s.length <= 16
s
仅由小写英文字母组成
本题初看起来会比较复杂,但如果解读成 切割字符串和判断回文串,就明朗很多了。我们可以遍历字符串切割的所有情况,判断这些切割出来的字符串是否是回文串,不是的话直接中断。这样就又可以变形成 n 叉树了。
稍微有点不同的是,之前的树层都是遍历取用其中一个值,而本题是遍历选定切割点,但大差不差。例如“aab”第一层遍历中,第一个切割点 i = 0,判断 " a "是否是回文串;第二个切割点 i = 1,判断" aa "是否是回文串;第三个切割点是 i = 2,判断 " aab" 是否是回文串。
而判断叶子节点则是判断cur是否等于字符串长度。
class Solution {
private $result = [];
/**
* @param String $s
* @return String[][]
*/
function partition($s) {
$this->backtracking($s, 0, []);
return $this->result;
}
function backtracking($s, $cur, $path) {
if($cur == strlen($s)) {
$this->result[] = $path;
return;
}
for($i = 1; $i <= strlen($s) - $cur; $i++) {
$arr = substr($s, $cur, $i);
if($this->judge($arr)) {
$path[] = $arr;
$this->backtracking($s, $cur + $i, $path);
array_pop($path);
}
continue;
}
}
function judge($string) {
$len = strlen($string);
for($i = 0; $i < intval($len / 2); $i++) {
if($string[$i] != $string[$len - $i - 1]) return false;
}
return true;
}
}