暴力递归
1.把问题转化为规模缩小了的同类问题的子问题
2.有明确的不需要继续进行递归的条件(base case)
3.有当得到了子问题的结果之后的决策过程
4.不记录每一个子问题的解
动态规划的基础
Q1:汉诺塔问题
3个柱子,其中一个柱子摆放n个盘子,将n个盘子放另一个柱子上,金字塔->金字塔
抽象成form,to,other
问题转为
1到i-1个盘子from-other
第i个盘子 form-to
第1到i-1个盘子other-to
只需要满足第i个盘子局部决策正确即可,不用理会全局是否还是初始状态,其余状态在递归中自动满足。
***考虑局部的决策正确,全局交给递归满足 只需要解决递归的子问题即可
只要最底下的盘子满足条件即可,其余盘子移动到正确盘子时也会满足移动的条件
问题逐渐转化为,摆放好第n个,摆放好第n-1个...摆放好第一个
public static void hanoi(int n){
if(n>0){
func(n,'左','右','中');
}
}
//从start移到end,借助other
public static void func(int i,int start,int end,int other){
if(i==1){
System.out.println("Move 1 from"+start+"to"+end);
}else{
func(i-1,start,other,end);
System.out.println("Move "+i+" from"+start+"to"+end);
func(i-1,other,end,start);
}
}
Q2:打印字符串的所有子串,含空串
二叉树,从左往右,每个字符分支为含or不含
public static void func(String str){
char[] chs=str.toCharArray();
process(chs,0,new ArrayList<Character>());
}
public static void process(char[] str,int i,List<Character> res){
if(i==str.length){
System.out.println(res);
return;
}
//加入该字符的路
List<Character> resKeep= copyList(res);
process(str,i+1,resKeep);
//不加入该字符的路
List<Character> resNotKeep= copyList(res);
process(str,i+1,resNotKeep);
}
//省空间做法,原地修改str再复原
public static void process1(char[] str,int i){
if(i==str.length){
System.out.println(str);
return;
}
char temp=str[i];
//加入该字符的路
process1(str,i+1);
str[i]=' ';
//不加入该字符的路
process1(str,i+1);
str[i]=temp;
}
Q3:打印字符串全部排列,要求不出现重复排列
//str[i,,,,n]未排,且后续的都可在i位置进行尝试
//str[0,,,i-1]已经排序
//res收集排序的结果
public static void process(char[] str,int i,ArrayList<String> res){
if(i==str.length){
res.add(String.valueOf(str));
return;
}
//用于去重
boolean[]visit =new boolean[26];
for(int j=i;j<str.length;j++){
if(!visit[str[j]-'a']){
visit[str[j]-'a']=true;//剪枝
swap(str,i,j);
process(str,i+1,res);
swap(str,j,i);
}
}
}
Q4:给定一组标有权值的卡牌,A,B轮流拿,只能拿最左或最右
,A,B不是笨蛋,卡牌为(1,2,100,4)如A先拿,只会拿1,而不会拿4把100留给B而输掉比赛,问最高得分。
//f和s都是对于同一张牌而言
//先手 返回在i,j范围的最大分数
public static int f(int[] arr,int i,int j){
if(i==j) return arr[i];
//在[i,j]先手 在[i+1,j]后手
return Math.max(arr[i]+s(arr,i+1,j),arr[j]+s(arr,i,j-1));
}
//后手 先手已经拿掉最大的了,后手只能拿最小(相对最小)
public static int s(int[] arr,int i,int j){
if(i==j) return 0;//一张牌的时候,先手拿掉后后手拿不到为0
return Math.min(arr[i]+f(arr,i+1,j),arr[j]+f(arr,i,j-1));
}
public static int win(int[] arr){
if(arr==null||arr.length==0){
return 0;
}
return Math.max(f(arr,0,arr.length-1),s(arr,0,arr.length-1));
}
Q5:只递归,不用额外的辅助,逆序一个栈
//将堆低弹出,其余不变
//123 ->23 1 ->3 2 -> null 3
public static int f(Stack<Integer> stack){
//拿到堆顶元素并保留
int result=stack.pop();
if(stack.isEmpty()){
//最后一个元素是底也是顶 base case
return result;
}else{
//拿掉堆顶的情况下继续拿下一个元素,直至栈空、
//拿堆底
int last=f(stack);
//恢复栈
stack.push(result);
return last;
}
}
public static void reverse(Stack<Integer> stack){
if(stack.isEmpty()){//base case
return;
}
//stack[1,n]
//拿堆底n,n-1,n-2,,,1
int i=f(stack);
reverse(stack);
//将堆底逆序压回去
//最后一次拿堆底为1后,递归返回重复执行下一句
stack.push(i);
}
Q6:解码问题
规定1-a,2-b,3-c..11-k...26-z
解数字组成的字符串一共可解出多少种组合 如111->(aaa/ak/ka)
已决定[0,i-1],待决定[i,n]
待决定有3种可能,
1.i为0,后续无法决定
2.i大于3 只有一种决定+[i+1,n]种决定
3.i小于3 “有一种+[i+1,n]”种 和“ 一种+[i+2,n]种 ”两个可能
public static int process(char[] str,int i){
if(i==str.length) return 1; //收集结果
if(str[i]=='0') return 0; //后续不可能转成功
if(str[i]=='1'){
//只转一个数
int res=process(str,i+1);
//转两个数
if(i+1<str.length){
res+=process(str,i+2);
}
return res;
}
if(str[i]=='2'){
//只转一个数
int res=process(str,i+1);
//转两个数,这时对第二个数有要求,转化范围只有1-26
if(i+1<str.length&&str[i+1]>'0'&&str[i+1]<'6'){
res+=process(str,i+2);
}
return res;
}
//只转一个数
return process(str,i+1);
}
Q7: 01背包问题
要or不要,暴力递归树类似Q2
数组weight[i],values[i]表示物品i的重量和价值,给定背包大小bag,求怎么装价值最大。
//i...的货物随便选,返回选择后的最大价值
//不理会i之前的
public static int process(int[] weights,int[] values,int i,int alreadyWeight,int bag){
//前两个if是base case 可理解为选后违规,可新加的价值为0
//选完超重
if(alreadyWeight>bag) return 0;
//已经没货可选了,还未选择
if(i==weights.length) return 0;
//拿i物品和不拿i物品
return Math.max(process(weights,values,i+1,alreadyWeight,bag),
values[i]+process(weights,values,i+1,weights[i]+alreadyWeight,bag));
}