# 【数据结构与算法】暴力递归实战入门

## 2. 汉诺塔问题

1 ~ (i-1) 的盘子移动的过程如何保证大盘子在小盘子之下？

``````public static void hanoi(int n) {
process(n, "左", "右", "中");
}

/**
* 尝试方法
* @param i 1~i 的圆盘
* @param from 移动起始杆
* @param to 移动目的杆
* @param other 另外一个杆
*/
public static void process(int i, String from, String to, String other) {
// base case 当只有一个圆盘时，该圆盘放哪个杆都可以
if (i == 1) {
System.out.println("Move 1 from " + from + " to " + to);
return ;
}
process(i - 1, from, other, to);
System.out.println("Move " + i + " from " + from + " to " + to);
process(i - 1, other, to, from);
}

## 3. 字符串子序列

``````public static void printAllSubStr(String str) {
char[] charSequence = str.toCharArray();
process(charSequence, 0, new ArrayList<Character>());
}

/**
* 尝试方法
* @param charSequence 字符串拆解的字符序列
* @param i 当前字符在字符序列中的下标
* @param chars 当前通路已经选择的字符
*/
public static void process(char[] charSequence, int i, List<Character> chars) {
// 当尝试到通路最后时，直接输出通路所有字符序列组成的字符串
if (i == charSequence.length) {
printList(chars);
return ;
}
// 添加当前字符往下走
// 不添加当前字符往下走
}

// 打印List
public static void printList(List<Character> chars) {
for (Character character : chars) {
System.out.print(character);
}
System.out.println();
}

// 拷贝List
public static List<Character> copyList(List<Character> chars) {
return new ArrayList<>(chars);
}

``````public static void printAllSubStr(String str) {
char[] charSequence = str.toCharArray();
process(charSequence, 0);
}

public static void process(char[] charSequence, int i) {
if (i == charSequence.length) {
// 当尝试到通路最后时，直接输出通路所有字符序列组成的字符串
System.out.println(String.valueOf(charSequence));
return ;
}
// 添加当前字符往下走
process(charSequence, i + 1);
// 在原字符序列上将当前字符抹去
char tmp = charSequence[i];
charSequence[i] = 0;
// 不添加当前字符往下走
process(charSequence, i + 1);
// 还原原字符序列
charSequence[i] = tmp;
}

## 4. 字符串排列

``````public static void printAllArrange(String str) {
char[] charSequence = str.toCharArray();
ArrayList<String> list = new ArrayList<>();
process(charSequence, 0, list);
// 打印
for (String string : list) {
System.out.println(string);
}
}

public static void process(char[] charSequence, int i, ArrayList<String> list) {
// 如果当前遍历到字符序列末尾，存储当前字符序列，并打印
if (i == charSequence.length) {
return ;
}
// 遍历当前字符后的每一个字符
for (int j = i; j < charSequence.length; ++ j) {
// 字符和当前字符交换
swap(charSequence, i, j);
// 递归到下一个字符
process(charSequence, i + 1, list);
// 还原字符串
swap(charSequence, j, i);
}
}

public static void swap(char[] charSequence, int i, int j) {
char temp = charSequence[i];
charSequence[i] = charSequence[j];
charSequence[j] = temp;
}

1. 使用上述代码，在得到所有结果后洗数据，将重复数据洗掉。
2. 使用下面代码，在尝试换字符时，判断目标替换字符有没有在当前位置出现过，如果出现过，就不能交进行交换。

``````public static void process(char[] charSequence, int i, ArrayList<String> list) {
if (i == charSequence.length) {
return ;
}
// 记录每一个字符(a-z)是否尝试过,默认全是false
boolean[] flag = new boolean[26];
// 遍历当前字符后的每一个字符
for (int j = i; j < charSequence.length; ++ j) {
int index = charSequence[j] - 'a';
// 判断是否在该位置尝试过该字符串
if (!flag[index]) {
swap(charSequence, i, j);
process(charSequence, i + 1, list);
swap(charSequence, j, i);
// 记录已经尝试
flag[index] = true;
}
}
}

## 5. 最长递增子序列

``````public static int longestIncreasingSubSequence(int[] arr, int begin) {
if (begin == arr.length - 1) {
return 1;
}
int longestLength = 1;
for (int i = begin; i < arr.length; i ++) {
if (arr[i] > arr[begin]) {
longestLength = Math.max(longestIncreasingSubSequence(arr, i) + 1, longestLength);
}
}
return longestLength;
}

## 6. 拿牌问题

arr = [1, 2, 100, 4]

``````public static int win(int[] arr) {
// base case
if (arr == null || arr.length == 1) {
return 0;
}
// 先手的是玩家A,后手的玩家B,谁大谁赢
return Math.max(offensive(arr, 0, arr.length - 1), defensive(arr, 0, arr.length - 1));
}

// 先手函数
public static int offensive(int[] arr, int left, int right) {
// 在先手的情况下只有一张牌,直接拿
if (left == right) {
return arr[left];
}
// 尝试拿左边牌和拿右边牌,然后后手
return Math.max(arr[left] + defensive(arr, left + 1, right), arr[right] + defensive(arr, left, right - 1));
}

// 后手函数
public static int defensive(int[] arr, int left, int right) {
// 在后手的情况下只有一张牌,无牌可拿
if (left == right) {
return 0;
}
// 分别在两种情况中先手,选择小的那个是因为对手肯定把差的牌给我
return Math.min(offensive(arr, left + 1, right), offensive(arr, left, right - 1));
}

## 7. 逆序栈

``````public static void reverseStack(Stack<Integer> stack) {
// base case
if (stack.isEmpty()) {
return ;
}

// 保存当前处理的栈底元素
int item = process(stack);
// 进行下一次处理
reverseStack(stack);

// 将保存的栈底元素压回栈中
stack.push(item);
}

// 该函数完成了取出栈底元素，并保持栈中其他元素位置不变的作用
public static int process(Stack<Integer> stack) {
// 弹栈，并保存栈顶元素
int item = stack.pop();
// 当栈空的时候，最后一个栈顶元素不压栈，直接返回
if (stack.isEmpty()) {
return item;
}
// 获取最后一个栈顶元素
int lastItem = process(stack);
// 将其他栈顶元素再压回栈中
stack.push(item);
// 返回最后一个栈顶元素
return lastItem;
}

## 8. 数字字符串转化

``````public static int transformStrCount(String str) {
return process(str.toCharArray(), 0);
}

// i表示从第i位开始转化
public static int process(char[] chars, int i) {
// str的最后一位也被转化完成，是一种可行的转化方案
if (i == chars.length) {
return 1;
}

// 转化到第i位的字符是0，没有对应字母与之对应，str无法被转换，该转化方案不可行
if (chars[i] == '0') {
return 0;
}

// 转化到第i位的字符是1
if (chars[i] == '1') {
// 单独转化第i位，构成一种方案
int count = process(chars, i + 1);

// 判断第i位后面一个字符是否存在
if (i + 1 < chars.length) {
// 如果存在，可以将第i位和后面一个字符合并转化，构成一种方案
count += process(chars, i + 2);
}

return count;
}

// 转化到第i位的字符是2
if (chars[i] == '2') {
// 单独转化第i位，构成一种方案
int count = process(chars, i + 1);

// 判断第i位后面的字符是否存在，，如果存在是否小与等于6
if (i + 1 < chars.length && chars[i + 1] >= '0' && chars[i + 1] <= '6') {
// 如果存在且小于6，可以将第i位后后面一个在字符合并转化，构成一种方案
count += process(chars, i + 2);
}

return count;
}

// 转化到第i位的字符是3-9，只能单独转化第i位
return process(chars, i + 1);
}

## 9. 背包问题

``````public static int knapsackProblem(int[] weights, int[] values, int bag) {
return process(weights, values, bag, 0, 0);
}

/**
* 对当前第i号物品做决策
* @param weights 所有物品重量
* @param values 所有物品价值
* @param bag 袋子最大重量
* @param i 当前第i号物品
* @return 最大价值
*/
public static int process(int[] weights, int[] values, int bag, int alreadyWeight, int i) {
// 如果所有物品尝试完
if (i == weights.length) {
return 0;
}

// 如果当前袋子超重
return 0;
}

// 放入第i号物品和不放入产生的价值大的返回
return Math.max(
// 将第i号物品放入袋子中
values[i] + process(weights, values, bag, alreadyWeight + weights[i], i + 1),
// 不将第i号物品放入袋子中
process(weights, values, bag, alreadyWeight, i + 1)
);

}

``````public static int knapsackProblem(int[] weights, int[] values, int bag) {
return process(weights, values, bag, 0, 0, 0);
}

public static int process(int[] weights, int[] values, int bag, int alreadyWeight, int alreadyValue, int i) {
// 所有物品尝试完
if (i == weights.length) {
}

// 如果当前袋子超重
return 0;
}

return Math.max(
// 将第i号物品放入袋子中
process(weights, values, bag, alreadyWeight - weights[i], alreadyValue + values[i], i + 1),
// 不将第i号物品放入袋子中
);
}

## 10. N皇后问题

N皇后问题是指在N * N的棋盘上要摆N个皇后，要求任何两个皇后不同行、不同列，也不在同一条斜线上。

``````public static int nQueen(int n) {
if (n < 1) {
return 0;
}

int[] record = new int[n];

return process(n, record, 0);
}

/**
* @param n n行n列
* @param record record[i] = j 表示一个Queen在第i行第j列
* @param i 当前做决策的行
* @return 摆放方法数
*/
public static int process(int n, int[] record, int i) {
// 所有行都已经做完决策，是一种合法方案
if (i == n) {
return 1;
}

int result = 0;

// 遍历第i行所有列，判断能否放Queen
for (int j = 0; j < n; j ++) {
if (isValid(record, i, j)) {
// 放皇后
record[i] = j;
// 去下一行做决策
result += process(n, record, i + 1);
}
}

return result;
}

// 判断第i行第j列放一个Queen是否合法
public static boolean isValid(int[] record, int i, int j) {
// 0 ~ i-1 行放的所有Queen不共列，不共斜线
for (int k = 0; k < record.length; k ++) {
if (record[k] == j || Math.abs(k - i) == Math.abs(record[k] - j)) {
return false;
}
}
return true;
}