day26 47. 全排列 II&&46. 全排列&&491. 非递减子序列

    1. 全排列 II
      问题描述
      给定一个可能包含重复数字的数组 nums,生成所有唯一的排列。
      代码逻辑
      初始化:
      对数组进行排序(Arrays.sort(nums)),以便后续去重。
      使用 List<List> res 存储最终结果。
      使用 LinkedList path 存储当前路径。
      使用布尔数组 used 标记数组中的数字是否被使用。
      递归函数 dfsPermuteUnique:
      如果 path 的长度等于数组长度,说明已经生成一个完整的排列,将其加入结果集。
      遍历数组:
      如果当前数字已经被使用(used[i]),跳过。
      如果当前数字与下一个数字相同且下一个数字未被使用(nums[i] == nums[i + 1] && !used[i + 1]),跳过(避免重复排列)。
      将当前数字加入路径(path.add(nums[i])),标记为已使用(used[i] = true)。
      递归调用 dfsPermuteUnique。
      回溯:移除路径中的最后一个数字(path.removeLast()),恢复未使用状态(used[i] = false)。
      关键点
      排序:通过排序确保相同的数字相邻,便于去重。
      去重条件:nums[i] == nums[i + 1] && !used[i + 1] 是核心去重逻辑,确保只选择第一个未被使用的重复数字。
点击查看代码
//47. 全排列 II
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        boolean[] used = new boolean[nums.length];//布尔型默认值为false
        List<List<Integer>> res = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        dfsPermuteUnique(nums,used,res,path);
        return res;
    }
    private void dfsPermuteUnique(int[] nums,boolean[] used ,List<List<Integer>> res, LinkedList<Integer> path) {
        if (path.size() == nums.length) {
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i=0;i<nums.length;i++){
            if (used[i]) continue;
            if(i+1<nums.length&&nums[i]==nums[i+1]&&!used[i+1]) continue;//之所以加上&&!used[i+1]是确保这后面那个元素是它可以取到的
            path.add(nums[i]);
            used[i] = true;//表示已用
            dfsPermuteUnique(nums,used, res, path);
            path.removeLast();
            used[i] = false;//还原为未用状态
        }
    }
    1. 全排列
      问题描述
      给定一个不包含重复数字的数组 nums,生成所有可能的排列。
      代码逻辑
      初始化:
      使用 List<List> res 存储最终结果。
      使用 LinkedList path 存储当前路径。
      使用布尔数组 used 标记数组中的数字是否被使用。
      递归函数 dfsPermute:
      如果 path 的长度等于数组长度,说明已经生成一个完整的排列,将其加入结果集。
      遍历数组:
      如果当前数字已经被使用(used[i]),跳过。
      将当前数字加入路径(path.add(nums[i])),标记为已使用(used[i] = true)。
      递归调用 dfsPermute。
      回溯:移除路径中的最后一个数字(path.removeLast()),恢复未使用状态(used[i] = false)。
      关键点
      布尔数组 used:用于标记数组中的数字是否被使用,避免重复选择同一个数字。
      递归终止条件:当路径长度等于数组长度时,生成一个完整的排列。
      优化建议
      由于数组中没有重复数字,不需要额外的去重操作。
      可以通过剪枝减少不必要的计算。
      总结
      这是一个经典的回溯问题,通过布尔数组标记使用状态,生成所有可能的排列。
      难点在于如何通过递归和回溯生成所有排列,布尔数组是解决此问题的关键。
点击查看代码
//46. 全排列
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        boolean[] used = new boolean[nums.length];
        dfsPermute(nums,used,res,path);
        return res;
    }
    private void dfsPermute(int[] nums, boolean[] used,List<List<Integer>> res, LinkedList<Integer> path) {
        if (path.size() == nums.length) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            //if (path.contains(nums[i])) continue;两种判断方式都可以,下面那个更具普适性
            if (used[i]) continue;//用过的不可取,跳过
            path.add(nums[i]);
            used[i] = true;//表示已用
            dfsPermute(nums,used ,res, path);
            path.removeLast();
            used[i] = false;//还原为未用状态
        }
    }

    1. 非递减子序列
      问题描述
      给定一个整数数组 nums,找出所有长度至少为2的非递减子序列。数组中的元素顺序不能改变,且结果中不能有重复的子序列。
      代码逻辑
      初始化:
      使用 List<List> res 存储最终结果。
      使用 LinkedList path 存储当前路径(即当前构建的子序列)。
      从索引 start 开始递归搜索。
      递归函数 dfsFindSubsequences:
      如果 path 的长度大于1,则将当前路径加入结果集(res.add(new ArrayList<>(path)))。
      如果 start 达到数组末尾,返回。
      使用 ArrayList used 记录当前层级已经使用过的数字,避免重复。
      遍历数组:
      如果当前数字小于 path 的最后一个数字(nums[i] < path.peekLast()),跳过。
      如果当前数字已经在当前层级使用过(used.contains(nums[i])),跳过。
      将当前数字加入路径(path.add(nums[i])),标记为已使用(used.add(nums[i]))。
      递归调用 dfsFindSubsequences,从下一个索引开始。
      回溯:移除路径中的最后一个数字(path.removeLast())。
      关键点
      非递减条件:通过 nums[i] >= path.peekLast() 确保子序列非递减。
      去重:通过 used 集合记录当前层级已经使用过的数字,避免重复子序列。
      优化建议
      used.contains(nums[i]) 使用了线性查找,可以改用 HashSet,将查找时间复杂度从 O(n) 优化到 O(1)。
      总结
      这是一个典型的回溯问题,通过递归和回溯生成所有可能的子序列,并通过条件判断和去重操作确保结果符合要求。
      难点在于如何处理重复子序列,使用集合记录当前层级的数字是一种有效的去重方法。
点击查看代码
//491. 非递减子序列
    public List<List<Integer>> findSubsequences(int[] nums) {
        //Arrays.sort(nums);不能改变数组中元素顺序
        List<List<Integer>> res = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        dfsFindSubsequences(nums,res,path,0);
        return res;
    }
    private void dfsFindSubsequences(int[] nums,List<List<Integer>> res,LinkedList<Integer> path ,int start){
        if (path.size()>1) res.add(new ArrayList<>(path));
        if(start==nums.length) return;
        ArrayList<Integer> used =new ArrayList<>();
        for(int i=start;i<nums.length;i++){
            //if (i>start && nums[i]==nums[i-1]) continue;不是有序的就不能这样去重了,只能弄个集合来记录这一层级已经使用过的数字
            if ((!path.isEmpty()&&nums[i]<path.peekLast())||used.contains(nums[i])) continue;
            path.add(nums[i]);
            used.add(nums[i]);
            dfsFindSubsequences(nums,res,path,i+1);
            path.removeLast();
        }
    }
posted @ 2025-02-17 17:38  123木头人-10086  阅读(32)  评论(0)    收藏  举报