跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

3661 可以被机器人摧毁的最大墙壁数目 - 离散化与线段树解法

解决 LeetCode 3661 题“可以被机器人摧毁的最大墙壁数目”。问题涉及在直线上分布的机器人和墙壁,机器人向左右发射子弹摧毁射程内的墙,但会被其他机器人阻挡。由于坐标范围大(10^9),直接开数组会导致内存超限。文章分析了错误的线段树动态规划解法,指出了重叠统计的问题。随后提出了正确的优化方案:使用最大值线段树维护 dp 值,结合离散化处理坐标空间。此外,还介绍了基于相邻机器人性质的简化动态规划解法,将空间复杂度降至 O(n)。最终通过代码实现和单元测试验证了算法的正确性。

宁静发布于 2026/4/6更新于 2026/7/1152 浏览
3661 可以被机器人摧毁的最大墙壁数目 - 离散化与线段树解法

问题描述

一条无限长的直线上分布着一些机器人和墙壁。给你整数数组 robots,distance 和 walls:

  • robots[i] 是第 i 个机器人的位置。
  • distance[i] 是第 i 个机器人的子弹可以行进的最大距离。
  • walls[j] 是第 j 堵墙的位置。

每个机器人有一颗子弹,可以向左或向右发射,最远距离为 distance[i] 米。子弹会摧毁其射程内路径上的每一堵墙。机器人是固定的障碍物:如果子弹在到达墙壁前击中另一个机器人,它会立即在该机器人处停止,无法继续前进。

返回机器人可以摧毁墙壁的最大数量。

注意:墙壁和机器人可能在同一位置;该位置的墙壁可以被该位置的机器人摧毁。机器人不会被子弹摧毁。

示例 1

输入:robots = [4], distance = [3], walls = [1,10] 输出:1 解释:robots[0] = 4 向左发射,distance[0] = 3,覆盖范围 [1, 4],摧毁了 walls[0] = 1。

示例 2

输入:robots = [10,2], distance = [5,1], walls = [5,2,7] 输出:3 解释:robots[0] = 10 向左发射,distance[0] = 5,覆盖范围 [5, 10],摧毁了 walls[0] = 5 和 walls[2] = 7。robots[1] = 2 向左发射,distance[1] = 1,覆盖范围 [1, 2],摧毁了 walls[1] = 2。

示例 3

输入:robots = [1,2], distance = [100,1], walls = [10] 输出:0 解释:在这个例子中,只有 robots[0] 能够到达墙壁,但它向右的射击被 robots[1] 挡住了,因此答案是 0。

提示:

  • 1 <= robots.length == distance.length <= 10^5
  • 1 <= walls.length <= 10^5
  • 1 <= robots[i], walls[j] <= 10^9
  • 1 <= distance[i] <= 10^5
  • robots 中的所有值都是互不相同的

错误解法:线段树 + 动态规划

动态规划的状态表示

dp[i] 表示只摧毁位置 ≤ i 的墙,最后能销毁多少堵墙。且之后不会消耗 ≤ i 的墙。 最大值线段树 maxTree 记录最大值。

动态规划的顺序

按机器人的位置从小到大处理。

动态规划的转移方程

每个机器人枚举两种状态,向左射击,向右射击。

动态规划的初始值

全为 0。

动态规划的返回值

maxTree 的最大值。

错误原因

由于两个机器人的射击范围不能重叠,否则会重复统计。故向左射击不一定是最大射程,各射程要一一枚举。 例如:robots = {4,10}, distance = {3,3}, walls = {6,7,8}。 第二个机器人向左射击能到 {7,8,9,10}。第一个机器人向右射击到 7,第二个机器人向左射击到 8,才是正解。

template <class TSave, class TRecord>
class CRangUpdateLineTree {
protected:
    virtual void OnQuery(TSave& ans,  TSave& save,  & iSaveLeft,  & iSaveRight) = ;
    = ;
    = ;
    = ;
};

const
const
int
const
int
0
virtual void OnUpdate(TSave& save, const int& iSaveLeft, const int& iSaveRight, const TRecord& update)
0
virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, const int& iSaveLeft, const int& iSaveRight)
0
virtual void OnUpdateRecord(TRecord& old, const TRecord& newRecord)
0
// ... (此处省略部分模板代码以保持简洁,逻辑同上)

正确解法

某个向右的机器人和某个向左的机器人射程重叠后。向右的机器人射程不变,向左的机器人缩短射程使之不重叠。向右射击仍然是一种状态,故只讨论向左。 令向左的机器人位于 x2,向左能射击到 x1。 则:射程非最大向左的最大值为 max_{x1}^{x2-1}(dp[x] + f(x)),其中 f(x) 是处于 x+1 ~ x2 的墙数。 令 g(x) 是 ≤ x 的墙数。 则 射程非最大向左的最大值为 max_{x1}^{x2-1}(dp[x] + g(x2) - g(x)) = g(x2) + max_{x1}^{x2-1}(dp[x] - g(x))。 我们用最大值线段树 maxTree2 记录:dp[x] - g(x)。 向左射击的最大值为:max(射程非最大向左的最大值,射程最大向左的最大值)。

向右也要枚举

比如:robots = {3,5}, distance = {2,2}, walls = {4,6}。 令向右的机器人在 x2,向右能射击到 x3。为了避免和前面的机器人重叠,我将此机器人的射程调整为:x+1 → x3。 则起点非 x2 向右的最大值为:max_{x:x2}^{x3-1} dp[x] + (x+1 ~ x3) 的墙的个数 = g(x3) + max_{x:x2}^{x3-1}(dp[x] - g(x))。 可以共用:maxTree2。

空间超限的解决方法

一、离散化。 二、改用最大值树状数组。

核心代码

template <class T = int>
class CDiscretize // 离散化
{
public:
    CDiscretize(vector<T> nums) {
        sort(nums.begin(), nums.end());
        nums.erase(std::unique(nums.begin(), nums.end()), nums.end());
        m_nums = nums;
        for (int i = 0; i < nums.size(); i++) {
            m_mValueToIndex[nums[i]] = i;
        }
    }
    int operator[](const T value) const {
        auto it = m_mValueToIndex.find(value);
        if (m_mValueToIndex.end() == it) return -1;
        return it->second;
    }
    int size() const { return m_mValueToIndex.size(); }
    vector<T> m_nums;
protected:
    unordered_map<T, int> m_mValueToIndex;
};

// ... (CSetMaxLineTree 等实现略,保持原有逻辑)

class Solution {
public:
    int maxWalls(vector<int>& robots, vector<int>& distance, vector<int>& walls) {
        N = robots.size();
        Init(robots, distance, walls);
        CSetMaxLineTree<int, int> maxTree(M + 1, 0), maxTree2(M + 1, -1000'000);
        for (const auto& [x1, x2, x3] : m_xs) {
            const int g2 = upper_bound(walls.begin(), walls.end(), x2) - walls.begin();
            const int g3 = upper_bound(walls.begin(), walls.end(), x3) - walls.begin();
            const int cnt1 = upper_bound(walls.begin(), walls.end(), x2) - lower_bound(walls.begin(), walls.end(), x1);
            const int left = maxTree.Query(0, x1 - 1) + cnt1;
            const int cnt2 = upper_bound(walls.begin(), walls.end(), x3) - lower_bound(walls.begin(), walls.end(), x2);
            const int right = maxTree.Query(0, x2 - 1) + cnt2;
            const int left2 = maxTree2.Query(x1, x2 - 1) + g2;
            const int right2 = maxTree2.Query(x2, x3 - 1) + g3;
            maxTree.Update(x2, max(left, left2));
            maxTree.Update(x3, max(right, right2));
            maxTree2.Update(x2, max(left, left2) - g2);
            maxTree2.Update(x3, max(right, right2) - g3);
        }
        return maxTree.QueryAll();
    }
    void Init(const vector<int>& robots, const vector<int>& distance, vector<int>& walls) {
        vector<pair<int, int>> rd;
        sort(walls.begin(), walls.end());
        auto tmp = walls;
        tmp.emplace_back(INT_MIN / 2); // 编码增加 0,实际编码从 1 开始
        for (int i = 0; i < N; i++) {
            rd.emplace_back(robots[i], distance[i]);
            tmp.emplace_back(robots[i]);
        }
        CDiscretize<int> disc(tmp);
        for (auto& i : walls) {
            i = disc[i];
        }
        sort(rd.begin(), rd.end());
        for (int i = 0; i < N; i++) {
            const auto& [pos, dis] = rd[i];
            const int iLeftRobot = i ? rd[i - 1].first : 1;
            const int iLeft = max(iLeftRobot, pos - dis);
            const int iRightRobot = (i + 1 == N) ? (INT_MAX / 2) : rd[i + 1].first;
            const int iRight = min(iRightRobot, pos + dis);
            const int x1 = lower_bound(disc.m_nums.begin(), disc.m_nums.end(), iLeft) - disc.m_nums.begin();
            const int x2 = disc[pos];
            const int x3 = upper_bound(disc.m_nums.begin(), disc.m_nums.end(), iRight) - disc.m_nums.begin() - 1;
            m_xs.emplace_back(x1, x2, x3);
        }
        M = disc.m_nums.size();
    }
    int N, M;
    vector<tuple<int, int, int>> m_xs;
};

单元测试

vector<int> robots, distance, walls;
TEST_METHOD(TestMethod00) {
    robots = {4,10}, distance = {3,3}, walls = {6,7,8};
    auto res = Solution().maxWalls(robots, distance, walls);
    AssertEx(3, res);
}
TEST_METHOD(TestMethod11) {
    robots = {4}, distance = {3}, walls = {1,10};
    auto res = Solution().maxWalls(robots, distance, walls);
    AssertEx(1, res);
}
// ... 其他测试用例略

简单版

性质一:如果机器人和墙挨着一起,此墙一定被摧毁。故只考虑不挨着机器人的墙。 性质二:由于子弹遇到机器人会停止,故墙只会被相邻的机器人摧毁。

动态规划的状态表示

dp0n 表示第 0 ~ i 号机器人都已经射击完毕,且最后一个机器人向左(右)射击能摧毁最后的墙数。 空间复杂度:O(n) 为了方便处理边界情况,增加一个机器人标兵,射击距离都是 0,位置分别在正负无穷大。

动态规划的填表顺序

n = 1 to N 枚举后继状态和选择。

动态规划的转移方程

如果 i-1 和 i 都向左射击,两者不会摧毁同一道墙。 如果 i-1 向右和 i 向左射击,两者可能摧毁同一道墙。需要去重。i 向右能够摧毁 g1 道墙,i-1 向左能够摧毁 g0 道墙,i 和 i-1 之间的机器人数量 c,则重复的墙数为:max(0, g1 + g0 - c)。 如果 i 向右射击,i-1 无论向左还是向右,两者都不会摧毁同一道墙。

动态规划的初始值

全为 0。

动态规划的返回值

max(dp0.back(), dp1.back())

代码

class Solution {
public:
    int maxWalls(vector<int>& robots, vector<int>& distance, vector<int>& walls) {
        const int N = robots.size();
        const int M = int(1e9 + 1e5 + 1);
        sort(walls.begin(), walls.end());
        vector<pair<int, int>> rd;
        for (int i = 0; i < N; i++) {
            rd.emplace_back(robots[i], distance[i]);
        }
        rd.emplace_back(INT_MIN / 2, 0);
        rd.emplace_back(INT_MAX / 2, 0);
        sort(rd.begin(), rd.end());
        vector<int> vLeft(N + 2), vRight(N + 2);
        for (int i = 0; i < rd.size(); i++) {
            const auto& [pos, dis] = rd[i];
            const int iLeftRobot = i ? rd[i - 1].first : -M;
            vLeft[i] = max(iLeftRobot, pos - dis - 1);
            const int iRightRobot = (i + 1 == N + 2) ? M : rd[i + 1].first;
            vRight[i] = min(iRightRobot, pos + dis + 1);
        }
        auto Cnt = [&](int left, int r) {
            int ans = lower_bound(walls.begin(), walls.end(), r) - upper_bound(walls.begin(), walls.end(), left);
            return ans;
        };
        // (left,r) 之间的墙数量,不包括 left,r。
        vector<int> dp0(N + 2), dp1(N + 2);
        for (int n = 1; n <= N; n++) {
            dp1[n] = max(dp0[n - 1], dp1[n - 1]) + Cnt(rd[n].first, vRight[n]);
            const int g = Cnt(vLeft[n], rd[n].first);
            dp0[n] = dp0[n - 1] + g;
            const int iRepeat = g + Cnt(rd[n - 1].first, vRight[n - 1]) - Cnt(rd[n - 1].first, rd[n].first);
            dp0[n] = max(dp0[n], dp1[n - 1] + g - max(0, iRepeat));
        }
        int cntSamePos = 0;
        for (const auto& pos : robots) {
            cntSamePos += Cnt(pos - 1, pos + 1);
        }
        return cntSamePos + max(dp0[N], dp1[N]);
    }
};

目录

  1. 问题描述
  2. 示例 1
  3. 示例 2
  4. 示例 3
  5. 错误解法:线段树 + 动态规划
  6. 动态规划的状态表示
  7. 动态规划的顺序
  8. 动态规划的转移方程
  9. 动态规划的初始值
  10. 动态规划的返回值
  11. 错误原因
  12. 正确解法
  13. 向右也要枚举
  14. 空间超限的解决方法
  15. 核心代码
  16. 单元测试
  17. 简单版
  18. 动态规划的状态表示
  19. 动态规划的填表顺序
  20. 动态规划的转移方程
  21. 动态规划的初始值
  22. 动态规划的返回值
  23. 代码
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • VSCode 远程 SSH 环境下 Copilot 使用 Claude 模型配置方案
  • FPGA 摄像头采集到 HDMI 显示完整链路实战
  • Python YAML 模块实战:接口测试参数存储与配置
  • 深入理解 HTML img 标签:性能、安全与无障碍最佳实践
  • Linux 中 GDB 与 CGDB 调试器的使用详解
  • VSCode Copilot 登录失败排查与解决方案
  • 本地部署大模型:Ollama 部署与实战指南
  • AI 技术前沿动态:Ouroboros、CoPaw、Claude 与 Cursor 更新
  • 编写第一个 Rocket 0.5 Web 应用
  • 从美团全栈化看 AI 冲击:前端转全栈,是自救还是必然
  • Docker 基础概念与常用命令实战
  • ERNIE-4.5-0.3B 轻量模型部署指南与实战测评
  • 前端日志本地持久化方案
  • 二叉搜索树 C++ 实现:增删查改详解
  • GitHub Copilot提示词终极攻略:从“能用”到“精通”的AI编程艺术
  • ERNIE-4.5-0.3B:文心一言轻量级大模型的产业落地实践
  • Rust 与 WebAssembly 深度实战:浏览器与 Node.js 高性能应用
  • 大语言模型 (LLM) 基础:原理、应用与挑战
  • RoboBrain2.0 具身大脑模型复现:统一感知、推理和规划能力
  • 无人机视觉语言导航(一):基本概念与定义

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online