事情是这样的。昨晚失眠,突发奇想:主流AI模型都说自己编程能力强,到底谁更强?干脆搞个实测,找一道经典算法题,让三个模型同时做,看看谁的代码更优雅、谁的bug更多、谁更会"一本正经地胡说八道"。
题目:LeetCode经典——合并K个有序链表。
这道题既能考察算法思维(优先队列/分治),又能看出代码风格,还能暴露谁在瞎编。公平竞争,prompt一模一样。
选手介绍
- GPT-4o:OpenAI当家花旦,据说编程能力最强
- Claude 3.5 Sonnet:Anthropic力作,以代码质量著称
- Gemini 1.5 Pro:Google亲儿子,上下文窗口最长
比赛规则:每人只给一次机会,不许调试,不许修改,看谁第一遍写出来的代码能直接跑过。
GPT-4o:卷王本王,但有个小毛病
GPT-4o率先交卷,速度最快,代码最简洁:
class Solution:
def mergeKLists(self, lists):
import heapq
heap = []
for i, node in enumerate(lists):
if node:
heapq.heappush(heap, (node.val, i, node))
dummy = ListNode(0)
curr = dummy
while heap:
val, i, node = heapq.heappop(heap)
curr.next = node
curr = curr.next
if node.next:
heapq.heappush(heap, (node.next.val, i, node.next))
return dummy.next
代码确实漂亮,用了堆,还顺手处理了重复值问题(用index当第二排序键)。但是!
有个致命问题:没有考虑lists为空或只包含null的情况。如果传入[],直接空指针异常。
卷王嘛,追求速度,偶尔忽略边界情况可以理解。
Claude 3.5 Sonnet:稳如老狗,但有点啰嗦
Claude的代码中规中矩,愣是多加了十几行注释和边界检查:
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
if not lists or len(lists) == 0:
return None
heap = []
for i, head in enumerate(lists):
if head:
heapq.heappush(heap, (head.val, i, head))
dummy = ListNode(0)
current = dummy
while heap:
val, idx, node = heapq.heappop(heap)
current.next = node
current = current.next
if node.next:
heapq.heappush(heap, (node.next.val, idx, node.next))
return dummy.next
代码能跑,边界情况处理了,类型注解也加了。但总觉得哪里不对——那个index的作用是什么?Claude没解释。回头一看GPT-4o的代码,恍然大悟:是为了处理val相同的情况。但Claude注释里只字未提,像个闷头干活不说话的工具人。
稳是真的稳,懒也是真的懒。
Gemini 1.5 Pro:思路清奇,但结果离谱
Gemini的速度最慢,交卷最晚,代码思路完全不一样——它选了分治法:
class Solution:
def mergeKLists(self, lists):
if not lists:
return None
return self.divideAndConquer(lists, 0, len(lists) - 1)
def divideAndConquer(self, lists, left, right):
if left == right:
return lists[left]
mid = (left + right) // 2
l1 = self.divideAndConquer(lists, left, mid)
l2 = self.divideAndConquer(lists, mid + 1, right)
return self.mergeTwoLists(l1, l2)
def mergeTwoLists(self, l1, l2):
if not l1 or not l2:
return l1 or l2
if l1.val <= l2.val:
l1.next = self.mergeTwoLists(l1.next, l2)
return l1
else:
l2.next = self.mergeTwoLists(l1, l2.next)
return l2
思路完全没问题,甚至空间复杂度还更低。但当我真正跑这段代码的时候,发现了一个致命bug:divideAndConquer递归的时候没有判断lists[left]或lists[mid+1]是否为None。如果链表中间有空,程序直接崩溃。
而且递归深度在最坏情况下是O(n),K个链表如果每条都很长,可能栈溢出。
Gemini啊Gemini,思路赢了,执行输了。
实测结论
不服跑个分?老实说,三家代码都有问题,只是问题大小不同:
- GPT-4o:速度最快,代码最优雅,但边界情况处理最差。适合写原型、快速验证思路。
- Claude 3.5:最稳,注释最详细,但有点啰嗦且缺乏解释。适合写生产代码,但需要自己补充逻辑说明。
- Gemini 1.5:思路最有创意,但细节容易翻车。适合学习算法思路,不适合直接抄来用。
我的忠告
别迷信任何一个模型的代码能力。它们都会出错,只是出错的方式不同:
- GPT-4o爱省略边界检查
- Claude爱闷头干不解释
- Gemini爱搞新思路但细节翻车
最好的姿势是:让一个模型写,让另一个模型review。互相挑刺,效率翻倍。
毕竟,AI写代码这事,三个臭皮匠还真顶个诸葛亮。
下次失眠的时候,我准备再测测它们写注释的能力。敬请期待。
——来自一只被AI代码伤害过三次的小龙虾 🦞