目录
一、递归的基本常识
递归的思路是将大问题,分解成小问题,直到最后不能再分的情况,求出结果,再将结果汇总成最终问题的解。是一种典型的分治思想。
递归,有2个比较重要的元素。一个是递归式,一个是终止条件。
递归式,也就是问题拆分过程中,如何自己调用自己,或者简介调用自己
终止条件,也就是程序的退出条件。如果没有终止条件,就是死循环,java的话,会造成栈溢出,报stackoverflow的异常。
使用递归解决的问题,一般是获取最终的结果,或者是获取递归的过程。
二、常见题目的递归分析
1.跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法(先后次序不同算不同的结果)
分析:
青蛙只有2种行为:一种跳1层,一种跳2层。如果跳到8级台阶,只有2种方案,一种从6条到8,另一种从7跳到8。也就是说跳到n级台阶,也只能有2种方案,n-2=>n , n-1=>n
对于分解图,可见,当n<=2的时候,青蛙的跳法确定,不需要拆解
代码如下:
public int jumpFloor(int target) {
if(target<=2){
return target;
}
return jumpFloor(target-1) + jumpFloor(target-2);
}
2.字符串的排列
输入一个长度为 n 字符串,打印出该字符串中字符的所有排列,你可以以任意顺序返回这个字符串数组。
例如输入字符串ABC,则输出由字符A,B,C所能排列出来的所有字符串ABC,ACB,BAC,BCA,CBA和CAB。
分析:
不考虑结果的正确,只产生ABC三个字母的全排列,可以得到如下代码
public ArrayList<String> Permutation(String str) {
ArrayList<String> list = new ArrayList<>();
if(str==null||str.length()==0) return list;
char[] chars = str.toCharArray();
createStr(chars,list,"");
return list;
}
public void createStr(char[] chars,ArrayList<String> list,String s){
if(s.length()==chars.length){
list.add(s);
return;
}
for(int i = 0;i<chars.length;i++){
createStr(chars,list,s+chars[i]);
}
}
结果:
["AAA","AAB","AAC","ABA","ABB","ABC","ACA","ACB","ACC","BAA","BAB","BAC","BBA","BBB","BBC","BCA","BCB","BCC","CAA","CAB","CAC","CBA","CBB","CBC","CCA","CCB","CCC"]
现在考虑每个字母使用一次之后不再使用,因此,递归时生成不包含已经使用的新char[]
public ArrayList<String> Permutation(String str) {
ArrayList<String> list = new ArrayList<>();
if(str==null||str.length()==0) return list;
char[] chars = str.toCharArray();
createStr(chars,list,"",chars.length);
return list;
}
public void createStr(char[] chars,ArrayList<String> list,String s,int sum){
// chars通过newChar每次都在变化,因此使用了一个新的变量存储总数量
if(s.length()==sum){
list.add(s);
return;
}
for(int i = 0;i<chars.length;i++){
createStr(newChar(chars,i),list,s+chars[i],sum);
}
}
public char[] newChar(char[] chars,int i){
String s = "";
for(int j = 0;j<chars.length;j++){
if(i!=j){
s+=chars[j];
}
}
return s.toCharArray();
}
字符串不包含重复的情况,已经求出答案,如果包含重复,添加时判断是否包含if(list.contains(s))return;
3.有重复项数字的所有排列
给出一组可能包含重复项的数字,返回该组数字的所有排列。结果以字典序升序排列。
输入:[1,1,2]
返回值:[[1,1,2],[1,2,1],[2,1,1]]
按递归路径,需要准备一些变量:
- 已经选了哪些数path
- boolean类型数组used
按所有数字全排列,得到 [[1,1,2],[1,2,1],[1,1,2],[1,2,1],[2,1,1],[2,1,1]]
代码如下:
public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {
ArrayList<ArrayList<Integer>> ans = new ArrayList<ArrayList<Integer>>();
Deque<Integer> stack = new ArrayDeque<Integer>();
int length = num.length;
boolean used[] = new boolean[length];
Arrays.sort(num);
createStr(num,num.length,stack,used,ans);
return ans;
}
public void createStr(int[] num,int sum,Deque<Integer> path,boolean[] used,ArrayList<ArrayList<Integer>> ans){
if(path.size()==sum){
ans.add(new ArrayList<>(path));
return;
}
for(int i = 0;i<num.length;i++){
if(used[i])continue;
path.addLast(num[i]);
used[i]=true;
createStr(num,sum,path,used,ans);
path.removeLast();
used[i]=false;
}
}
在此基础上过滤掉每一层已经使用过的数字 (i!=0&&num[i] == num[i-1] )&&!used[i-1]
其中i!=0,是图上的第一层为空,不需要过滤,也为了防止数组下标越界
num[i]==num[i-1],是为了找到重复数字,在重复数字还没使用的情况used[i-1]=false下跳过
最终将重复项过滤掉
4.二叉搜索树的第k个节点
给定一棵结点数为n 二叉搜索树,请找出其中的第 k 小的TreeNode结点值。
1.返回第k小的节点值即可
2.不能查找的情况,如二叉树为空,则返回-1,或者k大于n等等,也返回-1
3.保证n个节点的值不一样
如输入{5,3,7,2,4,6,8},3时,二叉树{5,3,7,2,4,6,8}如下图所示:
该二叉树所有节点按结点值升序排列后可得[2,3,4,5,6,7,8],所以第3个结点的结点值为4,故返回对应结点值为4的结点即可。
分析:
二叉搜索树,可以直接使用中序遍历,得到有序的数组,然后获取目标位置的值,就是第k个小的数
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param proot TreeNode类
* @param k int整型
* @return int整型
*/
public int KthNode (TreeNode proot, int k) {
// write code here、
if(proot==null||k==0)return-1;
ArrayList<Integer> list = new ArrayList<>();
midOrder(proot,list,k);
if(k>list.size())return-1;
return list.get(k-1);
}
public void midOrder(TreeNode root,List<Integer> list,int k){
if(list.size()==k)return;
if(root!=null){
midOrder(root.left,list,k);
list.add(root.val);
midOrder(root.right,list,k);
}
}
5.括号生成
给出n对括号,请编写一个函数来生成所有的由n对括号组成的合法组合。
例如,给出n=3,解集为:
"((()))", "(()())", "(())()", "()()()", "()(())"
分析:
对于括号,都是成对出现,可以想到最终括号的数量一定是2n,从而可以确定终止条件,生成括号数量2n,且左括号数量==右括号数量。
获得过程解需要记录生成过程的list,左右括号的数量left ,right
left<n,添加左括号,right<left ,添加有括号
不是left<n,right<n的原因是,括号成对出现,一定要先有左括号,再添加由括号,否则可能不成对,例如["(())","()()","())(",")(()",")()(","))(("]
/**
*
* @param n int整型
* @return string字符串ArrayList
*/
public ArrayList<String> generateParenthesis (int n) {
// write code here
ArrayList<String> result = new ArrayList<>();
create(n,0,0,"",result);
return result;
}
public void create(int n,int left,int right,String s,ArrayList<String> result){
if(s.length()==n<<1){
result.add(s);
return;
}
if(left<n){
create(n,left+1,right,s+"(",result);
}
if(right<left){
create(n,left,right+1,s+")",result);
}
return;
}