Leetcode 78 子集 & 90 子集II & leetcode 46全排列 回溯算法总结

78.Given a set of distinct integers, nums, return all possible subsets (the power set).

Note: The solution set must not contain duplicate subsets.

Example:

Input: nums = [1,2,3]
Output:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]

  1. 暴力解
    求子集,先加一个空集, 然后遍历数组,把每一个结果加上前一个结果。
    比如说,先加入空集,然后加入{1}, 然后再添加{1,2}, 也就是把2加入到前一个结果{1}中。然后添加{1,2,3}(把3添加到{1,2}中)。这是大循环里的第一步,然后再遍历到2,依次。。。下面来实现
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        //加空集
        res.add(new ArrayList<>());
        //大循环,循环nums
        for (int num : nums) {
        	//记录此时res的size,因为第二层循环只用到此时res已经存在的item,后面的size会变
        	int size = res.size();
        	//第二层循环
        	for (int i = 0; i < size; i++) {
        		//初始化一个新的list,用遍历到的num + 已经包含res中存在的结果,形成一个新的列表
        		List <Integer> temp = new ArrayList<>(res.get(i));
                temp.add(num);
        		res.add(temp);
        	}
        }
        return res;
    }
}

时间复杂度:O(N*2^N)
空间复杂度:O()
2. backtrack
回溯本质上也是暴力解,就是穷举。假设nums=[1,2,3]。先遍历1的子节点,把所有可能的解,加入集合,然后撤销这个选择。 然后选择2这条路径,再递归。
参考公众号labuladong的回溯算法框架, 我们直接开搞。(搞之前简单画一个树, 看一下结构,这里就不画了,太麻烦)

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        backtrack(res, nums, new ArrayList<>(), 0);
        return res;
    }

    private void backtrack(List<List<Integer>> res, int[] nums, List<Integer> temp, int index) {
    	res.add(new ArrayList<>(temp));
    	for (int i = index; i < nums.length; i++) {
    		//chose the path
    		temp.add(nums[i]);
    		// cuz we can't chose the same item, so we need to traverse from the next number i + 1.
    		backtrack(res, nums, temp, i + 1); 
    		// backtrack
    		temp.remove(temp.size() - 1);
    	}
    }
}

Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set).

Note: The solution set must not contain duplicate subsets.
Example:

Example:

Input: [1,2,2]
Output:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

这题临时写的,我会把我所有的思路直接写上来。
首先,有重复吗,那直接用set就是了,我们试试

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        backtrack(res, new ArrayList<>(), nums, 0);
        Set<List<Integer>> ans = new HashSet<>(res);
        List<List<Integer>> result = new ArrayList<>(ans);
        return result;
        
    }
    private void backtrack(List<List<Integer>> res, List<Integer> temp, int[] nums, int index) {
        res.add(new ArrayList<>(temp));
        for (int i = index; i < nums.length; i++) {
            temp.add(nums[i]);
            backtrack(res, temp, nums, i + 1);
            temp.remove(temp.size() - 1);
        }
    }
}

继续优化一下

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        Set<List<Integer>> res = new HashSet<>();
        backtrack(res, new ArrayList<>(), nums, 0);
        List<List<Integer>> result = new ArrayList<>(res);    
        return result;
        
    }
    private void backtrack(Set<List<Integer>> res, List<Integer> temp, int[] nums, int index) {
        res.add(new ArrayList<>(temp));
        for (int i = index; i < nums.length; i++) {
            temp.add(nums[i]);
            backtrack(res, temp, nums, i + 1);
            temp.remove(temp.size() - 1);
        }
    }
}

搞定,如果不用set,那么在递归中设置条件,如果后面一个数和前面的相同,那么跳过即可

  1. LeetCode 46 全排列

Given a collection of distinct integers, return all possible
permutations.

Example:

Input: [1,2,3]
Output:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
穷举的形式,那么用回溯算法。具体可以画个树的图来清晰算法。
我们还是基于78题,与78题不同的是, 全排列递归的base case是集合长度达到nums.length的时候,先把框架写出来:

class Solution {
    public List<List<Integer>> permute(int[] nums) {
    	Arrys.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        backtrack(res, nums, new ArrayList<>(), 0);
        return res;
    }

    private void backtrack(List<List<Integer>> res, int[] nums, List<Integer> temp, int index) {
    	if (temp.size() == nums.length) {
    		res.add(new ArrayList<>(temp));}
    	for (int i = index; i < nums.length; i++) {
    		//chose the path
    		temp.add(nums[i]);
    		// cuz we can't chose the same item, so we need to traverse from the next number i + 1.
    		backtrack(res, nums, temp, i + 1); 
    		// backtrack
    		temp.remove(temp.size() - 1);
    	}
    }
}

这样运行出来只有一个解,来修改。首先sort可以去掉。然后,问题是出在index上,全排列并不需要额外的指针:

class Solution {
    public List<List<Integer>> permute(int[] nums) {
    	Arrys.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        backtrack(res, nums, new ArrayList<>());
        return res;
    }

    private void backtrack(List<List<Integer>> res, int[] nums, List<Integer> temp) {
    	if (temp.size() == nums.length) {
    		res.add(new ArrayList<>(temp));}
    	for (int i = 0; i < nums.length; i++) {
    		if (temp.contains(nums[i])) continue;
    		//chose the path
    		temp.add(nums[i]);
    		// cuz we can't chose the same item, so we need to traverse from the next number i + 1.
    		backtrack(res, nums, temp); 
    		// backtrack
    		temp.remove(temp.size() - 1);
    	}
    }
}

运行结果:Memory Limit Exceeded
继续修改,
递归中会不断地重复添加nums[i], 因此我们需要剔除已经存在的情况
事实上并没有选择性遍历,而是每一遍都完整的遍历nums中的数,所以会存在重复的现象,导致一致在递归。 下面尝试用额外的指针来限制选择,这里用一个可能不太好弄,可以尝试一下用两个指针。
太麻烦了,两个指针的话既要考虑后面的数字,也要考虑前面的数字,放弃。
那只要加个条件即可

if (temp.contains(nums[i])) continue;
class Solution {
    public List<List<Integer>> permute(int[] nums) {
    	//Arrys.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        backtrack(res, nums, new ArrayList<>());
        return res;
    }

    private void backtrack(List<List<Integer>> res, int[] nums, List<Integer> temp) {
    	if (temp.size() == nums.length) {
    		res.add(new ArrayList<>(temp));}
    	for (int i = 0; i < nums.length; i++) {
            if (temp.contains(nums[i])) continue;
    		//chose the path
    		temp.add(nums[i]);
    		// cuz we can't chose the same item, so we need to traverse from the next number i + 1.
    		backtrack(res, nums, temp); 
    		// backtrack
    		temp.remove(temp.size() - 1);
    	}
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值