递归题目简记

1、汉诺塔问题

1.1、描述:

将 n 个盘子(均在from柱子上,上面的盘子最小,往下依次增大)从 from 盘移动到 to 盘,可以借助中间盘 mid,移动过程中要保证小盘子不能在大盘子之下

1.2、思路:

	- 抽象考虑,先将 1~n-1 的盘子从 from 移动到 mid 上,再将 n 从 from 移动到 to 上,再将 1~n-1 的盘子从 mid 移动到 to 上,就完成了。
	- 对于 1~i-1 的移动,是由子过程递归调用实现的,每个子问题只管自己的最下面一个和它上面的整体 两个部分。在调用过程中 from、to、mid 的实际值也在不断变化。
	- baseCase: 只剩一个盘子时,直接将它移动到 to 上即可。

1.3、源码:

public class Hanoi {
    public static void main(String[] args) {
        int n = 3;
        func(n,"左","右","中");
    }

    public static void func(int n,String from,String to,String mid){
        if(n==1){
            System.out.println("盘子 "+n+" 从 "+from+" 移动到 "+to);
            return;
        }
        func(n-1,from,mid,to);
        System.out.println("盘子 "+n+" 从 "+from+" 移动到 "+to);
        func(n-1,mid,to,from);
    }
}

1.4、运行结果:

盘子 1 从 左 移动到 右
盘子 2 从 左 移动到 中
盘子 1 从 右 移动到 中
盘子 3 从 左 移动到 右
盘子 1 从 中 移动到 左
盘子 2 从 中 移动到 右
盘子 1 从 左 移动到 右

Process finished with exit code 0

2、获取字符串的所有子集

2.1、描述

给定一个字符串,输出该字符串的所有子集,包括 ""

2.2、思路

很常见的从左到右判断要或不要当前字符的问题,就我浅薄的笔试经验来讲,这类题很容易遇到,一般是动态规划题目,需要优化,直接递归会超时。比如原串是 "abc" 到每一位判断要不要当前字符,全要就是 "abc" ,全不要就是"".

2.3、源码

public class StrSubset {
    public static void main(String[] args) {
        String str = "abc";
        func(str,0,"");
    }

    public static void func(String str,int index,String res){
        if(index == str.length()){
            System.out.print(res+" ");
            return;
        }
        //要该位置字符
        func(str,index+1,res+str.charAt(index));
        //不要该位置字符
        func(str,index+1,res);
    }
}

2.4、运行结果

abc ab ac a bc b c  
Process finished with exit code 0

3、字符串的全排列

3.1、描述

输出给定字符串的全排列,如 "abc" 的全排列:
abc  acb  bac  bca  cba  cab  

3.2、思路

	仍是从左到右的模型,但是全排列在于字符顺序的变化而不是要或者不要,即在于:对于每一个字符,要不要和它后面的字符交换位置,第一个字符一直不交换位置则结果还是在第一个位置,如果一直交换那么第一个字符最终就会出现在最后一个位置。对每一个位置的字符都如此。
	剪枝去重,对于每一个位置的字符,要想将它与后面的每一个字符换位置,如果前面的字符已经出现过了,比如 aba index=0, 那么 i=2 时,a 和 a 交换就没有意义了。

3.3、源码

public class StrFullArrangement {
    public static void main(String[] args) {
        String str = "aba";
        func(str.toCharArray(),0);
    }

    public static void func(char[] str,int index){
        if(index == str.length){
            System.out.println(str);
            return;
        }
        //去重剪枝
        boolean[] visitedArr = new boolean[26];
        for (int i = index; i < str.length; i++) {
            if(!visitedArr[str[i]-'a']){
                visitedArr[str[i]-'a'] = true;
                swap(str,i,index);
                func(str,index+1);
                swap(str,i,index);
            }
        }
    }

    public static void swap(char[] str, int i, int index){
        char temp = str[i];
        str[i] = str[index];
        str[index] = temp;
    }
}

3.4、输出

  • 后者表明达到了去重目的
"abc":
abc  acb  bac  bca  cba  cab  
Process finished with exit code 0

"aba":
aba  aab  baa  
Process finished with exit code 0

4、智者取数

4.1、描述

	- 给定一个整型数组arr,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。请返回最后获胜者的分数。

【举例】
arr=[1,2,100,4]。
	开始时,玩家A只能拿走1或4。如果开始时玩家A拿走1,则排列变为[2,100,4],接下来玩家B可以拿走2或4,然后继续轮到玩家A...
	如果开始时玩家A拿走4,则排列变为[1,2,100],接下来玩家B可以拿走1或100,然后继续轮到玩家A...
	玩家A作为绝顶聪明的人不会先拿4,因为拿4之后,玩家B将拿走100。所以玩家A会先拿1,让排列变为[2,100,4],接下来玩家B不管怎么选,100都会被玩家A拿走。玩家A会获胜,分数为101。所以返回101。

arr=[1,100,2]。
开始时,玩家A不管拿1还是2,玩家B作为绝顶聪明的人,都会把100拿走。玩家B会获胜,分数为100。所以返回100。

4.2、思路

先取的人在剩余的范围内尽量取最大的,后取的人只能在剩余的里面先取,但是取得的最小的,因为先取的人会尽量计算

在 L~R 上后手相当于在 L+1~R 或者 L~R-1 上先手,但是只能选结果中较小的,因为是后手。

4.3、源码

public class SmartPersonGetNum {
    public static void main(String[] args) {
        int[] arr = {1,2,100,4};
        System.out.println(Math.max(before(arr,0, arr.length-1),behind(arr,0, arr.length-1)));
    }


    public static int before(int[] arr,int L, int R){
        if(L==R) return arr[L];
        int num1 = arr[L]+behind(arr,L+1,R);
        int num2 = arr[R]+behind(arr,L,R-1);
        return Math.max(num1,num2);
    }

    public static int behind(int[] arr,int L,int R){
        if(L==R) return 0;
        int num1 = before(arr,L+1,R);
        int num2 = before(arr,L,R-1);
        return Math.min(num1,num2);
    }
}

5、逆序栈

5.1、描述

不使用额外的数据结构,只使用递归函数,得到逆序的栈。

5.2、思路

	-将栈画出来逐步操作,可以更好地理解下。
	-获取栈顶元素,递归,再放入栈顶元素,栈中元素的顺序不变,     获取栈底元素,递归,再放入栈底元素,栈元素逆序。
	-因此,需要先获得栈底元素,而且获取后,不将该元素压入栈。

5.3、源码

public class ReverseStack {
    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < 5; i++) {
            stack.push(i);
        }
        reverse(stack);
        while (!stack.isEmpty()){
            System.out.print(stack.pop() +" ");
        }
    }
    //反转栈
    public static void reverse(Stack<Integer> stack){
        if(stack.isEmpty()) return;
        int temp = func(stack);
        reverse(stack);
        stack.push(temp);
    }

    //得到栈底元素,并将上面的元素向下覆盖
    public static int func(Stack<Integer> stack){
        //获取栈底元素后不将元素重新入栈
        int res = stack.pop();
        if(stack.isEmpty()) return res;

        int last = func(stack);
        stack.push(res);
        return last;
    }
}

5.4、运行结果

0 1 2 3 4 
Process finished with exit code 0

6、数字字符串 --> 字母字符串

6.1、描述

有一串数字字符串,按照 '1'-'A' ~ '26'-'Z' 的规则将字符串转化为字母字符串,统计字符串的个数和转换的结果

6.2、思路

从左到右的模型,一次转换一个字符或者两个字符,直到转换完成,将结果存入集合内。
要注意的是,
 - 如果当前字符为0,那么就是说没有可以转换的,前面的决定有误,舍弃这种决定,直接返回0。
 - 如果当前字符为2,那么下一个字符必须小于7才有对应的数字。
 - 另外对于1和2来说,下一个下标位置不能越界。

6.3、源码

public class NumToChar {
    static ArrayList<String> list = new ArrayList<>();
    static int dis = 'A'-'1';
    public static void main(String[] args) {
        String str = "11112";
        System.out.println(func(str.toCharArray(),0,""));
        list.forEach(s -> System.out.print(s+"  "));
    }

    public static int func(char[] chars,int index,String temp){
        if(index >= chars.length) {
            list.add(temp);
            return 1;
        }
        if(chars[index]=='0') return 0;
        int res = func(chars,index+1,temp+(char)(chars[index]+dis));
        if(chars[index]=='1' && index+1< chars.length){
            res += func(chars,index+2,temp+(char)(Integer.parseInt(""+chars[index]+chars[index+1])+'A'));
            return res;
        }else if(chars[index]=='2'){
            if(index+1 < chars.length && chars[index+1]<='6'){
                res += func(chars,index+2,temp+(char)(Integer.parseInt(""+chars[index]+chars[index+1])+'A'));
            }
        }
        return res;
    }
}

6.4、运行结果

8
AAAAB  AAAM  AALB  ALAB  ALM  LAAB  LAM  LLB  
Process finished with exit code 0

7、0/1 背包

7.1、描述

n个物品,重量以此为 wei[i] ,价值依次为 val[i] ,在重量不超过限制 bag 的情况下,获取价值尽量大的价值,并返回价值。

7.2、思路

从左到右的模型,但是不能仅仅是加或者不加,要考虑容量的限制,如果超出容量了,也不能加。

7.3、源码

public class FamousBag {
    public static void main(String[] args) {
        int[] val = {5,2,4,1,100};
        int[] wei = {6,7,5,1,4};
        System.out.println(func(val,wei,0,0,10));
    }

    public static int func(int[] val, int[] weight, int index,int alreadyWei,int bag){
        if(alreadyWei > bag) return -1;
        if(index >= val.length) return 0;
        int val1 = 0;
        int temp = func(val, weight, index+1, alreadyWei+weight[index], bag);
        //超出容量,不能加
        if(temp != -1){
            val1 = val[index]+temp;
        }
        int val2 = func(val, weight, index+1, alreadyWei, bag);
        return Math.max(val1,val2);
    }
}

7.4、运行结果

105
Process finished with exit code 0

8、N 皇后

8.1、描述

在 NxN 的棋盘上摆放 N 个皇后棋子,在保证两两之间不同行不同列不同一45°倾角斜线的基础上,共有多少种摆放方式。

8.2、思路

对于每一行的每一个位置进行尝试,对于每一个位置进行递归尝试,使用一个 record 数组记录前 i 行的都放在那一列,在进行判断,看是否会产生冲突。同一列时,列相减为0,不能以计算斜率的方式判断,需要单独列出来。

8.3、源码

public class NQueen {

    public static void main(String[] args) {
        System.out.println(func(8,new int[8],0));
    }

    public static int func(int n,int[] record,int index){
        if(index==n) return 1;
        int res = 0;
        for (int i = 0; i < n; i++) {
            if(illegal(record,index,i)){
                record[index] = i;
                res += func(n,record,index+1);
            }
        }
        return res;
    }

    public static boolean illegal(int[] record,int i,int j){
        for (int k = 0; k < i; k++) {
            if(record[k] == j) return false;
            if(Math.abs(i-k) == Math.abs(j-record[k])) return false;
        }
        return true;
    }
}

8.4、运行结果

92
Process finished with exit code 0

8.5、位运算版本

public class QueenNPlus {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int N = in.nextInt();
        int limit = N==32 ? -1 : (1<<N)-1;
        System.out.println(func(limit, 0, 0, 0));
    }


    public static int func(int limit,int cloLimit,int leftLimit,int rightLimit){
        if(limit == cloLimit) return 1;
        int res = 0;
        //获取可以放置皇后的位置,可以放置的位置为 1
        int pos = limit & (~(cloLimit | leftLimit | rightLimit));
        //可放置位置不为全 0
        while(pos != 0){
            //取得最右边的 1,其他位置为 0
            int mostRightOne = pos & (~pos+1);
            //可放置位置减去这个 1
            pos = pos - mostRightOne;
            //继续递归求解
            //1、行限制 cloLimit直接 | 上下一个要放置的位置即可,但是在下一行,左右限制要分别左移右移,以满足仍是 45° 倾角的原则
            res += func(limit,cloLimit|mostRightOne,
                    (leftLimit|mostRightOne)<<1,(rightLimit|mostRightOne)>>>1);
        }
        return res;
    }
}
posted @ 2022-10-22 10:59  心是冰冰的  阅读(38)  评论(0)    收藏  举报