例子:
回溯和记忆化搜索
使用暴力回溯的思想来解题,首先是明确如何切西瓜:
每次切完西瓜后,都会剩下一个矩形,与原有型状类似,因此可以采用回溯递归的方式来判断。
采用对角顶点来定义每次处理的不同矩形,矩形的左上角会随着不断的切割发生变化,而右下角会保持不变,因此,只需要传递给方法体左上角的坐标值,就可以知道矩形的形状。
以例1为例:
当选择横向切时,会将上层的披萨送人,因此从 0 0 0 开始时,有两种横着切的方式,分别是:
1.切0-1之间的披萨
2.切1-2之间的披萨
当切完0-1之间的披萨时,披萨分为两块,分别是:
左上角坐标为[0,0],右下角坐标为[1,2]的上层披萨
左上角坐标为[1,0],右下角坐标为[2,2]的下层披萨
这时需要判断两块披萨中是否有苹果,通过遍历可以得到,遍历代码如下:
public boolean istrue(int i,int j,int k,int l,String[] pizza){
for(int indexx = i;indexx<k;indexx++){
for(int indexy = j;indexy<l;indexy++){
if(pizza[indexx].charAt(indexy)=='A'){
return true;
}
}
}
return false;
}
当两块披萨中都含有苹果时,才会进行下一步操作,下一步切的矩形左上角坐标根据切的不同方式来定:
若切到0-1之间的坐标,则下一步切的矩形左上角为[1,0]
若切到1-2之间的坐标,则下一步切的矩形左上角为[2,0]
竖着切的方式和横着切类似,下一步就是判断终止条件:
当 k k k 为 0 0 0 时,需要切割的次数已经用完,只需要判断剩下的矩形中是否有苹果,若有,则表示这种切披萨的方式可行,返回1;若无,则表示不可行,返回0。
采用上述方式来递归的求值时会出现超时问题,在递归过程中出现较多的重复计算,因此需要进行去重操作。
从题中可以看出,横着切一刀竖着切一刀与竖着切一刀横着切一刀所产生的矩形是一样的,因此不需要重复计算,可以使用一个数组
n
u
m
s
[
k
]
[
i
]
[
j
]
nums[k][i][j]
nums[k][i][j] 来保存前一次产生的结果,从而减少时间复杂度。
详细代码如下:
class Solution {
public static final int MOD = (int) 1e9 + 7;
public int ways(String[] pizza, int k) {
int[][][] nums = new int[k][pizza.length][pizza[0].length()];
return dfs(k-1,0,0,pizza,nums)%MOD;
}
public int dfs(int k, int i, int j,String[] pizza,int[][][] nums){
if(k==0){
if(istrue(i,j,pizza.length,pizza[0].length(),pizza)) return 1;
else return 0;
}
if(nums[k][i][j]!=0) return nums[k][i][j];
int result = 0;
for(int m=i+1;m<pizza.length;m++){
if(istrue(i,j,m,pizza[0].length(),pizza)){
result = (result+dfs(k-1,m,j,pizza,nums))%MOD;
}
}
for(int m=j+1;m<pizza[0].length();m++){
if(istrue(i,j,pizza.length,m,pizza)){
result = (result+dfs(k-1,i,m,pizza,nums))%MOD;
}
}
nums[k][i][j] = result;
return result;
}
public boolean istrue(int i,int j,int k,int l,String[] pizza){
for(int indexx = i;indexx<k;indexx++){
for(int indexy = j;indexy<l;indexy++){
if(pizza[indexx].charAt(indexy)=='A'){
return true;
}
}
}
return false;
}
}
最后结果别忘了 m o d ( 1 e − 9 + 7 ) mod(1e-9+7) mod(1e−9+7)。