LeetCode.2 兩數相加(Add Two Numbers)(JS)

上週日就想寫vue.nextTick的源碼分析,但是老是不知道從哪兒下手,今天有時間,先把leetcode第二題補了,感受這道題還挺簡單的

1、題目

兩數相加:vue

給出兩個 非空 的鏈表用來表示兩個非負的整數。其中,它們各自的位數是按照 逆序 的方式存儲的,而且它們的每一個節點只能存儲 一位 數字。若是,咱們將這兩個數相加起來,則會返回一個新的鏈表來表示它們的和。您能夠假設除了數字 0 以外,這兩個數都不會以 0 開頭。
示例:node

示例

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

2、個人答案

/**
 *. 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) { 
      function ListToArray(list) {
        if(list.next) {
          return [list.val, ...ListToArray(list.next)]
        } else {
          return [list.val]
        }
      }
      function sumArray(arr1, arr2) {
        if(arr1.length < arr2.length) {
          let arr = []
          arr = arr1
          arr1 = arr2
          arr2 = arr
        }
        let littleLen = arr2.length
        let i =0
        for(; i < littleLen; i++) {
          arr1[i] += arr2[i]
          if(arr1[i] >= 10) {
            arr1[i] -= 10
            arr1[i + 1] ? arr1[i + 1]++ : arr1[i+1] =1
          }
        }
        for(; i < arr1.length; i++ ) {
          if(arr1[i] >= 10) {
            arr1[i] -= 10
            arr1[i + 1] ? arr1[i + 1]++ : arr1[i+1] =1
          }
        }
        return arr1.reverse()
      }

      function ArrayToList(arr) {
        if(arr.length > 0) {
          let node = new ListNode(arr.pop())
          node.next = ArrayToList(arr)
          return node
        } else {
          return null
        }
      }
      return ArrayToList(sumArray(ListToArray(l1),ListToArray(l2)))
    };

計算兩個鏈表表示的數的和,統共分三步:數組

  1. 把冰箱門打開 鏈表轉數組;
  2. 兩個數組相加獲得和數組;
  3. 和數組轉鏈表

個人寫法答案簡單易懂,就不作解釋,這裏說一下寫的時候碰到一個坑
關於第二步兩個數組相加,一開始的思路並非這樣的,而是轉成String再轉Number相加再轉回來,那麼這個思路折哪兒了呢,有一個測試用例是[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]和[5,6,4],轉成Number相加得1e+30,沒錯,萬惡的弱類型,查詢能不能不轉換,未果,就只能遍歷各數位相加了函數

這題一開始沒懂啥意思,懂了以後思路還挺清晰的,擊敗33.45%,嘛,喜聞樂見,看看大手子們是咋寫的吧源碼分析

3、優秀答案

/**
 * 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, curr = 0) {
    if(l1 === null && l2 === null) {
        if(curr) return new ListNode(curr)
        else return null;
    } else {
        if(l1 == null) l1 = new ListNode(0)
        if(l2 == null) l2 = new ListNode(0)
        let nextVal = (l2.val || 0) + (l1.val || 0) + curr
        curr = 0
        if(nextVal > 9) {
            curr = 1
            nextVal -= 10
        }
        l1.val = nextVal
        l1.next = addTwoNumbers(l1.next, l2.next, curr)
        return l1
    }
};

這個是我看到的最優秀的答案,本地跑擊敗91%,甩我不知道幾條街,看各類優秀答案老是能刷新認知,來看他的思路
首先他把函數改了,函數的本意是相加兩個鏈表,他改爲了相加兩個節點,參數中curr的意思是上一組節點相加是否進位這什麼命名
再來看函數內,if部分的代碼意爲處理最後的邊界狀況,例[1],[9,9,9,9],即遍歷完兩個鏈表以後,若是上一次遍歷有進位,則new一個節點
else內則是主要的節點相加部分,其中測試

if(l1 == null) l1 = new ListNode(0)
    if(l2 == null) l2 = new ListNode(0)
    let nextVal = (l2.val || 0) + (l1.val || 0) + curr

(l2.val || 0)明顯是多餘的,我猜他原本寫的是let nextVal = (l2.val || 0) + (l1.val || 0) + curr可是執行發現null點不出val,就加了上面的判斷不過下面的沒刪,不精簡代碼差評。
剩下的邏輯就顯而易見了,進位什麼的,不過這種同步遍歷真的很巧妙,我怎麼就是學不會/捂臉優化

4、路漫漫其修遠兮

       這幾道題提交都是在力扣上的,可是作完這道題去看優秀答案,發現有一個排名較前的答案返回值是Array,我本身複製過來甚至根本過不了測試用例,應該是力扣沒有像leetcode更新全面吧,題改了可是答案卻沒有改,決定之後仍是提交到leetCode吧,
       再細看優秀答案的最後三行,怎麼總感受好像能夠用尾調用優化一下,有空看一下,今天這篇博客到此爲止,下面該作的第四題難度爲hard,懼怕之餘忽然有點期待看到各類神仙解法/捂臉。this

THE END


2019.3.27更新
以前說感受優秀答案的最後三行能夠用尾遞歸優化(不知道尾遞歸的小夥伴能夠點這裏),仔細想了一下,並不能。編碼

尾遞歸的實現,每每須要改寫遞歸函數,確保最後一步只調用自身。作到這一點的方法,就是把全部用到的內部變量改寫成函數的參數。

緣由以下:
        假設咱們按照上面描述實現尾遞歸,那麼函數須要三個參數,分別是l1, l2,還有當前組裝的listNode對象,那麼咱們每次調用的時候,都須要l1.val + l2.val賦值給當前組裝的對象的最深層,要想得到對象的最深層,就得遍歷,那咱們還優化個錘子。
        不過這麼分析下來,優秀答案做者可能原本的意圖就是用尾遞歸優化,因此給第三個參數命名爲curr,發現得不償失後放棄這種作法,把第三個參數的做用改成進位,可是並無再修改變量名,不過這個編碼習慣卻是很符合上文我猜想的let nextVal = (l2.val || 0) + (l1.val || 0) + curr這行代碼沒有精簡的理由。
        分析個優秀答案能分析出破案的感受我也是服我本身

相關文章
相關標籤/搜索