先说个题外话。如果你研究过最近这段时期的周赛题目,会发现一个趋势:Q1不再单纯考“你会不会写if else”,而是把字符串处理、数学规律、甚至一点点数感全部揉在一起。这次第477场周赛的Q1——3754. 连接非零数字并乘以其数字和 I,就是一个很典型的例子。题目一句话就能看懂,但很多人第一次写的时候会在“拼接”“乘数字和”的中间环节绕弯子。
这篇文章不打算只贴一份题解。我尽量从一个参赛者的角度,把这道题从读题、改思路、写代码、到最后填坑的完整过程都过一遍。你可以把它当成一份“周赛Q1速通手册”来读。
1. 先读懂题目:两个关键操作的拆解
题目名称很长,但核心动作其实只有两个。先看原题描述里反复出现的几个词:非零数字、连接、数字和。
1.1 “非零数字”到底指什么
输入给你的是一个字符串,里面可能混杂着数字0和1到9之间的其他数字。你需要先把所有'0'字符剔除,然后把剩下的数字字符按原本的顺序拼接成一个新数。
举个例子。
假设输入字符串是"10510"。
这里面的非零字符是'1'、'5'、'1'。保持原有顺序,拼接成字符串"151",然后转成数字就是151。
有两点容易踩坑:
- 剔除的是字符
'0'本身,不是“值为0的数字”。'0'就是字符零,没有歧义。 - 拼接顺序不能乱,必须是原字符串里从左到右出现的顺序。你不能把非零数字提出来之后自己排序,那样就错了。
1.2 “乘以数字和”的前后关系
数字和(digit sum),指的是一个整数的十进制表示中每一位数字相加的结果。
比如刚才得到的151,它的数字和是1 + 5 + 1 = 7。
最终答案是:拼接得到的整数与这个整数的数字和相乘。
所以151 × 7 = 1057。
整体流程用一句话概括:过滤掉0 → 拼接 → 转整数 → 求数字和 → 相乘。第一步和第二步解决“连接非零数字”,第三步和第四步解决“乘以其数字和”。
1.3 一个直观的示例跑通
题目里通常会带示例:
输入: s = "10510" 输出: 1057我按上面流程走一遍:
- 非零字符依次是
'1'、'5'、'1' - 拼接得到
"151" - 字符串转整数
151 - 数字和
1 + 5 + 1 = 7 - 乘积
151 * 7 = 1057
和输出一致。这个示例很温和,但它把题目所有的坑都藏在里面了。稍后我会讲为什么这个看似简单的示例足够检验你代码的正确性。
2. 思路演进:从模拟到优化的思维过程
老实说,这道题第一眼看到,正常的反应是:“直接模拟不就行了?”确实,模拟是能过的。但我建议你多走一步,把问题拆开看,否则周赛现场容易在细节上翻车。
2.1 最直接的模拟思路
纯模拟解法分四步走:
- 遍历字符串,跳过
'0'字符。 - 把非零字符追加到结果字符串。
- 结果字符串转整数
num。 - 计算
num的数字和digitSum,返回num * digitSum。
这个解法的时间复杂度是O(n),其中 n 是字符串长度。空间复杂度也是O(n),因为你保存了拼接后的字符串。对于一道 Q1 来说,这个复杂度已经非常舒服了。就算是 n 拉到 10^5,也没有任何压力。
2.2 能不能不拼接字符串?
有一种更省空间的写法:遇到非零字符时,用“乘10加digit”的方式直接构造整数。
比如字符'1',就是0 * 10 + 1 = 1;下一个字符'5',就是1 * 10 + 5 = 15;再下一个'1',就是15 * 10 + 1 = 151。
这样做的好处是省掉了临时字符串,直接从字符流得到整数结果。
num = 0 for ch in s: if ch != '0': num = num * 10 + (ord(ch) - ord('0'))到这里,拼接这一步已经被“数值构造”替代了。本质上还是模拟,但更接近机器思维。
2.3 更进一步的数学观察:省掉“数字和”的循环
计算数字和的常规办法是循环取模相加:
x = num digit_sum = 0 while x > 0: digit_sum += x % 10 x //= 10但如果你对字符串处理足够敏感,会发现一个事实:在拼接过程中,每一位非零数字后来都会成为最终整数的一位。也就是说,数字和不需要等整数构造完之后再算,而是可以在遍历字符串时同步累加。
这个小小的观察能把“求数字和”和“构造整数”合并成一次遍历,代码更简洁,逻辑也更顺。
num = 0 digit_sum = 0 for ch in s: if ch != '0': d = ord(ch) - ord('0') num = num * 10 + d digit_sum += d return num * digit_sum2.4 三种写法对比
为了方便你在比赛现场做选择,我把三种思路的特点整理成了表格:
| 写法 | 核心优点 | 潜在缺点 | 适合场景 |
|---|---|---|---|
| 字符串拼接再转整数 | 直观、不容易错 | 多了一次字符串转整数的开销 | 任何基础写法 |
| 乘10加digit构造整数 | 省字符串、效率高 | 需理解数值构造原理 | 追求干净代码 |
| 构造整数+同步累加数字和 | 一次遍历完成两件事 | 代码稍绕,但熟了就很顺 | 比赛推荐写法 |
我自己在周赛现场用第三种。第一是代码短,第二是少一次循环,第三是逻辑上更连贯。
3. 代码落地:Python与各语言的实现细节
思路清楚了,代码就是水到渠成的事。下面给出几种常见语言的写法,以及一些值得注意的实现细节。
3.1 Python版本:干净利落的同步写法
def result(s: str) -> int: num = 0 digit_sum = 0 for ch in s: if ch != '0': d = ord(ch) - ord('0') num = num * 10 + d digit_sum += d return num * digit_sum这里有个小知识点:ord(ch) - ord('0')是字符转数字最常见的方式,比int(ch)更底层、更快,尤其在大规模循环里能明显感觉到性能差异。
如果你更偏爱 Python 的函数式风格,也可以这样写:
def result(s: str) -> int: num_str = ''.join(ch for ch in s if ch != '0') num = int(num_str) digit_sum = sum(int(c) for c in num_str) return num * digit_sum这个写法可读性更高,但性能稍弱,因为遍历了两次字符串。周赛环境里差距不大,我这么说只是帮你做个取舍。
3.2 Java版本:注意字符数字转换细节
class Solution { public int result(String s) { long num = 0; int digitSum = 0; for (char ch : s.toCharArray()) { if (ch != '0') { int d = ch - '0'; num = num * 10 + d; digitSum += d; } } return (int)(num * digitSum); } }注意两点:
char - '0'在 Java 里是合法运算,直接得到数字值,不需要显式转换。- 我用了
long存num,因为如果拼接结果特别大,int可能会溢出。虽然这道题最终返回值要求int,但中间值是安全的。这种“中间用大类型,最后再转窄”的习惯,能帮你规避不少隐蔽的溢出问题。
3.3 C++版本:字符串与整数的取舍
class Solution { public: int result(string s) { long long num = 0; long long digitSum = 0; for (char ch : s) { if (ch != '0') { int d = ch - '0'; num = num * 10 + d; digitSum += d; } } return num * digitSum; } };C++ 里需要注意的同样是中间类型。num * digitSum在返回前先用long long算,再隐式转换回int,安全又省心。
3.4 一段代码是否正确的快速验证法
代码写完之后,别急着提交。按下面三步做一次自查:
- 拿题目的示例跑一遍。
- 构造一个全零输入
"0000",看结果是否为0。 - 构造一个无零输入
"1234",手动算一遍:拼接1234,数字和10,期望输出12340。
如果这三个 case 都通过,核心逻辑基本就稳了。
4. 边界场景与细节验证:实用经验汇总
周赛Q1的正确率通常不是被“难题”拉低的,而是被“边界条件”拉低的。你少考虑一个空串,负了;你多算一个前导零,错了。这题也不例外。
4.1 输入为全零字符串
输入可能是"000",这时候所有字符都是'0',一个非零数字都选不出来。会发生什么?
- 拼接结果为空字符串
"" - 如果按字符串转整数,
int("")会直接报错 - 如果按数值构造方式,
num保持为0
所以最佳实践是不要依赖字符串转换,而是用“乘10加digit”的数值构造方式,天然处理了“没有非零数字”的情况。
若你非要用字符串拼接写法,需要在最后单独判断一次:
if num_str == "": return 0这样也不算错,但多一行判断总归是隐患。数值构造写法直接覆盖这种情况,这也是我更喜欢它的原因之一。
4.2 最后一个非零数字是0吗?
很多人写的时候会想:“我不会把最后一个数字也过滤掉吧?”实际上不会,因为判断条件永远是ch != '0',跟位置无关。
真正容易出问题的是另一种情况:输入末尾有非零数字,但中间的某个非零数字恰好是字符'0'的ASCII码?
这个顾虑纯属多余。'0'在ASCII表里是48,数字'0'到'9'之间没有重叠字符。你只需要考虑字符是否等于'0',不要去想“数字0”和“字符0”之间的身份切换,它们是同一回事。
4.3 输入里的空格和其他符号
题目保证了输入是数字字符串,不会混入字母或符号。如果真的遇到混入情况,你只需要判断“是否为非零数字字符”:
if '1' <= ch <= '9': # 处理这种写法比ch != '0'更严格,同时也更安全。周赛题目不会给你这种额外输入,但面试题里没准会用上。
4.4 字符串很长的极端场景
如果 n 很大(比如10^6),你应该注意以下几点:
- Python 的
int是任意精度,不会溢出,不用管。 - Java/C++ 的
long在拼接结果超过9×10^18时就会溢出,这时候需要改用更高精度方案。但以周赛Q1的输入范围来看,几乎不可能触发。 - 用
join方法一次性拼接,比逐字符+=在性能上更优,因为 Python 字符串是不可变对象,逐字符追加会产生大量临时对象。
4.5 一个我踩过的坑:把“数字和”算成了“位数和”
有次我在做类似题目的时候,想当然地认为“数字和”是“有几个数字”,结果151的数字和算成了3,全程懵了十分钟。
记住:数字和是各位数字相加,不是位数。151的数字和是7,不是3。如果题目考你“位数”,它不会说“数字和”。
为了彻底避免这种精神滑坡,看到“数字和”三个字,你可以立刻转化成公式:
digitSum(x) = x的每一位相加5. 周赛定位与面试加分点
这道题作为周赛Q1,难度定在简单级别,但它在实际比赛里依然能筛掉不少人。原因不在于题目本身难,而在于它考察了三个容易被轻视的基本功。
5.1 Q1的筛选逻辑
周赛Q1一般设置60%~75%的通过率,但3754这道题的实际通过率可能会更低一点。为什么?因为“拼接非零数字”这个说法给了人一种“必须用字符串处理”的暗示,导致一部分人绕进int(''.join(...))的写法里,遇到全零输入时直接报错。
出题人把简单的数学问题藏在字符串操作外衣下,就达到了筛选目的。
5.2 面试官会从这个题抽考什么
如果你在面试里遇到类似的题,别只看表面要求,面试官通常会在你写完代码之后追加几个问题:
- “如果输入字符串长度为10^6,你的方案还能跑吗?”——考察对时间复杂度和字符串拼接性能的理解。
- “如果允许我把所有0都剔掉,但要求结果仍然保持整数,你如何处理全零输入?”——考察边界条件意识。
- “能不能用一次遍历完成所有操作?”——考察对题干信息的综合运用能力。
只要你在写的时候用了“数值构造 + 同步累加数字和”的思路,这追问全都能接住。
5.3 从这道题能迁移出去的知识点
我做完这道题之后,顺手把它和几个经典题目做了关联,发现模式完全相通:
- 字符串去重/过滤:类似 LeetCode 里的某些基础过滤题,核心都是用
if判断字符特征。 - 数字重构:
num = num * 10 + d是我处理“数字重组”类问题时的万能公式。 - 数字和计算:可以缓存、可以异步累加,也可以配合取模运算解决大数场景。
如果你刚接触算法,把这题背后的三个小技能练熟,对后续刷题帮助很大。
6. 常见问题与解题技巧梳理
我把参赛者常遇到的问题整理成了速查表,顺便补充一些我从实战里总结的微技巧。
6.1 常见问题速查表
| 问题现象 | 根本原因 | 解决方法 |
|---|---|---|
int("")报错 | 全零输入导致拼接结果为空字符串 | 用数值构造方式,避免字符串转整数 |
| 输出比预期多一位数字 | 把“数字和”算成“位数和” | 明确数字和是各位相加 |
| 大数情况输出异常 | 中间结果溢出了int | Java/C++ 中间用long/long long |
| 输出结果不对但逻辑感觉没错 | 拼接顺序被打乱了 | 检查是否对非零字符做了排序 |
| 字符串很长时运行超时 | 逐字符+=导致大量临时字符串 | 使用join或数值构造 |
6.2 独家技巧:处理拼接被“隔断”的情况
这个题是“选择性的拼接”,但很多变体是“遇到某些字符时插入一个指定数字”。面对这类题,我有个习惯:
先把所有字符统一处理为数字,再统一做“过滤 + 重构”,不要边过滤边插入指定值,那样容易乱。
拿这个题来说:
- 过滤:只保留非零字符
- 重构:乘10加digit
先把过滤做完,再做重构,逻辑天然清晰。如果你一边过滤一边判断“这个位置要不要加点什么”,就会引入不必要的复杂度。
6.3 现场比赛的答题策略
周赛时间紧,Q1的目标是快速且不出错,不是追求最优解。所以我建议:
- 第一遍按最直观的思路写(字符串拼接 + 字符串转整数 + 循环求数字和)。
- 如果能快速想到“乘10加digit”的写法,直接写这种。
- 写完立刻跑示例,再补一个全零输入的自测用例。
- 全部通过就提交,不要犹豫。
我见过很多人写Q1时花20分钟调一个根本不存在的边界问题,最后Q2/Q3都没时间碰。Q1不值得你纠结太久。
6.4 一行代码的极限版本
如果你只是想炫技,可以试试这个 Python 一行流:
def result(s: str) -> int: num = int(''.join(c for c in s if c != '0')) if any(c != '0' for c in s) else 0 return num * sum(int(c) for c in str(num))这个版本纯粹图一乐,不建议比赛用。但它的存在说明一个问题:你能把整个思路压缩成一行,说明你对每一步都足够熟悉了。
7. 扩展思考:从这道题看算法思维
做完一道简单题,如果只停留在 AC 层面,收获是很有限的。每次周赛题解结束后,我都会问自己一个问题:这道题真正想考察的是什么?
这道 3754,表面上考的是字符串处理和模拟,实际上考的是:你能不能把一个混合着多种操作的任务,拆成独立的小步骤,然后按正确的顺序组装起来。
过滤、拼接、求和、相乘,四个步骤单独拿出来都不难,但拧在一起之后,很多人就开始乱。
这也是为什么这类“简单题”值得专门记录:它检验的不是你对某个数据结构的掌握,而是你把需求翻译成操作的能力。
如果你以后要参加更多算法比赛,建议养成一个习惯:拿到任何题目,先不急着写代码,在纸上写下输入和输出之间发生了什么。这一步看似浪费几秒钟,实际能避免大多数低级错误。
我个人在做这道题时,真正花了心思的地方只有一处:确认清楚是否能把数字和的累加提前到遍历阶段。一旦这步想通了,整道题的代码就特别简洁。
希望这篇文章能帮你少走一些弯路。如果比赛时遇到类似题,记住这句话就行:非零数字依次拼接、边构造边累加数字和、最后乘起来。三步走完,基本离 AC 不远了。