力扣1001网格照明解法

力扣1001 网格照明 题解

问题分析

网格照明问题(LeetCode 1001)要求模拟在 n x n 网格上的一系列操作:放置灯和查询位置是否被照亮。核心挑战在于高效处理大规模网格(n 可达 10^9)和大量操作(lamps 和 queries 长度可达 2 * 10^4)。直接存储和遍历网格的二维数组显然不可行。

问题的关键在于理解:一盏灯会照亮其所在的行、列和两条对角线。因此,我们可以通过维护四个哈希表来分别记录被照亮的行、列和两条对角线的状态,从而将空间复杂度从 O(n²) 降低到 O(L + Q),其中 L 是灯的数量,Q 是查询的数量。

算法思路与数据结构设计

1. 核心数据结构

使用四个哈希表(在C++中为 unordered_map)来分别记录:

  • row:行索引 -> 该行上亮着的灯的数量。
  • col:列索引 -> 该列上亮着的灯的数量。
  • diag1:主对角线(左上-右下)索引 -> 该对角线上亮着的灯的数量。对角线索引可通过 行索引 - 列索引 计算。
  • diag2:副对角线(右上-左下)索引 -> 该对角线上亮着的灯的数量。对角线索引可通过 行索引 + 列索引 计算。

此外,需要一个快速查找灯位置的数据结构,用于在关闭周边灯时判断该位置是否真的有灯。使用 unordered_set 存储灯的唯一位置编码(例如 长整型字符串)是高效的选择。

2. 算法步骤

  1. 初始化 :将所有灯的位置加入 灯位置集合,并更新四个哈希表的计数。
  2. 处理查询
    • 对于每个查询位置 (query_x, query_y),检查四个哈希表中对应的计数是否大于0。只要有一个大于0,则该位置被照亮,答案记录为1,否则为0。
    • 随后,关闭 查询位置及其周围8个相邻格子(九宫格)内的所有灯。
      • 遍历九宫格内的每个位置 (nx, ny)
      • 如果 (nx, ny)灯位置集合 中,则将其从集合中移除,并将四个哈希表中对应的计数减1(如果减到0,可以选择删除该键值对以节省空间)。
  3. 返回结果:返回所有查询结果的数组。

C++ 代码实现

cpp 复制代码
#include <vector>
#include <unordered_map>
#include <unordered_set>
using namespace std;

class Solution {
public:
    vector<int> gridIllumination(int n, vector<vector<int>>& lamps, vector<vector<int>>& queries) {
        // 核心数据结构初始化
        unordered_map<int, int> row, col, diag1, diag2;
        // 使用长整型编码灯的位置,避免字符串操作开销
        unordered_set<long long> lampSet;
        // 方向数组,表示查询位置周围的8个邻居和自身(共9个位置)
        int dirs[9][2] = {{0,0}, {0,1}, {0,-1}, {1,0}, {-1,0}, {1,1}, {1,-1}, {-1,1}, {-1,-1}};

        // 步骤1:放置所有灯,初始化哈希表
        for (const auto& lamp : lamps) {
            int x = lamp[0];
            int y = lamp[1];
            long long key = (long long)x * n + y; // 将二维坐标编码为一维唯一键
            // 防止重复的灯被重复添加
            if (lampSet.insert(key).second) {
                row[x]++;
                col[y]++;
                diag1[x - y]++; // 主对角线索引
                diag2[x + y]++; // 副对角线索引
            }
        }

        vector<int> ans;
        ans.reserve(queries.size()); // 预分配空间提升效率

        // 步骤2:处理每个查询
        for (const auto& query : queries) {
            int x = query[0];
            int y = query[1];

            // 判断当前位置是否被照亮:检查对应的行、列、对角线是否有灯
            bool isLit = (row[x] > 0) || (col[y] > 0) || (diag1[x - y] > 0) || (diag2[x + y] > 0);
            ans.push_back(isLit ? 1 : 0);

            // 步骤3:关闭九宫格内的灯
            for (const auto& dir : dirs) {
                int nx = x + dir[0];
                int ny = y + dir[1];
                // 检查新坐标是否在网格范围内
                if (nx >= 0 && nx < n && ny >= 0 && ny < n) {
                    long long key = (long long)nx * n + ny;
                    // 如果该位置有灯,则关闭它
                    if (lampSet.erase(key)) {
                        // 从哈希表中移除该灯的影响
                        if (--row[nx] == 0) row.erase(nx);
                        if (--col[ny] == 0) col.erase(ny);
                        if (--diag1[nx - ny] == 0) diag1.erase(nx - ny);
                        if (--diag2[nx + ny] == 0) diag2.erase(nx + ny);
                    }
                }
            }
        }
        return ans;
    }
};

复杂度分析

维度 复杂度 说明
时间复杂度 O(L + Q) 其中 L 是 lamps 的长度,Q 是 queries 的长度。放置灯为 O(L),每个查询的检查和关灯操作是常数时间(最多检查9个位置)。
空间复杂度 O(L) 主要用于存储 lampSet 和四个哈希表,最坏情况下都与灯的数量 L 成正比。

关键点与技巧总结

  1. 坐标编码 :将二维坐标 (x, y) 编码为 (long long)x * n + y,确保在 unordered_set 中唯一且高效。
  2. 对角线索引计算
    • 主对角线(斜率1):索引值为 x - y。同一条主对角线上的点,此值相同。
    • 副对角线(斜率-1):索引值为 x + y。同一条副对角线上的点,此值相同。
  3. 避免重复灯 :使用 unordered_setinsert 方法返回值来判断灯是否已存在,防止哈希表计数错误增加。
  4. 哈希表清理 :当某个行、列或对角线的灯数量减为0时,使用 erase 方法删除该键,有助于减少哈希表的大小,提升后续查询效率(尽管不是必须的)。
  5. 方向遍历 :使用预定义的方向数组 dirs 使代码更简洁,避免手动书写9个条件判断。

此解法通过将空间状态从网格转换到线性的哈希表,巧妙地解决了超大网格带来的内存限制问题,是典型的利用数据结构降维思想的案例。


参考来源

相关推荐
kisshyshy15 小时前
🍦 雪糕、食堂、火车厢:三幅漫画吃透栈、队列与链表
javascript·算法
猿人谷1 天前
不只是 CPU 阈值:STAR 如何用 GAT + Transformer 做容器级自动扩缩容?
人工智能·算法
复杂网络1 天前
Stable Diffusion 视觉大模型微调技术深度调研
算法
复杂网络1 天前
基于 Stable Diffusion 架构的视觉大模型代表性工作与原理深度解析
算法
MrZhao4001 天前
Agent Loop 如何用 Hook 扩展:权限、日志与工具拦截
算法
MrZhao4001 天前
Agent 为什么需要 Skills:别把所有知识都塞进 system prompt
算法
JieE2122 天前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
JieE2123 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
Jack204 天前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法
小小杨树4 天前
读懂色彩:拍照调色不再难
算法·计算机视觉·配色