天天看點

Leetcode 002. 兩數相加 | 每日一題

題目描述

給出兩個 非空 的連結清單用來表示兩個非負的整數。其中,它們各自的位數是按照 逆序 的方式存儲的,并且它們的每個節點隻能存儲 一位 數字。

如果,我們将這兩個數相加起來,則會傳回一個新的連結清單來表示它們的和。

您可以假設除了數字 0 之外,這兩個數都不會以 0 開頭。

示例:

輸入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
輸出:7 -> 0 -> 8
原因:342 + 465 = 807

           

難度:

  • 難度:中等
  • 支援語言:JavaScript、Java、Python、C++

相關标簽

  • 連結清單
  • 數學

相關企業

  • 阿裡
  • 百度
  • 騰訊

思路與算法

  • 設立一個表示進位的變量 carried,建立一個新連結清單,把輸入的兩個連結清單從頭往後同時處理,每兩個相加,将結果加上 carried 後的值作為一個新節點到新連結清單後面,并更新 carried 值即可。
Leetcode 002. 兩數相加 | 每日一題

(圖檔來自: https://github.com/MisterBooo/LeetCodeAnimation)

  • 實際上兩個大數相加也是一樣的思路, 隻不過具體的操作 API 不一樣而已。這種題目思維難度不大,難點在于心細,是以大家一定要自己寫一遍,確定 bug free。
  • 由于輸入的兩個連結清單都是逆序存儲數字的位數的,是以兩個連結清單中同一位置的數字可以直接相加。
  • 我們同時周遊兩個連結清單,逐位計算它們的和,并與目前位置的進位值相加。具體而言,如果目前兩個連結清單處相應位置的數字為 n1,n2n1,n2,進位值為 \textit{carry}carry,則它們的和為 n1+n2+\textit{carry}n1+n2+carry;其中,答案連結清單處相應位置的數字為 (n1+n2+\textit{carry}) % 10(n1+n2+carry)%10,而新的進位值為 \lfloor\frac{n1+n2+\textit{carry}}{10}\rfloor⌊

    10

    n1+n2+carry

    ⌋。

  • 如果兩個連結清單的長度不同,則可以認為長度短的連結清單的後面有若幹個 00 。
  • 此外,如果連結清單周遊結束後,有 \textit{carry} > 0carry>0,還需要在答案連結清單的後面附加一個節點,節點的值為 \textit{carry}carry。

關鍵點解析

  1. 連結清單這種資料結構的特點和使用
  2. 用一個 carried 變量來實作進位的功能,每次相加之後計算 carried,并用于下一位的計算

Js中文網 - 前端進階資源教程 www.javascriptC.com,typescript 中文文檔

Javascript中文網是以前端進階資源教程分享為主的專業網站,包括:前端、大廠面試題、typescript教程、程式人生、React.js……等,以幫助開發者成長為願景的社群

複雜度分析

  • 時間複雜度: O ( N ) O(N) O(N)
  • 空間複雜度: O ( N ) O(N) O(N),其中 N 的空間是調用棧的開銷。
  • 空間複雜度:O(\max(m,n))O(max(m,n))。答案連結清單的長度最多為較長連結清單的長度 +1+1。

代碼

JavaScript 實作

/**
 * @來源: Javascript中文網 - 前端進階資源教程 https://www.javascriptc.com/
 * @介紹:前端中文網是以前端進階資源教程分享為主的專業網站,包括:前端、大廠面試題、typescript教程、程式人生、React.js
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function (l1, l2) {
  if (l1 === null || l2 === null) return null;

  // 使用dummyHead可以簡化對連結清單的處理,dummyHead.next指向新連結清單
  let dummyHead = new ListNode(0);
  let cur1 = l1;
  let cur2 = l2;
  let cur = dummyHead; // cur用于計算新連結清單
  let carry = 0; // 進位标志

  while (cur1 !== null || cur2 !== null) {
    let val1 = cur1 !== null ? cur1.val : 0;
    let val2 = cur2 !== null ? cur2.val : 0;
    let sum = val1 + val2 + carry;
    let newNode = new ListNode(sum % 10); // sum%10取模結果範圍為0~9,即為目前節點的值
    carry = sum >= 10 ? 1 : 0; // sum>=10,carry=1,表示有進位
    cur.next = newNode;
    cur = cur.next;

    if (cur1 !== null) {
      cur1 = cur1.next;
    }

    if (cur2 !== null) {
      cur2 = cur2.next;
    }
  }

  if (carry > 0) {
    // 如果最後還有進位,新加一個節點
    cur.next = new ListNode(carry);
  }

  return dummyHead.next;
};
           
/**
*  @作者:LeetCode-Solution
*  @連結:https://leetcode-cn.com/problems/add-two-numbers/solution/liang-shu-xiang-jia-by-leetcode-solution/
*/
var addTwoNumbers = function(l1, l2) {
    let head = null, tail = null;
    let carry = 0;
    while (l1 || l2) {
        const n1 = l1 ? l1.val : 0;
        const n2 = l2 ? l2.val : 0;
        const sum = n1 + n2 + carry;
        if (!head) {
            head = tail = new ListNode(sum % 10);
        } else {
            tail.next = new ListNode(sum % 10);
            tail = tail.next;
        }
        carry = Math.floor(sum / 10);
        if (l1) {
            l1 = l1.next;
        }
        if (l2) {
            l2 = l2.next;
        }
    }
    if (carry > 0) {
        tail.next = new ListNode(carry);
    }
    return head;
};

           
/**
*  @作者:afeng-xiu - 力扣(LeetCode)
*  @連結:https://leetcode-cn.com/problems/add-two-numbers/solution/liang-ge-shu-xiang-jia-zui-rong-yi-li-jie-de-jie-f/
*/
var addTwoNumbers = function(l1, l2) {
    let addOne = 0
    let sum = new ListNode('0')
    let head = sum
    while (addOne || l1 || l2) {
        let val1 = l1 !== null ? l1.val : 0 // 優化點
        let val2 = l2 !== null ? l2.val : 0 //優化點
        let r1 = val1 + val2 + addOne
        addOne = r1 >= 10 ? 1 : 0
        sum.next = new ListNode(r1 % 10)
        sum = sum.next
        if (l1) l1 = l1.next
        if (l2) l2 = l2.next
    }
    return head.next
};

           

C++ 實作:

C++代碼與上面的 JavaScript 代碼略有不同:将 carry 是否為 0 的判斷放到了 while 循環中
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* ret = nullptr;
        ListNode* cur = nullptr;
        int carry = 0;
        while (l1 != nullptr || l2 != nullptr || carry != 0) {
            carry += (l1 == nullptr ? 0 : l1->val) + (l2 == nullptr ? 0 : l2->val);
            auto temp = new ListNode(carry % 10);
            carry /= 10;
            if (ret == nullptr) {
                ret = temp;
                cur = ret;
            }
            else {
                cur->next = temp;
                cur = cur->next;
            }
            l1 = l1 == nullptr ? nullptr : l1->next;
            l2 = l2 == nullptr ? nullptr : l2->next;
        }
        return ret;
    }
};
           
/**
*  @作者:chenlele
*  @連結:https://leetcode-cn.com/problems/add-two-numbers/solution/liang-shu-xiang-jia-by-gpe3dbjds1/
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int len1=1;//記錄l1的長度
        int len2=1;//記錄l2的長度
        ListNode* p=l1;
        ListNode* q=l2;
        while(p->next!=NULL)//擷取l1的長度
        {
            len1++;
            p=p->next;
        }
        while(q->next!=NULL)//擷取l2的長度
        {
            len2++;
            q=q->next;
        }
        if(len1>len2)//l1較長,在l2末尾補零
        {
            for(int i=1;i<=len1-len2;i++)
            {
                q->next=new ListNode(0);
                q=q->next;
            }
        }
        else//l2較長,在l1末尾補零
        {
            for(int i=1;i<=len2-len1;i++)
            {
                p->next=new ListNode(0);
                p=p->next;
            }
        }
        p=l1;
        q=l2;
        bool count=false;//記錄進位
        ListNode* l3=new ListNode(-1);//存放結果的連結清單
        ListNode* w=l3;//l3的移動指針
        int i=0;//記錄相加結果
        while(p!=NULL&&q!=NULL)
        {
            i=count+p->val+q->val;
            w->next=new ListNode(i%10);
            count=i>=10?true:false;
            w=w->next;
            p=p->next;
            q=q->next;
        }
        if(count)//若最後還有進位
        {
            w->next=new ListNode(1);
            w=w->next;
        }
        return l3->next;
    }
};

           
/**
*  @作者:dnanki
*  @連結:https://leetcode-cn.com/problems/add-two-numbers/solution/di-gui-si-lu-jian-dan-dai-ma-duan-by-dnanki/
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        return dfs(l1, l2, 0);
    }

    ListNode dfs(ListNode l, ListNode r, int i) {
        if (l == null && r == null && i == 0) return null;
        int sum = (l != null ? l.val : 0) + (r != null ? r.val : 0) + i;
        var node = new ListNode(sum % 10);
        node.next = dfs(l != null ? l.next : null, r != null ? r.next : null, sum / 10);
        return node;
    }
}


           

Java 實作:

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummyHead = new ListNode(0);
        ListNode cur = dummyHead;
        int carry = 0;

        while(l1 != null || l2 != null)
        {
            int sum = carry;
            if(l1 != null)
            {
                sum += l1.val;
                l1 = l1.next;
            }
            if(l2 != null)
            {
                sum += l2.val;
                l2 = l2.next;
            }
            // 建立新節點
            carry = sum / 10;
            cur.next = new ListNode(sum % 10);
            cur = cur.next;

        }
        if (carry > 0) {
            cur.next = new ListNode(carry);
        }
        return dummyHead.next;
    }
}

           
/**
*  @作者:Sophia_fez
*  @連結:https://leetcode-cn.com/problems/add-two-numbers/solution/mo-ni-jia-fa-tong-yong-mo-ban-by-sophia_fez/
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = new ListNode(0), cur = head;
        int carry = 0;

        while (l1 != null || l2 != null) {
            int n1 = l1 != null ? l1.val : 0;
            int n2 = l2 != null ? l2.val : 0;
            int sum = n1 + n2 + carry;
            carry = sum / 10;
            cur.next = new ListNode(sum % 10);
            cur = cur.next;
            if (l1 != null) l1 = l1.next;
            if (l2 != null) l2 = l2.next;
        }
        if (carry > 0)  cur.next = new ListNode(carry);

        return head.next;
    }
}

           

Python 實作:

class Solution:
    def addTwoNumbers(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        res=ListNode(0)
        head=res
        carry=0
        while l1 or l2 or carry!=0:
            sum=carry
            if l1:
                sum+=l1.val
                l1=l1.next
            if l2:
                sum+=l2.val
                l2=l2.next
            # set value
            if sum<=9:
                res.val=sum
                carry=0
            else:
                res.val=sum%10
                carry=sum//10
            # creat new node
            if l1 or l2 or carry!=0:
                res.next=ListNode(0)
                res=res.next
        return head

           
# @作者:meng-zhi-hen-n
# @連結:https://leetcode-cn.com/problems/add-two-numbers/solution/zui-zhi-bai-de-xie-fa-by-meng-zhi-hen-n-2/
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        # 建立一個結點值為 None 的頭結點, dummy 和 p 指向頭結點, dummy 用來最後傳回, p 用來周遊
        dummy = p = ListNode(None)
        s = 0               # 初始化進位 s 為 0
        while l1 or l2 or s:
            # 如果 l1 或 l2 存在, 則取l1的值 + l2的值 + s(s初始為0, 如果下面有進位1, 下次加上)
            s += (l1.val if l1 else 0) + (l2.val if l2 else 0)
            p.next = ListNode(s % 10)       # p.next 指向新連結清單, 用來建立一個新的連結清單
            p = p.next                      # p 向後周遊
            s //= 10                        # 有進位情況則取模, eg. s = 18, 18 // 10 = 1
            l1 = l1.next if l1 else None    # 如果l1存在, 則向後周遊, 否則為 None
            l2 = l2.next if l2 else None    # 如果l2存在, 則向後周遊, 否則為 None
        return dummy.next   # 傳回 dummy 的下一個節點, 因為 dummy 指向的是空的頭結點, 下一個節點才是建立連結清單的後序節點

           

拓展

通過單連結清單的定義可以得知,單連結清單也是遞歸結構,是以,也可以使用遞歸的方式來進行 reverse 操作。

由于單連結清單是線性的,使用遞歸方式将導緻棧的使用也是線性的,當連結清單長度達到一定程度時,遞歸可能會導緻爆棧,

算法

  1. 将兩個連結清單的第一個節點值相加,結果轉為 0-10 之間的個位數,并設定進位資訊
  2. 将兩個連結清單第一個節點以後的連結清單做帶進位的遞歸相加
  3. 将第一步得到的頭節點的 next 指向第二步傳回的連結清單

Js中文網 - 前端進階資源教程 www.javascriptC.com,typescript 中文文檔

Javascript中文網是以前端進階資源教程分享為主的專業網站,包括:前端、大廠面試題、typescript教程、程式人生、React.js……等,以幫助開發者成長為願景的社群

C++實作

// 普通遞歸
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        return addTwoNumbers(l1, l2, 0);
    }

private:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2, int carry) {
        if (l1 == nullptr && l2 == nullptr && carry == 0) return nullptr;
        carry += (l1 == nullptr ? 0 : l1->val) + (l2 == nullptr ? 0 : l2->val);
        auto ret = new ListNode(carry % 10);
        ret->next = addTwoNumbers(l1 == nullptr ? l1 : l1->next,
                                 l2 == nullptr ? l2 : l2->next,
                                 carry / 10);
        return ret;
    }
};
// 類似尾遞歸
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* head = nullptr;
        addTwoNumbers(head, nullptr, l1, l2, 0);
        return head;
    }

private:
    void addTwoNumbers(ListNode*& head, ListNode* cur, ListNode* l1, ListNode* l2, int carry) {
        if (l1 == nullptr && l2 == nullptr && carry == 0) return;
        carry += (l1 == nullptr ? 0 : l1->val) + (l2 == nullptr ? 0 : l2->val);
        auto temp = new ListNode(carry % 10);
        if (cur == nullptr) {
            head = temp;
            cur = head;
        } else {
            cur->next = temp;
            cur = cur->next;
        }
        addTwoNumbers(head, cur, l1 == nullptr ? l1 : l1->next, l2 == nullptr ? l2 : l2->next, carry / 10);
    }
};
           

其他

  • 說明:leetcode 題解 | 每日一題🔥 所有題目并非全部為本人解答,部分為在複習學習中整理提取其他解題作者的優秀筆記,便于大家學習共同進步,如有侵權,請聯系删除。
  • 合作方:JavaScript中文網 – 全球極客摯愛的技術成長平台
  • 原題leetcode連結:2.add-two-numbers
  • 更多題解可以通路我的 碼農周刊 倉庫:https://github.com/meibin08/free-programming-books, 一起學習更多前端前沿知識。
Leetcode 002. 兩數相加 | 每日一題