随想录训练营Day29 | 回溯 491.递增子序列, 46. 全排列, 47. 全排列2

62 阅读2分钟

随想录训练营Day29 | 回溯 491.递增子序列, 46. 全排列, 47. 全排列2

标签: LeetCode闯关记


491.递增子序列

错解: 直接用的Boolean[] used 来判断,甚至没有sort

 if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true
                    || path.size() != 0 && nums[i] < path.getLast()){
                continue;
            }
            path.add(nums[i]);
            used[i] = true;

此题注意点

  • 思路: 去重(数层去重但要求不改变集合排列的顺序, cf:90.子集II) ---- 巧妙利用int[] used 数组+ 判断是否递增
  • 题目的重点: 集合中有重复元素, 找子序列即不改变排列顺序
class Solution {
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
    find(nums, 0);
    return res;
}
private void find(int[] nums, int startIndex){
    if(path.size() > 1){//注意res.add和重构used在for循环之前
        res.add(new ArrayList<>(path));
    }
    int[] used = new  int[201];//used[nums[i] + 100] 的作用是记录 nums[i] 是否已经被使用过。由于题目中给定了 -100 <= nums[i] <= 100,所以为了防止数组下标为负数,要将 nums[i] 的值加上 100 后再存储到 used 数组中。因此,当 used[nums[i] + 100] == 1 时,表示 nums[i] 已经被使用过,可以跳过当前循环。
    for (int i = startIndex; i < nums.length; i++) {
        if(i > 0 && used[nums[i] + 100] == 1 || !path.isEmpty() && nums[i] < path.getLast()){//注意i > 0 && used[nums[i] + 100] == 1 中不能将i > 0改为!path.isEmpty();因为当path为空时,即还没开始取第一个数放入path中时,但是i > 0
            //时,仍然需要比较同层的值是否被使用过
            continue;
        }
        used[nums[i] + 100] = 1;//注意思考used的作用域, 在每一次for循环记录,所以每次for循环之前会重新初始化,在for循环中不会被删除;
        path.add(nums[i]);
        find(nums, i + 1);
        path.removeLast();
    }
}
}

用时: 1h12min

46.全排列

重点: 用used数组来区别是否在同一根树枝上已经使用; for循环 i 的起始位置为0; 区别: 无重复元素---- means 无需数层去重

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> permute(int[] nums) {
        boolean[] used = new boolean[nums.length];//key 类比startIndex的作用,但有区别
        Arrays.fill(used, false);
        findPermute(nums,used);
        return res;
    }
    private void findPermute(int[] nums, boolean[] used){
        if(path.size() == nums.length){
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++) {//key: i = 0 instead of i = startIndex;
            if(used[i] == true){
                continue;
            }
            path.add(nums[i]);
            used[i] = true;
            findPermute(nums,used);
            path.removeLast();
            used[i] = false;
        }
    }
}

用时: 20min

47.全排列 II

思路: 在46.全排列 的基础上,加上数层去重(去重思路见491.递增子序列, 用 int[] used_breadth 记录被使用过的数值, 但是做复杂了,因为此题的集合中的排列顺序可以被打乱,既可以用40.组合总和II、90.子集II 的sort排序 套路 ) 个人AC码:

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        boolean[] used_deep =new boolean[nums.length];
        Arrays.fill(used_deep,false);
        findUnique(nums, used_deep);
        return res;


    }
    private void findUnique(int[] nums, boolean[] used_deep){
        int[] used_breadth = new int[21];
        if(path.size() == nums.length){
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if(used_deep[i] == true ){
                continue;
            }
            if(i > 0 && used_breadth[nums[i] + 10] == 1){/*犯错误地方: 写成了 if(!path.isEmpty() && used_breadth[nums[i] + 10] == 1){continue; } //注意i > 0 && used[nums[i] + 10] == 1 中不能将i > 0改为!path.isEmpty();因为当path为空时,即还没开始取第一个数放入path中时,但是i > 0
            时,仍然需要比较同层的值是否被使用过, 会造成测试用例:[1,1,2]
	测试结果:[[1,1,2],[1,2,1],[1,1,2],[1,2,1],[2,1,1],[2,1,1]]
	期望结果:[[1,1,2],[1,2,1],[2,1,1]],"1"被重复取*/
                continue;
            }
            path.add(nums[i]);
            used_breadth[nums[i] + 10] = 1;
            used_deep[i] = true;
            findUnique(nums, used_deep);
            used_deep[i] = false;
            path.removeLast();
        }
    }

}

待看随想录的优化思路......

用时: 50min