Python技术迷

新来的香港leader review我代码,跟我说注释能删就删掉。

这香港leader有点狠,但你别说,还真不是瞎讲。

新来的leader review代码,看到注释就来了一句:能删就删。理由也很简单,代码会改,注释没人改。最后代码已经不是那个意思了,注释还在旁边一本正经地胡说八道。

这玩意儿太真实了。

Image

很多项目里最坑人的不是没注释,是那种过期注释。你一看注释,以为自己懂了,结果照着改完,线上直接给你一巴掌。然后你回头一查,哦,三年前的逻辑,注释活得比代码还久。

当然也不是说所有注释都该死。复杂业务、历史坑、反直觉设计,该写还得写。但那种“i++ // i加一”,真删了吧,留着也怪尴尬的。

今日算法题

票都扫完了,内存还在涨,这种写法我第一眼就不太信。

“当选者”这题看着像统计票数:给一堆候选人编号,找出那个票数超过一半的人。最容易写的是哈希表,谁出现一次就加一票,最后扫一遍找最大值。能过,但味道不对。因为这题真正卡的不是“会不会数数”,而是你有没有发现一个条件:当选者的票数超过总票数的一半。

超过一半,这个条件很硬。

比如票是这样:

2 3 2 1 2 2 5

2 有 4 票,总共 7 票,超过一半,那它就是当选者。

这时候没必要把每个人的票都存下来。可以把不同候选人的票互相抵消。你支持 2,我支持 3,那这两票放在一起,对“谁超过一半”这件事没有影响。真正超过一半的人,怎么抵消,最后都不该被抵没。

代码我一般会这么写,不绕:

import sys


deffind_winner(votes):
    candidate = None
    balance = 0

for vote in votes:
if balance == 0:
            candidate = vote
            balance = 1
elif vote == candidate:
            balance += 1
else:
            balance -= 1

if candidate isNone:
return-1

    real_count = 0
for vote in votes:
if vote == candidate:
            real_count += 1

return candidate if real_count * 2 > len(votes) else-1


defmain():
    data = sys.stdin.read().strip().split()
ifnot data:
return

    n = int(data[0])
    votes = list(map(int, data[1:1 + n]))

    print(find_winner(votes))


if __name__ == "__main__":
    main()

这里最容易写漏的是第二次校验。

有些题目会明确说“一定存在当选者”,那第二次校验可以省。但我做这类题一般不省,除非题面写得非常死。因为第一轮投票只能筛出“可能当选的人”,不能证明它一定超过一半。

比如:

1 2 3 4

按抵消逻辑,最后可能留下 3,也可能留下 4,看实现细节。但它们都不是当选者。你要是直接输出 candidate,这种数据就会翻车。

这题的关键变量其实就两个。

candidate 表示当前擂台上的人。

balance 表示他现在还剩多少“净支持票”。

遇到相同的票,balance 加一;遇到不同的票,balance 减一。减到 0,说明前面这一段票已经互相抵完了,后面重新开局。

我当时第一次看这个算法,觉得有点像耍赖:凭什么抵消完剩下的就是答案?

后来用“超过一半”想就顺了。假设真正当选者是 x,其他所有人的票加起来都没有 x 多。每次拿一张 x 的票去抵一张非 x 的票,最后 x 也一定还有剩余。它不会被完全抵掉。

所以复杂度很干净:

时间复杂度:O(n)
空间复杂度:O(1)

哈希表那种写法也不是不能用,尤其数据量小的时候,写起来还更直。但这题既然叫“当选者”,题眼多半就在“多数票”上。看到超过一半,就该条件反射想到抵消,而不是上来就建字典。

这类题最怕写得太兴奋,把“票数最多”和“超过一半”混在一起。票数最多不一定能当选,超过一半才行。最后那一遍计数,就是防这个坑的。