前言 今天是学习回溯算法的第二天,从昨天的学习中我们可以知道回溯算法也有一套逻辑格式,我们对题目进行对应格式的逻辑分析即可,希望博主记录的内容能够对大家有所帮助 ,一起加油吧朋友们!💪💪💪
组合总和 LeetCode题目链接
这道题目是给一个无重复数的整数数组 candidates 和一个目标和 target ,然后要求找到candidates中的所有不同组合,组合的数和为target, candidates 中的数可以在组合中重复出现。
我们来梳理一下逻辑
使用回溯法 从 candidates 中每次选择一个数字加入当前组合,然后在递归过程中保持当前组合的和不超过 target🤔如果组合的和等于 target,则将其加入结果集。每次选择一个数字后,允许继续选择该数字(因为同一个数字可以重复使用),但不允许选择比当前数字更小的数字(避免重复组合)🤔 通过回溯和剪枝,避免无效的组合,直到找出所有符合条件的组合🤔
我们进一步来梳理回溯三要素
1 2 3 List<List<Integer>> result = new ArrayList <>(); List<Integer> path = new ArrayList <>(); private void backtracking (int [] candidates, int target, int startIndex) {}
1 2 3 4 5 6 int sum = path.stream().mapToInt(Integer::intValue).sum();if (sum > target) return ;if (sum == target){ result.add(new ArrayList <>(path)); return ; }
1 2 3 4 5 for (int i = startIndex; i < candidates.length; i++){ path.add(candidates[i]); backtracking(candidates, target, i); path.removeLast(); }
回溯的完整代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { List<List<Integer>> result = new ArrayList <>(); List<Integer> path = new ArrayList <>(); public List<List<Integer>> combinationSum (int [] candidates, int target) { backtracking(candidates, target, 0 ); return result; } private void backtracking (int [] candidates, int target, int startIndex) { int sum = path.stream().mapToInt(Integer::intValue).sum(); if (sum > target) return ; if (sum == target){ result.add(new ArrayList <>(path)); return ; } for (int i = startIndex; i < candidates.length; i++){ path.add(candidates[i]); backtracking(candidates, target, i); path.removeLast(); } } }
这里我们再来进行剪枝,对数组进行升序排序,在搜索时若提前发现已存组合和要添加的数和超出target则不进行搜索🤔🤔🤔
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { List<List<Integer>> result = new ArrayList <>(); List<Integer> path = new ArrayList <>(); public List<List<Integer>> combinationSum (int [] candidates, int target) { Arrays.sort(candidates); backtracking(candidates, target, 0 ); return result; } private void backtracking (int [] candidates, int target, int startIndex) { int sum = path.stream().mapToInt(Integer::intValue).sum(); if (sum > target) return ; if (sum == target){ result.add(new ArrayList <>(path)); return ; } for (int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++){ path.add(candidates[i]); backtracking(candidates, target, i); path.removeLast(); } } }
可以看到剪枝前后的速度有所提升
但实际上速度还能有所提升,可以把每次对组合的求和操作转换为递归的参数传入,递归时更新组合和这个参数即可🤔🤔🤔
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { List<List<Integer>> result = new ArrayList <>(); List<Integer> path = new ArrayList <>(); public List<List<Integer>> combinationSum (int [] candidates, int target) { Arrays.sort(candidates); backtracking(candidates, target, 0 , 0 ); return result; } private void backtracking (int [] candidates, int target, int sum,int startIndex) { if (sum == target){ result.add(new ArrayList <>(path)); return ; } for (int i = startIndex; i < candidates.length; i++){ if (sum + candidates[i] > target) break ; path.add(candidates[i]); backtracking(candidates, target, sum+ candidates[i], i); path.removeLast(); } } }
组合总和II LeetCode题目链接
这道题也是给定一个整数数组candidates,从其中选取数字组合使得其的和为目标和等于所给的target,但是数字不能重复啦,每个数字只能使用一次
我们来梳理思路
我们先对 candidates 进行排序,方便在递归中去重🤔 从第一个数字开始,每次选择一个数字加入当前组合,然后在递归过程中,如果发现当前数字与前一个数字相同,跳过该数字,避免生成重复组合🤔 如果组合的和等于 target,则将该组合加入结果集。如果和超过 target,则终止当前递归路径🤔 在递归返回时,移除最后一个加入的数字,尝试新的组合路径🤔
我们来梳理回溯三要素
1 2 3 4 List<List<Integer>> result = new ArrayList <>(); List<Integer> path = new ArrayList <>(); int sum = 0 ;private void backTracking (int [] candidates, int target, int startIndex) {}
回溯的终止条件(这里把判断和的大小的处理放在单层搜索过程的剪枝处理中)
1 2 3 if (sum == target){ result.add(new ArrayList <>(path)); }
1 2 3 4 5 6 7 8 for (int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++){ if (i > startIndex && candidates[i] == candidates[i - 1 ])continue ; sum += candidates[i]; path.add(candidates[i]); backTracking(candidates, target, i + 1 ); sum -= path.getLast(); path.removeLast(); }
回溯的完整代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { List<List<Integer>> result = new ArrayList <>(); List<Integer> path = new ArrayList <>(); int sum = 0 ; public List<List<Integer>> combinationSum2 (int [] candidates, int target) { Arrays.sort(candidates); backTracking(candidates, target, 0 ); return result; } private void backTracking (int [] candidates, int target, int startIndex) { if (sum == target){ result.add(new ArrayList <>(path)); } for (int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++){ if (i > startIndex && candidates[i] == candidates[i - 1 ])continue ; sum += candidates[i]; path.add(candidates[i]); backTracking(candidates, target, i + 1 ); sum -= path.getLast(); path.removeLast(); } } }
分割回文串 LeetCode题目链接
这道题就是给一个字符串,然后要把这个字符串分割成一些回文子串,返回所有分割方案,每个分割方案就是一个组合
我们来梳理一下思路
逐步从字符串中切割子串,检查子串是否为回文。如果是回文,则将子串加入当前分割路径🤔 在递归过程中,可以使用双指针或预先使用动态规划表来判断子串是否为回文,这里我们先使用双指针来判断回文🤔 当递归到字符串末尾时,当前分割路径即为一个有效的回文分割,将其加入结果集🤔 在递归返回时,移除最后一个加入的子串,尝试不同的分割方案🤔
我们来进一步梳理回溯三要素
1 2 3 List<List<String>> result = new ArrayList <>(); List<String> path = new ArrayList <>(); private void backtracking (String s, int startIndex) {}
回溯终止条件(当搜索的数字索引到字符串的尾部则将分割的方案加入结果集中)
1 2 3 4 if (startIndex == s.length()){ result.add(new ArrayList <>(path)); return ; }
1 2 3 4 5 6 7 8 for (int i = startIndex; i < s.length(); i++){ String subStr = s.substring(startIndex, i + 1 ); if (check(subStr)){ path.add(subStr); backtracking(s, i + 1 ); path.removeLast(); } }
完整的回溯代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { List<List<String>> result = new ArrayList <>(); List<String> path = new ArrayList <>(); public List<List<String>> partition (String s) { backtracking(s, 0 ); return result; } private void backtracking (String s, int startIndex) { if (startIndex == s.length()){ result.add(new ArrayList <>(path)); return ; } for (int i = startIndex; i < s.length(); i++){ String subStr = s.substring(startIndex, i + 1 ); if (check(subStr)){ path.add(subStr); backtracking(s, i + 1 ); path.removeLast(); } } } private boolean check (String str) { int len = str.length(); for (int i = 0 ; i < len / 2 ; i++){ if (str.charAt(i) != str.charAt(len - 1 - i)) return false ; } return true ; } }
总结 又是学习回溯的新一天,继续加油✊✊✊