对两数相加使用的技巧进行简单分析
众所周知,LeetCode 第 2 题“两数相加”是程序员刷题绕不开的基础题。这次我不想讲算法思路,而想聊聊其中某些解法里一个非常有趣的技巧——哑节点(Dummy Node)。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode dummy(0); // 哑节点,栈上分配
ListNode* tail = &dummy;
int carry = 0;
while (l1 || l2 || carry) {
int sum = carry;
if (l1) { sum += l1->val; l1 = l1->next; }
if (l2) { sum += l2->val; l2 = l2->next; }
tail->next = new ListNode(sum % 10);
tail = tail->next;
carry = sum / 10;
}
return dummy.next;
}
};
在做这道题的时候我觉得最有趣的就是哑节点的引入,看了一下网上的定义
哑节点的核心思想是:在真正的头节点前“粘”一个永远不变的占位节点。它不存储任何有效数据,却把原来的头节点从“特殊节点”降级为“普通节点”,从而消除了对头节点的边界判断。
它把实际的头节点(也就是没有一个指向它的节点)定义了一个指向它的占位用的节点。这个节点本身不起任何作用,只是把原本的头节点从特殊节点转为一般节点。
这能带来哪些好处呢?
我给出两个不用哑节点的实现
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* head = nullptr; // 结果链表的头
ListNode* tail = nullptr; // 结果链表的尾
int carry = 0;
while (l1 || l2 || carry) {
int sum = carry;
if (l1) { sum += l1->val; l1 = l1->next; }
if (l2) { sum += l2->val; l2 = l2->next; }
ListNode* node = new ListNode(sum % 10);
if (head == nullptr) { // 第一个节点
head = node;
tail = node;
} else { // 后续节点
tail->next = node;
tail = node;
}
carry = sum / 10;
}
return head;
}
};
这个版本需要在循环中判断 head == nullptr,代码多出分支,且容易遗忘更新 tail。
第二个是递归的,也是这道题本来希望考察的
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2, int carry = 0) {
if (!l1 && !l2 && !carry) return nullptr;
int sum = carry;
if (l1) sum += l1->val;
if (l2) sum += l2->val;
ListNode* node = new ListNode(sum % 10);
node->next = addTwoNumbers(
l1 ? l1->next : nullptr,
l2 ? l2->next : nullptr,
sum / 10
);
return node;
}
};
递归虽然简洁,但会占用栈空间,当链表较长时存在栈溢出风险。
而哑节点带来的好处有
1、避免了头节点的边界判断
2、局部变量自动销毁

浙公网安备 33010602011771号