带重复元素以及不带重复元素的全排列

被一块石头绊倒两次是真的丢人——不写引子不舒服斯基

递归思想

递归的思想中有几个很重要的特性,对于使用递归求解的问题,把握好这几个因素就能把代码写好了。
1. 终止条件。递归的方法有一个最终的终止条件,这个条件满足题目所提出的要求,而且不会无限的循环下去。
2. 子问题。问题中的子问题可以再次递归调用方法求解。这就是分治思想的运用了。在没有满足返回条件之前,每一步和前一步都只是在状态上有不同。

不带重复元素的

题目

给定一个数字列表,返回其所有可能的排列。

注意事项

你可以假设没有重复数字。

样例

给出一个列表[1,2,3],其全排列为:

[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]
解题思路

不带重复元素这个题是很早之前做的了。思路也大概就是写一个递归方法,在方法中判断是否已经全排列,是的话集合加一并返回,不是的话就循环。

很简单的递归全排列思路,甚至可以直接点地记下来。

public class Permute {
    public static List<List<Integer>> permute(int[] nums){
        List<List<Integer>> res = new ArrayList<>();
        ArrayList<Integer> list = new ArrayList<>();

        per(res,list,nums);

        return res;
    }

    public static void per(List res,List list,int[] nums){

        if(list.size() == nums.length){
            res.add(new ArrayList(list));
            return ;
        }

        for(int i = 0;i < nums.length; i++){
            if(list.contains(nums[i]))
                continue;
            list.add(nums[i]);
            per(res,list,nums);
            list.remove(list.size()-1);
        }

    }
}

带重复元素的

题目

给出一个具有重复数字的列表,找出列表所有不同的排列。

给出列表 [1,2,2],不同的排列有:

[
  [1,2,2],
  [2,1,2],
  [2,2,1]
]
递归解法

和上面的解法基本上一样,只是要多加一个visited数组来记录这个数据在这一次逐渐深入的递归中是否被使用过,递归返回时改回未被使用。因为允许列表元素重复,所以进行一次普通的全排列后,可能会出现两个相同的排列,为了避免出现重复的排列,所以要进行一次contains的判断。

public class PermuteUnique {

    public List<List<Integer>> permuteUnique(int[] nums){
        List<List<Integer>> ret = new ArrayList<>();
        List<Integer> temp = new ArrayList<>();

        int[] visited = new int[nums.length];
        permute(0,nums.length,ret,temp,nums,visited);

        return ret;
    }

    /**
     * 用来做递归的子方法,可以当模版参考
     * 
     * @param k 当前长度
     * @param n 每个的长度,nums.length
     * @param ret 返回的那个list
     * @param temp 每一次递归用的list
     * @param nums 初始数组
     * @param visited 元素是否被访问过
     */
    public void permute(int k,int n,List ret,List temp,int[] nums,int[] visited){
        if(k == n && !ret.contains(temp)){
            ret.add(new ArrayList(temp));
        }
        if(k > n){
            return;
        }
        else{
            for(int i = 0;i < n;i++){
                int pre = nums[i];
                if(visited[i] == 0){
                    temp.add(pre);
                    visited[i] = 1;
                    permute(k + 1,n,ret,temp,nums,visited);
                    visited[i] = 0;
                    temp.remove(temp.size() - 1);
                }
            }
        }
    }
}

这一份代码中还有一些地方是可以优化的,比如那个参数n就是完全多于的东西,按照上一份代码其实这个参数是可以不用的,包括k也是,明明就是一个list.size的事,但是当时写代码的时候本着跑起来再说的想法,没有考虑这些问题,虽然之后发现了,但是本着提醒自己的想法,就把没改的版本贴到了这个博客中。

非递归解法

这个,偷个懒,以后再说。

指针问题

说到指针,按理说学了这么几年已经不应该再在这种事上糊涂了,但是还是犯了错,做之前全排列的那个题时愣是没想通,好在今天做这个题的时候反应过来了,所以借此机会记录一下。

上面的递归代码中,当排序好的List要放入总的集合中时,之前的我是这样操作的。

if(k == n && !ret.contains(temp)){
    //stupid
    ret.add(temp);

用这种add,执行后你会发现结果会大错特错。而应该换用:

if(k == n && !ret.contains(temp)){
    ret.add(new ArrayList(temp));
}

对,没错。

使用上面那个错误操作所存进集合中的并不是一个对象,而是指向这个List对象的指针。这就引起了问题:add进集合中的永远是同一个元素,而且,在带重复元素这道题中,需要进行一次是否已经contains的判断,这个会一直为true,让你甚至连重复元素都加不进去。写代码验证一下这个想法:

List<List<Integer>> ret = new ArrayList<>();
List<Integer> temp = new ArrayList<>();

temp.add(5);
ret.add(temp);
System.out.println(ret.contains(temp));
temp.add(7);
System.out.println(ret.contains(temp));
ret.add(temp);

Iterator it = ret.iterator();
while(it.hasNext()){
    System.out.println(it.next());
}

执行结果为:

true
true
[5, 7]
[5, 7]

证明确实如此。所以每次进行add操作时,要new一个新的对象用来存放已经排好序的部分。

整体感想

多看,多写,多总结。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值