代码随想录 day27 第七章 回溯算法part03

本文介绍了LeetCode中的三道问题:组合总和(允许重复和不重复元素),组合总和II(不允许重复元素),以及分割回文串。重点讲解了如何通过剪枝、去重等技巧优化解题算法。
摘要由CSDN通过智能技术生成

今日内容:

●  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 来去重会超时
        • 搜索的时候就需要去掉重复组合了
    • 破题关键:去重
      • 需要先对数组进行排序
        • 将相邻元素放在一起
      • 两个层面去重
        • 同一层树上
          • “使用过的”元素,需要去重
            • 去重操作前,需要对数组排序
        • 同一根树枝上
          • 一个组合里面的元素,不用去重
  • 题解

    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】
        • 单层 向后移动 切割终止位置
        • 判断切合出来的字串是否满足条件【本题是回文】
  • 题解

    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. 题外话

  • 剪枝
    • 剪枝应该放在停止条件哪里判断
      • 不应该放在单层循环代码里面
        • 否则会导致回溯遗漏

  • 32
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值