LCP 57. 打地鼠
链接:https://leetcode-cn.com/problems/ZbAuEH
欢迎各位勇者来到力扣城,本次试炼主题为「打地鼠」。
勇者面前有一个大小为 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
1. 动态规划 (TLE)
由于每秒能走一步,那么给定 t
秒,是否可以从 (x1, y1)
到达 (x2, y2)
可以表示为:
abs(x1 - x2) + abs(y1 - y2) <= t
类似于 LIS,令 dp[i]
表示:如果我们选择击中地鼠 moles[i]
的条件下,在 [0, i]
范围内能够击中地鼠的最大数量。
那么有:
dp[i] = max(dp[i], dp[j] + 1) where (0 <= j < i) and moles[j] can reach moles[i]
代码:
using vec_t = vector<int>;
class Solution {
public:
int getMaximumNumber(vector<vector<int>>& moles) {
int n = moles.size();
vec_t dp(n + 1, 0);
sort(begin(moles), end(moles));
for (int i = 1; i <= n; ++i)
{
auto &cur = moles[i - 1];
dp[i] = reach(vec_t{0, 1, 1}, cur);
for (int j = 1; j < i; ++j)
{
if (reach(moles[j - 1], cur))
dp[i] = max(dp[i], dp[j] + 1);
}
}
return *max_element(begin(dp), end(dp));
}
bool reach(const vec_t &pre, const vec_t &cur)
{
int t1 = pre[0], x1 = pre[1], y1 = pre[2];
int t2 = cur[0], x2 = cur[1], y2 = cur[2];
return abs(x1 - x2) + abs(y1 - y2) <= t2 - t1;
}
};
时间复杂度
2. 优化
上面超时的原因,是因为没有考虑地图是 3 x 3
的,但这也有一个好处,这样的解法对于任意 m x n
的地图是通用的。
地图是 3 x 3
,最远距离是 (0, 0)
到 (2, 2)
,因此最多只需要 4 秒。此外,在「相同时间相同位置最多仅有一只地鼠」,因此在某一秒,地图最多有 9 只地鼠。
考虑足够大的 i
,是否有必要扫描 [1, i)
整个区间?答案是否定的,我们只需要扫描区间 [i - 45, i)
这个区间,因为 [i - 45, i)
这一区间的 dp
值必然大于 [0, i - 45)
这一区间的值。
为什么是 i
之前的 45 个元素呢,因为与 cur = moles[i - 1]
同一时间出现的可能还有 8 只地鼠(这些可以不扫描,忽略之),我们需要考虑的其实是 cur
前 4 秒的 36 只地鼠(最坏的情况下)。
i -> (t, 0, 0)
...
i - 8 -> (t, 2, 2)
i - 9 -> (t - 1, 0, 0)
...
i - 17 -> (t - 1, 2, 2)
...
代码(原来离 AC 只差了一行代码):
using vec_t = vector<int>;
class Solution {
public:
int getMaximumNumber(vector<vector<int>>& moles) {
int n = moles.size(), res = 0;
vec_t dp(n + 1, 0);
sort(begin(moles), end(moles));
for (int i = 1; i <= n; ++i)
{
auto &cur = moles[i - 1];
dp[i] = reach(vec_t{0, 1, 1}, cur);
for (int j = max(1, i - 45); j < i && moles[j - 1][0] < cur[0]; ++j)
{
if (reach(moles[j - 1], cur))
dp[i] = max(dp[i], dp[j] + 1);
}
res = max(res, dp[i]);
}
return res;
}
bool reach(const vec_t &pre, const vec_t &cur)
{
int t1 = pre[0], x1 = pre[1], y1 = pre[2];
int t2 = cur[0], x2 = cur[1], y2 = cur[2];
return abs(x1 - x2) + abs(y1 - y2) <= t2 - t1;
}
};
3. 细节
上面的算法虽然可以 AC ,但仍然忽略了一个细节,我们通过一个例子来解释。
[
[0, 1, 0], [0, 0, 1], [0, 2, 1], [0, 1, 2], [0, 0, 2],
[1, 2, 2], [1, 0, 0], [1, 0, 2]
]
在上述例子中,期望输出是 0 ,但我们的算法输出 1 。究其原因,是 t = 0
时出现的地鼠,是不可能状态,锤子无法到达,但 t = 1
时,我们选取了 t = 0
时的某些地鼠进行状态转移。
因此需要对输入进行预处理,去除 (0, x, y)
的所有地鼠,仅仅保留 (0, 1, 1)
作为初始值(如果有的话)。
using vec_t = vector<int>;
class Solution {
public:
int preprocess(vector<vector<int>>& moles)
{
int init = 0;
vector<vector<int>> buf;
for (auto &v : moles)
{
if (v[0] == 0 && v[1] == 1 && v[2] == 1)
init = 1;
if (v[0] != 0)
buf.emplace_back(v);
}
moles = std::move(buf);
return init;
}
int getMaximumNumber(vector<vector<int>>& moles) {
int init = preprocess(moles);
sort(begin(moles), end(moles));
int n = moles.size(), res;
vec_t dp(n + 1, 0);
res = dp[0] = init;
for (int i = 1; i <= n; ++i)
{
auto &cur = moles[i - 1];
dp[i] = reach(vec_t{0, 1, 1}, cur);
for (int j = max(1, i - 46); j < i && moles[j - 1][0] < cur[0]; ++j)
{
if (reach(moles[j - 1], cur))
dp[i] = max(dp[i], dp[j] + 1);
}
res = max(res, dp[i]);
}
return res;
}
bool reach(const vec_t &pre, const vec_t &cur)
{
int t1 = pre[0], x1 = pre[1], y1 = pre[2];
int t2 = cur[0], x2 = cur[1], y2 = cur[2];
return abs(x1 - x2) + abs(y1 - y2) <= t2 - t1;
}
};
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】