LeetCode 面试题 16.22. 兰顿蚂蚁

题目链接

面试题 16.22. 兰顿蚂蚁

一只蚂蚁坐在由白色和黑色方格构成的无限网格上。开始时,网格全白,蚂蚁面向右侧。每行走一步,蚂蚁执行以下操作。

(1) 如果在白色方格上,则翻转方格的颜色,向右(顺时针)转 90 度,并向前移动一个单位。

(2) 如果在黑色方格上,则翻转方格的颜色,向左(逆时针方向)转 90 度,并向前移动一个单位。

编写程序来模拟蚂蚁执行的前 K 个动作,并返回最终的网格。

网格由数组表示,每个元素是一个字符串,代表网格中的一行,黑色方格由 'X' 表示,白色方格由 '_' 表示,蚂蚁所在的位置由 'L', 'U', 'R', 'D' 表示,分别表示蚂蚁 左、上、右、下 的朝向。只需要返回能够包含蚂蚁走过的所有方格的最小矩形。

示例 1:

复制代码
输入:0
输出:["R"]

示例 2:

复制代码
输入:2
输出:
[
  "_X",
  "LX"
]

示例 3:

复制代码
输入:5
输出:
[
  "_U",
  "X_",
  "XX"
]

说明:

  • K <= 100000

思路解析(稀疏存储 + 模拟 + 动态边界)

1. 为什么不能开一个"大棋盘"?

  • 蚂蚁在无限网格上走,K 最多 1e5。
  • 如果直接开一个 grid[200001][200001] 之类的二维数组,空间完全爆炸。
  • 但实际上,每一步只会翻转当前一个格子的颜色,大部分格子仍是白色。

👉 结论:

只需要记录"黑色格子"的集合,白色格子默认存在但不存储。


2. 状态设计

我们需要维护:

  1. 蚂蚁的位置:(x, y)

    • 初始位置可以设为 (0, 0)
  2. 蚂蚁的方向:

    约定方向编码为:

    • 0: 上 (U)
    • 1: 右 (R)
    • 2: 下 (D)
    • 3: 左 (L)

    初始方向为 右 ® ,即 dir = 1

  3. 黑色格子集合:

    可以用 unordered_set<long long> 存储,每个格子坐标 (x, y) 编码成一个 long long

    cpp 复制代码
    auto encode = [](int x, int y) -> unsigned long long {
        return ((unsigned long long)(unsigned int)x << 32) | (unsigned long long)(unsigned int)y;
    };

    这样就可以 O(1) 平均时间判断一个格子是否是黑色。

    注意 int 符号拓展的问题。

  4. 已访问区域的最小矩形边界:

    • minX, maxX, minY, maxY
    • 每次蚂蚁"站在某个格子上"时,把这个格子纳入边界中:
      • 模拟过程中:每一步操作前,将当前 (x, y) 更新到边界;
      • 模拟结束后,再把"最终位置"也更新到边界。

    题目中要求返回能包含蚂蚁走过所有方格的最小矩形,用这个四元组就可以定位最终输出的矩形大小。


3. 每一步如何模拟?

当前蚂蚁在 (x, y),方向为 dir

  1. 看当前格子颜色:

    • 如果不在黑色集合中:它是 白色
    • 如果在黑色集合中:它是 黑色
  2. 如果是白色

    • 把它翻成黑色:把 (x, y) 加入集合
    • 右转:dir = (dir + 1) % 4
  3. 如果是黑色

    • 把它翻成白色:把 (x, y) 从集合中删掉
    • 左转:dir = (dir + 3) % 4(相当于 -1,注意要加 4 再取模或直接 +3 再模)
  4. 按新的方向前进一步:

    cpp 复制代码
    /* 顺时针方向:0 = 上, 1 = 右, 2 = 下, 3 = 左
               +----+----+----+----+
               | \  | y0 | y1 | y2 |
               +----+----+----+----+
               | x0 |    |    |    |
               +----+----+----+----+
               | x1 |    |    |    |
               +----+----+----+----+
               | x2 |    |    |    |
               +----+----+----+----+
    */
    int dx[4] = {-1, 0, 1, 0};
    int dy[4] = {0, 1, 0, -1}; 

循环执行 K 次即可。


4. 如何构造最后的输出网格?

我们现在有:

  • 黑色格子集合 black
  • 蚂蚁最终位置 (x, y) & 方向 dir
  • 覆盖所有走过格子的边界 minX, maxX, minY, maxY

构造网格:

  1. 网格的高度/宽度:

    cpp 复制代码
    int rows = maxX - minX + 1;
    int cols = maxY - minY + 1;
  2. 初始化为全白 '_'

    cpp 复制代码
    vector<string> res(rows, string(cols, '_'));
  3. 把所有黑色格子标成 'X'

    cpp 复制代码
    for(auto &key : black) {
        int bx = (int)(key >> 32);
        int by = (int)(key & 0xffffffff);
        res[bx - minX][by - minY] = 'X';
    }

    遍历黑色集合中的每个 (bx, by),在 ans[row][col] = 'X'

  4. 最后填蚂蚁所在的位置,覆盖之前可能填的 'X'

    cpp 复制代码
    res[x - minX][y - minY] = "URDL"[dir];

C++ 代码

cpp 复制代码
class Solution {
public:
    vector<string> printKMoves(int K) {
        // 把 (x,y) 编码成一个 unsigned long long
            // 先将 int:x,y 转换为 unsigned int 避免有符号数符号拓展污染高位
        auto encode = [](int x, int y) -> unsigned long long{
            return ((unsigned long long)(unsigned int)x << 32) ^ (unsigned long long)(unsigned int)y;
        };

        // 用 hash set 存黑色格子(白色默认不存)
        unordered_set<unsigned long long> black;
        black.reserve(K * 2); // 预留两倍空间减少扩容次数

        // 方向:0 = 上, 1 = 右, 2 = 下, 3 = 左
        // (0,0) -----> x轴
        // |
        // |
        // ↓
        // y轴
        int dx[4] = {0, 1, 0, -1};
        int dy[4] = {-1, 0, 1, 0};

        int x = 0, y = 0; // 当前位置
        int dir = 1; // 初始向右

        // 边界(用来计算最终矩形大小)
        int minX = 0, maxX = 0;
        int minY = 0, maxY = 0;

        // 模拟 K 步
        for (int i = 0; i < K; ++i) {
            unsigned long long key = encode(x, y);
            bool isBlack = (black.count(key) > 0);

            if (isBlack) {
                // 黑色:变白 + 左转
                black.erase(key);
                dir = (dir - 1 + 4) % 4; // +4 避免出现负数
            } else {
                // 白色:变黑 + 右转
                black.insert(key);
                dir = (dir + 1) % 4;
            }

            // 前进
            x += dx[dir];
            y += dy[dir];

            minX = min(minX, x);
            maxX = max(maxX, x);
            minY = min(minY, y);
            maxY = max(maxY, y);
        }

        int rows = maxY - minY + 1;
        int cols = maxX - minX + 1;

        // 创建全白画布
        vector<string> ans(rows, string(cols, '_'));

        // 填黑格子
        for (auto &key : black) {
            int bx = (int)(key >> 32);
            int by = (int)(key & 0xffffffff);

            int row = by - minY;  // y 对应行
            int col = bx - minX;  // x 对应列

            ans[row][col] = 'X';
        }

        // 放蚂蚁
        ans[y - minY][x - minX] = "URDL"[dir];

        return ans;
    }
};

时间 & 空间复杂度分析

设步数为 K,最终最小矩形为 rows × cols

  1. 模拟过程

    • 每一步只做:
      • 在 hash 表中插入/删除/查询一次(均摊 O(1))
      • 若干常数操作
    • 总时间:O(K)(均摊)
  2. 构造输出网格

    • 初始化网格:O(rows × cols)
    • 填黑色格子:至多访问 black.size() 个格子,black.size() ≤ K,所以 O(K)
    • 综合:O(rows × cols + K)

在这道题给定的数据范围下,Langton 蚂蚁实际走过区域的面积相对 K 是线性的量级,因此整体时间可以认为是 O(K)

  1. 空间复杂度
    • 黑色格子集合:最多 K 个位置 → O(K)
    • 输出网格:O(rows × cols),在本题约为 O(K)
    • 综合空间复杂度:O(K)
相关推荐
TL滕1 小时前
从0开始学算法——第五天(初级排序算法)
数据结构·笔记·学习·算法·排序算法
Q741_1471 小时前
C++ 高精度计算的讲解 模拟 力扣67.二进制求和 题解 每日一题
c++·算法·leetcode·高精度·模拟
夏乌_Wx1 小时前
练题100天——DAY19:含退格的字符串+有序数组的平方
算法
Ayanami_Reii1 小时前
进阶数据结构应用-线段树扫描线
数据结构·算法·线段树·树状数组·离散化·fenwick tree·线段树扫描线
leoufung1 小时前
LeetCode 98 Validate Binary Search Tree 深度解析
算法·leetcode·职场和发展
水木姚姚1 小时前
C++ begin
开发语言·c++·算法
浅川.251 小时前
xtuoj 素数个数
数据结构·算法
jyyyx的算法博客2 小时前
LeetCode 面试题 16.18. 模式匹配
算法·leetcode
uuuuuuu2 小时前
数组中的排序问题
算法