题目1 39. 组合总和
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
思路
利用回溯法找出所有组合,判断当前路径的组合的和是否等于target,等于就加入,小于就返回,这里要注意的是同一个数字可以被选择多次,所以再向下遍历的时候,每次都从当前元素开始,要包含当前元素。
var combinationSum = function(candidates, target) {
let result = [];
let path = [];
candidates.sort((a, b) => a - b)
const combine = (candidates, target, startIndex) => {
if (target < 0) {
return;
}
if (target === 0) {
result.push(path.slice());
return;
}
for (let i = startIndex; i < candidates.length; i++) {
path.push(candidates[i]);
target -= candidates[i];
combine(candidates, target, i);
path.pop();
target += candidates[i];
}
}
combine(candidates, target, 0);
return result
};
这里有可以剪枝的地方,就是在同层遍历的时候,判断target是否小于0了,如果小于0,则直接退出,因为我们排过序了,当减去当前值小于0不符合了,那再去遍历后面比它大的数字,明显也是不符合条件的,所以可以提前跳出。
var combinationSum = function(candidates, target) {
candidates.sort((a, b) => a - b);
const len = candidates.length;
const result = [];
const path = [];
const backtracking = (index, sum) => {
if (sum > target) {
return;
}
if (sum === target) {
result.push(path.slice());
return;
}
for (let i = index; i < len; i++) {
path.push(candidates[i]);
sum += candidates[i];
if (sum > target) {
path.pop();
sum -= candidates[i];
return;
}
backtracking(i, sum);
path.pop();
sum -= candidates[i];
}
};
backtracking(0, 0);
return result;
};
题目2 40. 组合总和 II
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
思路
找出所有的组合,和上一个题目不同的是每次递归时从下一个元素开始,这样一个元素只会出现一次。然后不能包含重复的组合,如何判断,需要将数组进行排序,然后判断,如果当前层的元素和上一个元素相同,并且不是一条路径上相同的元素,则可以继续搜索,如果不符合,就跳过。同时剪枝操作,遍历时需要target减去当前元素大于等于0,否则无需遍历。
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum2 = function(candidates, target) {
let result = [];
let path = [];
let used = new Array(candidates.length).fill(0);
candidates.sort((a, b) => a - b)
const combine = (candidates, target, startIndex, used) => {
if (target === 0) {
result.push(path.slice());
return;
}
for (let i = startIndex; i < candidates.length && target - candidates[i] >= 0; i++) {
if (i > 0 && candidates[i] === candidates[i - 1] && !used[i-1]) {
continue;
}
if (target < 0) {
return;
}
path.push(candidates[i]);
target -= candidates[i];
used[i] = 1;
combine(candidates, target, i + 1, used);
path.pop();
target += candidates[i];
used[i] = 0;
}
}
combine(candidates, target, 0, used);
return result;
};
如果不用used数组,直接用startIndex来判断:
var combinationSum2 = function(candidates, target) {
candidates.sort((a, b) => a - b);
let result = [];
let path = [];
const backtracking = (startIndex, sum) => {
if (sum === target) {
result.push(path.slice());
return;
}
for (let i = startIndex; i < candidates.length; i++) {
if (i > startIndex && candidates[i] === candidates[i - 1]) {
continue;
}
path.push(candidates[i]);
sum += candidates[i];
if (sum > target) {
sum -= candidates[i];
path.pop();
return;
}
backtracking(i + 1, sum);
path.pop();
sum -= candidates[i];
}
};
backtracking(0, 0);
return result;
};
题目3 131. 分割回文串
给你一个字符串 s,请你将 **s **分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
思路
分割获取子串切割方案和求组合一样,startIndex作为切割点,子串就是startIndex到i的子串,回溯过程中判断子串是否是回文串,是的话,加入path,否则跳过这个子集。如果startIndex的值大于了s的长度,则返回。
function isPalindrome(s, startIndex, endIndex) {
while (startIndex < endIndex) {
if (s[startIndex] !== s[endIndex]) {
return false;
}
startIndex++;
endIndex--;
}
return true;
}
/**
* @param {string} s
* @return {string[][]}
*/
var partition = function(s) {
let result = []
let path = []
const len = s.length;
const getPartition = (startIndex) => {
if (startIndex >= len) {
result.push(path.slice());
return;
}
for (let i = startIndex; i < len; i++) {
let subStr = s.slice(startIndex, i+1);
if (isPalindrome(s, startIndex, i)) {
path.push(subStr);
} else {
continue;
}
getPartition(i + 1);
path.pop();
}
}
getPartition(0);
return result;
};
ps:脑容量不够了,这个递归还是有点抽象,有问题,还没彻底掌握。