leetcode之回溯backtracing专题2

46 Permutations

 输入一个不重复的数组 ,写出这个数组的排列,不能重复。
 例如 输入nums=[1,2,3],输出
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

 思路:可以看到 第0位(从左开始数)有nums.length个数字可以选择:1,2,3,在第0位确定以后,第1位有nums.length-1个数字可以选择,例如p[0] = 1,p[1]只可能为2 或者3。所以重要的是记录哪些下标的元素已经被选择过。

public class Solution {
    private boolean[] p;//记录选中过的下标
    private List<List<Integer>> list;
    public List<List<Integer>> permute(int[] nums) {
        int[] newarray=new int[nums.length];//存放选中的数字
        p = new boolean[nums.length];
        list = new ArrayList<List<Integer>>();
        robot(0,newarray,nums);
        return list;
    }

    public void robot(int idx,int[] newarray,int[] nums){
        if(idx>=nums.length){
            List<Integer> l = new ArrayList<Integer>();
            for(int i=0;i<newarray.length;i++){
                l.add(newarray[i]);
            }
            list.add(l);
            return;
        }
        for(int i=0;i<nums.length;i++){
            if(p[i]==false){
                newarray[idx]=nums[i];
                p[i]=true;
                robot(idx+1,newarray,nums);
                p[i]=false;
            }
        }
    }
}

47 Permutations II

 输入一个重复的数组 ,写出这个数组的排列,不能重复。
 例如输入 [1,1,2] 输出
[
[1,1,2],
[1,2,1],
[2,1,1]
]
 思路:这与46的区别是输入的数组中有重复的数字。如果按照46的思路做,会出现重复的结果。我们先按照46的来做一下吧。用”数字(下标)”这种方式表示数组元素:1(0) 1(1) 2(2)。这样才能把相同的数字区分开来。为了方便,我们先将输入的数组排序。按照46的思路得到以下组合:
1(0) 1(1) 2(2)
1(0) 2(2) 1(1)
1(1) 1(0) 2(2)
1(1) 2(2) 1(0)
2(2) 1(0) 1(1)
2(2) 1(1) 1(0)
 重点标记的是重复的元素。分析一下。第0位可以选择的元素有 nums[0], nums[1], nums[2],但是当已经选择nums[0]之后,再遇到nums[1]=nums[0]的时候nums[1]是不能选择的,否则就重复了。所以第0位可以选择的元素有 nums[0], nums[2]。每一位都是,判断nums[idx]能不能选择标准是nums[idx-1]=nums[idx]是否为true。
这个判断怎么加呢?下面记录一下我自己犯过的错。

处理情况1

for(int i=0;i<nums.length;i++){
          if(p[i]==false){
              newarray[idx]=nums[i];
              p[i]=true;
              robot(idx+1,newarray,nums);
              p[i]=false;
              for (int j = i + 1; j < nums.length; j++){
                  if (nums[j] != nums[j - 1]) {
                      i = j - 1;
                      break;
                  }
              }
          }

      }  

 每到一个位置,先选择一个元素,再处理下一个元素和前面的元素是否相同。这里判断的时候没有考虑前面的元素是否会选中,可能会有多余的操作。更重要的是没有处理如果重复元素是最后一个元素怎么处理。失败用例:[1,1]。
 

    for(int i=0;i<nums.length;i++){
            if(p[i]==false){
                newarray[idx]=nums[i];
                p[i]=true;
                robot(idx+1,newarray,nums);
                p[i]=false;
                int j = i + 1;
                for (; j < nums.length; j++){
                    if (nums[j] != nums[j - 1]) {
                        i = j - 1;
                        break;
                    }
                }
                if(j==nums.length){
                    break;
                }
            }

        }

 改成这样就对了。但是这样代码不够优雅。

处理情况2

    for(int i=0;i<nums.length;i++){
            if(p[i]==false && (i==0 || (nums[i]!=newarray[idx]))){
                newarray[idx]=nums[i];
                p[i]=true;
                robot(idx+1,newarray,nums);
                p[i]=false;
            }
        }  

 因为newarray[idx]记录了上一次选择的值,所以想到只要判断这次的候选值nums[i]和newarray[idx]不同就好了。这次的错误是因为i==0的判断是有误的。因为不一定每次都是nums[0]是第一个被选中的。例如当第0位选择nums[0],在选第2位的时候第一个被选中的一定是nums[1] ,所以这里应该用一个boolean变量表示本次是不是对idx已经选过一个值了。
 

        boolean selected = false;
        for(int i=0;i<nums.length;i++){
            if(p[i]==false && (!selected || (nums[i]!=newarray[idx]))){
                selected = true;
                newarray[idx]=nums[i];
                p[i]=true;
                robot(idx+1,newarray,nums);
                p[i]=false;
            }
        }  

处理情况3

 接着我还用了一种方式处理。使用了一个单独的变量记录在选择idx的候选值时候,上一个选择的值。

 int preNum = -1;
 boolean selected = false;
 for(int i=0;i<nums.length;i++){
     if(p[i]==false && (selected==false || (nums[i]!=preNum))){
         selected = true;
         preNum = nums[i];
         newarray[idx]=nums[i];
         p[i]=true;
         robot(idx+1,newarray,nums);
         p[i]=false;
     }
 }   

 总结:大概可以在编程的时候行不通。一定是一定可以才可以。在backtracing中最重要的是理解每一步的状态。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值