代码随想录算法训练营Day31|回溯part04

66 阅读4分钟

LeetCode 491 递增子序列

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

文档讲解:programmercarl.com/0491.递增子序列.…

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

思路

递增子序列最少有两个元素,相同元素也视为递增。所以满足条件的节点不一定是叶子节点,因此加入结果集的操作也在主循环里。 考虑去重:

  1. 数组不可排序,相同的数字不一定相邻,故有可能遍历到相同的递增序列
  2. 考虑树结构,本题去重其实是在同一父节点下,兄弟节点直接不能选取相同的数字。即在一个同层的for循环内,不能选两次相同的数字。故可以在for循环内用局部set集合记录用过的数字。

考虑回溯三要素:

  1. 回溯函数的参数和返回值 参数:数组nums,开始索引start 返回值:空
  2. 回溯函数的结束条件 start等于nums长度,返回
  3. 回溯搜索的遍历过程 新建集合set,从start开始对每个元素nums[i]遍历
    1. 如果nums[i]存在于set中,跳过本次循环.
    2. 如果path不为空,且nums[i]大于等于path中最后一个元素则把元素加入路径,更新set,再把path加入result。如果不构成递增,跳过本次循环
    3. 如果path为空,直接把元素加入路径path,更新set
    4. 递归调用回溯函数
    5. 还原path

解法

class Solution {

	List<List<Integer>> result;	
	List<Integer> path;		  
	
	public List<List<Integer>> findSubsequences(int[] nums) {		
		result = new ArrayList<>();		
		path = new ArrayList<>();		
		backTracking(nums, 0);		
		return result;	
	}		  
	
	public void backTracking(int[] nums, int start) {	
		if (start == nums.length) {		
			return ;		
		}		
		Set<Integer> set = new HashSet<>();		
		for (int i = start; i < nums.length; i++) {		
			if (set.contains(nums[i])) {			
				continue;			
			}			
			if (path.size() == 0) {			
				path.add(nums[i]);				
				set.add(nums[i]);			
			}			
			else {			
				if (nums[i] >= path.get(path.size()-1)) {				
					path.add(nums[i]);					
					set.add(nums[i]);					
					result.add(new ArrayList<>(path));				
				}				
				else {				
					continue;				
				}			
			}			
			backTracking(nums, i+1);			
			path.remove(path.size()-1);		
		}	
	}
}

LeetCode 46 全排列

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

文档讲解:programmercarl.com/0046.全排列.ht…

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

思路

注意:排列有序,组合无序。所以组合需要使用start防止回头选择元素造成重复,但排列中回头选择会有不同的顺序,因此是不同的排列,无需使用start。每次for循环都从0开始。 虽然无需规定开始索引,但数组中的数字不可重复使用,我们需要判断一个数字是否使用过,引入inPath布尔数组

考虑回溯三要素:

  1. 回溯函数的参数和返回值 参数:nums 返回值:空 全局变量:result,path,inPath,还没使用的元素个数left
  2. 回溯函数的结束条件 当left为0。把path加入result,返回
  3. 回溯搜索的遍历过程 对inPath中每个值为假的索引i:
    1. 把nums[i]加入path,inPath[i]置为真,left-1
    2. 递归调用回溯函数
    3. 还原path,inPath[i]置为假,left+1

解法

class Solution {

	List<List<Integer>> result;	
	List<Integer> path;	
	boolean[] inPath;	
	int left;
	
	public List<List<Integer>> permute(int[] nums) {	
		result = new ArrayList<>();		
		path = new ArrayList<>();		
		inPath = new boolean[nums.length];		
		left = nums.length;		
		backTracking(nums);		
		return result;	
	}
		  	
	public void backTracking(int[] nums) {	
		if (left == 0) {		
			result.add(new ArrayList<>(path));		
		}		
		for (int i = 0; i < nums.length; i++) {		
			if (inPath[i]) {			
				continue;			
			}			
			path.add(nums[i]);			
			inPath[i] = true;			
			left--;			
			backTracking(nums);			
			left++;			
			inPath[i] = false;			
			path.remove(path.size()-1);		
		}	
	}
}

LeetCode 47 全排列 II

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

文档讲解:programmercarl.com/0047.全排列II.…

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

思路

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。 考虑去重:

  1. 此题中可以改变元素顺序,所以我们先对数组排序
  2. 排列需要所有数字都存在,不同的排列来自于对元素的选择顺序不同,所以我们只需要在搜索树中同层不选择相同元素即可。使用集合记录本层使用过的数字,如果存在就跳过

考虑回溯三要素:

  1. 回溯函数的参数和返回值 参数:数组 返回值:空 全局变量:result,path,inPath,left剩余元素数量
  2. 回溯函数的结束条件 left为0,把path加入result,返回
  3. 回溯搜索的遍历过程 初始化局部变量set,对数组中每个元素nums[i]:
    1. 如果inPath[i]为真,跳过
    2. 如果nums[i]在集合中,跳过
    3. 把nums[i]加入path,更新inPath,更新left,nums[i]加入set
    4. 递归调用回溯函数
    5. 还原path,inPath,left

解法

class Solution {
	List<List<Integer>> result;	
	List<Integer> path;	
	int left;	
	boolean[] inPath;
	
	public List<List<Integer>> permuteUnique(int[] nums) {	
		Arrays.sort(nums);		
		result = new ArrayList<>();		
		path = new ArrayList<>();		
		left = nums.length;		
		inPath = new boolean[nums.length];		
		backTracking(nums);		
		return result;	
	}		  
	
	public void backTracking(int[] nums) {	
		if (left == 0) {		
			result.add(new ArrayList<>(path));			
			return ;		
		}		
		Set<Integer> set = new HashSet<>();		
		for (int i = 0; i < nums.length; i++) {		
			if (inPath[i]) {			
				continue;			
			}			
			if (set.contains(nums[i])) {			
				continue;			
			}			
			set.add(nums[i]);			
			path.add(nums[i]);			
			inPath[i] = true;			
			left--;			
			backTracking(nums);			
			left++;			
			inPath[i] = false;			
			path.remove(path.size()-1);		
		}	
	}
}