- 科学计算
【免费下载链接】codeforces-go
算法竞赛模板库 by 灵茶山艾府 💭💡🎈
导读
本文讲解 LeetCode 第 274 场周赛第二题(题号 2125,Number of Laser Beams in a Bank)的完整题解。该题以二维网格描述银行安全设备的排布,要求计算相邻非空行之间所有可能的激光束数量。通过本文你将掌握"先压缩行、再对相邻行计数相乘求和"的 O(mn) 线性扫描套路,并看到同一算法在 Python / Java / C++ / C / Go / JavaScript / Rust 七种语言下的等价实现,以及它在 codeforces-go 仓库中的源码与自动化测试验证。
题目背景与题意
bank是一个二维字符数组(网格),每一行是一个长度为n的二进制字符串,'1'表示该位置有一台安全设备(security device),'0'表示空位。
激光束的规则如下:
- 一台位于第
r1行、第c1列的安全设备,可以发出激光束到第r2行、第c2列的另一台设备,当且仅当两行满足r1 < r2; - 在
r1与r2之间的所有中间行(即第r1+1到r2-1行)中,每一行都必须不含任何安全设备; - 同一行的设备之间不发射激光束(规则中列位置不影响相邻性的判断,真正起决定作用的是行之间是否有"空行"隔开)。
因此,任意两台设备之间是否存在激光束,只取决于两行之间是否夹着"完全没有设备"的行。这一约束让问题从"网格"退化成一维的"行序列"问题。
示例推导
示例 1 的网格有 4 行,安全设备的个数分别为3, 0, 2, 1:
| 行 | 内容 | 设备数 |
|---|---|---|
| 0 | 011001 | 3 |
| 1 | 000000 | 0 |
| 2 | 010100 | 2 |
| 3 | 001000 | 1 |
第一步,去掉没有安全设备的行(第 1 行),剩下3, 2, 1;
第二步,计算相邻行之间激光束的数量之和:
- 第 0 行与第 2 行:两台来自第 0 行的设备 × 两台来自第 2 行的设备 =
3 × 2 = 6; - 第 2 行与第 3 行:
2 × 1 = 2; - 总和
3 × 2 + 2 × 1 = 8,即答案为8。
(仓库测试用例还覆盖了另一个边界示例:["000","111","000"]去掉空行后只剩一行设备,没有相邻非空行对,答案为0。)
核心思路:压缩 + 相邻非空行计数相乘
既然激光束只可能在"相邻的两个非空行"之间产生,我们只需从左到右扫描各行:
- 统计当前行的设备数
cnt(即字符串中'1'的个数); - 若
cnt == 0,跳过该行——它不会参与任何激光束,且会"隔离"它两侧的行; - 若
cnt > 0,则它与其上一个非空行之间会产生preCnt * cnt条激光束,累加到答案,并把preCnt更新为cnt。
用数学语言描述:设压缩后各非空行的设备数为a_1, a_2, ..., a_k,答案即为
ans = a_1 × a_2 + a_2 × a_3 + ... + a_{k-1} × a_k整个过程只需一次遍历,每次统计当前行设备数时遍历该行字符串,因此不需要构建任何辅助数组,空间占用为常数。
复杂度分析
- 时间复杂度:O(mn),其中 m 和 n 分别是
bank的行数和列数。每一行的每个字符都会被检查一次(统计'1'的个数); - 空间复杂度:O(1),只使用两个整数变量
ans与preCnt。
七种语言实现
以下各语言实现思路完全一致:ans累计结果,preCnt记录上一个非空行的设备数。
class Solution: def numberOfBeams(self, bank: List[str]) -> int: ans = pre_cnt = 0 for row in bank: cnt = row.count('1') if cnt > 0: ans += pre_cnt * cnt pre_cnt = cnt return ansclass Solution { public int numberOfBeams(String[] bank) { int ans = 0; int preCnt = 0; for (String row : bank) { int cnt = 0; for (char ch : row.toCharArray()) { cnt += ch - '0'; } if (cnt > 0) { ans += preCnt * cnt; preCnt = cnt; } } return ans; } }class Solution { public: int numberOfBeams(vector<string>& bank) { int ans = 0, pre_cnt = 0; for (auto& row : bank) { int cnt = ranges::count(row, '1'); if (cnt > 0) { ans += pre_cnt * cnt; pre_cnt = cnt; } } return ans; } };int numberOfBeams(char** bank, int bankSize) { int ans = 0, pre_cnt = 0; for (int i = 0; i < bankSize; i++) { char* row = bank[i]; int cnt = 0; for (int j = 0; row[j]; j++) { cnt += row[j] - '0'; } if (cnt > 0) { ans += pre_cnt * cnt; pre_cnt = cnt; } } return ans; }func numberOfBeams(bank []string) (ans int) { preCnt := 0 for _, row := range bank { cnt := strings.Count(row, "1") if cnt > 0 { ans += preCnt * cnt preCnt = cnt } } return }var numberOfBeams = function(bank) { let ans = 0, preCnt = 0; for (const row of bank) { const cnt = row.split('1').length - 1; if (cnt > 0) { ans += preCnt * cnt; preCnt = cnt; } } return ans; };impl Solution { pub fn number_of_beams(bank: Vec<String>) -> i32 { let mut ans = 0; let mut pre_cnt = 0; for row in bank { let cnt = row.bytes().filter(|&c| c == b'1').count() as i32; if cnt > 0 { ans += pre_cnt * cnt; pre_cnt = cnt; } } ans } }实现细节说明:
- 统计行内设备数的方式因语言而异:Python 的
str.count、Java/C 的ch - '0'累加、C++ 的ranges::count、Go 的strings.Count、JavaScript 的split('1').length - 1、Rust 的bytes().filter字节计数,本质都是 O(n) 的单行扫描; - 所有语言都只维护两个标量变量,体现了"滚动压缩"思想:遇到空行时什么都不做,遇到非空行时利用上一个非空行的信息做乘积累加。
仓库源码与自动化测试佐证
该题在 codeforces-go 仓库中有完整的 Go 实现与测试:
- 实现文件 leetcode/weekly/274/b/b.go:函数
numberOfBeams使用命名返回值(ans int),配合strings.Count(row, "1")统计每行设备数,逻辑与上文 Go 解法完全一致; - 测试文件 leetcode/weekly/274/b/b_test.go:由
copypasta/template/leetcode/generator_test.go生成,内嵌两个官方样例——["011001","000000","010100","001000"] → 8与["000","111","000"] → 0,并调用testutil.RunLeetCodeFuncWithExamples校验; - 测试框架 leetcode/testutil/leetcode.go 中的
RunLeetCodeFuncWithExamples使用反射自动解析输入输出:入参[]string由parseRawArg的 Slice 分支解析,答案通过assert.Equal逐用例比对,并支持DebugTLE超时检测(默认 2 秒,见 leetcode/testutil/config.go)。
在仓库根目录执行以下命令即可本地复现全部样例:
go test ./leetcode/weekly/274/b该测试同时验证了边界行为:当所有设备集中在同一行、或银行中非空行不足两行时,不存在任何激光束,答案恒为0——这正是preCnt初始为0且空行被跳过的自然结果。
题目归类与延伸
从算法归类看,本题属于"贪心与思维"中常见的脑筋急转弯式化简:表面上是二维网格,实际上通过"中间行必须全空"的约束,将问题压缩为一维相邻计数问题。这类"先化简、再扫描"的思路同样适用于其他以网格为壳、以相邻关系为核的题目。
从仓库组织方式看,本题文档位于 leetcode/weekly/274/b/2125.md,与同目录的b.go、b_test.go构成"题目说明 + 解法实现 + 样例测试"三位一体的目录结构,这也是 codeforces-go 仓库对每周力扣周赛题目的标准归档方式:按比赛场次(weekly/274)与题号(a/b/c/d)组织,便于检索与复盘。
总结
LeetCode 2125 的核心是识别出"激光束只存在于相邻非空行之间"这一关键性质,随后用一次 O(mn) 扫描完成压缩与乘积求和。整个算法没有复杂的推导,考察的是对题目条件的精确解读与实现效率。配合仓库中的 Go 实现与自动化测试,可以快速验证正确性并作为同类"相邻行/相邻元素统计"题目的模板参考。
- 科学计算
【免费下载链接】codeforces-go
算法竞赛模板库 by 灵茶山艾府 💭💡🎈
相关推荐
Sliver 项目中的 coder/websocket:RFC 6455 最小化 Go WebSocket 库的 API 与实战指南
Sliver 项目中的 coder/websocket:RFC 6455 最小化 Go WebSocket 库的 API 与实战指南 本指南以当前仓库 vend
科学计算codeforces-go 仓库题解精讲:LeetCode 1513 全 1 子串计数(线性扫描 + 最后 0 位置法)
codeforces go 仓库题解精讲:LeetCode 1513 全 1 子串计数(线性扫描 + 最后 0 位置法) 本篇基于 codeforces go
科学计算codeforces-go 仓库题解精讲:LeetCode 1200 最小绝对差(Minimum Absolute Difference)——排序后相邻扫描的一趟贪心法
codeforces go 仓库题解精讲:LeetCode 1200 最小绝对差(Minimum Absolute Difference)——排序后相邻扫描的一
科学计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考