- 科学计算
【免费下载链接】codeforces-go
算法竞赛模板库 by 灵茶山艾府 💭💡🎈
本篇技术指南以算法竞赛模板库 codeforces-go(灵茶山艾府维护)中 leetcode/biweekly/102/a/README.md 题解文档为主体,完整拆解 LeetCode 双周赛 102 第一题「Find the Width of Columns of a Grid(网格列宽)」的两种解法:逐列枚举求字符串长度的朴素做法,以及仅考察列最值的 O(n(m+log U)) 优化做法。文中结合仓库内 Go 实现、测试用例 与 测试数据文件,给出可复制、可验证的 Python / Java / C++ / Go / JavaScript / Rust 多语言代码,并解释“负号占一位”这一易错点背后的长度计算原理。
题目背景与仓库对应关系
该题是 LeetCode 双周赛第 102 场的第一题(Problem 1),题目编号为 2639,官方题名为 “Find the Width of Columns of a Grid”。在 codeforces-go 仓库中,该题解位于 leetcode/biweekly/102/a/README.md,配套的 Go 实现位于 a.go,测试文件为 a_test.go,输入输出数据存放在 a.txt。仓库的目录组织方式是按照比赛场次(biweekly/102)与题号(a、b、c、d)分层存放,a目录下固定包含README.md、a.go、a.txt、a_test.go四个文件,便于读者在同一目录内完成“读题解 → 看实现 → 跑用例”的完整闭环。
题目描述:给定一个由整数构成的二维网格grid(grid[i][j]为int类型),对于每一列,需要求出该列中所有整数以十进制字符串形式表示时的最大字符长度,返回一个长度为grid[0].length的整数数组。注意负号-也计入字符长度,例如-15的长度为 3。
方法一:逐列枚举,求数字转字符串后的最大长度
最直观的思路:逐列遍历,将每一列的每个数字转换为十进制字符串,取其长度的最大值。由于负号属于字符串的一部分,str(-15)的长度自然是 3,因此转字符串的做法天然正确。
多语言实现(转字符串版)
class Solution: def findColumnWidth(self, grid: List[List[int]]) -> List[int]: return [max(len(str(x)) for x in col) for col in zip(*grid)]class Solution { public int[] findColumnWidth(int[][] grid) { int n = grid[0].length; int[] ans = new int[n]; for (int j = 0; j < n; j++) { for (int[] row : grid) { ans[j] = Math.max(ans[j], Integer.toString(row[j]).length()); } } return ans; } }class Solution { public: vector<int> findColumnWidth(vector<vector<int>>& grid) { int n = grid[0].size(); vector<int> ans(n); for (int j = 0; j < n; j++) { for (auto& row : grid) { ans[j] = max(ans[j], (int) to_string(row[j]).length()); } } return ans; } };func findColumnWidth(grid [][]int) []int { ans := make([]int, len(grid[0])) for j := range grid[0] { for _, row := range grid { ans[j] = max(ans[j], len(strconv.Itoa(row[j]))) } } return ans }var findColumnWidth = function(grid) { const n = grid[0].length; const ans = Array(n).fill(0); for (let j = 0; j < n; j++) { for (const row of grid) { ans[j] = Math.max(ans[j], row[j].toString().length); } } return ans; };impl Solution { pub fn find_column_width(grid: Vec<Vec<i32>>) -> Vec<i32> { (0..grid[0].len()).map(|j| { grid.iter().map(|row| row[j].to_string().len()).max().unwrap() as i32 }).collect() } }手动计算长度(不依赖字符串转换)
若希望避免字符串转换的开销,也可以手动统计位数。核心思路是:x <= 0时(包括0与负数)先令长度len = 1(0本身占一位,负数的负号占一位),再对x反复除以 10 累加位数。注意0的处理:x != 0的循环条件对0不会执行,因此必须由初始的len = 1兜底。
class Solution: def findColumnWidth(self, grid: List[List[int]]) -> List[int]: ans = [0] * len(grid[0]) for j, col in enumerate(zip(*grid)): for x in col: x_len = int(x <= 0) x = abs(x) while x: x_len += 1 x //= 10 ans[j] = max(ans[j], x_len) return ansclass Solution { public int[] findColumnWidth(int[][] grid) { int n = grid[0].length; int[] ans = new int[n]; for (int j = 0; j < n; j++) { for (int[] row : grid) { int len = row[j] <= 0 ? 1 : 0; for (int x = row[j]; x != 0; x /= 10) { len++; } ans[j] = Math.max(ans[j], len); } } return ans; } }class Solution { public: vector<int> findColumnWidth(vector<vector<int>>& grid) { int n = grid[0].size(); vector<int> ans(n); for (int j = 0; j < n; j++) { for (auto& row : grid) { int len = row[j] <= 0; for (int x = row[j]; x; x /= 10) { len++; } ans[j] = max(ans[j], len); } } return ans; } };func findColumnWidth(grid [][]int) []int { ans := make([]int, len(grid[0])) for j := range grid[0] { for _, row := range grid { xLen := 0 if row[j] <= 0 { xLen = 1 } for x := row[j]; x != 0; x /= 10 { xLen++ } ans[j] = max(ans[j], xLen) } } return ans }var findColumnWidth = function(grid) { const n = grid[0].length; const ans = Array(n).fill(0); for (let j = 0; j < n; j++) { for (const row of grid) { let len = row[j] <= 0 ? 1 : 0; for (let x = Math.abs(row[j]); x; x = Math.floor(x / 10)) { len++; } ans[j] = Math.max(ans[j], len); } } return ans; };impl Solution { pub fn find_column_width(grid: Vec<Vec<i32>>) -> Vec<i32> { let n = grid[0].len(); let mut ans = vec![0; n]; for j in 0..n { for row in &grid { let mut len = if row[j] <= 0 { 1 } else { 0 }; let mut x = row[j]; while x != 0 { len += 1; x /= 10; } ans[j] = ans[j].max(len); } } ans } }该方法在仓库中有对应的直接实现:findColumnWidth2,见 a.go 第 21-36 行,它正是上述“手动统计位数”的 Go 版本,用于与优化版findColumnWidth进行对照验证。
方法一复杂度分析
- 时间复杂度:O(mn log U),其中 m 和 n 分别为
grid的行数和列数,U 为grid[i][j]的绝对值的最大值。 - 空间复杂度:O(1),返回值不计入;Python 中
zip(*grid)产生的临时列视图空间同样忽略不计。
方法二:优化——只对每一列的最小值和最大值求长度
方法一需要为每一个数字计算长度。但观察到:数字的绝对值越大,其十进制长度越长(即位数单调递增)。因此,一列的最大宽度必然由该列的最小值或最大值决定,只需取二者之一计算长度即可,无需遍历整列所有元素。
设某列的最小值为mn,最大值为mx。由于负数中的负号也占一个长度,可以直接取
max(mx, -10 * mn)的长度作为答案(Python 一行写法即基于此公式)。
或者,为避免10 * mn乘法溢出,可以改写为取
max(mx // 10, -mn)的长度再加一作为答案,此时要把0的长度视作 0。注意:上述公式在一整列全为负数或全为正数时同样成立。
为什么可以这样变换?核心在于位数与数值规模的关系:长度为 k 的十进制数的取值范围约为[10^(k-1), 10^k)。max(mx, -10*mn)的意义是把最小的负数“放大十倍”后与最大正数比较,从而让负数的位数能体现出来;而max(mx//10, -mn)再 +1 则是利用整除降位的等价变形,避免了乘法溢出。
多语言实现(优化版)
class Solution: def findColumnWidth(self, grid: List[List[int]]) -> List[int]: return [len(str(max(max(col), -10 * min(col)))) for col in zip(*grid)]class Solution: def findColumnWidth(self, grid: List[List[int]]) -> List[int]: ans = [] for col in zip(*grid): x_len = 1 x = max(max(col) // 10, -min(col)) while x: x_len += 1 x //= 10 ans.append(x_len) return ansclass Solution { public int[] findColumnWidth(int[][] grid) { int n = grid[0].length; int[] ans = new int[n]; for (int j = 0; j < n; j++) { int mn = 0; int mx = 0; for (int[] row : grid) { mn = Math.min(mn, row[j]); mx = Math.max(mx, row[j]); } int len = 1; for (int x = Math.max(mx / 10, -mn); x > 0; x /= 10) { len++; } ans[j] = len; } return ans; } }class Solution { public: vector<int> findColumnWidth(vector<vector<int>>& grid) { int n = grid[0].size(); vector<int> ans(n); for (int j = 0; j < n; j++) { int mn = 0, mx = 0; for (auto& row : grid) { mn = min(mn, row[j]); mx = max(mx, row[j]); } int len = 1; for (int x = max(mx / 10, -mn); x; x /= 10) { len++; } ans[j] = len; } return ans; } };func findColumnWidth(grid [][]int) []int { ans := make([]int, len(grid[0])) for j := range grid[0] { mn, mx := 0, 0 for _, row := range grid { mn = min(mn, row[j]) mx = max(mx, row[j]) } xLen := 1 for x := max(mx/10, -mn); x > 0; x /= 10 { xLen++ } ans[j] = xLen } return ans }var findColumnWidth = function(grid) { const n = grid[0].length; const ans = Array(n); for (let j = 0; j < n; j++) { let mn = 0, mx = 0; for (const row of grid) { mn = Math.min(mn, row[j]); mx = Math.max(mx, row[j]); } let len = 1; for (let x = Math.max(Math.floor(mx / 10), -mn); x; x = Math.floor(x / 10)) { len++; } ans[j] = len; } return ans; };impl Solution { pub fn find_column_width(grid: Vec<Vec<i32>>) -> Vec<i32> { let n = grid[0].len(); let mut ans = vec![0; n]; for j in 0..n { let mut mn = 0; let mut mx = 0; for row in &grid { mn = mn.min(row[j]); mx = mx.max(row[j]); } let mut len = 1; let mut x = (mx / 10).max(-mn); while x > 0 { len += 1; x /= 10; } ans[j] = len; } ans } }仓库 Go 实现与测试验证
仓库中的正式提交实现位于 a.go 第 4-19 行,与 README 中的优化版 Go 代码一致:先初始化mn, mx = 0, 0,对每一列扫描求得列最小值与最大值,再以xLen := 1起步,对max(mx/10, -mn)反复整除 10 累加位数。
测试侧由 a_test.go 驱动,它通过反射调用RunLeetCodeFuncWithFile(t, findColumnWidth, "a.txt", targetCaseNum)读取 a.txt 中的用例(该机制在 leetcode/testutil/leetcode.go 第 340-370 行实现:将文件内容按“函数入参个数 + 返回个数”分组解析),并使用RunFuncWithRandomInput进行随机输入对拍。当前测试数据共两组:
| 输入 grid | 期望输出 |
|---|---|
[[1],[22],[333]] | [3] |
[[-15,1,3],[15,7,12],[5,6,-2]] | [3,1,2] |
第二组用例很好地覆盖了负号占位的情况:第一列-15与15的字符串长度为 3,故答案为 3;第二列各数为1,7,6,长度均为 1;第三列3,12,-2中12与-2长度为 2。
方法二复杂度分析
- 时间复杂度:O(n(m + log U)),其中 m 和 n 分别为
grid的行数和列数,U 为grid[i][j]的绝对值的最大值。相比方法一的 O(mn log U),将“对所有元素求长度”降为“仅对列最值求长度”,log 部分只承担一次位数统计。 - 空间复杂度:O(1),返回值不计入;Python 中
zip(*grid)的空间同样忽略。
总结与延伸
本题的关键考点有两个:一是处理0与负数(负号占一位)时的长度计算细节,二是借助“位数随绝对值单调递增”这一性质,把逐元素统计优化为只考察列最小值与最大值。方法一实现直观、不易出错,适合作为“保底”写法;方法二在 m 很大时能显著减少字符串转换或除法统计的次数,是竞赛场景下的推荐写法。
该仓库还提供了完整的题解索引 leetcode/SOLUTIONS.md,其中收录了作者(灵茶山艾府)按类别整理的力扣题解精选,便于读者继续学习同类型的网格、位运算、动态规划等专题;仓库根目录下的 go.mod 与 go.sum 则管理着上述测试依赖(如 testify),确保go test ./leetcode/biweekly/102/a/可以直接运行验证。
- 科学计算
【免费下载链接】codeforces-go
算法竞赛模板库 by 灵茶山艾府 💭💡🎈
相关推荐
codeforces-go 题解:LeetCode 双周赛 102「数组所有前缀的得分」—— 前缀最大值与得分累计的单遍扫描
codeforces go 题解:LeetCode 双周赛 102「数组所有前缀的得分」—— 前缀最大值与得分累计的单遍扫描 导读 本文基于 leetcode/
科学计算Sails 框架下通过自定义 HTTP 中间件配置 P3P 隐私策略头(P3P 兼容旧版 IE 应用实战指南)
Sails 框架下通过自定义 HTTP 中间件配置 P3P 隐私策略头(P3P 兼容旧版 IE 应用实战指南) 导读 本文讲解如何在 Sails(Node.js
科学计算掌握Go优先队列:算法竞赛中的高效性能优化指南
掌握Go优先队列:算法竞赛中的高效性能优化指南 在算法竞赛中,时间复杂度往往是决定解题成败的关键因素。Go语言的优先队列(Priority Queue)作为一种
科学计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考