474. 一和零
public static int zeros, ones;
public int findMaxForm(String[] strs, int m, int n) {
/**
空间压缩,创建一个二维表,
最简单的状态就是第len层,值全是零
二维表中每个格子的状态依赖于上一层的同一位置或是左边和下边的所有格子
要求的是第0层的右上角格子
*/
int[][] dp=new int[m+1][n+1];
// 遍历字符串数组,每遍历一个字符串就更新一次dp表
for(String str: strs){
zerosAndOnes(str);
// 每一层的dp表都是从右上角开始更新,即从右到左,从上到下
// 注意,当满足下列条件时,才需要更新,否则,就依赖上一层的同一位置,无需更新
for(int z = m; z>= zeros; z--){
for(int o=n;o>=ones;o--){
dp[z][o]=Math.max(dp[z][o],1+dp[z-zeros][o-ones]);
}
}
}
return dp[m][n];
}
public static void zerosAndOnes(String str){
zeros=0;
ones=0;
for(int i =0; i<str.length(); i++){
if(str.charAt(i)=='0'){
zeros++;
}else{
ones++;
}
}
}
879. 盈利计划
public static int mod = 1000000007;
public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {
/**
空间压缩,二维dp,依赖于上一层的当前位置及左下角的格子
*/
int[][] dp = new int[n+1][minProfit+1];
// 当没有工作时,即i==group.length,
// 这种情况下,当minProfit=0时,即计划+1,否则为0
for(int r=0; r<=n; r++){
dp[r][0]=1;
}
for(int i = group.length-1;i>=0;i--){
for(int r = n;r>=0;r--){
for(int s=minProfit;s>=0;s--){
// 第一种情况,不选择当前工作
int p1=dp[r][s];
// 选择做当前工作,
// Math.max(0,minProfit-profit[i])是为了保证dp表不超出索引,
// 因为剩余利润为0或是负数时,都表示该计划生效
int p2 = group[i]<=r ? dp[r-group[i]][Math.max(0,s-profit[i])] :0;
dp[r][s]=(p1+p2)%mod;
}
}
}
return dp[n][minProfit];
}
688. 骑士在棋盘上的概率
public double knightProbability(int n, int k, int row, int column) {
double[][][] dp = new double[n][n][k+1];
// 初始化dp表
for(int i=0; i<n;i++){
for(int j=0;j<n;j++){
for(int t=0;t<=k;t++){
dp[i][j][t]=-1;
}
}
}
return f(n,row,column,k,dp);
}
public double f(int n, int i,int j,int k, double[][][] dp){
// 如果越界
if(i<0 || i>=n || j<0 || j>=n){
return 0;
}
// 来到这儿就表示未越界
if(dp[i][j][k] != -1){
return dp[i][j][k];
}
double ans = 0;
// 在未越界的情况下,判断是否停在了棋盘上
if(k==0){
return 1;
}else{
// 8个方向的概率均等
ans += (f(n,i-2,j-1,k-1,dp)/8);
ans+=(f(n,i-2,j+1,k-1,dp)/8);
ans+=(f(n,i-1,j-2,k-1,dp)/8);
ans+=(f(n,i-1,j+2,k-1,dp)/8);
ans+=(f(n,i+1,j-2,k-1,dp)/8);
ans+=(f(n,i+1,j+2,k-1,dp)/8);
ans+=(f(n,i+2,j-1,k-1,dp)/8);
ans+=(f(n,i+2,j+1,k-1,dp)/8);
}
dp[i][j][k]=ans;
return ans;
}
2435. 矩阵中和能被K整除的路径
public int numberOfPaths(int[][] grid, int k) {
/**
位置依赖版本,依赖于下边和右边的值
看成一个n*m的二维表中,每个格子中有一个长度为k的数组
要得到的是0,0,0,即左上角格子的余数为0的值
*/
int n =grid.length;
int m = grid[0].length;
int[][][] dp = new int[n][m][k];
// 先填上最简单的位置,即右下角的余数为r的值
dp[n-1][m-1][grid[n-1][m-1] % k]=1;
// 接下来填最右列的值
for(int i = n-2; i>=0; i--){
for(int r=0;r<k;r++){
dp[i][m-1][r]=dp[i+1][m-1][(k+r-grid[i][m-1] % k)% k];
}
}
// 填最下行的值
for(int j=m-2;j>=0;j--){
for(int r = 0;r<k;r++){
dp[n-1][j][r]=dp[n-1][j+1][(k+r-grid[n-1][j] %k)%k];
}
}
for(int i = n-2,need;i>=0;i--){
for(int j = m-2;j>=0;j--){
for(int r=0;r<k;r++){
// 后续需要凑出来的余数need
need = (k+r-grid[i][j]%k)%k;
dp[i][j][r]=dp[i+1][j][need];
dp[i][j][r]=(dp[i][j][r]+dp[i][j+1][need])%mod;
}
}
}
return dp[0][0][0];
}
87. 扰乱字符串
public boolean isScramble(String s1, String s2) {
/**
位置依赖版本,依赖于低层的格子
dp[i][j][k]表示第一个字符串从i开始的k长度字符串对应于第二个字符串从j开始k长度
要求的是dp[0][0][n]的值
*/
char[] str1=s1.toCharArray();
char[] str2=s2.toCharArray();
int n = str1.length;
boolean[][][] dp=new boolean[n][n][n+1];
// 填写len=1层所有的格子
for(int l1=0; l1<n; l1++){
for(int l2=0;l2<n;l2++){
dp[l1][l2][1]=str1[l1]==str2[l2];
}
}
// 填写所有层
// 注意边界条件,只要当l1<=n-len时才能执行
for(int len=2;len<=n;len++){
for(int l1=0;l1<=n-len;l1++){
for(int l2=0;l2<=n-len;l2++){
// 不交错
for(int k=1;k<len;k++){
if(dp[l1][l2][k] && dp[l1+k][l2+k][len-k]){
dp[l1][l2][len]=true;
break;
}
}
// 交错
if(!dp[l1][l2][len]){
for(int i=l1+1,j=l2+len-1,k=1;k<len;i++,j--,k++){
if(dp[l1][j][k] && dp[i][l2][len-k]){
dp[l1][l2][len]=true;
break;
}
}
}
}
}
}
return dp[0][0][n];
}