暴力递归就是尝试
- 把问题转化为规模缩小了的同类问题的子问题
- 有明确的不需要继续进行递归的条件(base case)
- 有当得到了子问题的结果之后的决策过程
- 不记录每一个子问题的解
题目一
打印一个字符串的全部子序列,包括空字符串
public static void main(String[] args){
String test = "abc";
printAllSubsquence(test);
System.out.println("=====================");
char[] chs2 = new char[]{'f', 0, 'g'};
System.out.println(String.valueOf(chs2));//输出是fg也就是0给去掉了
for (char i : chs2){
System.out.println(i);
//是 f
//
// g
System.out.print(i);
//是fg
}
System.out.println("=====================");
}
public static void printAllSubsquence(String str) {
char[] chs = str.toCharArray();
process(chs, 0);
process2(chs, 0, new ArrayList<Character>());
}
//法一:就像是二叉树一样,分为要这个节点和不要这个节点,直到到达长度
public static void process(char[] chs, int i) {
if (i == chs.length){
System.out.println(String.valueOf(chs));
return;
}
//要了该节点
process(chs, i + 1);
char tmp = chs[i];
//将该节点置空
chs[i] = 0;
process(chs, i + 1);
chs[i] = tmp;
}
//法二,一样的思路,空间复杂度高些
public static void process2(char[] chs, int i, List<Character> res) {
if (i == chs.length){
printList(res);
}
List<Character> resKeep = copyList(res);
resKeep.add(chs[i]);
process2(chs, i + 1, resKeep);
List<Character> resNoInclude = copyList(res);
process2(chs, i + 1, resNoInclude);
}
public static List<Character> copyList(List<Character> res) {
return null;
}
public static void printList(List<Character> res) {
}
题目二
输入一个字符串,打印出该字符串中字符的所有排列。
public static void main(String[] args) {
String test = "abc";
ArrayList<String> result = Permutation(test);
for (String i : result){
System.out.println(i);
}
}
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;
}
//核心代码:每一个字符都有机会交换到i位置
public static void process(char[] chs, int i, ArrayList<String> res){
if (i == chs.length){
res.add(String.valueOf(chs));
}
//得定义在这里,因为它是比较i之后是不是重复交换,每个i都不同
boolean[] visit = new boolean[26];//默认值全false
//去重,推荐在分支的过程中就去重,比完成之后再去重快
for (int j = i; j < chs.length; j++){
if (!visit[chs[j] - 'a']){
visit[chs[j] - 'a'] = true;
swap(chs, i, j);
process(chs, i + 1, res);
swap(chs, i ,j);
}
}
}
public static void swap(char[] chs, int i, int j){
char tmp = chs[i];
chs[i] = chs[j];
chs[j] = tmp;
}
题目三
给定一个整型数组arr,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A 和玩家B都绝顶聪明。请返回最后获胜者的分数。
arr=[1,2,100,4]。 开始时,玩家A只能拿走1或4。如果开始时玩家A拿走1,则排列变为[2,100,4],接下来 玩家 B可以拿走2或4,然后继续轮到玩家A...
如果开始时玩家A拿走4,则排列变为[1,2,100],接下来玩家B可以拿走1或100,然后继续轮到玩家A... 玩家A作为绝顶聪明的人不会先拿4,因为拿4之后,玩家B将拿走100。所以玩家A会先拿1, 让排列变为[2,100,4],接下来玩家B不管怎么选,100都会被玩家 A拿走。玩家A会获胜, 分数为101。所以返回101。
arr=[1,100,2]。 开始时,玩家A不管拿1还是2,玩家B作为绝顶聪明的人,都会把100拿走。玩家B会获胜, 分数为100。所以返回100。
//法一:整体思路是分为先手函数和后手函数,先手函数是我先拿,
//后手函数是我后拿,先手和后手相互嵌套拿到
public static int win1(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
//返回A先拿,和B后拿,看谁大返回谁
return Math.max(f(arr, 0, arr.length - 1), s(arr, 0, arr.length - 1));
}
//因为是先手函数,所以返回的是取了这个值之后+后手函数是最大的
public static int f(int[] arr, int i, int j) {
//base case,只剩一个值的话,只能拿了
if (i == j) {
return arr[i];
}
//先手函数返回的是,现在的先手,加上在(i + 1, j)区间上的后手。。返回最大值
return Math.max(arr[i] + s(arr, i + 1, j), arr[j] + s(arr, i, j - 1));
}
//因为是后手函数,所以轮到我i + 1 ~ j 或者 i ~ j-1上我先手了,
//但是我在那个地方先手,是对方决定的,对方导致了我取min,毕竟我后手
public static int s(int[] arr, int i, int j) {
//base case只剩一个值,因为是后手所以没法拿
if (i == j) {
return 0;
}
return Math.min(f(arr, i + 1, j), f(arr, i, j - 1));
}
//动态规划,之后讲
public static int win2(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
int[][] f = new int[arr.length][arr.length];
int[][] s = new int[arr.length][arr.length];
for (int j = 0; j < arr.length; j++) {
f[j][j] = arr[j];
for (int i = j - 1; i >= 0; i--) {
f[i][j] = Math.max(arr[i] + s[i + 1][j], arr[j] + s[i][j - 1]);
s[i][j] = Math.min(f[i + 1][j], f[i][j - 1]);
}
}
return Math.max(f[0][arr.length - 1], s[0][arr.length - 1]);
}
public static void main(String[] args) {
int[] arr = { 1, 9, 1 };
System.out.println(win1(arr));
System.out.println(win2(arr));
}
题目四
给你一个栈,请你逆序这个栈,不能申请额外的数据结构,只能使用递归函数。 如何实现?
//整体思路,将栈中的都弹出来,反着压回去
public static void reverse(Stack<Integer> stack) {
if (stack.isEmpty()) {
return;
}
//取出当前栈底的元素
int i = getAndRemoveLastElement(stack);
reverse(stack);
//最后压入的是最先取出来的
stack.push(i);
}
//依据这个算法可以将栈中的最后一个元素取出来
//5 4 3 2 1->5 4 3 2 取出了栈底的1
public static int getAndRemoveLastElement(Stack<Integer> stack) {
int result = stack.pop();
if (stack.isEmpty()) {
return result;
} else {
int last = getAndRemoveLastElement(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.print(test.pop() + " ");
}
}
题目五
规定1和A对应、2和B对应、3和C对应... 那么一个数字字符串比如"111",就可以转化为"AAA"、"KA"和"AK"。 给定一个只有数字字符组成的字符串str,返回有多少种转化结果。
相关的分析:如果当前字符压住了0,那么整个流程转化结果数是0。如果当前字符压住了1,那么可以分为1单独压住,后面的去看转化数,或者1和后面的一起压住,后面的去看转化数。如果当前字符压住了2,那么可以分为2单独压住,后面去看转化数,或者2和后面的(06)一起压住,后面的去看转化数。如果当前字符压住了39就只能单独一组,后面去看转化数。
public static int number(String str) {
if (str == null || str.length() == 0) {
return 0;
}
return process(str.toCharArray(), 0);
}
public static int process(char[] chs, int i) {
//到最后了,返回转化数1
if (i == chs.length) {
return 1;
}
//压中了0,整个流程转化数为0
if (chs[i] == '0') {
return 0;
}
if (chs[i] == '1') {
int res = process(chs, i + 1);
if (i + 1 < chs.length) {
res += process(chs, i + 2);
}
return res;
}
if (chs[i] == '2') {
int res = process(chs, i + 1);
if (i + 1 < chs.length && (chs[i + 1] >= '0' && chs[i + 1] <= '6')) {
res += process(chs, i + 2);
}
return res;
}
//数字在3~9之间
return process(chs, i + 1);
}
public static void main(String[] args) {
System.out.println(number("11111"));
}
题目六
给定两个长度都为N的数组weights和values,weights[i]和values[i]分别代表 i号物品的重量和价值。给定一个正数bag,表示一个载重bag的袋子,你装的物 品不能超过这个重量。返回你能装下最多的价值是多少?
public static int maxValue1(int[] weights, int[] values, int bag) {
return process1(weights, values, 0, 0, bag);
}
//整体思路:这个袋子可以要,也可以不要
public static int process1(int[] weights, int[] values, int i, int alreadyweight, int bag) {
//重量大于bag,返回0
if (alreadyweight > bag) {
return 0;
}
//到达数组的长度,返回0,和上一题不同是因为上一题是返回的方法数(到头才算一种),这里是价值总量(随时累加)
if (i == weights.length) {
return 0;
}
return Math.max(
//不要当前袋子
process1(weights, values, i + 1, alreadyweight, bag),
//要当前的袋子
values[i] + process1(weights, values, i + 1, alreadyweight + weights[i], bag));
}
//另一种思路,但是方法体中参数比上一种的多,所以选第一种(我们希望可变参数形式最简单,数量最少的方法,固定参数不管)
public static int process2(int[] weights, int[] values, int i, int alreadyWeight, int alreadyValue, int bag){
if (alreadyWeight > bag){
return 0;
}
if (i == values.length){
return alreadyValue;
}
return Math.max(process2(weights,values,i + 1, alreadyWeight, alreadyValue, bag),
process2(weights, values, i + 1, alreadyWeight + weights[i], alreadyValue
+ values[i], bag));
}
//动态规划
public static int maxValue2(int[] c, int[] p, int bag) {
int[][] dp = new int[c.length + 1][bag + 1];
for (int i = c.length - 1; i >= 0; i--) {
for (int j = bag; j >= 0; j--) {
dp[i][j] = dp[i + 1][j];
if (j + c[i] <= bag) {
dp[i][j] = Math.max(dp[i][j], p[i] + dp[i + 1][j + c[i]]);
}
}
}
return dp[0][0];
}
public static void main(String[] args) {
int[] weights = { 3, 2, 4, 7 };
int[] values = { 5, 6, 3, 19 };
int bag = 11;
System.out.println(maxValue1(weights, values, bag));
System.out.println(maxValue2(weights, values, bag));
}
题目七
N皇后问题是指在N*N的棋盘上要摆N个皇后,要求任何两个皇后不同行、不同列, 也不在同一条斜线上。 给定一个整数n,返回n皇后的摆法有多少种。
public static int num1(int n) {
//在n x n的棋盘中,N皇后要求放置N个棋子
// ,每个棋子要求不共列,不共行,不共斜线
if (n < 1) {
return 0;
}
//record[]数组中记录着每一行放到了第几列上了
int[] record = new int[n];
//传入放置的行数,record[]数组,N行
return process1(0, record, n);
}
//核心代码:这里的i应该是i- 1行(从0开始的话)
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++) {
//如果当前行的这一列可以放置,就在record[]数组中记录,在进行下一次的递归
if (isValid(record, i, j)) {
record[i] = j;
res += process1(i + 1, record, n);
}
}
return res;
}
//判断某一行
public static boolean isValid(int[] record, int i, int j) {
for (int k = 0; k < i; k++) {
//依次检查0~i行之前已经用过的列数,要求不同
//行数一定是不同的
//斜线可以比较两者之间的斜率
if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) {
return false;
}
}
return true;
}
public static int num2(int n) {
//位运算只满足32位之内的N皇后问题,因为int就是32位,更多的话可以用long
if (n < 1 || n > 32) {
return 0;
}
//是几皇后的问题,就要求32位中后几位是1,其他都是0
int upperLim = n == 32 ? -1 : (1 << n) - 1;
return process2(upperLim, 0, 0, 0);
}
public static int process2(int upperLim, int colLim, int leftDiaLim,
int rightDiaLim) {
//如果列数也是后N位全是1,那么说明全部走完了,返回这一种
if (colLim == upperLim) {
return 1;
}
int pos = 0;
int mostRightOne = 0;
//pos中1的位置就是可以放的位置
pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim));
int res = 0;
while (pos != 0) {
//取出最右边的第一个1
mostRightOne = pos & (~pos + 1);
//这个位置可以填后,去掉这个位置,依次再循环
pos = pos - mostRightOne;
//下一行不能占的位置是列的位置和相应位置的左移和右移,十分厉害的想法
res += process2(upperLim, colLim | mostRightOne,
(leftDiaLim | mostRightOne) << 1,
(rightDiaLim | mostRightOne) >>> 1);
}
return res;
}
public static void main(String[] args) {
int n = 14;
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");
}