39. 组合总和
题目链接:39. 组合总和
文档讲解:代码随想录/组合总和
视频讲解:视频讲解-组合总和
状态:已完成(1遍)
解题过程
看到题目的第一想法
这道题和组合总和III的区别应该就是给定数组内的数字可以重复使用,那么我想到的就是在for循环中i的起始值startIndex就不能够在每次递归的时候传入i+1了,还得从i开始。
这样提交了之后发现有bug,也就是给出的剪枝操作,如果给定数组是从大到小排列的话,就会跳过正确答案,所以在最先开始我先把给定数组按从小到大顺序排列了一下避免这种情况的发生。
手搓代码如下:
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum = function (candidates, target) {
let newCandidates = candidates.sort((a,b)=>a-b);//先对数组排好序,避免出现从大到小排序时,for循环中if判断出错
let ans = [], smallArr = [];
const sumOne = function (startIndex,nowSum) {
if (nowSum == target) {
ans.push([...smallArr]);
return;
}
for (let i = startIndex; i < candidates.length; i++) {
nowSum += candidates[i];
smallArr.push(candidates[i]);
if (nowSum > target) {
smallArr.pop();
nowSum -= candidates[i];
break;
}
sumOne(i,nowSum);//和组合总和III的区别就在下一层递归开始的时候还得从当前这个数字开始遍历
smallArr.pop();
nowSum -= candidates[i];
}
}
sumOne(0,0);
return ans;
};
提交没有问题。
看完代码随想录之后的想法
基本思路也差不多,我爱回溯。
讲解代码如下:
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum = function(candidates, target) {
const res = [], path = [];
candidates.sort((a,b)=>a-b); // 排序
backtracking(0, 0);
return res;
function backtracking(j, sum) {
if (sum === target) {
res.push(Array.from(path));
return;
}
for(let i = j; i < candidates.length; i++ ) {
const n = candidates[i];
if(n > target - sum) break;
path.push(n);
sum += n;
backtracking(i, sum);
path.pop();
sum -= n;
}
}
};
总结
两种方式我还是觉得我的那种对我自己来说更好理解。
40.组合总和II
题目链接:40.组合总和II
文档讲解:代码随想录/组合总和II
视频讲解:视频讲解-组合总和II
状态:已完成(1遍)
解题过程
看到题目的第一想法
这题我的第一个想法是,不能重复使用数组里的数字,那么startIndex就要传递i+1;第二个想法是,数组中本身就有重复的数字,那么会造成例如说target=7,candidate=[1,1,6],预期输出是[[1,6]],但我会输出[[1,6],[1,6]]的结果。所以在for循环开头我加了一个去重的操作,如果当前递归层的for循环遍历到的i不是开头的那个i,那么看看它和上一个i所对应的数字是否相等,如果相等,直接跳过。
手搓代码如下:
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum2 = function (candidates, target) {
let newCan = candidates.sort((a, b) => a - b);
let ans = [], smallArr = [];
const sumTwo = function (startIndex, nowSum) {
if (nowSum == target) {
ans.push([...smallArr]);
return;
}
for (let i = startIndex; i < newCan.length; i++) {
if(i != startIndex && newCan[i]==newCan[i-1])continue;//我做的去重处理
// 在每一层递归的for循环里,如果遍历到的不是第一个,那么看看当前数字是否和上一个数字相等,相等就跳
smallArr.push(newCan[i]);
nowSum += newCan[i];
if (nowSum > target) {
nowSum -= newCan[i];
smallArr.pop();
break;
}
sumTwo(i + 1, nowSum);//这里递归传递的startIndex得是i+1,因为不能重复使用了
nowSum -= newCan[i];
smallArr.pop();
}
}
sumTwo(0,0);
return ans;
};
提交没有问题。 我 爱 回 溯 。
看完代码随想录之后的想法
基本一致。
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum2 = function(candidates, target) {
const res = []; path = [], len = candidates.length;
candidates.sort((a,b)=>a-b);
backtracking(0, 0);
return res;
function backtracking(sum, i) {
if (sum === target) {
res.push(Array.from(path));
return;
}
for(let j = i; j < len; j++) {
const n = candidates[j];
if(j > i && candidates[j] === candidates[j-1]){
//若当前元素和前一个元素相等
//则本次循环结束,防止出现重复组合
continue;
}
//如果当前元素值大于目标值-总和的值
//由于数组已排序,那么该元素之后的元素必定不满足条件
//直接终止当前层的递归
if(n > target - sum) break;
path.push(n);
sum += n;
backtracking(sum, j + 1);
path.pop();
sum -= n;
}
}
};
总结
这道题的关键在于理解什么时候去重,是在每一层递归的for循环中去重,且需要判断当前遍历的是不是for循环遍历的第一个。
131.分割回文串
题目链接:131.分割回文串
文档讲解:代码随想录/分割回文串
视频讲解:视频讲解-分割回文串
状态:已完成(1遍)
解题过程
看到题目的第一想法
首先去搜了一下回文串是什么,回文串是正着读和反着读完全一样的字母字符串,如'aba','bbabb'。然并卵,还是没有什么想法。
看完代码随想录之后的想法
知道了这里如何判断是不是回文串,用for循环从两边开始判断。
知道了把切割问题看成组合问题,切完第一个切后面的类似组合中取出第一个再取第二个。
看了讲解手搓代码如下:
/**
* @param {string} s
* @return {string[][]}
*/
var partition = function(s) {
let ans = [],smallArr = [];
const isHui = (left, right) => {
for (let i = left, j = right; i < j; i++, j--) {
if(s[i] !== s[j]) return false;//从两边往中间搜索,出现不一样的就不是回文串
}
return true;
}
const divide = function(startIndex){
if(startIndex == s.length){
ans.push([...smallArr]);
return;
}
for(let i = startIndex;i<s.length;i++){
if(!isHui(startIndex,i))continue;//如果切的这一块不是回文串,直接跳过往后切
smallArr.push(s.slice(startIndex,i+1))//是回文串把当前切的部分放进数组中
divide(i+1);//如果是回文串,进行下一层递归,从后面一个字符串开始切
smallArr.pop();
}
}
divide(0);
return ans;
};
提交没有问题。希望二刷可以写出来。
总结
- 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个.....。
- 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段.....。
相似性这样一列就十分明显了。