【刷题day29】回溯算法 |491.递增子序列、46.全排列、47.全排列 II

491.递增子序列

题目+讲解
注意: 由于所给数组涉及前后顺序,所以不能进行排序然后去重,(如上一章节题目 90.子集II )。故此处考虑两种方式去重:

  1. 结果集用 set 存储,将所有 path 直接存入set进行去重;
  2. 使用 used 数组 或者 map 或者 set 在遍历过程中对同一层数据进行去重
class Solution { // set结果集去重
	private Set<List<Integer>> set = new HashSet<List<Integer>>();
	private Deque<Integer> path = new LinkedList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
    	backTracking(nums, 0);
    	return new ArrayList<List<Integer>>(set);
    }
    private void backTracking(int[] nums, int startIndex) {
    	if(path.size() > 1) set.add(new ArrayList<>(path));
    	if(startIndex >= nums.length) return;
    	for(int i = startIndex; i < nums.length; i++) {
    	//注意此处如果判断条件为 if(i>startIndex && nums[i]<nums[i-1]),会错
    	//因为向下一层遍历时,即便是比本层小的数字也会加进path
    		if(!path.isEmpty() && nums[i] < path.peekLast()) continue;
    		path.offerLast(nums[i]);
    		backTracking(nums, i+1);
    		path.pollLast();
    	}
    }
}

class Solution { // 同一层数据去重
    private List<Integer> path = new ArrayList<>();
    private List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
        backtracking(nums,0);
        return res;
    }

    private void backtracking (int[] nums, int start) {
        if (path.size() > 1)  res.add(new ArrayList<>(path));
        
        // 数组模拟map,直接用下标代替key,由于-100 <= nums[i] <= 100
        // 所以数组大小设置200,nums[i]+100 即为下标
        int[] used = new int[201]; 
        for (int i = start; i < nums.length; i++) {
            if (!path.isEmpty() && nums[i] < path.get(path.size() - 1) ||
                    (used[nums[i] + 100] == 1)) continue;
            used[nums[i] + 100] = 1;
            path.add(nums[i]);
            backtracking(nums, i + 1);
            path.remove(path.size() - 1);
        }
    }
}

46.全排列

题目+讲解

class Solution {
 	private List<List<Integer>> ans = new ArrayList<>();
	private List<Integer> path = new ArrayList<>();
	private boolean[] used;
    public List<List<Integer>> permute(int[] nums) {
    	used = new boolean[nums.length]; // 默认全false
    	backTracking(nums);
    	return ans;
    }
    private void backTracking(int[] nums) {
    	if(path.size() == nums.length) {
    		ans.add(new ArrayList<Integer>(path));
    		return;
    	}
    	for(int i = 0; i < nums.length; i++) {
    		if(used[i]) continue;
            // 做选择
            path.add(nums[i]);
            used[i] = true;
            // 进入下一层决策树
            backTracking(nums);
            // 取消选择
            path.remove(path.size()-1);
            used[i] = false;
    	}  	
    }
}

47.全排列 II

题目+讲解

注意: 本题目中的 used 数组作用有二

  • 判断同一树层上是否有过重复,避免最终结果集中的结果重复(注意需要提前排序)
  • 判断同一树枝上是否有过重复,避免同一个位置元素的重复取值(根据全排列的遍历特征而决定,每次的回溯迭代都会遍历所有元素,而不是像排列Ⅱ一样,每次遍历都是数据集剩下的元素),同时需要注意: used 存的是下标而不是元素,所以不同位置的相同元素取值是合理的)
class Solution {
 	private List<List<Integer>> ans = new ArrayList<>();
	private List<Integer> path = new ArrayList<>();
	private boolean[] used;
    public List<List<Integer>> permuteUnique(int[] nums) {  	
    	Arrays.sort(nums);
    	used = new boolean[nums.length];
    	backTracking(nums);
    	return ans;
    }
    private void backTracking(int[] nums) {
    	if(path.size() == nums.length) {
    		ans.add(new ArrayList<Integer>(path));
    		return;
    	}
    	for(int i = 0; i < nums.length; i++) {
    	    // 数层去重
    	    // used[i - 1] == true,说明同⼀树⽀nums[i - 1]使⽤过
            // used[i - 1] == false,说明同⼀树层nums[i - 1]使⽤过
            // 如果同⼀树层nums[i - 1]使⽤过则直接跳过
    		if(i>0 && nums[i]==nums[i-1] && !used[i-1]) continue;
    		// 数枝去重
    		// 如果上一树层且同⼀树⽀上面nums[i]已经使⽤过,则直接跳过
    		//(因为全排列每次回溯需要遍历所有元素,为避免重复取一个元素故而选择used来判断)
    		if(used[i]) continue;
    		used[i] = true;
    		path.add(nums[i]);
    		backTracking(nums);
    		path.remove(path.size()-1);
    		used[i] = false;
    	} 
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值