题目详情
小M最近迷上了一款弹子游戏机,规则如下:
玩家可以在版面最上方任意一个位置放置弹珠。弹珠会通过得分点时为玩家赢得分数,目标是获得尽可能高的分数。
弹子游戏机的版面由两种组成要素构成:
- 钉子(用
-1表示),当弹珠碰到钉子时,有可能弹射到左下或者右下的位置。- 得分点(非负整数),弹珠经过得分点时可以获得对应的分数。
如果弹珠所在的格子为空(即没有钉子或者得分点),弹珠会直接往下落。
小M想知道,在一个给定的版面布局中,他能够获得的最高分数是多少。
n表示版面的高度。m表示版面的宽度。array是一个n x m的二维数组,其中:-1表示该位置为钉子;0表示该位置为空;- 正整数表示该位置为得分点,值为该得分点的分数。
解决方案
解法1 递归实现
本题每一轮的决策在于三个位置的状态变化。左,中,右 有分数,求最大分数s 设状态[i,j]
- [i,j]=0 [i+1,j]
- [i,j]=-1 [i+1,j-1] (要求j>0) or [i+1,j+1] (要求j<array[i].length-1)
若使用递归实现
伪代码为
i=array.length return array[i,j]
if(array[i,j]==0){sol(i+1,j)}
if(array[i,j]==-1)
if(j==0){sol(i+1,j+1)}
else if(j==array[i].length-1){sol(i+1,j-1)}
else{max(sol(i+1,j-1),sol(i+1,j+1))}
return max(maxv,上面接口)
代码实现:
public class Main {
public static int solution(int n, int m, int[][] array) {
// Edit your code here
int maxv = -1;
for (int j = 0; j < m; j++) {
maxv = Math.max(maxv, sol(0, j, array));
}
return maxv;
}
private static int sol(int i, int j, int[][] array) {
if (i == array.length - 1) {
return rcheck(array[i][j]);
}
if (array[i][j] == 0) {
return sol(i + 1, j, array);
} else if (array[i][j] == -1) {
if (j == 0) {
return rcheck(array[i][j]) + sol(i + 1, j + 1, array);
} else if (j == array[i].length - 1) {
return rcheck(array[i][j]) + sol(i + 1, j - 1, array);
} else {
return rcheck(array[i][j]) + Math.max(sol(i + 1, j - 1, array), sol(i + 1, j + 1, array));
}
} else {
return array[i][j] + sol(i + 1, j, array);
}
}
private static int rcheck(int n) {
if (n == -1) {
return 0;
} else {
return n;
}
}
public static void main(String[] args) {
// Add your test cases here
System.out.println(solution(3, 3, new int[][] { { -1, 0, -1 }, { 100, 0, 0 }, { 0, 50, 70 } }) == 50);
System.out.println(
solution(4, 3, new int[][] { { -1, 0, -1 }, { 0, -1, 0 }, { 50, 100, 70 }, { 80, 200, 50 } }) == 130);
}
}
复杂度分析:
时间复杂度
-
递归调用次数:
- 在最坏情况下,每次递归调用会分裂成两个子问题(当遇到钉子时)。
- 递归树的深度为
n(版面的高度)。 - 因此,递归树的节点数大约为
2^n。
-
每个节点的操作:
- 每个节点的操作是常数时间(如比较、加法等)。
综合以上分析,递归算法的时间复杂度为 O(2^n)。
解法2 动态规划实现
这题最好的办法是使用动态规划实现。
初始化:
创建一个与版面布局相同大小的二维数组 dp,用于存储从每个位置开始的最大得分。
状态转移:
从最底层开始向上逐层计算每个位置的最大得分。 对于每个位置 (i, j): 如果当前位置是得分点,则 dp[i][j] 为当前得分加上下一层对应位置的最大得分。 如果当前位置是钉子,则 dp[i][j] 为下一层左下和右下位置的最大得分中的较大值。 如果当前位置是空位,则 dp[i][j] 为下一层对应位置的最大得分。
最终结果:
最上层的 dp[0][j] 中的最大值即为所求的最大得分。
动态规划实现代码:
public class Main {
public static int solution(int n, int m, int[][] array) {
// Edit your code here
int dp[][] = new int[n][m];
int max = -1;
for (int i = 0; i < m; i++) {
dp[n - 1][i] = checkt(array[n - 1][i]);
}
for (int i = n - 2; i >= 0; i--) {
for (int j = 0; j < m; j++) {
if (array[i][j] == -1) {
if (j == 0) {
dp[i][j] = dp[i + 1][j + 1];
} else if (j == m - 1) {
dp[i][j] = dp[i + 1][j - 1];
} else {
dp[i][j] = Math.max(dp[i + 1][j + 1], dp[i + 1][j - 1]);
}
} else if (array[i][j] == 0) {
dp[i][j] = dp[i + 1][j];
} else {
dp[i][j] = array[i][j] + dp[i + 1][j];
}
}
}
for (int i = 0; i < m; i++) {
max = Math.max(max, dp[0][i]);
}
return max;
}
private static int checkt(int n) {
if (n == -1) {
return 0;
}
return n;
}
public static void main(String[] args) {
// Add your test cases here
System.out.println(solution(3, 3, new int[][] { { -1, 0, -1 }, { 100, 0, 0 }, { 0, 50, 70 } }) == 50);
System.out.println(
solution(4, 3, new int[][] { { -1, 0, -1 }, { 0, -1, 0 }, { 50, 100, 70 }, { 80, 200, 50 } }) == 130);
}
}
时间复杂度
-
初始化:
- 初始化
dp数组的最底层需要遍历m个元素,时间复杂度为O(m)。
- 初始化
-
状态转移:
- 对于每一层
i,需要遍历m个元素,并对每个元素进行常数时间的操作(如赋值、比较、加法等)。 - 总共有
n层,因此状态转移的总时间复杂度为O(n * m)。
- 对于每一层
-
最终结果:
- 在最上层遍历
dp[0][i]并找到最大值,时间复杂度为O(m)。
- 在最上层遍历
故动态规划算法的时间复杂度为 O(n * m)。