leetCode 2 Add Two Numbers

就是兩個連結串列表示的數相加,這樣就可以實現兩個很大的數相加了,無需考慮數值 int ,float 的限制了。
由於自己實現的很亂,直接按答案的講解了。
圖示

連結串列最左邊表示個位數,代表 342 + 465 =807 。
思路
首先每一位相加肯定會產生進位,我們用 carry 表示。進位最大會是 1 ,因為最大的情況是無非是 9 + 9 + 1 = 19 ,也就是兩個最大的數相加,再加進位,這樣最大是 19 ,不會產生進位 2 。下邊是虛擬碼。
- 初始化一個節點的頭,dummy head ,但是這個頭不儲存數字。並且將 curr 指向它。
- 初始化進位 carry 為 0 。
- 初始化 p 和 q 分別為給定的兩個連結串列 l1 和 l2 的頭,也就是個位。
- 迴圈,直到 l1 和 l2 全部到達 null 。
- 設定 x 為 p 節點的值,如果 p 已經到達了 null,設定 x 為 0 。
- 設定 y 為 q 節點的值,如果 q 已經到達了 null,設定 y 為 0 。
- 設定 sum = x + y + carry 。
- 更新 carry = sum / 10 。
- 建立一個值為 sum mod 10 的節點,並將 curr 的 next 指向它,同時 curr 指向變為當前的新節點。
- 向前移動 p 和 q 。
- 判斷 carry 是否等於 1 ,如果等於 1 ,在連結串列末尾增加一個為 1 的節點。
- 返回 dummy head 的 next ,也就是個位數開始的地方。
初始化的節點 dummy head 沒有儲存值,最後返回 dummy head 的 next 。這樣的好處是不用單獨對 head 進行判斷改變值。也就是如果一開始的 head 就是代表個位數,那麼開始初始化的時候並不知道它的值是多少,所以還需要在進入迴圈前單獨對它進行值的更正,不能像現在一樣只用一個迴圈簡潔。
程式碼
class ListNode { int val; ListNode next; ListNode(int x) { val = x; } } public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode dummyHead = new ListNode(0); ListNode p = l1, q = l2, curr = dummyHead; int carry = 0; while (p != null || q != null) { int x = (p != null) ? p.val : 0; int y = (q != null) ? q.val : 0; int sum = carry + x + y; carry = sum / 10; curr.next = new ListNode(sum % 10); curr = curr.next; if (p != null) p = p.next; if (q != null) q = q.next; } if (carry > 0) { curr.next = new ListNode(carry); } return dummyHead.next; } 複製程式碼
時間複雜度:O(max(m,n)),m 和 n 代表 l1 和 l2 的長度。
空間複雜度:O(max(m,n)),m 和 n 代表 l1 和 l2 的長度。而其實新的 List 最大長度是 O(max(m,n))+ 1,因為我們的 head 沒有儲存值。
擴充套件

如果連結串列儲存的順序反過來怎麼辦?
我首先想到的是連結串列先逆序計算,然後將結果再逆序唄,這就轉換到我們之前的情況了。不知道還有沒有其他的解法。下邊分析下單鏈表逆序的思路。
迭代思想
首先看一下原連結串列。

總共需要新增兩個指標,pre 和 next。
初始化 pre 指向 NULL 。

然後就是迭代的步驟,總共四步,順序一步都不能錯。
-
next 指向 head 的 next ,防止原連結串列丟失
-
head 的 next 從原來連結串列脫離,指向 pre 。
-
pre 指向 head
-
head 指向 next
一次迭代就完成了,如果再進行一次迭代就變成下邊的樣子。

可以看到整個過程無非是把舊連結串列的 head 取下來,新增的新的連結串列上。程式碼怎麼寫呢?
next = head -> next; //儲存 head 的 next , 以防取下 head 後丟失 head -> next = pre; //將 head 從原連結串列取下來,新增到新連結串列上 pre = head;// pre 右移 head = next; // head 右移 複製程式碼
接下來就是停止條件了,我們再進行一次迴圈。

可以發現當 head 或者 next 指向 null 的時候,我們就可以停止了。此時將 pre 返回,便是逆序了的連結串列了。
迭代程式碼
public ListNode reverseList(ListNode head){ if(head==null) return null; ListNode pre=null; ListNode next; while(head!=null){ next=head.next; head.next=pre; pre=head; head=next; } return pre; } 複製程式碼
遞迴思想
-
首先假設我們實現了將單鏈表逆序的函式,ListNode reverseListRecursion(ListNode head) ,傳入連結串列頭,返回逆序後的連結串列頭。
-
接著我們確定如何把問題一步一步的化小,我們可以這樣想。
把 head 結點拿出來,剩下的部分我們呼叫函式 reverseListRecursion ,這樣剩下的部分就逆序了,接著我們把 head 結點放到新連結串列的尾部就可以了。這就是整個遞迴的思想了。
-
head 結點拿出來
-
剩餘部分呼叫逆序函式 reverseListRecursion ,並得到了 newhead
-
將 2 指向 1 ,1 指向 null,將 newhead 返回即可。
-
-
找到遞迴出口
當然就是如果結點的個數是一個,那麼逆序的話還是它本身,直接 return 就夠了。怎麼判斷結點個數是不是一個呢?它的 next 等於 null 就說明是一個了。但如果傳進來的本身就是 null,那麼直接找它的 next 會報錯,所以先判斷傳進來的是不是 null ,如果是,也是直接返回就可以了。
程式碼
public ListNode reverseListRecursion(ListNode head){ ListNode newHead; if(head==null||head.next==null ){ return head; } newHead=reverseListRecursion(head.next); //head.next 作為剩餘部分的頭指標 head.next.next=head; //head.next 代表新連結串列的尾,將它的 next 置為 head,就是將 head 加到最後了。 head.next=null; return newHead; } 複製程式碼