目录
大家觉得写得可以的话,可以加入QQ群907575059.
【案例1 尼姆博弈问题】
【题目描述】
【思路解析】
异或和 指初始情况下,所有的铜板堆数量的异或和。
如果有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("先手赢!");
}
}
}
【案例2 k伪进制问题】
【题目描述】
【思路解析】
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<Integer, Integer> 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);
}
}
【案例3 最大路径和】
【题目描述】
【思路解析】
它不一定要达到最右边才会得到它的最长长度,即它可能在二维数组的任一位置得到最长长度。遍历数组每一个位置得到信息(包括没使用能力的最大长度,和使用能力的最大长度)。
但是每个位置又依靠它的左上位置,,左位置,左下位置,则需要使用递归。可以改为记忆化搜索和动态规划的版本。此题因为不存在枚举行为,所以记忆化搜索和动态规划的时间复杂度和空间复杂度相似。
【代码实现】
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];
}
}
【案例4 优先级的递归套路】
【题目描述】
【思路解析】
先用栈实现不含括号的表达式求值,如果遇到运算符,且此时栈顶的运算符不为乘,除,就将运算符之前的数值和运算符加入栈,否则弹出运算符和运算符之前的数值,并且将其和当前数值进行运算后,在再将运算的值和当前运算符加入栈。
实现这个后我们考虑一个信息结构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<String> 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<String> 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<String> 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;
}
}
【案例5 子串的递归套路 动态规划的空间压缩技巧】
【题目描述】
【思路解析】
因为子串一定是在字符串中连续的字符子串。两个字符串的最长公共子串可能以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);
}
}
【案例6 子序列的递归问题】
【问题描述】
【思路解析】
因为子串一定是在字符串中满足原顺序的字符序列,可以不连续。
所以对任意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;
}
}