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; |
| |
| int pos = limit & (~(cloLimit | leftLimit | rightLimit)); |
| |
| while(pos != 0){ |
| |
| int mostRightOne = pos & (~pos+1); |
| |
| pos = pos - mostRightOne; |
| |
| |
| res += func(limit,cloLimit|mostRightOne, |
| (leftLimit|mostRightOne)<<1,(rightLimit|mostRightOne)>>>1); |
| } |
| return res; |
| } |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?