在这篇文章里,我们主要讨论和递归相关的话题。递归是数据结构中解决复杂问题时非常有效的一种思考方式,对于一个复杂的问题,我们可以从中抽取一个可以通过迭代方式解决的子问题,而子问题是一个容易解决的问题。在使用递归时,有两个问题需要注意:1)抽取递归体;2)确定递归边界条件以及处理方式。
下面我们来看相关的题目。
-
斐波那契数列
我想这应该是最常见的递归的例子了,我们可以使用递归和非递归两种方式来实现它。
首先来看非递归的方式:
1 public static int fibo2(int n) 2 { 3 if (n < 0) return -1; 4 if (n == 0) return 0; 5 6 int a = 0,b = 1,c; 7 for (int i = 2; i <= n; i++) 8 { 9 c = a + b; 10 a = b; 11 b = c; 12 } 13 return b; 14 }
接着是使用递归方式:
1 public static int fibo1(int n) 2 { 3 if (n < 0) return -1; 4 if (n == 0) return 0; 5 if (n == 1) return 1; 6 return fibo1(n - 1) + fibo1(n - 2); 7 }
-
对于一个N*N的矩阵,从左上角开始遍历,每次只能横着走或者竖着走,问一共有多少种方式可以到达矩阵的右下角。
思路:可以把矩阵看成是N行N列的数据结构,初始时,对角线距离为N行、N列,当走一步之后,距离变为(N行、N-1列)或者是(N-1行、N列),直到距离变为(0行、0列)后,表明已经走到了右下角。
1 public static void printAllPath(int n) 2 { 3 int[][] matrix = Matrix.initMatrix(n, 50); 4 Matrix.printMatrix(matrix); 5 ArrayList<Integer> path = new ArrayList<Integer>(); 6 walk(matrix, n, 0, 0, path); 7 } 8 9 private static void walk(int[][] matrix, int n, int row, int column, ArrayList<Integer> path) 10 { 11 path.add(matrix[row][column]); 12 if (row == n - 1 && column == n -1) 13 { 14 StringBuffer sb = new StringBuffer(); 15 for(int value : path) sb.append(value).append("->"); 16 System.out.println(sb.substring(0, sb.length() - 2)); 17 } 18 if (row < n -1) 19 { 20 ArrayList<Integer> path1 = (ArrayList<Integer>)path.clone(); 21 walk(matrix, n, row + 1, column, path1); 22 } 23 if (column < n - 1) 24 { 25 ArrayList<Integer> path2 = (ArrayList<Integer>)path.clone(); 26 walk(matrix, n, row, column + 1, path2); 27 } 28 }
22 17 35 16 3 38 46 39 4 22->16->46->39->4 22->16->3->39->4 22->16->3->38->4 22->17->3->39->4 22->17->3->38->4 22->17->35->38->4
-
列出给定集合的所有子集
思路:按照子集的定义,对于长度为N的集合来说,它的子集长度是从1到N-1,我们可以先假设求长度为M的子集,那应该怎么做呢?首先可以循环遍历集合,取出某一个元素,然后计算除去该元素的集合的长度为M-1的子集,然后递归,直到最后要找长度为1的子集为止。
然后按照上述思路,将M从1开始一直设置到N-1,就可以得到所有的子集。
需要注意的是,在递归的过程中,会产生重复的子集,需要删除这些子集。
1 public static void getAllSubSet(int n) 2 { 3 int[] arrValue = Matrix.initArray(n, 50); 4 StringBuffer sb = new StringBuffer(); 5 for(int value : arrValue) sb.append(value).append("->"); 6 System.out.println(sb.substring(0, sb.length() - 2)); 7 8 ArrayList<Integer> list = new ArrayList<Integer>(); 9 ArrayList<ArrayList<Integer>> subsets = new ArrayList<ArrayList<Integer>>(); 10 for(int i = 1; i < n; i++) 11 { 12 subset(arrValue, i, i, list, subsets); 13 } 14 } 15 16 private static void subset(int[] arrValue, int subSetLength, int left, ArrayList<Integer> list, ArrayList<ArrayList<Integer>> subsets) 17 { 18 if (left == 0 || list.size() == subSetLength) 19 { 20 boolean bExist = false; 21 for(ArrayList<Integer> temp : subsets) 22 { 23 if (temp.size() == list.size()) 24 { 25 int j = 0; 26 for (int value : list) 27 { 28 if (!temp.contains(value))break; 29 j++; 30 } 31 if (j == subSetLength) 32 { 33 bExist = true; 34 break; 35 } 36 } 37 } 38 if (!bExist) 39 { 40 StringBuffer sb = new StringBuffer(); 41 for(int value1 : list) sb.append(value1).append("->"); 42 System.out.println(sb.substring(0, sb.length() - 2)); 43 subsets.add(list); 44 } 45 46 } 47 for (int temp : arrValue) 48 { 49 if (list.contains(temp)) continue; 50 ArrayList<Integer> listTemp = (ArrayList<Integer>)list.clone(); 51 listTemp.add(temp); 52 subset(arrValue, subSetLength, left - 1, listTemp, subsets); 53 } 54 }
执行结果(假设一个长度为4的集合)
35->6->40->33 35 6 40 33 35->6 35->40 35->33 6->40 6->33 40->33 35->6->40 35->6->33 35->40->33 6->40->33
-
求给定字符串的所有可能组合(假设字符串没有重复字符)
思路:这个题目和上面的题目非常类似,只不过这个题目求的是对于长度为N的集合,我们要列出长度为N的“子集”。注意要去除重复组合。
1 public static void perm(String value) 2 { 3 char[] arrChars = value.toCharArray(); 4 char[] arrAlready = new char[arrChars.length]; 5 ArrayList<char[]> all = new ArrayList<char[]>(); 6 permRecursive(arrChars, arrChars.length, arrAlready, all); 7 } 8 9 private static void permRecursive(char[] arrChars, int left, char[] arrAlready, ArrayList<char[]> all) 10 { 11 if (left == 0) 12 { 13 boolean bExist = false; 14 for(char[] arrTemp : all) 15 { 16 int j=0; 17 for (int i = 0; i < arrChars.length; i++) 18 { 19 if (arrTemp[i] != arrAlready[i]) break; 20 j++; 21 } 22 if (arrChars.length == j) 23 { 24 bExist = true; 25 break; 26 } 27 } 28 if (!bExist) 29 { 30 all.add(arrAlready); 31 for(char ch:arrAlready)System.out.print(ch); 32 System.out.println(); 33 } 34 } 35 for(char ch : arrChars) 36 { 37 int i = 0; 38 for(i = 0; i < arrChars.length - left; i++) 39 { 40 if (ch == arrAlready[i]) break; 41 } 42 if (i == arrChars.length - left) 43 { 44 arrAlready[arrChars.length - left] = ch; 45 char[] arrTemp = arrAlready.clone(); 46 permRecursive(arrChars, left - 1, arrTemp, all); 47 } 48 } 49 }
执行结果(以“abc”为例)
abc acb bac bca cab cba
-
给出N个'('和')',列出所有可能的合法组合
思路:依然使用递归的套路,需要注意已输出的'('的数目不能小于')'的数目。
1 public static void printPar(int n) 2 { 3 char[] arrResult = new char[2*n]; 4 printParRecursive(n, n, arrResult, 0); 5 } 6 7 private static void printParRecursive(int lCount, int rCount, char[] arrResult, int totalCount) 8 { 9 if (lCount == 0 && rCount == 0) 10 { 11 System.out.println(arrResult); 12 } 13 if (lCount > 0) 14 { 15 arrResult[totalCount] = '('; 16 printParRecursive(lCount - 1, rCount, arrResult, totalCount + 1); 17 } 18 if (rCount > lCount) 19 { 20 arrResult[totalCount] = ')'; 21 printParRecursive(lCount, rCount - 1, arrResult, totalCount + 1); 22 } 23 }
执行结果(假设N=4)
(((()))) ((()())) ((())()) ((()))() (()(())) (()()()) (()())() (())(()) (())()() ()((())) ()(()()) ()(())() ()()(()) ()()()()
-
硬币组合,假设我们有25美分、15美分、5美分以及1美分的硬币,硬币的数目不限,对于指定的N美分,请列出所有可能的组合。
思路:典型的递归,假设已经有25美分,那么需要找出N-25的所有组合,同样需要找出N-15、N-5、N-1的组合。
1 public static void combine(int n) 2 { 3 ArrayList<Integer> list = new ArrayList<Integer>(); 4 ArrayList<ArrayList<Integer>> all = new ArrayList<ArrayList<Integer>>(); 5 combinRecursive(n, list, all); 6 } 7 8 private static void combinRecursive(int n, ArrayList<Integer> list, ArrayList<ArrayList<Integer>> all) 9 { 10 if (n == 0) 11 { 12 Collections.sort(list); 13 boolean bExist = false; 14 for(ArrayList<Integer> temp : all) 15 { 16 if (temp.size() == list.size()) 17 { 18 int j = 0; 19 for(int i = 0; i < temp.size(); i++) 20 { 21 if (temp.get(i) != list.get(i)) break; 22 j++; 23 } 24 if (j == temp.size()) 25 { 26 bExist = true; 27 break; 28 } 29 } 30 } 31 if (!bExist) 32 { 33 all.add(list); 34 StringBuffer sb = new StringBuffer(); 35 for(int value1 : list) sb.append(value1).append("->"); 36 System.out.println(sb.substring(0, sb.length() - 2)); 37 } 38 } 39 40 if (n >= 25) 41 { 42 ArrayList<Integer> temp = (ArrayList<Integer>)list.clone(); 43 temp.add(25); 44 combinRecursive(n - 25, temp, all); 45 } 46 if (n >= 10) 47 { 48 ArrayList<Integer> temp = (ArrayList<Integer>)list.clone(); 49 temp.add(10); 50 combinRecursive(n - 10, temp, all); 51 } 52 if (n >= 5) 53 { 54 ArrayList<Integer> temp = (ArrayList<Integer>)list.clone(); 55 temp.add(5); 56 combinRecursive(n - 5, temp, all); 57 } 58 if (n >= 1) 59 { 60 ArrayList<Integer> temp = (ArrayList<Integer>)list.clone(); 61 temp.add(1); 62 combinRecursive(n - 1, temp, all); 63 } 64 }
执行结果(假设N=25)
25 5->10->10 1->1->1->1->1->10->10 5->5->5->10 1->1->1->1->1->5->5->10 1->1->1->1->1->1->1->1->1->1->5->10 1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->10 5->5->5->5->5 1->1->1->1->1->5->5->5->5 1->1->1->1->1->1->1->1->1->1->5->5->5 1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->5->5 1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->5 1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1->1
最后,欢迎大家提出更多和递归相关的面试题目,我们可以一起讨论。