算法学习-Day17-暴力递归

暴力递归在leetcode刷题时,虽然经常超时,毕竟递归压栈比较耗时,但是暴力递归可以让我们完成算法上的进阶,许多我们不会的算法题,通过暴力递归都可以做出来,而且,这是一种承上启下的做法,之后,暴力递归可以进化成动态规划,所以,学习暴力递归也是很重要的。

题目1:汉诺塔问题

有n个编号从1到n到圆盘,按从小到大的顺序依次排列在start柱上,要求将圆盘移到end柱上,且顺序不变,另外提供other柱,请输出移动方式。

思想:对于汉诺塔问题,我们会容易想到,可以想将1——n-1的圆盘放在other柱上,然后将n圆盘放在end柱上,这样,第n个圆盘就放好了,接下来,我们将n-1号圆盘也放到end柱上就可以了,所以,递归的主题就找到了,至于base-case,也就是最后一个圆盘,直接放到end柱就可以了。

public static void recursion(int n,String start,String end,String other){
    if(n == 1){
        System.out.println("Move 1 from " + start + " to " + end);
    }else {
        //将1——n-1的圆盘先移到other柱上
        recursion(n-1,start,other,end);
        //将n的圆盘移到end上
        System.out.println("Move " + n +" from " + start + " to " + end);
        //将1——n-1的圆盘从other移到end上
        recursion(n-1,other,end,start);
    }
}

题目2:输出字符串的子串

所谓子串,就是在字符串任意位置的字符都选择要或者不要,最后输出的字符串,例如:‘abc’的子串为'a'、'b'、'c'、'ab'、'ac'、'bc'、'abc'、''。

思想:假设1——i-1的字符都已经选好了,对于i位置的字符,我们可以选或者不选,然后交给i+1位置,对于base-case,也就是i等于字符串的长度时,我们可以直接打印出来(或者拿个数组收集)

public static void recursion(char[] str,int i,List res){
    if(i == str.length){
        //到了最后
        System.out.println(listToString(res));
        return;
    }
    //当前来到了i位置,要不要当前字符两条路
    List resKeep = copyList(res);
    resKeep.add(str[i]);
    recursion(str,i+1,resKeep);
    List resNoInclude = copyList(res);
    recursion(str,i+1,resNoInclude);
}
public static String listToString(List<Character> charList) {
        StringBuilder stringBuilder = new StringBuilder();
        for (Character c : charList) {
            stringBuilder.append(c);
        }
        return stringBuilder.toString();
    }
public static List<Character> copyList(List<Character> list){
        return new ArrayList<>(list);
    }

题目3:输出字符串可能的排列

例子:字符串'abc',可能的排列是'abc'、'acb'、'bac'、'bca'、'cba'、'cab'

思想:假设1——n-1位置的字符都已经选好了,那么,n位置的字符可以是n——str.length,但要注意的是,如果当前位置的字符和之后某一位置的字符相同,那么交换之后的字符串是相同的,相当于又走了一遍,所以,我们要将这种情况排除,这就是分支限界。

public static void recursion(char[] chr, int i, ArrayList res) {
    if (i == chr.length) {
        res.add(String.valueOf(chr));
    }
    //去除重复
    boolean[] visit = new boolean[26];
    for (int j = i; j < chr.length; j++) {
        //只有未交换过的字母才会进行递归,例如下面是aa,第一个字母交换过之后,第二个字母就不必进行下去,因为是重复的
        if (!visit[chr[j] - 'a']) {
            visit[chr[j] - 'a'] = true;
            //按顺序交换i和j位置的字符(选择下一个字符)
            swap(chr, i, j);
            recursion(chr, i + 1, res);
            //将字符还原
            swap(chr, i, j);
        }
    }
}
public static void swap(char[] chr,int i,int j){
    char temp = chr[i];
    chr[i] = chr[j];
    chr[j] = temp;
}

题目4:选积分

给定一个整形数组,A和B两个人一次只能选择一个作为他的积分,且只能从第一个和最后一个中选,返回积分最多的人的积分,A和B都是聪明绝顶的人,例如:对于[1,2,100,4],A一定会选择先拿1,因为先拿四的话,剩下的数组为[1,2,100],这样B就赢了,A先拿1的话,剩下的数组为[1,100,2],B选择之后,A就可以拿到100了。所以A一定会先拿1。

思想:对于A和B,每个人都是先先手,在后手,同时,A和B都是聪明绝顶的人,所以,在先手时,他们一定会选择后续得到积分最多的那个,后手时,由于另一个人也聪明,所以,只能拿到后续先手积分最少的那个。

/**
 *先手函数,返回先手拿完的分数
 */
public static int first(int[] nums,int l,int r){
    if(l == r){
        //只有一个数了,那就直接返回
        return nums[l];
    }
    //先手拿完之后,就是后手,要选择拿结果最大的
    return Math.max(nums[l]+after(nums,l+1,r),nums[r]+after(nums,l,r-1));
}
/**
 *后手函数,返回最小的
 */
public static int after(int[] nums,int l,int r){
    if(l == r){
        //只剩一个数,别人拿了,就没了
        return 0;
    }
    //由于是后手,所以另一个人一定会选择我能拿到最低积分的情况
    return Math.min(first(nums,l+1,r),first(nums,l,r-1));
}

题目5:逆序栈

不使用其他数据结构,将栈逆序

/**
     * 先介绍一个递归函数,它的作用是将栈底的元素取出,剩下的元素直接盖下来
     */
public static int popLast(Stack stack){
    int res = stack.pop();
    if(stack.isEmpty()){
        return res;
    }else{
        int last = popLast(stack);
        stack.push(res);
        return last;
    }
}
public static void reverse(Stack stack){
    if(stack.isEmpty()){
        return;
    }
    int i = popLast(stack);
    reverse(stack);
    stack.push(i);
}

题目6:字符串转化

例如111,我们可以转化为aaa、ka、ak三种转化方式,给定一个数字字符串,返回可以转化的方式的种类数

思想:假设1——n-1的数字都已经转为字符,那么,n位置的数字如果是0,则说明前面那种选择不可行,如果n等于字符的长度,也就是到了结束,那么,返回1,说明这种转化方式可行,如果n在中间,那么有两种情况,n等于1的话,n可以自己转化为字符,同时,n也可以和n+1位置的数字结合成一个字符,n等于2的话,n可以自己转化为字符,同时,如果n+1位置的字符在0到6之间,那么n也可以和n+1位置的数字结合成一个字符。

public static int recursion(char[] str,int i){
        if(i == str.length){
            return 1;
        }
        if(str[i] == '0'){
            //当前转化无效
            return 0;
        }
        if(str[i] == '1'){
            int res = recursion(str,i+1);
            if(i+1<str.length){
                res += recursion(str,i+2); //从1开始的,一定可以和后一个字符联合转化
            }
            return res;
        }
        if(str[i] == '2'){
            int res = recursion(str,i+1);
            if(i+1<str.length&&(str[i+1] >= '0'&&str[i+1] <= '6')){
                res += recursion(str,i+2);
            }
            return res;
        }
        return recursion(str,i+1);
    }

题目7:选择最大价值

给定weight[]和value[],其中定义了货物的重量和价值,在bag规定的重量内,返回能获得的最高价值

思想:假设1——n-1位置的货物都已经选择好,则n位置的货物,可以选,也可以不选,这两种情况要选择价值最大的情况,如果选择好的重量超过了bag,那么要减去前一个货物的价值,当成未选择。

public static int recursion(int[] weight,int[] value,int i,int alreadyWeight,int bag){
    if(i == weight.length){
        return 0;
    }
    if(alreadyWeight > bag){
        return -value[i-1];
    }
    return Math.max(recursion(weight,value,i+1,alreadyWeight,bag),
            value[i]+recursion(weight,value,i+1,alreadyWeight+weight[i],bag));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

世故枉然

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

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

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

打赏作者

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

抵扣说明:

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

余额充值