力扣題目地址:https://leetcode-cn.com/problems/add-two-numbers/
首先看題目:
給出兩個 非空 的鏈表用來表示兩個非負的整數。其中,它們各自的位數是按照 逆序 的方式存儲的,並且它們的每個節點只能存儲 一位 數字。
如果,我們將這兩個數相加起來,則會返回一個新的鏈表來表示它們的和。
您可以假設除了數字 0 之外,這兩個數都不會以 0 開頭。
示例:
輸入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
輸出:7 -> 0 -> 8
原因:342 + 465 = 807
解決思路:
首先明白主線任務:我們要將兩個鏈表的相同節點的值求和,然後新建一個節點把值放進去。這就是我們的主要思路。
至於其他的東西可以遇上了在考慮;因爲對於我們這種剛開始練習的新手來說,一下子思考太多的因素會把這個問題變得太過複雜;我們可以一步一步來解決問題。
實踐代碼:
首先加入題目中給的 ListNode 類
package com.gyx.util;
/**
* 節點類
* @author GeYuxuan 2020/02/29 17:09
*/
public class ListNode {
/**
* 節點值
*/
int val;
/**
* 下一個節點引用
*/
ListNode next;
/**
* 構造函數,初始化設置val值
* @param x
*/
ListNode(int x){
val = x;
}
}
然後我們開始我們的解題之旅:
首先,通過觀察上面的 ListNode 類,我們知道鏈表(單向鏈表,以下統稱鏈表)一共有兩個屬性:val 是當前的鏈表節點的值,next 是下一個節點的引用。
根據主線任務,我們需要先取出每個節點的值,然後相加,放入一個新的節點。
因爲鏈表不知道有幾個元素,所以我們無法用for循環來進行遍歷,只能使用while語句來遍歷。我們思考一下終結條件是什麼?肯定是當前節點如果爲null的話,我們就可以退出了。這個時候我們需要多考慮一些了,l1和l2兩個鏈表的長度是不是有可能不一樣?有可能l1遍歷完了,l2還有節點存在。所以我們是不是要對兩個鏈表都做校驗?並且還是或連接符,只要有一個鏈表的節點不爲空,我們就需要繼續往下遍歷。
鏈表遍歷的情況考慮好之後,我們再來考慮具體實現細節:(對應代碼1.2.3.)
- 取出兩個鏈表節點值
- 節點值求和,並放入新節點。
- 移動鏈表指針,指向鏈表下一個節點。
代碼實現如下:
package com.gyx.util;
/**
* 兩數相加 v1版
* @author GeYuxuan 2020/03/01 13:40
*/
public class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
while(l1 != null || l2 != null){
//1.取出 l1和l2當前節點的值,如果當前節點爲空則補0
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
//2.將兩節點值求和並增加一個新節點
int sum = x + y;
//存在問題:新建的當前節點如何指向下一個節點?
ListNode cur= new ListNode(sum);
//3.移動l1和l2的指針
if(l1 != null){
l1 = l1.next;
}
if(l2 != null){
l2 = l2.next;
}
}
//存在問題:該返回哪個節點?
return cur;
}
}
第一版遇到的問題:新建的當前節點如何指向下一個節點?我們該返回哪個節點?
我們在求和設置新節點時,遇到了新建節點的指向問題。我們這時候發現在方法中我們沒有新建一個新的鏈表來填充求和值。所以我們需要在方法外面新建一個新的鏈表。
//新建返回鏈表頭結點
ListNode cur = new ListNode();
while(l1 != null || l2 != null){
//1.取出 l1和l2當前節點的值,如果當前節點爲空則補0
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
//2.將兩節點值求和並增加一個新節點
int sum = x + y;
//存在問題:新建的當前節點如何指向下一個節點?(解決)
//增加一個新節點
cur.next = new ListNode(sum);
cur = cur.next;
//3.移動l1和l2的指針
if(l1 != null){
l1 = l1.next;
}
if(l2 != null){
l2 = l2.next;
}
}
//存在問題:該返回哪個節點?(未解決)
return cur;
}
上面的代碼解決了新建節點的指向問題,但是沒有解決我們的第二個問題:我們怎麼返回求和鏈表的頭結點呢?因爲每次新建節點,都會把 cur 節點的指針往後移一位。所以我們返回的cur節點是最後一個節點,這樣的鏈表也就只有一位數字了。
所以我們需要增加一個pre虛擬頭結點,來保證這個虛擬頭結點的指針永遠指向返回鏈表的第一位數字節點。
package com.gyx.util;
/**
* 兩數相加 v2版
* @author GeYuxuan 2020/03/01 13:40
*/
public class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//新鏈表虛擬頭結點
ListNode pre = new ListNode(0);
ListNode cur = pre;
while(l1 != null || l2 != null){
//1.取出 l1和l2當前節點的值,如果當前節點爲空則補0
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
//2.將兩節點值求和並增加一個新節點
int sum = x + y;
//存在問題:新建的當前節點如何指向下一個節點?(解決)
//增加一個新節點
cur.next = new ListNode(sum);
cur = cur.next;
//3.移動l1和l2的指針
if(l1 != null){
l1 = l1.next;
}
if(l2 != null){
l2 = l2.next;
}
}
//存在問題:該返回哪個節點?(解決,返回虛擬頭結點的下個節點)
return pre.next;
}
}
解決完發現的問題後,我們離成功不遠了。我們接下來就可以測試了,我們可以自己寫測試數據來進行測試。
package com.gyx.util;
/**
* 測試數據
* @author GeYuxuan 2020/02/29 17:35
*/
public class Test {
public static void main(String[] args) {
//設置 l1,l2 鏈表 321 + 321 = 642
ListNode l1 = new ListNode(1);
l1.next = new ListNode(2);
l1.next.next = new ListNode(3);
ListNode l2 = new ListNode(1);
l2.next = new ListNode(2);
l2.next.next = new ListNode(3);
Solution solution = new Solution();
ListNode result = solution.addTwoNumbers(l1,l2);
while (result != null){
System.out.println(result.val);
result = result.next;
}
}
}
運行之後我們會得到打印的數字:2,4,6,這不就是我們要的結果嗎?我們成功了。
但是這個簡易版的是存在漏洞的,什麼漏洞呢?你們有沒有考慮到進位的問題?簡單來說,剛剛的數字都是相加小於10的數字;如果相加大於10的話,我們的鏈表節點就會出現兩位數。
public static void main(String[] args) {
//設置 l1,l2 鏈表 342 + 465 = 807
ListNode l1 = new ListNode(2);
l1.next = new ListNode(4);
l1.next.next = new ListNode(3);
ListNode l2 = new ListNode(5);
l2.next = new ListNode(6);
l2.next.next = new ListNode(4);
Solution solution = new Solution();
ListNode result = solution.addTwoNumbers(l1,l2);
while (result != null){
System.out.println(result.val);
result = result.next;
}
}
這個運行之後的打印的數字就變成:7,10,7。這肯定是有問題的,我們計算的和應該是807。我們從這裏可以看出,我們沒有考慮進位的問題,這裏的10的1應該加載7上面,這樣就是 807 了。
第二版遇到的問題:沒有考慮進位問題,遇到超過10的數字,我們需要往前進一位。
首先,我們考慮進位之後,肯定是在10~18之間,因爲兩個最大的一位正整數就是9.兩個加起來也就是18。那我們該怎麼取這兩位數字呢?很簡單,個位用取模%,十位用除號 /。
int sum = x + y;
//取進位值 進位值都爲1。
int carry = sum / 10;
//取餘數值 0~8
sum = sum % 10;
得到兩個值之後我們要考慮,如何將這兩個值放入鏈表中。(對應代碼1.2.3.4.)
- 餘數值當然是放在當前新建的鏈表節點。
- 進位值我們需要在下一個循環中加到求和值上面去。
- 因爲2可知我們需要將進位值帶入下一個循環,這個進位值我們就不能放在循環體中了,我們需要放在循環體外面。
- 如果最後一位數字存在進位,沒有下一次循環了,我們需要再額外添加一個節點存放進位值1.
package com.gyx.util;
/**
* 兩數相加 v3版
* @author GeYuxuan 2020/03/01 13:40
*/
public class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//新鏈表虛擬頭結點
ListNode pre = new ListNode(0);
ListNode cur = pre;
//3.進位值放在循環體外
int carry = 0;
while(l1 != null || l2 != null){
//取出 l1和l2當前節點的值,如果當前節點爲空則補0
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
//將兩節點值求和 (增加進位值)
int sum = x + y +carry;
//1.取餘數
sum = sum % 10;
//2.取進位值
carry = sum / 10;
//增加一個新節點
cur.next = new ListNode(sum);
cur = cur.next;
//移動l1和l2的指針
if(l1 != null){
l1 = l1.next;
}
if(l2 != null){
l2 = l2.next;
}
}
//4.如果最後一次循環,存在進位
if(carry == 1){
cur.next = new ListNode(carry);
}
return pre.next;
}
}
好了,這就是完全體的題解了。接下來讓我們再去試試這個新玩具吧;試試上面的342+465=807。
public static void main(String[] args) {
//設置 l1,l2 鏈表 342 + 465 = 807
ListNode l1 = new ListNode(2);
l1.next = new ListNode(4);
l1.next.next = new ListNode(3);
ListNode l2 = new ListNode(5);
l2.next = new ListNode(6);
l2.next.next = new ListNode(4);
Solution solution = new Solution();
ListNode result = solution.addTwoNumbers(l1,l2);
while (result != null){
System.out.println(result.val);
result = result.next;
}
}
運行結果打印:7,0,8。
再試試 555 + 555 = 1110;
public static void main(String[] args) {
//設置 l1,l2 鏈表 555 + 555 =1110
ListNode l1 = new ListNode(5);
l1.next = new ListNode(5);
l1.next.next = new ListNode(5);
ListNode l2 = new ListNode(5);
l2.next = new ListNode(5);
l2.next.next = new ListNode(5);
Solution solution = new Solution();
ListNode result = solution.addTwoNumbers(l1,l2);
while (result != null){
System.out.println(result.val);
result = result.next;
}
}
運行之後打印:0,1,1,1。
不忘初心,砥礪前行。