[回溯法] -- 47 - 全排列Ⅱ - python + Java

41 阅读2分钟

给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

输入: [1,1,2]
输出:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

给定的序列中包含有重复数字,因此结果不能有重复的元素存在。一种方法就是使用上一道题的代码,只是在最后保存结果时多加一个判断。

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        if nums == []: return []

        results= []
        def backtrack(nums, track, vis, depth= 0):
            if depth== len(nums):
            	# 只有res中不包含和当前pre相同的元素时才保留
                if track not in results:
                    results.append(track.copy())
                    return 

            for i in range(len((nums))):
                if vis[i] == False:
                	track.append(nums[i])
                    vis[i] = True
                    backtrack(nums, track, vis, depth + 1)
                    vis[i] = False
                    track.pop()
                    
        nums = sorted(nums)
        vis = [False] * len(nums)
        
        backtrack(nums, [], vis, 0)
        
        return results

但这样的方法多考虑了很多重复的情况,更好的方法是在递归的过程中进行剪枝。如果当前选择列表中的元素和上一次递归使用的元素相同,则使用它得到的结果必然是重复的,此时需跳过进行下一个元素的判断。

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        results = []
        l = len(nums)
        
        def backtrack(nums, track, depth= 0):
        	# if nums == []表示当前选择列表为空,已到达叶子结点
            if depth== l:
                results .append(track.copy())
                return
                
            for i in range(len(nums)):
            	# 剪枝
                if i > 0 and nums[i] == nums[i - 1]:
                    continue
                    
                track.append(nums[i])
                backtrack(nums[:i] + nums[i + 1:], track, depth+ 1)
                track.pop()


        if nums == []: return []
        nums = sorted(nums)
        
        backtrack(nums, [], 0)
        return results 

2020 - 8 - 2 更新Java解题代码,如下所示:


/**
 * @Author dyliang
 * @Date 2020/8/2 0:14
 * @Version 1.0
 */
class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> results = new LinkedList<>();
        LinkedList<Integer> track = new LinkedList<>();
		// 先将数组中数字按需排列,便于后续对可能的重复情况进行剪枝
        Arrays.sort(nums);
		
        boolean[] vis = new boolean[nums.length];
        dfs(nums, track, vis, 0, results);
        
        results.forEach(System.out :: println);
        return results;
    }

    private void dfs(int[] nums, LinkedList<Integer> track, boolean[] vis, int depth, List<List<Integer>> results) {
		// 终止条件
        if (depth == nums.length) {
            results.add(new LinkedList<>(track));
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            if(vis[i]){
                continue;
            }
			// 如果当前索引不为0的情况下,当前选择的元素和上一个选择的相同,进行剪枝
            if (i > 0 && nums[i] == nums[i - 1] && vis[i - 1] == false){
                continue;
            }
			// 做出选择
            track.add(nums[i]);
            vis[i] = true;
            dfs(nums, track, vis, depth + 1, results);
			// 撤销选择
            track.removeLast();
            vis[i] = false;
        }
    }
}

在提交的过程中发现,如果去重的判断发生在最后保存可能路径的时候,虽然可以通过所有的测试样例,但是会出现时间超过的问题。显然判断当前可选择的元素是否和上一个选择的相同,相比于在路径列表中判断当前路径是否存在时间复杂度更低。