刷题中遇到的记忆化方法
记忆化搜索需要解决的一个重要问题就是如何对状态进行编辑
我能赢吗
在 “100 game” 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到或超过 100 的玩家,即为胜者。
如果我们将游戏规则改为 “玩家不能重复使用整数” 呢?
例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。
给定一个整数 maxChoosableInteger (整数池中可选择的最大数)和另一个整数 desiredTotal(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?
你可以假设 maxChoosableInteger 不会大于 20, desiredTotal 不会大于 300。
输入:
maxChoosableInteger = 10
desiredTotal = 11
输出:
false
解释:
无论第一个玩家选择哪个整数,他都会失败。
第一个玩家可以选择从 1 到 10 的整数。
如果第一个玩家选择 1,那么第二个玩家只能选择从 2 到 10 的整数。
第二个玩家可以通过选择整数 10(那么累积和为 11 >= desiredTotal),从而取得胜利.
同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。
//弄了半天理解错题了,这个指的是那个使得总累计和最先达标的玩家,运气
//这题问的是稳赢而不是可以达到的最大数值,意味着需要将所有的状态遍历一遍,抽取出是不是赢这个信息
//而不是记录最大数值
public int desire;
public int[] maxcur;
public int maxcou;
public void dfs(int cur,int curnum){
//记忆数组的意思可以理解为,在这个局面下,还剩这些num的时候是否能稳赢
//稳赢的意思可以理解为,对方在这种情况下无论怎么拿都会输
int tt1=-1;
if(maxcur[cur]!=0){
return ;
}else {
for (int i = 0; i < maxcou; i++) {
if ((cur & 1 << i) != 0) {
continue;
} else {
int tem = cur | (1 << i);
if(curnum-i-1<=0){
//在本次已经胜了
maxcur[cur]=1;
return ;//在这个唯一的局面下,只有赢这一种选择,但是上一层循环不应该在这终止
}
dfs(tem,curnum-i-1);
int a=maxcur[tem];
if(a==-1){
tt1=1;
}
}
}
}
maxcur[cur]=tt1;
return;
}
public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
//最多只有20个数字设置记忆化数组进行记忆化搜索
//设计记忆化数据结构maxcur,这个数据结构的意思可以理解为,当前剩余数据的情况,做出选择可以得到的最好效果
if (maxChoosableInteger >= desiredTotal) {
return true;
}
//第二种特殊情况,所有元素的和都小于预期值,则永远无法赢
if (maxChoosableInteger * (maxChoosableInteger + 1) / 2 < desiredTotal) {
return false;
}
this.maxcur=new int[(1<<maxChoosableInteger)];
this.desire=desiredTotal;
this.maxcou=maxChoosableInteger;
dfs(0,desiredTotal);
if(maxcur[0]==1){
return true;
}
return(false);
}
本题的几个要点
- 理解博弈问题的意思:都发挥出最好状态->可以理解为回溯到本部,所有情况中最好的,如果有希望则一定能赢。
- 位运算,考虑清楚i循环代表的什么内容,例如,本题中的i代表的其实是本书-1
- 对于特殊情况要分开考虑
矩阵中的最长递增路径
给定一个整数矩阵,找出最长递增路径的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)
输入: nums =
[
[9,9,4],
[6,6,8],
[2,1,1]
]
输出: 4
解释: 最长递增路径为 [1, 2, 6, 9]。
这题没啥好说的,就记录一个数组,数组的意思是,以本数开头所能达到的最长序列即可,由于序列中的数是一个个递减的,不用怕有循环的情况。
public int[][] mem;
public int[][] mat;
public boolean inrange(int a,int b,int x,int y){
return 0<=x&&x<mem.length&&0<=y&&y<mem[0].length&&mat[a][b]<mat[x][y];
}
public int dfs(int x,int y){
if(mem[x][y]!=0){
return mem[x][y];
}
int max=1;
if(inrange(x,y,x+1,y)){
max=Math.max(max,dfs(x+1,y)+1);
}
if(inrange(x,y,x-1,y)){
max=Math.max(max,dfs(x-1,y)+1);
}
if(inrange(x,y,x,y+1)){
max=Math.max(max,dfs(x,y+1)+1);
}
if(inrange(x,y,x,y-1)){
max=Math.max(max,dfs(x,y-1)+1);
}
mem[x][y]=max;
return max;
}
public int longestIncreasingPath(int[][] matrix) {
if(matrix.length==0||matrix[0].length==0){
return 0;
}
this.mem=new int[matrix.length][matrix[0].length];
this.mat=matrix;
int fin=1;
for(int i=0;i< mem.length;i++){
for(int j=0;j<mem[0].length;j++){
//这里写错了
fin=Math.max(fin,dfs(i,j));
}
}
return fin;
}
1411. 给 N x 3 网格图涂色的方案数
记忆化方法,虽然很慢,但在考场上确实只能想清楚此种方法了
class Solution {
long[][][][] dp;
int n;
long mod=1000000007;
public long dfs(int color1,int color2,int color3,int dep){
long fin=0;
if(dep==n){
dp[color1][color2][color3][dep]=1;
return 1;
}
if(dp[color1][color2][color3][dep]!=0){
return dp[color1][color2][color3][dep];
}
for(int i=1;i<=3;i++){
for(int j=1;j<=3;j++){
for(int k=1;k<=3;k++){
if(i==j||j==k||color1==i||color2==j||color3==k){
continue;
}
fin=fin+(dfs(i,j,k,dep+1))%mod;
}
}
}
dp[color1][color2][color3][dep]=fin%mod;
return(fin%mod);
}
public int numOfWays(int n) {
this.n=n;
this.dp=new long[4][4][4][n+1];
return (int)(dfs(0,0,0,0));
}
}