代码随想录算法训练营第二十七天| 39. 组合总和,40.组合总和II,131.分割回文串

 题目与题解

39. 组合总和

题目链接:39. 组合总和

代码随想录题解:39. 组合总和

视频讲解:带你学透回溯算法-组合总和(对应「leetcode」力扣题目:39.组合总和)| 回溯法精讲!_哔哩哔哩_bilibili

解题思路:

        组合总和这一类题的基本思路是一样的,这道题是在组合总和III这题的基础上,增加了一个元素可重复选取的需求,实际上正如提示所说,只要修改startIndex的入参即可。

        同样还是用回溯法做,三部曲如下:

        参数有返回的结果result,中间记录结果的变量list,用于计算当前数字之和的变量sum,递归函数的入参是输入的数组candidates,目标和target,以及每一次递归的起始值startIndex;

        终止条件仍然为sum等于target,将list插入result,并返回;

        单层遍历条件也是类似的,不同的地方在于startIndex不是i+1,而是i(因为当前元素可以重复选取)。

        考虑到输入数组的元素都是正数,所以当sum>target时,可以剪枝返回。

class Solution {
	List<List<Integer>> result = new ArrayList<>();
	LinkedList<Integer> list = new LinkedList<>();
	int sum;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
 		combinationSum(candidates, target, 0);
		 return result;
    }

	public void combinationSum(int[] candidates, int target, int startIndex) {
		if (sum > target) return;
		if (sum == target) {
			result.add(new ArrayList<>(list));
			return;
		}
		for (int i = startIndex; i < candidates.length; i++) {
			sum += candidates[i];
			list.add(candidates[i]);
			combinationSum(candidates, target, i);
			list.pollLast();
			sum -= candidates[i];
		}
	}
}

看完代码随想录之后的想法 

        有些可以优化的细节:

        sum参数可以不需要,递归时每次入参用target-candidates代替target即可,如果某次递归时target直接为0,说明当前list已经符合要求,可以直接返回。

        剪枝可以再优化:如果先对数组进行排序,取值就会从小到大取得,这样一旦前面某个序列已经大于target了,同一层后面的序列就不用再计算了。

// 剪枝优化
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(candidates); // 先进行排序
        backtracking(res, new ArrayList<>(), candidates, target, 0, 0);
        return res;
    }

    public void backtracking(List<List<Integer>> res, List<Integer> path, int[] candidates, int target, int sum, int idx) {
        // 找到了数字和为 target 的组合
        if (sum == target) {
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = idx; i < candidates.length; i++) {
            // 如果 sum + candidates[i] > target 就终止遍历
            if (sum + candidates[i] > target) break;
            path.add(candidates[i]);
            backtracking(res, path, candidates, target, sum + candidates[i], i);
            path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素
        }
    }
}

遇到的困难

        无

40.组合总和II

题目链接:​​​​​​​40.组合总和II

代码随想录题解:​​​​​​​40.组合总和II

视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II_哔哩哔哩_bilibili

解题思路:

        总体套路仍旧和之前做过的组合总和题目类似,不同的地方在于,candidates中的元素会有重复,但每个元素不可以重复选取,所以做的时候需要去重。

        入参,终止条件和剪枝条件不作赘述,关键在于单层遍历的逻辑。因为考虑到有重复元素,首先在candidates入参之前要进行排序,这样方便后序遇到重复元素的时候跳过处理。跳过的条件为:如果当前元素不是startIndex,且它和它的前一个元素相同,就直接跳过本次循环。

class Solution {
	List<List<Integer>> result = new ArrayList<>();
	LinkedList<Integer> list = new LinkedList<>();
	int sum = 0;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
		Arrays.sort(candidates);
		combinationSum2(candidates, target, 0);
    	return result;
	}

	public void combinationSum2(int[] candidates, int target, int startIndex) {
		if (sum > target) return;
		if (sum == target) {
			result.add(new ArrayList<>(list));
		}
		for (int i = startIndex; i < candidates.length; i++) {
			if (i > startIndex && candidates[i] == candidates[i-1]) continue;
			sum += candidates[i];
			list.add(candidates[i]);
			combinationSum2(candidates, target, i + 1);
			list.pollLast();
			sum -= candidates[i];
		}
	}
}

看完代码随想录之后的想法 

        都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。那么问题来了,我们是要同一树层上使用过,还是同一树枝上使用过呢?

        回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重

        随想录这里讲的非常清楚,用一个标记去重数组或者startIndex去重都可以,剪枝也是跟前一题相同。这里学习一下用标记去重数组。

class Solution {
  LinkedList<Integer> path = new LinkedList<>();
  List<List<Integer>> ans = new ArrayList<>();
  boolean[] used;
  int sum = 0;

  public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    used = new boolean[candidates.length];
    // 加标志数组,用来辅助判断同层节点是否已经遍历
    Arrays.fill(used, false);
    // 为了将重复的数字都放到一起,所以先进行排序
    Arrays.sort(candidates);
    backTracking(candidates, target, 0);
    return ans;
  }

  private void backTracking(int[] candidates, int target, int startIndex) {
    if (sum == target) {
      ans.add(new ArrayList(path));
    }
    for (int i = startIndex; i < candidates.length; i++) {
      if (sum + candidates[i] > target) {
        break;
      }
      // 出现重复节点,同层的第一个节点已经被访问过,所以直接跳过
      if (i > 0 && candidates[i] == candidates[i - 1] && !used[i - 1]) {
        continue;
      }
      used[i] = true;
      sum += candidates[i];
      path.add(candidates[i]);
      // 每个节点仅能选择一次,所以从下一位开始
      backTracking(candidates, target, i + 1);
      used[i] = false;
      sum -= candidates[i];
      path.removeLast();
    }
  }
}

遇到的困难

        去重方法一开始不好想,本来想用hashset试图去重,但是不确定顺序不同但元素相同的中间list在hashset算不算同一种序列,所以最后还是根据测试用例的结果似乎是排过序的,想到可以排序后比较前后元素去重。

131.分割回文串

题目链接:131.分割回文串

代码随想录题解:​​​​​​​131.分割回文串

视频讲解:带你学透回溯算法-分割回文串(对应力扣题目:131.分割回文串)| 回溯法精讲!_哔哩哔哩_bilibili

解题思路:

       关键点在于分割线如何划分,划分后后续的字符串又可以通过回溯函数递归的再次划分。但是不知道怎么写就直接看答案了。

看完代码随想录之后的想法 

        

        这里同样需要取startindex作为每次递归的起点,以及一个判断是否是回文的函数

递归函数参数:全局变量数组path存放切割后回文的子串,二维数组result存放结果集,以及输入参数s和startIndex。

终止条件:在处理组合问题的时候,递归参数需要传入startIndex,表示下一轮递归遍历的起始位置,这个startIndex就是切割线。

单层搜索逻辑:不断取startIndex到i之间的字符串,如果取到回文,就用剩下的字符串调用回溯函数,否则continue。

class Solution {
    List<List<String>> lists = new ArrayList<>();
    Deque<String> deque = new LinkedList<>();

    public List<List<String>> partition(String s) {
        backTracking(s, 0);
        return lists;
    }

    private void backTracking(String s, int startIndex) {
        //如果起始位置大于s的大小,说明找到了一组分割方案
        if (startIndex >= s.length()) {
            lists.add(new ArrayList(deque));
            return;
        }
        for (int i = startIndex; i < s.length(); i++) {
            //如果是回文子串,则记录
            if (isPalindrome(s, startIndex, i)) {
                String str = s.substring(startIndex, i + 1);
                deque.addLast(str);
            } else {
                continue;
            }
            //起始位置后移,保证不重复
            backTracking(s, i + 1);
            deque.removeLast();
        }
    }
    //判断是否是回文串
    private boolean isPalindrome(String s, int startIndex, int end) {
        for (int i = startIndex, j = end; i < j; i++, j--) {
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
        }
        return true;
    }
}

还有判断回文有剪枝方法,后面再补 

遇到的困难

        思路不是很清晰,无从下手

今日收获

        基本回溯套路掌握,但稍微变化一点就不太会做,还是没有掌握回溯的真正技巧,需要练习。

  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值