【leetCode】全排列问题,不可重复+可重复

本文详细介绍了如何使用回溯算法解决全排列和全排列II问题,包括无重复数字和包含重复数字的情况。通过示例代码展示了在Java中如何实现这两种情况的全排列,并解释了如何通过剪枝避免重复,特别是在处理重复数字时,通过排序和状态跟踪来优化算法,确保每个数字仅被填充一次。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

思路:回溯算法的方式进行寻找数字,只需要一个arraylist 就可以,不过记住在进行复制的时候及进行深拷贝,不然都是一个引用,呜呜!
对于可重复的情况下,要在进行回溯之前进行剪枝,想到这个问题的本质的问题。

46. 全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

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

47. 全排列 II

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

示例:

输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
在这里插入图片描述

第一次写的比较复杂

class Solution {
     public  List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> ans=new ArrayList<>();
        List<Integer> single=new ArrayList<>();
        ans= findpermute(ans,nums,single);
        return ans;
    }
    private  List<List<Integer>> findpermute(List<List<Integer>> ans, int[] nums,List<Integer> single) {
        for (int i = 0; i < nums.length; i++) {
            if (! single.contains(i)){
                single.add(i);
                if (single.size()==nums.length){
                    List<Integer> temp= new ArrayList<>();
                    for (int i1 = 0; i1 < single.size(); i1++) {
                        temp.add(nums[single.get(i1)]);
                    }
                    ans.add(temp);
                }else {
                    ans= findpermute(ans, nums,single);
                }
                single.remove(single.indexOf(i));
            }
        }
        return ans;
    }
}

修改之后是这个样子

可以直接使用增强for ,而且直接在方法一开始做判断;最重要的是拷贝的时候直接使用
new ArrayList<>(single)

 public static List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> ans=new ArrayList<>();
        findpermute(ans,nums,new ArrayList<>());
        return ans;
    }
    private static  void findpermute(List<List<Integer>> ans, int[] nums,List<Integer> single) {
        if (single.size()==nums.length) ans.add(new ArrayList<>(single));
        for (int num : nums) {
            if (!single.contains(num)) {
                single.add(num);  // 动态维护数组
                findpermute(ans, nums, single); // 继续递归填下一个数
                single.remove((Integer) num);   // 撤销操作,回溯
            }
        }
    }

可重复全排列

第一次自己的想法写的,超时,思考一下有没有什么剪枝的方法
在这里插入图片描述

    public static List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> ans=new ArrayList<>();
        findpermuteUnique(ans,nums,new ArrayList<>());
        return ans;
    }
    private static  void findpermuteUnique(List<List<Integer>> ans, int[] nums,List<Integer> single) {
        if (single.size()==nums.length)
        {
            List<Integer> temp=new ArrayList<>();
            for(int i=0;i<single.size();i++) temp.add(nums[single.get(i)]);
            int flag=0;
            for (List<Integer> an : ans){
                flag=0;
                for (int i = 0; i < an.size(); i++) if (an.get(i).equals(temp.get(i))) flag++;
                if (flag==temp.size()) break;
            }
            if (flag!=temp.size()) ans.add(temp);
        }
        for (int i = 0; i < nums.length; i++) {
            if (!single.contains(i)) {
                single.add(i);  // 动态维护数组
                findpermuteUnique(ans, nums, single); // 继续递归填下一个数
                single.remove((Integer) i);   // 撤销操作,回溯
            }
        }
    }

要解决重复问题,我们只要设定一个规则,保证在填第 idx 个数的时候重复数字只会被填入一次即可。而在本题解中,我们选择对原数组排序,保证相同的数字都相邻,然后每次填入的数一定是这个数所在重复数集合中「从左往右第一个未被填过的数字」,即如下的判断条件:

    if(vis[i] || i>0 && nums[i-1]==nums[i] && vis[i-1]) continue;

这个判断条件保证了对于重复数的集合,一定是从左往右逐个填入的。

假设我们有 33 个重复数排完序后相邻,那么我们一定保证每次都是拿从左往右第一个未被填过的数字,即整个数组的状态其实是保证了 [未填入,未填入,未填入] 到 [填入,未填入,未填入],再到 [填入,填入,未填入],最后到 [填入,填入,填入] 的过程的,因此可以达到去重的目标。

   private static boolean[] vis ;
    public static List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> ans=new ArrayList<>();
        vis=new boolean[nums.length];
        List<Integer> perm=new ArrayList<>();
        Arrays.sort(nums);
        findpermuteUnique(ans,0,nums,perm);
        return ans;
    }

    private static void findpermuteUnique(List<List<Integer>> ans,int idx, int[] nums,List<Integer> perm) {
        if(idx == nums.length){
            ans.add(new ArrayList<>(perm));
        }
        for (int i = 0; i < nums.length; i++) {
            if(vis[i] || i>0 && nums[i-1]==nums[i] && vis[i-1]) continue;
            perm.add(nums[i]);
            vis[i]=true;
            findpermuteUnique(ans,idx+1,nums,perm);
            perm.remove(idx);  //移除的是序号
            vis[i]=false;
        }
    }

### LeetCode 全排列问题解析 #### 一、解题思路概述 全排列问题是给定一个不含重复数字的数组 `nums`,返回这些数字能组成的全部不同排列。对于长度为 \( n \) 的数组,共有 \( n! \) 种不同的排列方式。 为了生成所有的排列组合,可以采用回溯算法(Backtracking),这是一种通过构建所有可能的选择树并逐步探索每一条路径的方法。每当找到一组完整的排列时就将其加入到结果集中[^1]。 具体来说,在每次递归调用中都会尝试将当前元素放置于当前位置,并继续处理剩余部分直到完成整个序列;如果发现某条分支无法形成有效解,则立即停止该方向上的进一步搜索而转向其他可能性[^2]。 此外,由于题目条件指出输入列表中的数值范围以及唯一性约束(-10 <= nums[i] <= 10),这使得我们可以放心地利用交换操作而不必担心越界或重复访问同一个位置的情况发生[^3]。 #### 二、Python 实现代码示例 下面是一段基于上述思想编写的 Python 版本实现: ```python def permute(nums): result = [] def backtrack(start=0): if start == len(nums): result.append(nums[:]) # 当前排列已完成 for i in range(start, len(nums)): nums[start], nums[i] = nums[i], nums[start] # 交换起始位与其他各位置字符 backtrack(start + 1) # 进入下一层决策树节点 nums[start], nums[i] = nums[i], nums[start] # 恢复原状以便后续迭代使用 backtrack() return result ``` 这段程序定义了一个名为 `permute` 函数用于接收参数 `nums` 并最终输出其所有可能的不同排列形式。内部辅助函数 `backtrack()` 负责执行实际的回溯逻辑,它接受一个可选参数表示当前正在考虑的位置索引,默认从第一个元素开始处理。随着递归深入,会逐渐固定住前面若干个位置上的值直至遍历完所有选项为止[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

和你在一起^_^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值