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;
    }
};

时间复杂度 O(n2)


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;
    }
};
posted @   sinkinben  阅读(72)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示