LeetCode-數據結構-兩數相加

力扣題目地址: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.)

  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.)

  1. 餘數值當然是放在當前新建的鏈表節點。
  2. 進位值我們需要在下一個循環中加到求和值上面去。
  3. 因爲2可知我們需要將進位值帶入下一個循環,這個進位值我們就不能放在循環體中了,我們需要放在循環體外面。
  4. 如果最後一位數字存在進位,沒有下一次循環了,我們需要再額外添加一個節點存放進位值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。

不忘初心,砥礪前行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章