目录
【案例1 尼姆博弈问题】
【题目描述】
【思路解析】
【代码实现】
【案例2 k伪进制问题】
【题目描述】
【思路解析】
【代码实现】
【案例3 最大路径和】
【题目描述】
【思路解析】
【代码实现】
【案例4 优先级的递归套路】
【题目描述】
【思路解析】
【代码实现】
【案例5 子串的递归套路 动态规划的空间压缩技巧】
【题目描述】
【思路解析】
【代码实现】
【案例6 子序列的递归问题】
【问题描述】
【思路解析】
【代码实现】
大家觉得写得可以的话,可以加入QQ群907575059.
异或和 指初始情况下,所有的铜板堆数量的异或和。
如果有1堆铜板,先手必赢,此时异或和不为0.
如果有2堆铜板,(1)两堆铜板数量相同,先手必输,此时异或和为0(2)两堆铜板数量不同,先手必胜,此时异或和不为0.
如果继续下去,我们可以发现我们最后怎么才能赢,就是拿过之后,必须保证异或和为0。当拿空时,异或和也为0。所以可以得出结论,当初始时异或和不为0,先手赢,但是如果硬币异或和不为0,后手赢。
/**
* @ProjectName: study3
* @FileName: Ex1
* @author:HWJ
* @Data: 2023/9/23 16:46
* 尼姆博弈问题
*/
public class Ex1 {
public static void main(String[] args) {
}
public static void getWin(int[] num){
int xor = num[0];
for (int i = 1; i < num.length; i++) {
xor ^= num[i];
}
if (xor == 0){
System.out.println("后手赢!");
}else {
System.out.println("先手赢!");
}
}
}
k伪进制和k进制的区别就在于它每个位置上必须有值,值的范围在 [1,k]之间,所以有个普遍想法就是先开始每个位置上尽量放1(从右至左),直到放不了了,然后从那个位置开始往右重新填数。
假如将103用7伪进制表示:1 1 1,只能填三个位置,剩下46,然后用这三个位置继续表示46即可。
package AdvancedPromotion4;
import java.util.HashMap;
/**
* @ProjectName: study3
* @FileName: Ex2
* @author:HWJ
* @Data: 2023/9/23 16:56
*/
public class Ex2 {
public static void main(String[] args) {
char[] chars = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
kConversion(123, chars);
}
public static void kConversion(int num, char[] chars) {
int len = chars.length;
HashMap map = new HashMap<>();
int max = 0;
for (int i = 0; Math.pow(len, i) <= num; i++) {
num -= (int) Math.pow(len, i);
map.put(i, 1);
max = i;
}
for (int i = max; i >= 0; i--) {
int a = num / (int) Math.pow(len, i);
num %= (int) Math.pow(len, i);
map.put(i, map.get(i) + a);
}
StringBuilder str = new StringBuilder();
for (int i = max; i >= 0; i--) {
str.append(chars[map.get(i) - 1]);
}
System.out.println(str);
}
}
它不一定要达到最右边才会得到它的最长长度,即它可能在二维数组的任一位置得到最长长度。遍历数组每一个位置得到信息(包括没使用能力的最大长度,和使用能力的最大长度)。
但是每个位置又依靠它的左上位置,,左位置,左下位置,则需要使用递归。可以改为记忆化搜索和动态规划的版本。此题因为不存在枚举行为,所以记忆化搜索和动态规划的时间复杂度和空间复杂度相似。
package AdvancedPromotion4;
/**
* @ProjectName: study3
* @FileName: Ex3
* @author:HWJ
* @Data: 2023/9/23 20:12
*/
public class Ex3 {
public static void main(String[] args) {
int[][] matrix = {{1, -4, 10}, {3, -2, -1}, {2, -1, 0}, {0, 5, -2}};
System.out.println(getMaxLength1(matrix));
}
public static class Info {
public int use;
public int no;
public Info(int no, int use) {
this.use = use;
this.no = no;
}
}
// 递归版本
public static int getMaxLength1(int[][] matrix) {
int res = Integer.MIN_VALUE;
for (int j = 0; j < matrix[0].length; j++) {
for (int i = 0; i < matrix.length; i++) {
Info cur = process1(matrix, i, j);
int ans = Math.max(cur.no, cur.use);
res = Math.max(ans, res);
}
}
return res;
}
// 递归版本
public static Info process1(int[][] matrix, int i, int j) {
if (j == 0) {
return new Info(matrix[i][j], -matrix[i][j]);
}
int preNo = -1;
int preUse = -1;
Info preLeft = process1(matrix, i, j - 1);
if (preLeft.no >= 0) {
preNo = preLeft.no;
}
if (preLeft.use >= 0) {
preUse = preLeft.use;
}
if (i > 0) {
Info preUp = process1(matrix, i - 1, j - 1);
if (preUp.no >= 0) {
preNo = Math.max(preUp.no, preNo);
}
if (preUp.use >= 0) {
preUse = Math.max(preUse, preUp.use);
}
}
if (i < matrix.length - 1) {
Info preDown = process1(matrix, i + 1, j - 1);
if (preDown.no >= 0) {
preNo = Math.max(preDown.no, preNo);
}
if (preDown.use >= 0) {
preUse = Math.max(preUse, preDown.use);
}
}
int use = -1;
int no = -1;
if (preUse >= 0) {
use = preUse + matrix[i][j];
}
if (preNo >= 0) {
no = preNo + matrix[i][j];
use = Math.max(use, preNo - matrix[i][j]);
}
return new Info(no, use);
}
// 记忆化搜索版本
public static int getMaxLength2(int[][] matrix) {
int res = Integer.MIN_VALUE;
Info[][] map = new Info[matrix.length][matrix[0].length];
for (int j = 0; j < matrix[0].length; j++) {
for (int i = 0; i < matrix.length; i++) {
Info cur = process2(matrix, i, j, map);
int ans = Math.max(cur.no, cur.use);
res = Math.max(ans, res);
}
}
return res;
}
// 记忆化搜索版本版本
public static Info process2(int[][] matrix, int i, int j, Info[][] map) {
if (map[i][j] != null) { // 如果已经得到信息的地方,之间调用之前的信息
return map[i][j];
}
if (j == 0) {
map[i][j] = new Info(matrix[i][j], -matrix[i][j]);
return map[i][j];
}
int preNo = -1;
int preUse = -1;
Info preLeft = process2(matrix, i, j - 1, map);
if (preLeft.no >= 0) {
preNo = preLeft.no;
}
if (preLeft.use >= 0) {
preUse = preLeft.use;
}
if (i > 0) {
Info preUp = process2(matrix, i - 1, j - 1, map);
if (preUp.no >= 0) {
preNo = Math.max(preUp.no, preNo);
}
if (preUp.use >= 0) {
preUse = Math.max(preUse, preUp.use);
}
}
if (i < matrix.length - 1) {
Info preDown = process2(matrix, i + 1, j - 1, map);
if (preDown.no >= 0) {
preNo = Math.max(preDown.no, preNo);
}
if (preDown.use >= 0) {
preUse = Math.max(preUse, preDown.use);
}
}
int use = -1;
int no = -1;
if (preUse >= 0) {
use = preUse + matrix[i][j];
}
if (preNo >= 0) {
no = preNo + matrix[i][j];
use = Math.max(use, preNo - matrix[i][j]);
}
map[i][j] = new Info(no, use);
return map[i][j];
}
}
先用栈实现不含括号的表达式求值,如果遇到运算符,且此时栈顶的运算符不为乘,除,就将运算符之前的数值和运算符加入栈,否则弹出运算符和运算符之前的数值,并且将其和当前数值进行运算后,在再将运算的值和当前运算符加入栈。
实现这个后我们考虑一个信息结构process(str,i)返回两个数值a,b a表示从i开始到停止地方得到的运算结果,b表示它在哪里停止。遇到右括号或者到边界就停止。
package AdvancedPromotion4;
import java.util.LinkedList;
/**
* @ProjectName: study3
* @FileName: Ex4
* @author:HWJ
* @Data: 2023/9/23 20:49
*/
public class Ex4 {
public static void main(String[] args) {
String str = "48*((70-65)-43)+8*1";
System.out.println(getNum(str));
}
public static int getNum(String str) {
return process(str, 0)[0];
}
// 将每一个括号里面包含的运算式记作为一个整体进行计算。
public static int[] process(String str, int index) {
LinkedList list = new LinkedList<>();
int num = 0;
while (index < str.length() && str.charAt(index) != ')') {
if (str.charAt(index) >= '0' && str.charAt(index) <= '9') {
num = num * 10 + str.charAt(index) - '0';
index++;
} else if (str.charAt(index) == '(') { // 这里遇到左括号
int[] ans = process(str, index + 1);
index = ans[1] + 1; // 此次运算的结束位置
num = ans[0]; // 括号里运算式总体的运算结果。
} else { // 遇到运算符
addNum(list, num);
list.addLast(String.valueOf(str.charAt(index++)));
num = 0;
}
}
addNum(list, num);
return new int[]{totalNum(list), index};
}
// 当栈顶运算符为乘或除时,暂时对运算式进行计算。
public static void addNum(LinkedList list, int num) {
if (!list.isEmpty()) {
int cur = 0;
String operator = list.pollLast();
if (operator.equals("+") || operator.equals("-")) {
list.addLast(operator);
} else {
cur = Integer.valueOf(list.pollLast());
num = operator.equals("*") ? num * cur : cur / num;
}
}
list.addLast(String.valueOf(num));
}
// 处理完后整个栈里只剩下数值和加减运算符
public static int totalNum(LinkedList list) {
int res = 0;
boolean add = true;
String cur = null;
int num = 0;
while (!list.isEmpty()) {
cur = list.pollFirst();
if (cur.equals("+")) {
add = true;
}else if(cur.equals("-")){
add = false;
}else {
num = Integer.valueOf(cur);
res += add ? num : (-num);
}
}
return res;
}
}
因为子串一定是在字符串中连续的字符子串。两个字符串的最长公共子串可能以str1的任意位置结尾和str2的任意位置结尾,但是因为是公共子串则要求两个的对应位置字符相同,所以如果str1以i结尾和str2以j结尾的公共子串长度取决于str1以i - 1结尾和str2以j - 1结尾的公共子串长度 + 1.(str1以i结尾和str2以j结尾的字符相同)
根据标红的对应关系可知,dp[i][j]只依赖与dp[i-1][j-1],所以我们可以不必要创建一个二维数组来进行dp。例如如下规则来对动态规划空间进行压缩。
10 | 7 | 4 | 2 | 1 |
13 | 11 | 8 | 5 | 3 |
15 | 14 | 12 | 9 | 6 |
以上数字代表优化后的填表技巧,当填3时,我们只记录2,填4时只记录3.将整个表优化为一个变量。
package AdvancedPromotion4;
/**
* @ProjectName: study3
* @FileName: Ex5
* @author:HWJ
* @Data: 2023/9/23 21:27
*/
public class Ex5 {
public static void main(String[] args) {
String str1 = "kaaabcFght";
String str2 = "ksaaabmFght";
System.out.println(getSameLen1(str1, str2));
System.out.println(getSameLen2(str1, str2));
}
public static int getSameLen1(String s1, String s2){
int[][] dp = new int[s1.length()][s2.length()];
char[] str1 = s1.toCharArray();
char[] str2 = s2.toCharArray();
int res = 0;
for (int i = 0; i < str1.length; i++) {
for (int j = 0; j < str2.length; j++) {
if (i == 0 || j == 0){
if (str1[i] == str2[j]){
dp[i][j] = 1;
res = Math.max(dp[i][j], res);
}
}else {
if (str1[i] == str2[j]){
dp[i][j] = dp[i - 1][j - 1] + 1;
res = Math.max(dp[i][j], res);
}
}
}
}
return res;
}
public static String getSameLen2(String s1, String s2){
int len = 0;
char[] str1 = s1.toCharArray();
char[] str2 = s2.toCharArray();
int max = 0;
int end = 0;
int row = 0;
int col = s2.length() - 1;
while (row < s1.length()){
len = 0;
int i = row;
int j = col;
while (i < s1.length() && j < s2.length()){
if (str1[i] == str2[j]){
len++;
if (len > max){
max = len;
end = i;
}
}else {
len = 0;
}
i++;
j++;
}
if (col > 0){
col--;
}else {
row++;
}
}
return s1.substring(end - max + 1, end + 1);
}
}
因为子串一定是在字符串中满足原顺序的字符序列,可以不连续。
所以对任意dp[i][j]表示以i位置结尾的str1,表示以j位置结尾的str2的两个子串上最长公共子序列的长度。
对于这个最长公共子序列有4种可能性:
(1)最长公共子序列包含i位置,包含j位置。
(2)最长公共子序列不包含i位置,包含j位置。
(3)最长公共子序列包含i位置,不包含j位置。
(4)最长公共子序列不包含i位置,不包含j位置。
package AdvancedPromotion4;
/**
* @ProjectName: study3
* @FileName: Ex6
* @author:HWJ
* @Data: 2023/9/23 22:04
*/
public class Ex6 {
public static void main(String[] args) {
String str1 = "kaaabcFght";
String str2 = "ksaaabmFght";
System.out.println(getSameLen(str1, str2));
}
public static int getSameLen(String s1, String s2){
int[][] dp = new int[s1.length()][s2.length()];
char[] str1 = s1.toCharArray();
char[] str2 = s2.toCharArray();
int res = 0;
for (int i = 0; i < str1.length; i++) {
for (int j = 0; j < str2.length; j++) {
if (i == 0 && j == 0){
if (str1[i] == str2[j]){
dp[i][j] = 1;
res = dp[i][j];
}
}else {
if (i == 0){
dp[i][j] = dp[i][j - 1];
res = Math.max(dp[i][j], res);
}else if(j == 0){
dp[i][j] = dp[i - 1][j];
res = Math.max(dp[i][j], res);
}else {
int p1 = dp[i][j - 1];
int p2 = dp[i - 1][j];
int p3 = dp[i - 1][j - 1] + (str1[i] == str2[j] ? 1 : 0);
dp[i][j] = Math.max(p1, Math.max(p2, p3));
res = Math.max(dp[i][j], res);
}
}
}
}
return res;
}
}