首页 > 技术文章 > LeetCode刷题之路-2. 两数相加

yimeixiaobai1314 2021-02-08 00:04 原文

LeetCode刷题之路-2. 两数相加

题目介绍

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

示例 2:

输入:l1 = [0], l2 = [0]
输出:[0]

示例 3:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

提示:

每个链表中的节点数在范围 [1, 100] 内
0 <= Node.val <= 9
题目数据保证列表表示的数字不含前导零

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum

解题思路

  1. 这道题实际上思路很容易想出来,难点在于单链表(不带头结点)的相关操作,再就是考虑进位的问题。要考虑三种情况:l1长度更长,l2长度更长,l1和l2长度同样长,这三种情况分开来写。

    具体请看下面代码中的注释:

    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
            //注意这两个链表都是不带头结点的单链表
            ListNode *p1 = l1;
            ListNode *p2 = l2;
            ListNode *L = nullptr; // L为结果链表
            ListNode *r, *p; // r代表链表的尾节点,p代表当前要分配内存的节点
            int flag = 0; // 代表是否有进位,有进位为1,无进位为0
            int v1, v2; // v1、v2代表两个链表当前的值
        	//分情况讨论,1.链表1,2同样长,2.链表1更长,3.链表2更长,
            while(p1 != nullptr && p2 != nullptr){ // 1. 在两个链表都已经到达链尾时才结束循环,链表1,2同样长
                v1 = p1 -> val;
                v2 = p2 -> val;
                int sum = v1 + v2 + flag;
                if(sum >= 10){ // 会产生进位
                    flag = 1;
                    sum -= 10;
                }
                else{ // 不会产生进位
                    flag = 0;
                }
                p = new ListNode(sum); // 为结果链表开辟一个新节点
                if(L == nullptr){ // 结果链表的第一个节点,
                    r = p; // 尾结点即为当前节点
                    L = p; // 头结点即为当前节点
                }
                else{
                    r -> next = p; // 当前尾结点的next域设置为当前节点
                    r = p; // 更新尾结点为当前节点
                }
                p1 = p1 -> next;
                p2 = p2 -> next;
            }
            while(p1 != nullptr){ // 2.链表1更长
                int sum = p1 -> val + flag;
                if(sum >= 10){
                    flag = 1;
                    sum -= 10;
                }else{
                    flag = 0;
                }
                p = new ListNode(sum);
                if(L == nullptr){ // 结果链表的第一个节点,
                    r = p; // 尾结点即为当前节点
                    L = p; // 头结点即为当前节点
                }
                else{
                    r -> next = p; // 当前尾结点的next域设置为当前节点
                    r = p; // 更新尾结点为当前节点
                }
                p1 = p1 -> next;
            }
            while(p2 != nullptr){ // 3.链表2更长
                int sum = p2 -> val + flag;
                if(sum >= 10){
                    flag = 1;
                    sum -= 10;
                }else{
                    flag = 0;
                }
                p = new ListNode(sum);
                if(L == nullptr){ // 结果链表的第一个节点,
                    r = p; // 尾结点即为当前节点
                    L = p; // 头结点即为当前节点
                }
                else{
                    r -> next = p; // 当前尾结点的next域设置为当前节点
                    r = p; // 更新尾结点为当前节点
                }
                p2 = p2 -> next;
            }
            if(flag > 0){ // 单独处理最高位会产生进位的情况
                p = new ListNode(flag);
                r -> next = p;
            }
            return L;
        }
    
  2. 现在把上面的三种情况的代码精简一下,通过一个循环中的条件将三种情况都考虑到。主要操作是将循环条件设为||,然后在获取val和next时需要加上if条件判断当前节点是否为nullptr。代码如下:

    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
            //注意这两个链表都是不带头结点的单链表
            ListNode *p1 = l1;
            ListNode *p2 = l2;
            ListNode *L = nullptr; // L为结果链表
            ListNode *r, *p; // r代表链表的尾节点,p代表当前要分配内存的节点
            int flag = 0; // 代表是否有进位,有进位为1,无进位为0
            int v1, v2; // v1、v2代表两个链表当前的值
            while(p1 != nullptr || p2 != nullptr){ // 在两个链表都已经到达链尾时才结束循环
                if(p1 != nullptr) // 如果链表1还未到达链表尾部
                    v1 = p1 -> val;
                else // 如果链表1已到达链表尾部
                    v1 = 0;
                if(p2 != nullptr) // 如果链表2还未到达链表尾部
                    v2 = p2 -> val;
                else // 如果链表1已到达链表尾部
                    v2 = 0;
                int sum = v1 + v2 + flag;
                if(sum >= 10){ // 会产生进位
                    flag = 1;
                    sum -= 10;
                }
                else{ // 不会产生进位
                    flag = 0;
                }
                p = new ListNode(sum); // 为结果链表开辟一个新节点
                if(L == nullptr){ // 结果链表的第一个节点,
                    r = p; // 尾结点即为当前节点
                    L = p; // 头结点即为当前节点
                }
                else{
                    r -> next = p; // 当前尾结点的next域设置为当前节点
                    r = p; // 更新尾结点为当前节点
                }
                if(p1 != nullptr) // 如果链表1还未到达链表尾部
                    p1 = p1 -> next;
                if(p2 != nullptr) // 如果链表2还未到达链表尾部
                    p2 = p2 -> next;
            }
            if(flag > 0){ // 单独处理最高位会产生进位的情况
                p = new ListNode(flag);
                r -> next = p;
            }
            return L;
        }
    

    注:再解释一下nullptr:nullptr是C++11新引入的,C++98中是没有的。如果使用C++98编译器,可以用将nullptr换成NULL。实际上NULL表示空指针会带来二义性问题(具体来说NULL在C++中被宏定义为int型的0,而不直接表示空指针,这就带来了二义性问题),所以在C++11中引入了nullptr替代NULL。

    因为题目给出的是部分代码,没法在编译器直接运行。主要有单链表打印函数代码,还有主函数代码。其余的测试代码如下:

    void print(ListNode *p)
    { // 单链表打印节点值
        while(p != nullptr){
            cout << p -> val << " ";
            p = p -> next;
        }
        cout << endl;
    }
    int main()
    {
    // 第一个例子 243、564
    //    ListNode *p = new ListNode(2);
    //    p -> next =  new ListNode(4);
    //    p -> next -> next = new ListNode(3);
    //    print(p);
    //    ListNode *q = new ListNode(5);
    //    q -> next =  new ListNode(6);
    //    q -> next -> next =  new ListNode(4);
    //    print(q);
    
    // 第二个例子 999、99
        ListNode *p = new ListNode(9);
        p -> next =  new ListNode(9);
        p -> next -> next = new ListNode(9);
        print(p);
        ListNode *q = new ListNode(9);
        q -> next =  new ListNode(9);
        print(q);
        Solution s;
        ListNode *ans = s.addTwoNumbers(p, q);
        print(ans);
        return 0;
    }
    

    全部代码如下:

    #include <iostream>
    
    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) {
            //注意这两个链表都是不带头结点的单链表
            ListNode *p1 = l1;
            ListNode *p2 = l2;
            ListNode *L = nullptr; // L为结果链表,
                                   // 再解释一下nullptr:nullptr是C++11新引入的,C++98中是没有的。如果使用C++98编译器,可以用将nullptr换成NULL。
                                   // 实际上NULL表示空指针会带来二义性问题(具体来说NULL在C++中被宏定义为int型的0,而不直接表示空指针,这就带来了二义性问题)
                                   // 所以在C++11中引入了nullptr替代NULL。
            ListNode *r, *p; // r代表链表的尾节点,p代表当前要分配内存的节点
            int flag = 0; // 代表是否有进位,有进位为1,无进位为0
            int v1, v2; // v1、v2代表两个链表当前的值
            while(p1 != nullptr || p2 != nullptr){ // 在两个链表都已经到达链尾时才结束循环,
                                                    // 当然也可以分情况讨论,链表1更短,链表2更短,链表1,2同样长
                if(p1 != nullptr) // 如果链表1还未到达链表尾部
                    v1 = p1 -> val;
                else // 如果链表1已到达链表尾部
                    v1 = 0;
                if(p2 != nullptr) // 如果链表2还未到达链表尾部
                    v2 = p2 -> val;
                else // 如果链表1已到达链表尾部
                    v2 = 0;
                int sum = v1 + v2 + flag;
                if(sum >= 10){ // 会产生进位
                    flag = 1;
                    sum -= 10;
                }
                else{ // 不会产生进位
                    flag = 0;
                }
                p = new ListNode(sum); // 为结果链表开辟一个新节点
                if(L == nullptr){ // 结果链表的第一个节点,
                    r = p; // 尾结点即为当前节点
                    L = p; // 头结点即为当前节点
                }
                else{
                    r -> next = p; // 当前尾结点的next域设置为当前节点
                    r = p; // 更新尾结点为当前节点
                }
                if(p1 != nullptr) // 如果链表1还未到达链表尾部
                    p1 = p1 -> next;
                if(p2 != nullptr) // 如果链表2还未到达链表尾部
                    p2 = p2 -> next;
            }
            if(flag > 0){ // 单独处理最高位会产生进位的情况
                p = new ListNode(flag);
                r -> next = p;
            }
            return L;
        }
    };
    
    void print(ListNode *p)
    { // 单链表打印节点值
        while(p != nullptr){
            cout << p -> val << " ";
            p = p -> next;
        }
        cout << endl;
    }
    int main()
    {
    // 第一个例子 243、564
    //    ListNode *p = new ListNode(2);
    //    p -> next =  new ListNode(4);
    //    p -> next -> next = new ListNode(3);
    //    print(p);
    //    ListNode *q = new ListNode(5);
    //    q -> next =  new ListNode(6);
    //    q -> next -> next =  new ListNode(4);
    //    print(q);
    
    // 第二个例子 999、99
        ListNode *p = new ListNode(9);
        p -> next =  new ListNode(9);
        p -> next -> next = new ListNode(9);
        print(p);
        ListNode *q = new ListNode(9);
        q -> next =  new ListNode(9);
        print(q);
        Solution s;
        ListNode *ans = s.addTwoNumbers(p, q);
        print(ans);
        return 0;
    }
    

结语

这道题思路确实不难,但是这道题难就难就在需要考虑单链表的相关操作和两个链表长度的不同情况上。尤其不带头结点的单链表的尾插法建立需要想清楚如何操作,至于其他的也就相当简单了。

本来不想写这道题的,但是因为这道题让我知道了了C++中NULL和nullptr的区别,所以还是写了一篇博客来记录一下。以后就不用NULL来表示空指针的,改用nullptr了。

坚持不懈地努力才能成为大神!

推荐阅读