暴力递归在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));
}