zoukankan      html  css  js  c++  java
  • LeetCode47. 全排列 II

    思路1:回溯搜索全排列,使用Set暴力去重。

    ☆☆☆思路2:回溯搜索 + 剪枝。

      对原数组排序,保证相同的数字都相邻,然后每次填入的数一定是这个数所在重复数集合中「从左往右第一个未被填过的数字」

    代码1:回溯搜索 + Set去重

      代码1.1 ——交换位置确定数字(耗时:30ms)

    class Solution {
        public List<List<Integer>> permuteUnique(int[] nums) {
            List<List<Integer>> res = new ArrayList<>();
            if (nums == null || nums.length == 0) return res;
            Set<List<Integer>> set = new HashSet<>();
            dfs(nums, 0, set);
            res.addAll(set);
            return res;
        }
        private void dfs(int[] nums, int index, Set<List<Integer>> set) {
            if (index == nums.length) {
                List<Integer> list = new ArrayList<>();
                for (int num : nums) {
                    list.add(num);
                }
                set.add(list);
            }
            for (int i = index; i < nums.length; i++) {
                swap(nums, index, i);
                dfs(nums, index + 1, set);
                swap(nums, index, i);
            }
        }
        private void swap(int[] nums, int a, int b) {
            int temp = nums[a];
            nums[a] = nums[b];
            nums[b] = temp;
        }
    }

      代码1.2 ——设置标记数组(耗时:40ms)

    class Solution {
        public List<List<Integer>> permuteUnique(int[] nums) {
            Set<List<Integer>> set = new HashSet<>();
            boolean[] visited = new boolean[nums.length];
            dfs(nums, visited, new ArrayList<>(), set);
            List<List<Integer>> res = new ArrayList<>(set);
            return res;
        }
        private void dfs(int[] nums, boolean[] visited, List<Integer> list, Set<List<Integer>> set) {
            if (list.size() == nums.length) {
                set.add(new ArrayList<>(list));
                return;
            }
            for (int i = 0; i < nums.length; i++) {
                if (visited[i]) continue;
                visited[i] = true;
                list.add(nums[i]);
                dfs(nums, visited, list, set);
                visited[i] = false;
                list.remove(list.size() - 1); // 注意remove传入的参数是index
            }
        }
    }

    代码2:回溯搜索 + 剪枝

      代码2.1 ——交换位置确定数字(耗时:1ms)

    class Solution {
        public List<List<Integer>> permuteUnique(int[] nums) {
            List<List<Integer>> res = new ArrayList<>();
            dfs(nums, 0, res);
            return res;
        }
        private void dfs(int[] nums, int index, List<List<Integer>> res) {
            if (index == nums.length) {
                List<Integer> list = new ArrayList<>();
                for (int num : nums) {
                    list.add(num);
                }
                res.add(list);
                return;
            }
            for (int i = index; i < nums.length; i++) {
                // 搜索前 先判断是否已经被选过
                if (canSwap(nums, index, i)) {
                    swap(nums, index, i);
                    dfs(nums, index + 1, res);
                    swap(nums, index, i);
                }
            }
        }
        /**
         *  如果当前准备选的下标是cur,而在index至cur-1中出现过相同的数字,
         *  说明数字肯定已经选过了。
         */
        private boolean canSwap(int[] nums, int start, int end) {
            for (int k = start; k < end; k++) {
                if (nums[k] == nums[end]) {
                    return false;
                }
            }
            return true;
        }
        private void swap(int[] nums, int a, int b) {
            int tmp = nums[a];
            nums[a] = nums[b];
            nums[b] = tmp;
        }
    }

      代码2.2 ——设置标记数组(耗时:1ms)

    class Solution {
        public List<List<Integer>> permuteUnique(int[] nums) {
            List<List<Integer>> res = new ArrayList<>();
            boolean[] visited = new boolean[nums.length];
            Arrays.sort(nums);  // 排序保证相同数字都相邻
            dfs(nums, 0, visited, new ArrayList<>(), res);
            return res;
        }
        private void dfs(int[] nums, int index, boolean[] visited, List<Integer> list, List<List<Integer>> res) {
            if (index == nums.length) {
                res.add(new ArrayList<>(list));
                return;
            }
            for (int i = 0; i < nums.length; i++) {
                if (visited[i]) continue;
                // 排序保证了相同数字都相邻,每次填入的数是这个数所在重复数集合中「从左往右第一个未被填过的数字」
                // 对 !visited[i-1] 的理解很关键
                if (i > 0 && nums[i] == nums[i - 1] && !visited[i-1]) continue;
    
                visited[i] = true;
                list.add(nums[i]);
                dfs(nums, index + 1, visited, list, res);
                visited[i] = false;
                list.remove(list.size() - 1);
            }
        }
    }

    难点: !vis[i - 1] (前一个元素还未使用过)的理解

      如果前一个相同元素未被使用过,则不使用当前元素。那么每次填入的数一定是这个数所在重复集合中最左边那个。

      当前值等于前一个值,有两种情况:

        1. nums[i-1] 没用过,为false。 说明回溯到了同一层,此时接着用nums[i]会与 nums[i-1]重复。

        2. nums[i-1] 用过了,为true。 说明此时在nums[i-1]的下一层,相等不会重复。

    用 !vis[i-1]是判断填入perm同一个位置时,这个数是否被使用过,如果是false代表填入过(因为回溯时被撤销标记了)

      虽然使用 vis[i-1]也能AC,但 !vis[i-1]更高效。

  • 相关阅读:
    An impassioned circulation of affection(尺取+预处理)
    旅游(CSUST省赛选拔赛2+状压dp+最短路)
    Islands and Bridges(POJ2288+状压dp+Hamilton 回路)
    Travelling(HDU3001+状压dp+三进制+最短路)
    Hie with the Pie(POJ3311+floyd+状压dp+TSP问题dp解法)
    hash(2018年CSUST省赛选拔赛第一场B题+hash+字典树)
    Everything Has Changed(HDU6354+圆交+求周长)
    [iOS Animation]-CALayer 图层几何学
    [iOS Animation]-CALayer 显示方式
    [iOS Animation]-CALayer 图层树
  • 原文地址:https://www.cnblogs.com/HuangYJ/p/14194209.html
Copyright © 2011-2022 走看看