LCP 57. 打地鼠
欢迎各位勇者来到力扣城,本次试炼主题为「打地鼠」。
{:height="200px"}
勇者面前有一个大小为 3*3
的打地鼠游戏机,地鼠将随机出现在各个位置,moles[i] = [t,x,y]
表示在第 t
秒会有地鼠出现在 (x,y)
位置上,并于第 t+1
秒该地鼠消失。
勇者有一把可敲打地鼠的锤子,初始时刻(即第 0
秒)锤子位于正中间的格子 (1,1)
,锤子的使用规则如下:
1
秒可以往上、下、左、右中的一个方向移动一格,也可以不移动请返回勇者最多能够敲击多少只地鼠。
注意:
示例 1:
输入:
moles = [[1,1,0],[2,0,1],[4,2,2]]
输出:
2
解释: 第 0 秒,锤子位于 (1,1) 第 1 秒,锤子移动至 (1,0) 并敲击地鼠 第 2 秒,锤子移动至 (2,0) 第 3 秒,锤子移动至 (2,1) 第 4 秒,锤子移动至 (2,2) 并敲击地鼠 因此勇者最多可敲击 2 只地鼠
示例 2:
输入:
moles = [[2,0,2],[5,2,0],[4,1,0],[1,2,1],[3,0,2]]
输出:
3
解释: 第 0 秒,锤子位于 (1,1) 第 1 秒,锤子移动至 (2,1) 并敲击地鼠 第 2 秒,锤子移动至 (1,1) 第 3 秒,锤子移动至 (1,0) 第 4 秒,锤子在 (1,0) 不移动并敲击地鼠 第 5 秒,锤子移动至 (2,0) 并敲击地鼠 因此勇者最多可敲击 3 只地鼠
示例 3:
输入:
moles = [[0,1,0],[0,0,1]]
输出:
0
解释: 第 0 秒,锤子初始位于 (1,1),此时并不能敲击 (1,0)、(0,1) 位置处的地鼠
提示:
1 <= moles.length <= 10^5
moles[i].length == 3
0 <= moles[i][0] <= 10^9
0 <= moles[i][1], moles[i][2] < 3
原站题解
golang 解法, 执行用时: 440 ms, 内存消耗: 14.1 MB, 提交时间: 2023-10-26 23:47:11
// 动态规划 f(i)表示只考虑前i个地鼠,打中了第i个地鼠的情形能够打到的最大地鼠数量,f(i)=max(f(j)+1), //其中j<i,且i与j之间的时间间隔t大于两者距离d // 这里还有一个重要的优化,因为题目说明了是3X3的矩阵, //两个位置的最大距离是4,那么当t>=4时,从前面任意一个位置都有足够的时间过来, //所以可以直接取前缀最大值。没有这一步就会超时。 // 参考了优秀题解https://leetcode.cn/problems/ZbAuEH/solution/by-tsreaper-ey5v/ 对于时间为0的地鼠处理有所不同 func getMaximumNumber(moles [][]int) int { // 必须按照时间排序,否则在考虑一个地鼠是否可达的时候会出问题。比如说(0,2,0)显然是不可达的,因为初始位置在(1,1)。考虑一种情形,(1,1,0)是可达的, //如果(0,2,0)紧跟其后,就会误判(0,2,0)可达。我把这个叫做时间倒流陷阱。 sort.Slice(moles, func(i, j int) bool { return moles[i][0] < moles[j][0] }) n := len(moles) dp := make([]int, n) preMax := make([]int, n) ans := 0 for i := 0; i < n; i++ { for j := i - 1; j >= 0; j-- { t := moles[i][0] - moles[j][0] // 可以从前面任意一个位置过来,所以直接取前缀最大值 if t >= 4 { dp[i] = max(dp[i], preMax[j]+1) break } // dp[j]==0表示这个地鼠是不可能达到的 if dp[j] > 0 && t >= absDiff(moles[i][1], moles[j][1])+absDiff(moles[i][2], moles[j][2]) { dp[i] = max(dp[i], dp[j]+1) } } // 考虑从初始位置是否可达 if dp[i] == 0 && absDiff(moles[i][0], 0) >= absDiff(moles[i][1], 1)+absDiff(moles[i][2], 1) { dp[i] = 1 } ans = max(ans, dp[i]) if i == 0 { preMax[0] = dp[0] } else { preMax[i] = max(preMax[i-1], dp[i]) } } return ans } func absDiff(i, j int) int { if i > j { return i - j } return j - i }
golang 解法, 执行用时: 508 ms, 内存消耗: 22.2 MB, 提交时间: 2023-10-26 23:46:40
func getMaximumNumber(moles [][]int) int { sort.Slice(moles, func(i, j int) bool { return moles[i][0] < moles[j][0] }) type point struct{ x, y int } var molesByTime [][]*point var timeSort []int for _, mole := range moles { if len(timeSort) == 0 || mole[0] > timeSort[len(timeSort)-1] { timeSort = append(timeSort, mole[0]) molesByTime = append(molesByTime, []*point{}) } molesByTime[len(molesByTime)-1] = append(molesByTime[len(molesByTime)-1], &point{mole[1], mole[2]}) } var preTime int //0时刻,只有1,1有锤子,所以要保证其他8个位置,不会因为有地鼠而得到正数 dp := [3][3]int{{-3, -3, -3}, {-3, 0, -3}, {-3, -3, -3}} for i, points := range molesByTime { for j := 0; j < 4 && preTime+1+j <= timeSort[i]; j++ { var dpNew [3][3]int //硬编码了9个点分别依赖哪几个点 dpNew[0][0] = max(dp[0][0], max(dp[0][1], dp[1][0])) dpNew[0][1] = max(dp[0][1], max(dp[0][0], max(dp[0][2], dp[1][1]))) dpNew[0][2] = max(dp[0][2], max(dp[0][1], dp[1][2])) dpNew[1][0] = max(dp[1][0], max(dp[0][0], max(dp[2][0], dp[1][1]))) dpNew[1][1] = max(dp[1][1], max(dp[0][1], max(dp[1][0], max(dp[1][2], dp[2][1])))) dpNew[1][2] = max(dp[1][2], max(dp[0][2], max(dp[1][1], dp[2][2]))) dpNew[2][0] = max(dp[2][0], max(dp[1][0], dp[2][1])) dpNew[2][1] = max(dp[2][1], max(dp[2][0], max(dp[2][2], dp[1][1]))) dpNew[2][2] = max(dp[2][2], max(dp[1][2], dp[2][1])) dp = dpNew } for _, p := range points { dp[p.x][p.y]++ } preTime = timeSort[i] } var res int for r := 0; r < 3; r++ { for c := 0; c < 3; c++ { res = max(res, dp[r][c]) } } return res } func max(a, b int) int { if a > b { return a } return b }
python3 解法, 执行用时: 9484 ms, 内存消耗: 68.6 MB, 提交时间: 2023-10-26 23:46:04
class Solution: def getMaximumNumber(self, moles: List[List[int]]) -> int: moles.sort(key = lambda x:x[0]) n = len(moles) # dp[i][j]表示当我们遇见第i个地鼠时,且锤子位置在j时能够打到的最大地鼠个数, dp = [[-float('inf') for _ in range(9)] for _ in range(n + 1)] dp[0][4] = 0 pre = 0 d = [[0 for _ in range(9)] for _ in range(9)] for i in range(9): for j in range(9): d[i][j] = abs(i // 3 - j // 3) + abs(i % 3 - j % 3) for i in range(1,n + 1): t,x,y = moles[i - 1] diff = t - pre pre = t for last in range(9): for next_ in range(9): if d[last][next_] <= diff: dp[i][next_] = max(dp[i][next_],dp[i - 1][last]) dp[i][x * 3 + y] += 1 return max(dp[-1])
java 解法, 执行用时: 106 ms, 内存消耗: 69.7 MB, 提交时间: 2023-10-26 23:45:26
class Solution { public int getMaximumNumber(int[][] m) { //按时间排序 Arrays.sort(m, Comparator.comparingInt((a)->(a[0]))); //拷贝一份数组m,把起始位置(0,1,1)考虑进去。arraycopy还挺慢的,伙计们可以换别的实现 int[][] q = new int [m.length + 1][3]; System.arraycopy(m, 0, q, 1, m.length); q[0] = new int[] {0, 1, 1}; // dp[i][0] 表示前 i 只地鼠中,第 i 只地鼠不打的情况下,获得的最大分数。 // dp[i][1]表示前 i 只地鼠中,第 i 只地鼠打的情况下,获得的最大分数。 int[][] dp = new int[q.length][2]; // i 可以从1开始遍历,因为在dp[0][0]和dp[0][1]的值为0; for(int i = 1; i < q.length; i++){ dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]); //假如无法从0状态(初始状态)到达i状态(当前状态),则dp[i][1]=0; if(!time(q, i, 0)) continue; for(int j = i - 1; j >= 0; j--){ //time函数用来判断j能否到达i; if(time(q, i, j)){ dp[i][1]=Math.max(dp[j][1] + 1, dp[i][1]); } //相差4打破循环。 if(q[i][0] - q[j][0] >= 4){ dp[i][1]=Math.max(dp[j][0] + 1, dp[i][1]); break; } } } return (int)Math.max(dp[m.length][0], dp[m.length][1]); } boolean time(int[][] m, int i, int j){ int t = m[i][0] - m[j][0]; int need = Math.abs(m[i][1] - m[j][1]) + Math.abs(m[i][2] - m[j][2]); return need <= t; } }
cpp 解法, 执行用时: 1324 ms, 内存消耗: 242.9 MB, 提交时间: 2023-10-26 23:44:30
class Solution { public: int getMaximumNumber(vector<vector<int>>& moles) { // 把所有时间 0 出现的地鼠排除 vector<vector<int>> A; bool flag = false; for (auto &mole : moles) { if (mole[0] == 0) { // 看一下有没有时间 0 位于 (1, 1) 的地鼠,一开始就能打 if (mole[1] == 1 && mole[2] == 1) flag = true; } else { A.push_back(mole); } } // 初始位置位于 (1, 1) A.push_back(vector<int>{0, 1, 1}); int n = A.size(); sort(A.begin(), A.end()); vector<int> f(n), g(n); int ans = 0; for (int i = 1; i < n; i++) { f[i] = -1e8; // 打了前i只地鼠,最多能打几只 for (int j = i - 1; j >= 0; j--) { int t = A[i][0] - A[j][0], d = abs(A[i][1] - A[j][1]) + abs(A[i][2] - A[j][2]); // 能从任何位置移过来,用前缀 max 更新答案 if (t > 4) { f[i] = max(f[i], g[j] + 1); break; } // 虽然有时间限制,但移过来能来得及,更新答案 else if (d <= t) f[i] = max(f[i], f[j] + 1); } ans = max(ans, f[i]); g[i] = max(g[i - 1], f[i]); } return ans + (flag ? 1 : 0); } };