今日内容:
● 39. 组合总和
● 40.组合总和II
● 131.分割回文串
1. 组合总和
关联 leetcode 39. 组合总和
-
思路
- 类似前面的组合算法
- 题目预设 正整数数组集合,值大于等于 2
- 不会出现重复选取到 0 值,导致无限循环的问题
- 多了一个可以重复使用元素,控制 startIdx 的位置,保持不变即可
- 剪枝优化思路:
- 当前的累加和与目标值的关系
-
题解
func combinationSum(candidates []int, target int) [][]int { rets := make([][]int, 0) ret := make([]int, 0) var backtracking func(candidates []int, target int, sum int, startIdx int) backtracking = func(candidates []int, target int, sum int, startIdx int) { if sum > target {//剪枝 return } if target == sum { tmp := make([]int, len(ret)) copy(tmp, ret) rets = append(rets, tmp) return } for i := startIdx; i < len(candidates); i++ { ret = append(ret, candidates[i]) sum += candidates[i] backtracking(candidates, target, sum, i) sum -= candidates[i] ret = ret[:len(ret)-1] } } backtracking(candidates, target, 0, 0) return rets }
2. 组合总和II
关联 leetcode 40.组合总和II
-
思路
- 和上一道 39 的区别,不允许包含重复元素的组合
- 用 map 来去重会超时
- 搜索的时候就需要去掉重复组合了
- 用 map 来去重会超时
- 破题关键:去重
- 需要先对数组进行排序
- 将相邻元素放在一起
- 两个层面去重
- 同一层树上
- “使用过的”元素,需要去重
- 去重操作前,需要对数组排序
- “使用过的”元素,需要去重
- 同一根树枝上
- 一个组合里面的元素,不用去重
- 同一层树上
- 需要先对数组进行排序
- 和上一道 39 的区别,不允许包含重复元素的组合
-
题解
func combinationSum2(candidates []int, target int) [][]int { rets := make([][]int, 0) ret := make([]int, 0) used := make([]bool, len(candidates)) sort.Ints(candidates) var backtracking func(candidates []int, target int, sum int, startIdx int, used []bool) backtracking = func(candidates []int, target int, sum int, startIdx int, used []bool) { if sum > target { //剪枝 return } if target == sum { tmp := make([]int, len(ret)) copy(tmp, ret) rets = append(rets, tmp) return } for i := startIdx; i < len(candidates); i++ { //树层去重, 横向,used[i] == false , i 这个元素未使用 if i > 0 && candidates[i] == candidates[i-1] && !used[i-1] { continue //直接continue不对当前数做处理即可 } //处理树枝元素, 纵向 ret = append(ret, candidates[i]) sum += candidates[i] used[i] = true backtracking(candidates, target, sum, i+1, used) used[i] = false sum -= candidates[i] ret = ret[:len(ret)-1] } } backtracking(candidates, target, 0, 0, used) return rets }
3.分割回文串
关联 leetcode 131.分割回文串
- 思路
- 切割字串的步骤类似于组合的选取过程
- 逐步切割子串
- 定下一个 切割起始位置【切割线,startIdx】
- 单层 向后移动 切割终止位置
- 判断切合出来的字串是否满足条件【本题是回文】
- 定下一个 切割起始位置【切割线,startIdx】
-
题解
func partition(s string) [][]string { rets := make([][]string, 0) path := make([]string, 0) var backtracking func(s string, startIdx int) backtracking = func(s string, startIdx int) { if startIdx == len(s) { // 如果起始位置已经等于s的大小,说明已经找到了一组分割方案了,已经全部切完了 tmp := make([]string, len(path)) copy(tmp, path) rets = append(rets, tmp) return } // 回文逻辑放在单层搜索里面判断 for i := startIdx; i < len(s); i++ { //切割字串 subStr := s[startIdx : i+1] // 做回文判断 if isPalindrome(subStr) { //切出来的是回文子串 path = append(path, subStr) } else { continue } backtracking(s, i+1)//传入整个字符串,移动切割终止位置 path = path[:len(path)-1] //回溯 } } backtracking(s, 0) return rets } // 判断字符串是否是回文字符串 func isPalindrome(str string) bool { for i, j := 0, len(str)-1; i < j; i, j = i+1, j-1 { if str[i] != str[j] { return false } } return true }
4. 题外话
- 剪枝
- 剪枝应该放在停止条件哪里判断
- 不应该放在单层循环代码里面
- 否则会导致回溯遗漏
- 不应该放在单层循环代码里面
- 剪枝应该放在停止条件哪里判断