算法基础11
一、暴力递归
1.概念
暴力递归就是尝试
(1)把问题转化为规模缩小了的同类问题的子问题
(2)有明确的不需要继续进行递归的条件(base case)
(3)有当得到了子问题的结果之后的决策过程
(4)不记录每一个子问题的解
2.熟悉什么叫尝试
打印n层汉诺塔从最左边移动到最右边的全部过程
打印一个字符串的全部子序列
打印一个字符串的全部子序列,要求不要出现重复字面值的子序列
打印一个字符串的全部排列
打印一个字符串的全部排列,要求不要出现重复的排列
package class11;
import java.util.Stack;
public class Code01_Hanoi {
public static void hanoi1(int n) {
leftToRight(n);
}
// 请把1~N层圆盘 从左 -> 右
public static void leftToRight(int n) {
if (n == 1) {
System.out.println("Move 1 from left to right");
return;
}
leftToMid(n - 1);
System.out.println("Move " + n + " from left to right");
midToRight(n - 1);
}
// 请把1~N层圆盘 从左 -> 中
public static void leftToMid(int n) {
if (n == 1) {
System.out.println("Move 1 from left to mid");
return;
}
leftToRight(n - 1);
System.out.println("Move " + n + " from left to mid");
rightToMid(n - 1);
}
public static void rightToMid(int n) {
if (n == 1) {
System.out.println("Move 1 from right to mid");
return;
}
rightToLeft(n - 1);
System.out.println("Move " + n + " from right to mid");
leftToMid(n - 1);
}
public static void midToRight(int n) {
if (n == 1) {
System.out.println("Move 1 from mid to right");
return;
}
midToLeft(n - 1);
System.out.println("Move " + n + " from mid to right");
leftToRight(n - 1);
}
public static void midToLeft(int n) {
if (n == 1) {
System.out.println("Move 1 from mid to left");
return;
}
midToRight(n - 1);
System.out.println("Move " + n + " from mid to left");
rightToLeft(n - 1);
}
public static void rightToLeft(int n) {
if (n == 1) {
System.out.println("Move 1 from right to left");
return;
}
rightToMid(n - 1);
System.out.println("Move " + n + " from right to left");
midToLeft(n - 1);
}
public static void hanoi2(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 from " + 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);
}
}
public static class Record {
public boolean finish1;
public int base;
public String from;
public String to;
public String other;
public Record(boolean f1, int b, String f, String t, String o) {
finish1 = false;
base = b;
from = f;
to = t;
other = o;
}
}
public static void hanoi3(int N) {
if (N < 1) {
return;
}
Stack<Record> stack = new Stack<>();
stack.add(new Record(false, N, "left", "right", "mid"));
while (!stack.isEmpty()) {
Record cur = stack.pop();
if (cur.base == 1) {
System.out.println("Move 1 from " + cur.from + " to " + cur.to);
if (!stack.isEmpty()) {
stack.peek().finish1 = true;
}
} else {
if (!cur.finish1) {
stack.push(cur);
stack.push(new Record(false, cur.base - 1, cur.from, cur.other, cur.to));
} else {
System.out.println("Move " + cur.base + " from " + cur.from + " to " + cur.to);
stack.push(new Record(false, cur.base - 1, cur.other, cur.to, cur.from));
}
}
}
}
public static void main(String[] args) {
int n = 3;
hanoi1(n);
System.out.println("============");
hanoi2(n);
System.out.println("============");
hanoi3(n);
}
}
package class11;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class Code02_PrintAllSubsquences {
public static List<String> subs(String s) {
char[] str = s.toCharArray();
String path = "";
List<String> ans = new ArrayList<>();
process1(str, 0, ans, path);
return ans;
}
// str固定,不变
// index此时来到的位置, 要 or 不要
// 如果index来到了str中的终止位置,把沿途路径所形成的答案,放入ans中
// 之前做出的选择,就是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);
}
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;
}
// str index 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);
}
public static void main(String[] args) {
String test = "aacc";
List<String> ans1 = subs(test);
List<String> ans2 = subsNoRepeat(test);
for (String str : ans1) {
System.out.println(str);
}
System.out.println("=================");
for (String str : ans2) {
System.out.println(str);
}
System.out.println("=================");
}
}
package class11;
import java.util.ArrayList;
import java.util.List;
public class Code03_PrintAllPermutations {
public static ArrayList<String> permutation(String str) {
ArrayList<String> res = new ArrayList<>();
if (str == null || str.length() == 0) {
return res;
}
char[] chs = str.toCharArray();
process(chs, 0, res);
return res;
}
// str[0..i-1]已经做好决定的
// str[i...]都有机会来到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++) { // j i后面所有的字符都有机会
swap(str, i, j);
process(str, i + 1, ans);
swap(str, i, j);
}
}
public static ArrayList<String> permutationNoRepeat(String str) {
ArrayList<String> res = new ArrayList<>();
if (str == null || str.length() == 0) {
return res;
}
char[] chs = str.toCharArray();
process2(chs, 0, res);
return res;
}
// str[0..i-1]已经做好决定的
// str[i...]都有机会来到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;
}
boolean[] visit = new boolean[26]; // visit[0 1 .. 25]
for (int j = i; j < str.length; j++) {
// str[j] = 'a' -> 0 visit[0] -> 'a'
// str[j] = 'z' -> 25 visit[25] -> 'z'
if (!visit[str[j] - 'a']) {
visit[str[j] - 'a'] = true;
swap(str, i, j);
process2(str, i + 1, res);
swap(str, i, j);
}
}
}
public static void swap(char[] chs, int i, int j) {
char tmp = chs[i];
chs[i] = chs[j];
chs[j] = tmp;
}
public static void main(String[] args) {
String s = "aac";
List<String> ans1 = permutation(s);
for (String str : ans1) {
System.out.println(str);
}
System.out.println("=======");
List<String> ans2 = permutationNoRepeat(s);
for (String str : ans2) {
System.out.println(str);
}
}
}
3.练习题
给你一个栈,请你逆序这个栈。
不能申请额外的数据结构,只能使用递归函数,如何实现
package class11;
import java.util.Stack;
public class Code04_ReverseStackUsingRecursive {
public static void reverse(Stack<Integer> stack) {
if (stack.isEmpty()) {
return;
}
int i = f(stack);
reverse(stack);
stack.push(i);
}
public static int f(Stack<Integer> stack) {
int result = stack.pop();
if (stack.isEmpty()) {
return result;
} else {
int last = f(stack);
stack.push(result);
return last;
}
}
public static void main(String[] args) {
Stack<Integer> test = new Stack<Integer>();
test.push(1);
test.push(2);
test.push(3);
test.push(4);
test.push(5);
reverse(test);
while (!test.isEmpty()) {
System.out.println(test.pop());
}
}
}
4.从左往右的尝试模型1
规定1和A对应、2和B对应、3和C对应…
那么一个数字字符串比如“111”就可以转化为:
“AAA”、“KA”和“AK”
给定一个只有数字字符组成的字符串str,返回有多少种转化结果
package class11;
public class Code06_ConvertToLetterString {
public static int number(String str) {
if (str == null || str.length() == 0) {
return 0;
}
return process(str.toCharArray(), 0);
}
// str[0...i-1]已经转化完了,固定了
// i之前的位置,如何转化已经做过决定了, 不用再关心
// i... 有多少种转化的结果
public static int process(char[] str, int i) {
if (i == str.length) { // base case
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);
if (i + 1 < str.length && (str[i + 1] >= '0' && str[i + 1] <= '6')) {
res += process(str, i + 2); // (i和i+1)作为单独的部分,后续有多少种方法
}
return res;
}
return process(str, i + 1);
}
public static int dpWays2(String s) {
if (s == null || s.length() == 0) {
return 0;
}
char[] str = s.toCharArray();
int N = str.length;
int[] dp = new int[N+1];
dp[N] = 1;
for(int i = N-1; i >= 0; i--) {
if (str[i] == '0') {
dp[i] = 0;
}
if (str[i] == '1') {
dp[i] = dp[i + 1];
if (i + 1 < str.length) {
dp[i] += dp[i + 2];
}
}
if (str[i] == '2') {
dp[i] = dp[i + 1];
if (i + 1 < str.length && (str[i + 1] >= '0' && str[i + 1] <= '6')) {
dp[i] += dp[i + 2]; // (i和i+1)作为单独的部分,后续有多少种方法
}
}
}
return dp[0];
}
public static int dpWays(String s) {
if (s == null || s.length() == 0) {
return 0;
}
char[] str = s.toCharArray();
int N = str.length;
int[] dp = new int[N + 1];
dp[N] = 1;
for (int i = N - 1; i >= 0; i--) {
if (str[i] == '0') {
dp[i] = 0;
} else if (str[i] == '1') {
dp[i] = dp[i + 1];
if (i + 1 < N) {
dp[i] += dp[i + 2];
}
} else if (str[i] == '2') {
dp[i] = dp[i + 1];
if (i + 1 < str.length && (str[i + 1] >= '0' && str[i + 1] <= '6')) {
dp[i] += dp[i + 2];
}
} else {
dp[i] = dp[i + 1];
}
}
return dp[0];
}
public static void main(String[] args) {
System.out.println(number("11111"));
System.out.println(dpWays2("11111"));
}
}
5.从左往右的尝试模型2
给定两个长度都为N的数组weights和values,
weights[i]和values[i]分别代表i号物品的重量和价值。
给定一个正数bag,表示一个载重bag的袋子,
你装的物品不能超过这个重量。
返回你能装下最多的价值是多少?
package class11;
public class Code07_Knapsack {
public static int getMaxValue(int[] w, int[] v, int bag) {
return process(w, v, 0, 0, bag);
}
// 不变 : w[] v[] bag
// index... 最大价值
// 0..index-1上做了货物的选择,使得你已经达到的重量是多少alreadyW
// 如果返回-1,认为没有方案
// 如果不返回-1,认为返回的值是真实价值
public static int process(int[] w, int[] v, int index, int alreadyW, int bag) {
if (alreadyW > bag) {
return -1;
}
// 重量没超
if (index == w.length) {
return 0;
}
int p1 = process(w, v, index + 1, alreadyW, bag);
int p2next = process(w, v, index + 1, alreadyW + w[index], bag);
int p2 = -1;
if (p2next != -1) {
p2 = v[index] + p2next;
}
return Math.max(p1, p2);
}
public static int maxValue(int[] w, int[] v, int bag) {
return process(w, v, 0, bag);
}
// 只剩下rest的空间了,
// index...货物自由选择,但是剩余空间不要小于0
// 返回 index...货物能够获得的最大价值
public static int process(int[] w, int[] v, int index, int rest) {
if (rest < 0) { // base case 1
return -1;
}
// rest >=0
if (index == w.length) { // base case 2
return 0;
}
// 有货也有空间
int p1 = process(w, v, index + 1, rest);
int p2 = -1;
int p2Next = process(w, v, index + 1, rest - w[index]);
if(p2Next!=-1) {
p2 = v[index] + p2Next;
}
return Math.max(p1, p2);
}
public static int dpWay(int[] w, int[] v, int bag) {
int N = w.length;
int[][] dp = new int[N + 1][bag + 1];
for (int index = N - 1; index >= 0; index--) {
for (int rest = 1; rest <= bag; rest++) {
dp[index][rest] = dp[index + 1][rest];
if (rest >= w[index]) {
dp[index][rest] = Math.max(dp[index][rest], v[index] + dp[index + 1][rest - w[index]]);
}
}
}
return dp[0][bag];
}
public static void main(String[] args) {
int[] weights = { 3, 2, 4, 7 };
int[] values = { 5, 6, 3, 19 };
int bag = 11;
System.out.println(maxValue(weights, values, bag));
System.out.println(dpWay(weights, values, bag));
}
}
6.范围上尝试的模型
给定一个整型数组arr,代表数值不同的纸牌排成一条线,玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。请返回最后获胜者的分数。
package class11;
public class Code08_CardsInLine {
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)
);
}
// L....R
// F S L+1..R
// L..R-1
public static int f(int[] arr, int L, int R) {
if (L == R) {
return arr[L];
}
return Math.max(
arr[L] + s(arr, L + 1, R),
arr[R] + s(arr, L, R - 1)
);
}
// arr[L..R]
public static int s(int[] arr, int L, int R) {
if (L == R) {
return 0;
}
return Math.min(
f(arr, L + 1, R), // arr[i]
f(arr, L, R - 1) // arr[j]
);
}
public static int win2(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
int N = arr.length;
int[][] f = new int[N][N];
int[][] s = new int[N][N];
for(int i = 0; i < N;i++) {
f[i][i] = arr[i];
}
// s[i][i] = 0;
for(int i = 1; i < N;i++) {
int L =0;
int R =i;
while(L < N && R < N) {
f[L][R] = Math.max(
arr[L] + s[L + 1][ R],
arr[R] + s[L][R - 1]
);
s[L][R] = Math.min(
f[L + 1][R], // arr[i]
f[L][R - 1] // arr[j]
);
L++;
R++;
}
}
return Math.max(f[0][N-1], s[0][N-1]);
}
public static void main(String[] args) {
int[] arr = { 4,7,9,5,19,29,80,4 };
// A 4 9
// B 7 5
System.out.println(win1(arr));
System.out.println(win2(arr));
}
}
7.N皇后问题
package class11;
public class Code09_NQueens {
public static int num1(int n) {
if (n < 1) {
return 0;
}
// record[0] ? record[1] ? record[2]
int[] record = new int[n]; // record[i] -> i行的皇后,放在了第几列
return process1(0, record, n);
}
// 潜台词:record[0..i-1]的皇后,任何两个皇后一定都不共行、不共列,不共斜线
// 目前来到了第i行
// record[0..i-1]表示之前的行,放了的皇后位置
// n代表整体一共有多少行 0~n-1行
// 返回值是,摆完所有的皇后,合理的摆法有多少种
public static int process1(int i, int[] record, int n) {
if (i == n) { // 终止行
return 1;
}
// 没有到终止位置,还有皇后要摆
int res = 0;
for (int j = 0; j < n; j++) { // 当前行在i行,尝试i行所有的列 -> j
// 当前i行的皇后,放在j列,会不会和之前(0..i-1)的皇后,不共行共列或者共斜线,
// 如果是,认为有效
// 如果不是,认为无效
if (isValid(record, i, j)) {
record[i] = j;
res += process1(i + 1, record, n);
}
}
return res;
}
// record[0..i-1]你需要看,record[i...]不需要看
// 返回i行皇后,放在了j列,是否有效
public static boolean isValid(int[] record, int i, int j) {
for (int k = 0; k < i; k++) { // 之前的某个k行的皇后
// k, record[k] i, j
if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) {
return false;
}
}
return true;
}
// 请不要超过32皇后问题
public static int num2(int n) {
if (n < 1 || n > 32) {
return 0;
}
// 如果你是13皇后问题,limit 最右13个1,其他都是0
int limit = n == 32 ? -1 : (1 << n) - 1;
return process2(limit, 0, 0, 0);
}
// limit 划定了问题的规模 -> 固定
// colLim 列的限制,1的位置不能放皇后,0的位置可以
// leftDiaLim 左斜线的限制,1的位置不能放皇后,0的位置可以
// rightDiaLim 右斜线的限制,1的位置不能放皇后,0的位置可以
public static int process2(
int limit,
int colLim,
int leftDiaLim,
int rightDiaLim) {
if (colLim == limit) { // base case
return 1;
}
// 所有可以放皇后的位置,都在pos上
// colLim | leftDiaLim | rightDiaLim -> 总限制
// ~ (colLim | leftDiaLim | rightDiaLim) -> 左侧的一坨0干扰,右侧每个1,可尝试
int pos = limit & ( ~(colLim | leftDiaLim | rightDiaLim) );
int mostRightOne = 0;
int res = 0;
while (pos != 0) {
// 其取出pos中,最右侧的1来,剩下位置都是0
mostRightOne = pos & (~pos + 1);
pos = pos - mostRightOne;
res += process2(limit,
colLim | mostRightOne,
(leftDiaLim | mostRightOne) << 1,
(rightDiaLim | mostRightOne) >>> 1);
}
return res;
}
public static void main(String[] args) {
int n = 15;
long start = System.currentTimeMillis();
System.out.println(num2(n));
long end = System.currentTimeMillis();
System.out.println("cost time: " + (end - start) + "ms");
start = System.currentTimeMillis();
System.out.println(num1(n));
end = System.currentTimeMillis();
System.out.println("cost time: " + (end - start) + "ms");
}
}