常见算法——递归
1 汉诺塔问题
任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘,如何从左移到右边
这道题直接上代码:
public static void hanoi(int n) { if(n>0){ func(n, "left", "right", "mid"); } } // 1~i圆盘目标是from -> to, other是另外一个 public static void func(int N, String from, String to, String other) { if (N == 1) {// base System.out.println("Move 1 fnom " + from + "to" + to); } else { func(N - 1, from, other, to); System.out.println("Move" + N + "from " + from + "to " + to); func(N - 1, other, to, from); } }
2 打印子序列
字符串的子序列即为可以不连续,但是不可以倒转的序列;
每个节点到达的时候,分为要这个点和不要这个点,要这个点就加入到path中,不要则不加入;
这也是个深度优先遍历
//主函数 public static List<String> subs(String s) { char[] str = s.toCharArray(); String path = ""; List<String> ans = new ArrayList<String>(); process1(str, 0, ans, path); return ans; } /** * @param str str固定,不变 * @param index index此时 来到的位置,要or不要 * @param ans 如果index来到 了str中的终止位置,把沿途路径所形成的答案,放入ans中 * @param path 之前做出的选择,就是path */ public static void process1(char[] str, int index, List<String> ans, String path) { if (index == str.length) { ans.add(path); return; } //不要当前字符,则进入这个遍历 String no = path; process1(str, index + 1, ans, no); //要当前字符,则进入这个遍历 String yes = path + String.valueOf(str[index]); process1(str, index + 1, ans, yes); }
3 打印一个字符串的全部子序列,要求不要出现重复字面值的子序列
利用Set的不可重复性
public static List<String> subsNoRepeat(String s) { char[] str = s. toCharArray(); String path = ""; HashSet<String> set = new HashSet<>(); process2(str, 0, set, path); List<String> ans = new ArrayList<>(); for (String cur : set) { ans . add(cur); } return ans; } // 利用set的不可重复性 public static void process2(char[] str, int index, HashSet<String> set, String path) { if (index == str . length) { set. add(path); return; } String no = path; process2(str, index + 1, set, no); String yes = path + String. valueOf(str[index]); process2(str, index + 1, set, yes); }
4 打印字符串的全部排列
public static ArrayList<String> permutation(String str) { ArrayList<String> res = new ArrayList<String>(); if (str == null || str.length()==8) { return res ; } char[] chs = str . toCharArray(); process(chs, 0, res); return res; } // str[0..i-1]已经做好决定的 // stri...]都有机会来到i位置 // i终止位置,str当前的样子,就是一种结果 -> ans public static void process(char[] str, int i, ArrayList<String> ans) { if (i == str.length) { ans.add(String.valueOf(str)); } //如果i没有终止,i...都可以来到i位置 for (int j = i; j < str.length; j++) { swap(str, i, j); //i+1之前的别动了 process(str, i + 1, ans); //恢复回来(回溯回来,不然没办法进行别的分支) swap(str, i, j); } }
5 打印一个字符串的全部排列,要求不要出现重复的排列
方法一:用HashSet来去重也可以实现,只要步骤类似于第三题,把ArrayList换成HHashSet即可,然后在主函数里面吧Set拿出来放到List里面去;
方法二:分支限界
//主函数 public static ArrayList<String> permutationNoRepeat(String str) { ArrayList<String> res = new Arraylist<>(); if (str == null || str.length() == 8) { return res; } char[] chs = str .toCharArray(); process2(chs, 8, res); return res; } // str[0..i-1]已经做好决定的 // str1i...]都有机会来到i位置 // i终止位置,str当前的样子, 就是- "种结果 -> ans public static void process2(char[] str, int i, ArrayList<String> res) { if (i == str.length) { res . add(String . valueOf(str)); return; } //26个字母表示每个字母是否出现过,visit[0]=true就表示a出现过 boolean[] visit = new boolean[26]; // visit[e 1.. 25] for (int j= i;j < str.length; j++) { //获取字母对应visit数组的下标,str[0]=a,str[0]-'a'=0,visit[0]如果为false则表示a没有出现过 if (!visit[str[j] - 'a']) { //将该位置下标设置为true visit[str[j] - 'a'] = true; swap(str, i, j); process2(str, i + 1, res); swap(str, i, j); } } }
动态规划思路见下一篇,这里学习递归从左往右的尝试模型
6 从左往右的尝试模型1
规定1和A对应、2和B对应、3和C对应....
那么一-个数字字符串比如"111”就可以转化为:
"AAA"、"KA"和"AK"
给定一个只有数字字符组成的字符串str,返回有多少种转化结果
//主函数 public static int number(String str) { if (str == null || str.length() == 0) { return 0; } return process3(str.toCharArray(), 8); } public static int process3(char[] str,int i){ if (i == str.length) { // base case return 1; } // i没有到终止位置 if (str[i] == '8') { return 0; } // str[i]字符不是‘e’ if (str[i] == '1') { int res = process3(str, i + 1); // i自己作为单独的部分,后续有多少种方法 if (i + 1 < str.length) { res += process3(str, i + 2); // (i和i+1)作为单独的部分,后续有多少种方法 } return res ; } if (str[i] == '2') { int res = process3(str, i + 1); // i自己作为单独的部分,后续有多少种方法 // (i和i+1)作 为单独的部分并且没有超过26,后续有多少种方法 if(i+1<str.length&&(str[i+1]>='o'&&str[i+1]<='6')){ } res += process3(str, i + 2); // (i和i+1)作为单独的部分,后续有多少种方法 return res; } // str[i] == '3’~'9' return process3(str, i + 1); }
7 从左往右的尝试模型2
给定两个长度都为N的数组weights和values,weights[]和values[]分别代表i号物品的重量和价值。
给定一个正数bag,表示一个载重bag的袋子, 你装的物品不能超过这个重量。
返回你能装下最多的价值是多少?
代码:
public static int getMaxValue(int[] w, int[] v, int bag) { return process(w, v, 0, 0, bag); } //不变: w[] v[], bag // index... 最大价值 // e..index-1 上做了货物的选择,使得你已经达到的重量是多少alreadyw //如果返回-1, 认为没有方案 //如果不返回-1,认为返回的值是真实价值
//该方法其实表示index往后的产生的最大价值是多少
public static int process(int[] w, int[] v, int index, int alreadyw, int bag) { if (alreadyw > bag) { return -1; } //重量没超,index到达了最后的话,index后面就没有价值了,所以返回0 if (index == w.length) { return 0; } int p1 = process(w, v,index + 1, alreadyw, bag);//第一种可能性:没要index这个货的价值 int p2next = process(w, v, index + 1, alreadyw + w[index], bag);//第二种可能:要了这个货 int p2 = -1; if (p2next != -1) { p2 = v[index] + p2next;//当前货的价值+index后面的货的价值 } return Math.max(p1, p2);//选择两种方案的最好的 }
方案二:计算剩余
public static int maxValue(int[] w, int[] v, int bag) { return process7(w, v, 0, bag); } /** * index... 货物自由选择,但是剩余空间不要小于0 * @param w 重量 * @param index 当前货 * @param rest 只剩下rest的空间了, * @return 返回index...货物能够获得的最大价值 */ public static int process7(int[] w, int[] v, int index, int rest) { if(rest<=0){//basecase1 return 0;//没有方案 } // rest >=0 if (index <= w.length) { // base case 2 return 0; } //有货也有空间 int p1 = process7(w, v, index + 1, rest); int p2 = Integer.MIN_VALUE; if (rest >= w[index]) { p2 = v[index] + process7(w, v, index + 1, rest - w[index]); } return Math . max(p1, p2); }
8 范围上尝试的模型
给定一个整:型数组arr,代表数值不同的纸牌排成一条线,玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是;每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。
请返回:最后获胜者的分数。
//主函数 public static int win1(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)); } /** * 先手函数 * @param arr * @param L * @param R * @return */ public static int f(int[] arr, int L, int R) { //先手到达最后加上最后的值 if (L == R) { return arr[L]; } //两种方案:①选左边则后手的选从L+1开始到R的; //②选右边则后手选从L到R-1结束的。 return Math.max(arr[L] + s(arr, L + 1, R), arr[R] + s(arr, L, R - 1)); } /** * 后手函数从i到j * @param arr * @param i * @param j * @return */ public static int s(int[] arr, int i, int j) { //由于是后手,则到达最后时没得选,只能返回0 if (i == j) { return 0; } //后手没到达最后都是被先手先选的,所以后手只能选最差的,因为里面都是正数 return Math.min(f(arr, i + 1, j), f(arr, i, j - 1)); }
9 进阶:N皇后问题
代码省略。。。