【算法】445.两数相加Ⅱ--通俗讲解

59 阅读4分钟

一、题目是啥?一句话说清

给定两个非空链表,每个节点代表一个数字的一位,最高位在链表头部,将这两个数字相加并返回结果链表。

示例:

  • 输入:(7 → 2 → 4 → 3) + (5 → 6 → 4)
  • 输出:7 → 8 → 0 → 7(因为7243 + 564 = 7807)

二、解题核心

使用栈来反转链表顺序,从最低位开始相加,处理进位,然后用头插法构建结果链表。

这就像我们做竖式加法一样,从个位开始加,有进位就加到下一位,只是这里数字是链表形式存储的。

三、关键在哪里?(3个核心点)

想理解并解决这道题,必须抓住以下三个关键点:

1. 栈的使用

  • 是什么:用两个栈分别存储两个链表的节点值,这样出栈时就是倒序(从低位到高位)。
  • 为什么重要:因为题目中最高位在链表头部,但加法需要从最低位开始,栈的LIFO特性正好满足这个需求。

2. 进位处理

  • 是什么:相加时,当前位的和等于两个数字加进位,新的进位等于和除以10。
  • 为什么重要:正确处理进位是加法运算的核心,确保结果正确。

3. 头插法构建结果链表

  • 是什么:创建新节点时,将其插入到结果链表的头部。
  • 为什么重要:这样构建的链表自然就是高位在前、低位在后,符合题目要求。

四、看图理解流程(通俗理解版本)

假设链表A:7 → 2 → 4 → 3,链表B:5 → 6 → 4

  1. 入栈

    • 栈A:[7, 2, 4, 3] → 出栈顺序:3, 4, 2, 7
    • 栈B:[5, 6, 4] → 出栈顺序:4, 6, 5
  2. 逐位相加

    • 个位:3 + 4 + 进位0 = 7,进位0
    • 十位:4 + 6 + 进位0 = 10,当前位0,进位1
    • 百位:2 + 5 + 进位1 = 8,进位0
    • 千位:7 + 0 + 进位0 = 7,进位0
  3. 头插法构建链表

    • 先处理个位7:链表 → 7
    • 再处理十位0:链表 → 0 → 7
    • 再处理百位8:链表 → 8 → 0 → 7
    • 最后处理千位7:链表 → 7 → 8 → 0 → 7
  4. 最终结果:7 → 8 → 0 → 7

五、C++ 代码实现(附详细注释)

#include <iostream>
#include <stack>
using namespace std;

// 链表节点定义
struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        stack<int> s1, s2;
        
        // 将两个链表的节点值分别压入栈中
        while (l1 != nullptr) {
            s1.push(l1->val);
            l1 = l1->next;
        }
        while (l2 != nullptr) {
            s2.push(l2->val);
            l2 = l2->next;
        }
        
        int carry = 0; // 进位
        ListNode* result = nullptr; // 结果链表的头节点
        
        // 当两个栈不为空或还有进位时继续处理
        while (!s1.empty() || !s2.empty() || carry != 0) {
            int sum = carry;
            
            // 从栈中取出数字相加
            if (!s1.empty()) {
                sum += s1.top();
                s1.pop();
            }
            if (!s2.empty()) {
                sum += s2.top();
                s2.pop();
            }
            
            // 计算当前位的值和新的进位
            carry = sum / 10;
            int digit = sum % 10;
            
            // 使用头插法构建结果链表
            ListNode* newNode = new ListNode(digit);
            newNode->next = result;
            result = newNode;
        }
        
        return result;
    }
};

// 辅助函数:打印链表
void printList(ListNode* head) {
    while (head != nullptr) {
        cout << head->val << " ";
        head = head->next;
    }
    cout << endl;
}

// 测试代码
int main() {
    // 构建链表1: 7->2->4->3
    ListNode* l1 = new ListNode(7);
    l1->next = new ListNode(2);
    l1->next->next = new ListNode(4);
    l1->next->next->next = new ListNode(3);
    
    // 构建链表2: 5->6->4
    ListNode* l2 = new ListNode(5);
    l2->next = new ListNode(6);
    l2->next->next = new ListNode(4);
    
    Solution solution;
    ListNode* result = solution.addTwoNumbers(l1, l2);
    
    printList(result); // 输出:7 8 0 7
    
    // 释放内存(简单示例)
    // 实际应用中需要完整释放所有链表节点
    return 0;
}

六、时间空间复杂度

  • 时间复杂度:O(max(m,n)),其中m和n分别是两个链表的长度。需要遍历两个链表各一次,然后处理最多max(m,n)次相加操作。
  • 空间复杂度:O(m+n),用于存储两个栈的空间。结果链表的空间不计入,因为这是输出所需。

七、注意事项

  • 进位处理:最后如果还有进位,需要额外创建一个节点。
  • 栈的使用:栈的大小等于链表长度,对于很长的链表可能占用较多内存。
  • 头插法:使用头插法确保结果链表高位在前,符合题目要求。
  • 空链表处理:题目保证链表非空,但实际应用中应该处理空链表的情况。
  • 数字开头不为0:题目假设除了数字0之外,数字不会以0开头,所以不需要处理前导0的情况。
  • 大数相加:这种方法适用于任意长度的数字相加,不会出现整数溢出的问题。