剑指offer-回溯-深度优先搜索+剪枝

1.打印从1到最大的n位数

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]

题目链接:力扣

分析:

1)int最大用32位字节表示,本题没有说明n的范围,所以极可能超出int数组的范围,所以转化为字符输出。

2)生成数字的字符串集:当输入为n时,想当于n位上0-9的全排列,先确定最高位,后面的n-1位全排列。

 

3)上面的全排列存在两个问题

  • 数一开始是从0开始的
  • 数的高位有多余的0;这时需要利用字符的截取(是因为当进行递归时,产生的字符的数组,通过方法将数组转化为字符串),获得不含高位是0的数;当每次递归遇到9时,就会产生进位,此时高位就少一个0;所以用nine从0开始统计9的数量,start是从数组的第n-1位开始截取的。当n-nine ==start,此时,start--,使截取字符数量减少。   
  •      下面的nine--;不太明白;但是举例n=2可以理解,当个位第一次出现9时加        
class Solution {
    //根据n确定一个数的对应字符的长度-》对应的字符数组
    char[] num;
    StringBuffer res;//定义结果
    int n,start,nine;//数组长度;截取字符的下标;9的数量
    char[] loop ={'0','1','2','3','4','5','6','7','8','9'};//用于递归的字符串
    public int[] printNumbers(int n) {
        //初始化属性
        this.n = n;
        res = new StringBuffer();
        start = n-1;
        nine =0;
        num = new char[n];
        df(0);//从最高位索引位置开始递归
        res.deleteCharAt(res.length()-1);//删除最后一个逗号
        return res.toString;
        
    }
    //递归开始,没有返回值,因为res在类属性中定义可以直接用
    void dfs(int x){
        //退出方法的条件
        if(x==n){
            //此时说明字符数组索引为n-1的已经安排好了
            //所以此时,x是从0开始的
            string s = String.valueOf(num).subString(start);
            if(!s.equals("0")) res.append(s+’,);//这里字符串重写了方法,就判断两个值是否相等


            if(n-nine == start) start--;
            return;
        }

        //一位位递归
        //这里递归完一位数才会执行下面的nine--;
        for(char i :loop){
            num[x] = i;//高位的值确定了,所以要递归数组确定下一位
            dfs(x+1);//比如上面是确定num[0],这个递归确定num[1],从0到9字符中选择
            //当i每次递归到9时,说明后面的值要进位,但当这个循环结束时,把nine恢复到原来的值
            if(i=='9') nine++;
        }
        nine--;//在回溯前让nine恢复到之前的状态
    }
}

2.输入一个字符串,打印出该字符串中字符的所有排列。

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

题目链接:力扣

class Solution {
    char[] chars;
    List<String> res = new ArrayList<>();
    public String[] permutation(String s) {
       chars = s.toCharArray();
      
       dfs(0);
       return res.toArray(new String[res.size()]);

    }
    //x是要固定的元素索引
    void dfs(int x){
        //回溯条件:x递归到最后一个元素时;因为交换是影响的两个元素,所以此时,x是长度减一
        if(x==chars.length-1) {
            //将字符数组转为字符串
            res.add(String.valueOf(chars));
            return;
        } 
        HashSet<Character> s = new HashSet<>();
        for(int i=x;i<chars.length;i++){
            //将这位上的元素加入set,当有重复元素时方便剪枝
            if(s.contains(chars[i])) continue;
            s.add(chars[i]);
            swap(i,x);//固定第x位的元素
            dfs(x+1);//固定第x+1位元素
            swap(x,i);//恢复位置,等进入for循环确保顺序不变
        }
    }
    void swap(int i,int j){
        char tem= chars[i];
        chars[i]=chars[j];
        chars[j]=tem;
    }
}

3.现在有一个只包含数字的字符串,将该字符串转化成IP地址的形式,返回所有可能的情况。

例如:

给出的字符串为"25525522135",

返回["255.255.22.135", "255.255.221.35"]. (顺序没有关系)

数据范围:字符串长度 

要求:空间复杂度 0(n),时间复杂度 o(n)

注意:ip地址是由四段数字组成的数字序列,格式如 "x.x.x.x",其中 x 的范围应当是 [0,255]。

题目链接:力扣

方法一:隔板法(确定三个隔板存放的位置,之后在进行筛选合适的位置)

class Solution {
    List<String> res = new ArrayList<>();
    public List<String> restoreIpAddresses(String s) {
        //暴力:先一位位确定,当长度正好是12时,加入数组中
        //判断条件直接不符的情况(4,12)
        if(s == null || s.length()<4 || s.length()>12){
            return res;
        }
        
        
        //隔板法确定板的所有位置,在进行筛选
        int len = s.length();
        //这里substring,截取包括前面元素,不包括后面元素
        for(int i =1;i<len-2;i++){//第一块板的活动位置,最后三个位置不能取(三个值有两个空),留给剩余两个板
            for(int j =i+1;j<len-1;j++){//第二个板,不能取最后两个位置(还有一个空,)留给最后一个板
                for(int n =j+1;n<len;n++){
                    //对各个隔板的情况检查
                    if(check(s,i,j,n)){
                        StringBuffer r = new StringBuffer();
                        r.append(s.substring(0,i)+".");
                        r.append(s.substring(i,j)+".");
                        r.append(s.substring(j,n)+".");
                        r.append(s.substring(n));
                        res.add(r.toString());
                    }
                }

            }

        }
        
        return res;
    }

    public boolean check(String s,int i,int j,int m){
        String a = s.substring(0,i);
        //判断截取的值是否在0-255之间
        if(a.length()>1 && a.charAt(0) =='0' || Integer.parseInt(a)>255){
            return false;
        }
        String b = s.substring(i,j);
        if(b.length()>1 && b.charAt(0) =='0' || Integer.parseInt(b)>255){
            return false;
        }
        String c = s.substring(j,m);
        if(c.length()>1 && c.charAt(0) =='0' || Integer.parseInt(c)>255){
            return false;
        }
        String d = s.substring(m);
        if(d.length()>1 && d.charAt(0) =='0' || Integer.parseInt(d)>255){
            return false;
        }

        return true;
    }
}

方法二:回溯

class Solution {
    List<String> res = new ArrayList<>();
    public List<String> restoreIpAddresses(String s) {
        if (s.length() < 4 || s.length() > 12)  {
            return new ArrayList<>();
        }
        String str = "";
        dfs(s,4,0,str);
        return res;
    }
    public void dfs(String s,int cnt,int i,String str) {
        if (cnt == 0 && i == s.length()) {
            // 去掉最后一个'.'位置
            res.add(str.substring(0,str.length() - 1));
            return;
        }
        for (int step=1;step<=3;++step){
            // 条件判断
            /**
            1.如果当前网段长度大于1 但是前缀为0,则这个位置不可用
            2.如果当前索引大于字符串长度,且仍处于递归过程中,表示不可用
            3.如果当前网断的长度为3,但是大于255 则网段非法 不可用
             */
            if ((step!=1 && s.charAt(i) == '0') || (step+i>s.length()) || (step==3&&s.substring(i,i+step).compareTo("255")>0)) return;
            dfs(s,cnt-1,i+step,str+s.substring(i,i+step) + ".");
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值