代码随想录算法训练营Day29|回溯part02

93 阅读5分钟

LeetCode 39 组合总和

题目链接:leetcode.cn/problems/co…

文档讲解:programmercarl.com/0039.组合总和.h…

视频讲解:www.bilibili.com/video/BV1KT…

思路

组合中可重复选取同一个数字。为了方便剪枝,可以对候选数字排序成递增。

考虑剪枝:

  1. 当路径和已经大于目标和,由于候选序列递增,同深度下后方数字都可以舍弃,更大深度下也可舍弃
  2. 为了不重复生成组合,每次选择的新数字从路径最后一个数字开始。由于数值需要对应索引,传参时需要上一个数字的索引

考虑回溯三要素:

  1. 回溯函数的参数和返回值 参数:candidates候选数字,target目标和,start开始选择数字的索引 返回值:空 全局变量:path选择路径,sum路径和,result结果集
  2. 回溯函数的结束条件 sum等于target时,path加入result,返回
  3. 回溯搜索的遍历过程 从start索引开始逐个选择候选集中的数字
    1. candidates[i]加入path,更新sum
    2. 如果sum大于target,还原path和sum,退出循环
    3. 递归调用回溯函数,start参数为i
    4. 还原path和sum

解法

class Solution {
	List<List<Integer>> result;	
	List<Integer> path;	
	int sum;
	
	public List<List<Integer>> combinationSum(int[] candidates, int target) {	
		result = new ArrayList<>();		
		path = new ArrayList<>();		
		sum = 0;		
		Arrays.sort(candidates);		
		backTracking(candidates, target, 0);		
		return result;	
	}
		  	
	public void backTracking(int[] candidates, int target, int start) {	
		if (sum == target) {		
			result.add(new ArrayList<>(path));			
			return ;		
		}
		
		for (int i = start; i < candidates.length; i++) {		
			sum += candidates[i];			
			if (sum > target) {			
				sum -= candidates[i];				
				break ;			
			}			
			path.add(candidates[i]);			
			backTracking(candidates, target, i);			
			path.remove(path.size()-1);			
			sum -= candidates[i];		
		}		
	}
}

LeetCode 40 组合总和II

题目链接:leetcode.cn/problems/co…

文档讲解:programmercarl.com/0040.组合总和II…

视频讲解:www.bilibili.com/video/BV12V…

思路

candidates中会有数字重复,且其中的数字只能使用一次。 根据上一题的思路,我们可以先对数组排序。

考虑剪枝:

  1. 当路径和已经大于目标和,由于候选序列非递减,同深度下后方数字都可以舍弃,更大深度下也可舍弃

考虑去重:

  1. 为了不重复使用一个数字,每次选择的新数字从索引i+1开始。
  2. 数组排序后相同的数字相邻,为了防止相同次数使用相同的数字,考虑与前一个数字相同的candidates[i],仅当candidates[i-1]在路径中,才可以使用candidates[i](可以理解为相同的数字只能连续使用,这样对于出现次数k,只可能取前k个此数字)。为了判断每个数字是否存在路径中,增设数组inPath,长度等于candidates,1标识在路径中,0标识不在

考虑回溯三要素:

  1. 回溯函数的参数和返回值 参数:candidates候选数字,target目标和,start开始选择数字的索引 返回值:空 全局变量:path选择路径,sum路径和,result结果集
  2. 回溯函数的结束条件 sum等于target时,path加入result,返回
  3. 回溯搜索的遍历过程 从start索引开始逐个选择候选集中的数字
    1. 如果不符合去重2️⃣,跳过本轮循环
    2. candidates[i]加入path,更新sum
    3. 如果sum大于target,还原path和sum,退出循环
    4. 递归调用回溯函数,start参数为i
    5. 还原path和sum

解法

class Solution {
	List<List<Integer>> result;	
	List<Integer> path;	
	int sum;	
	int[] inPath;
	
	public List<List<Integer>> combinationSum2(int[] candidates, int target) {		
		result = new ArrayList<>();		
		path = new ArrayList<>();		
		sum = 0;		
		inPath = new int[candidates.length];		
		Arrays.sort(candidates);		
		backTracking(candidates, target, 0);		
		return result;	
	}		  
	
	public void backTracking(int[] candidates, int target, int start) {	
		if (sum == target) {		
			result.add(new ArrayList<>(path));		
			return ;		
		}		
		for (int i = start; i < candidates.length; i++) {		
			if (i > 0 && candidates[i] == candidates[i-1] && inPath[i-1] == 0) {			
				continue;			
			}			
			sum += candidates[i];			
			if (sum > target) {			
				sum -= candidates[i];				
				break;			
			}			
			path.add(candidates[i]);			
			inPath[i] = 1;			
			backTracking(candidates, target, i+1);			
			inPath[i] = 0;			
			path.remove(path.size()-1);			
			sum -= candidates[i];		
		}		
	}
}

LeetCode 131 分割回文串

题目链接:leetcode.cn/problems/pa…

文档讲解:programmercarl.com/0131.分割回文串.…

视频讲解:www.bilibili.com/video/BV1c5…

思路

考虑如何判断回文:

  1. 可以每次用双指针法左右相互比较
  2. 给定一个字符串s, 长度为n, 它成为回文字串的充分必要条件是s[0] == s[n-1]s[1:n-1]是回文字串。故可以在一开始计算s的任意子串是否为回文串(动态规划),回溯时只需要查询

考虑回溯三要素:

  1. 回溯函数的参数和返回值 参数:串s,下一个字串的起始索引start 返回值:空 全局变量:结果集result,path切割路径
  2. 回溯函数的结束条件 start大于等于s长度,path加入结果集
  3. 回溯搜索的遍历过程 从start+1开始的每个切割位置i
    1. 判断子串[start, i]是否是回文串(左闭右开),不是就跳过本次循环
    2. 把子串加入path
    3. 递归调用回溯函数,start参数为i
    4. 还原path

解法

class Solution {
	boolean[][] isPalindrome;	
	List<List<String>> result;	
	List<String> path;
	
	public List<List<String>> partition(String s) {	
		isPalindrome = computePalindrome(s);		
		result = new ArrayList<>();		
		path = new ArrayList<>();		
		backTracking(s, 0);		
		return result;	
	}
		  	
	public void backTracking(String s, int start) {	
		if (start == s.length()) {		
			result.add(new ArrayList<>(path));			
			return ;		
		}		
		for (int i = start+1; i <= s.length(); i++) {		
			if (!isPalindrome[start][i-1]) {			
				continue;			
			}			
			path.add(s.substring(start, i));			
			backTracking(s, i);			
			path.remove(path.size()-1);		
		}	
	}
	
	private boolean[][] computePalindrome(String s) {	
		boolean[][] res = new boolean[s.length()][s.length()];		
		for (int i = res.length-1; i >= 0; i--) {		
			for (int j = i; j < res.length; j++) {			
				if (i == j) {				
					res[i][j] = true;				
				}				
				else if (j == i + 1) {				
					res[i][j] = (s.charAt(i) == s.charAt(j));				
				}				
				else {				
					res[i][j] = res[i+1][j-1] && (s.charAt(i) == s.charAt(j));				
				}			
			}		
		}		
		return res;	
	}
}

今日收获总结

今日学习2.5小时,用回溯思想暴力搜索可以把问题解决过程抽象为树结构