我第一次做“数组元素积的符号”这道题时,心里想的是:这不就是白给?遍历一趟,把所有元素乘起来,判断正负不就行了。结果提交之后,第一个大用例直接把我打懵了——返回的符号根本不是正确答案。我盯着屏幕看了半天,才意识到问题出在乘积溢出上。
这道题在很多刷题平台上标着 Easy,但 Easy 不代表无脑。它考察的核心不是“能不能算出乘积”,而是“能不能识别出题目根本不要求你算乘积”。这篇文章我会完整拆解这道数组题目的数学本质、标准解法、四个主流语言的实现,再分享几个我实测踩过的坑和延伸变体。适合正在准备算法面试的开发者,也适合刚接触数组遍历、想搞明白“为什么不能硬乘”的编程新手。
1. 题目真正的坑:先乘后判会把符号判成“溢出符号”
1.1 我的第一版实现:直接累乘然后判断正负
先说说我最初的错误代码,C++ 版本:
cpp复制int arraySign(vector<int>& nums) {
int product = 1;
for (int x : nums) {
product *= x;
}
if (product > 0) return 1;
if (product < 0) return -1;
return 0;
}
从逻辑上看,这个代码完全自洽:乘积大于 0 返回 1,小于 0 返回 -1,等于 0 返回 0。问题不出在逻辑,而出在 int product 这个变量根本装不下真正的乘积。
题目里 nums[i] 的范围通常是 -100 到 100,数组长度最大 1000。也就是说最极端情况下乘积的绝对值是 100 的 1000 次方,这是一个 1 后面跟 2000 个零的天文数字。int 在 32 位环境下最大值是 2147483647,约 21.47 亿;long long 最大值约 9.22e18。这两个都远远装不下。
有人可能会说:“我测试小数组没问题啊。”对,小数组没问题,乘积在 int 范围内时一切正常。但问题恰恰在于,只要有一个测试用例的乘积超出范围,结果就完全不可控了。
1.2 极端测试用例:乘积溢出后,符号变成不可控的数字
我实际跑过的例子:
cpp复制vector<int> nums = {100000, 100000}; // 实际乘积 10^10
在 C++ 里,int 乘法溢出是未定义行为(UB),但在常见的 x86 编译器下,实际结果可能是某个被截断的数。比如 100000 * 100000 = 10000000000,转成 int 后变成了 1410065408,超出的高位直接被截掉,符号位也可能被改写。然后你的函数返回的是“溢出结果的符号”,而不是“真实乘积的符号”。
更闹心的是,溢出后符号可能变成正数,也可能变成负数,完全没有规律。你拿着这段代码去测一个乘积刚好超过 int 范围但真实符号为负的用例,结果可能返回 1,直接误判。
所以“先算乘积再判断符号”这条路,从工程角度来看就是错的。哪怕你把 product 换成 long long,也只是把爆掉的临界点往后移了移,并没有解决本质问题。
1.3 数学本质:乘积符号只依赖于负数个数的奇偶性
我在想通之后发现,这道题其实是一个很漂亮的数学观察:
- 只要数组里有一个 0,乘积就是 0,符号就是 0。
- 如果数组里没有 0,那么乘积的正负只取决于负数出现了奇数次还是偶数次。
- 负数出现奇数次,乘积为负;
- 负数出现偶数次,乘积为正。
- 正数不管出现多少次,都不影响符号。
这里用到的是乘法符号的“同号得正、异号得负”规则。每乘一个负数,都会把当前乘积的符号翻转一次;每乘一个正数,符号保持不变。所以,统计负数个数就足够了。
这个结论意味着:数组里每个元素的具体数值根本不重要,只需要知道它是不是 0、是不是负数。题目问的是“元素积的符号”,不是“元素积的值”,这就是整道题的核心突破口。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 标准解法:一趟遍历、三个分支、有零即零
2.1 遍历逻辑与“有零即零”的提前返回
有了上面的数学基础,代码就变得非常简单。核心逻辑是:
- 初始化负数计数器
