LeetCode題集-2 - 兩數相加

IT规划师發表於2024-09-05

這個題目是什麼意思呢?簡單來說就是把兩個連結串列平鋪開,頭節點對齊,然後從頭開始相同的節點相加,滿10則進位,進位值與下個節點繼續相加,當一個連結串列沒有節點時候則可以把沒有節點當做0繼續與有節點的連結串列繼續相加。具體示例如下:

到這裡不知道你是否已經有解題思路了呢?

01、解法一:遞迴法

我第一反應就是遞迴,為什麼?想想題目,對兩個連結串列相同節點位置值按順序求值,第一個算完算第二個,以此類推直至所有節點計算完成,這不正好使用遞迴嗎,定義一個方法計算兩個節點和,然後以頭節點的下個節點作為遞迴點,即計算頭節點後,頭節點的下個節點計算直接呼叫自身方法直至所有節點計算完成,具體程式碼如下:

public static ListNode AddTwoNumbersRecursion(ListNode l1, ListNode l2)
{
    return AddTwoNumbersRecursive(l1, l2, 0);
}
private static ListNode AddTwoNumbersRecursive(ListNode l1, ListNode l2, int carry)
{
    //當兩個連結串列節點都為空並且進位值等於0,則結束遞迴
    if (l1 == null && l2 == null && carry == 0)
    {
        return null;
    }
    //以進位值為初始值定義兩節點和變數,
    var sum = carry;
    //如果l1節點不為空,則累加其節點值,並且把其下個節點賦值給自身,用於下次迭代
    if (l1 != null)
    {
        sum += l1.val;
        l1 = l1.next;
    }
    //如果l2節點不為空,則累加其節點值,並且把其下個節點賦值給自身,用於下次迭代
    if (l2 != null)
    {
        sum += l2.val;
        l2 = l2.next;
    }
    //計算進位值
    carry = sum / 10;
    //以當前位值,建立下一個節點
    return new ListNode(sum % 10)
    {
        //遞迴點
        next = AddTwoNumbersRecursive(l1, l2, carry)
    };
}

然後我們執行程式碼驗證一下,結果如下:

02、解法二:迭代法

我們知道因為每次遞迴都會需要額外的棧空間,因此深度遞迴可能會引發一系列效能問題,因此我們是否還有其他辦法呢?

遞迴有個同義詞叫迭代,而迭代只需要在一個迴圈裡重複執行一個計算即可,這樣就可以避免遞迴產生的問題。

因此我們只需要把遞迴方法改造成迭代方法即可,裡面的解題思路基本都是一樣的,只不過是不通的寫法。程式碼如下:

public static ListNode AddTwoNumbersIteration(ListNode l1, ListNode l2)
{
    //建立頭節點,即第一位計算結果
    var head = new ListNode(0);
    //用於迭代節點
    var current = head;
    //初始化進位值
    int carry = 0;
    //當兩個連結串列節點都不為空並且進位值不等於0,則繼續迭代
    while (l1 != null || l2 != null || carry != 0)
    {
        //以進位值為初始值定義兩節點和變數,
        var sum = carry;
        //如果l1節點不為空,則累加其節點值,並且把其下個節點賦值給自身,用於下次迭代
        if (l1 != null)
        {
            sum += l1.val;
            l1 = l1.next;
        }
        //如果l2節點不為空,則累加其節點值,並且把其下個節點賦值給自身,用於下次迭代
        if (l2 != null)
        {
            sum += l2.val;
            l2 = l2.next;
        }
        //計算進位值
        carry = sum / 10;
        //以當前位值,建立下一個節點
        current.next = new ListNode(sum % 10);
        //把下個節點賦值給當前迭代節點,繼續下次迭代
        current = current.next;
    }
    //返回實際結果連結串列的頭節點
    return head.next;
}

執行結果如下:

對於這一題核心解題思路是一樣,問題在於如何選擇方法,遞迴有遞迴的好處,迭代有迭代的好處,因此要根據自己實際情況進行選擇。

下面對遞迴和迭代做個點單對比:

遞迴:程式碼更簡潔直觀,邏輯更接近問題的自然描述易於理解;但是遞迴會消耗更多記憶體,深度遞迴可能會導致棧溢位。

迭代:節省記憶體,效能會更好;但是程式碼更難理解。

題目到這裡就做完了,但是不知道有沒有人會有這樣的疑惑?

在迭代法中,連結串列head是一個引用型別,並且被賦值給了連結串列current,而連結串列current在迭代中不停的被current.next覆蓋,那麼為什麼這個覆蓋過程沒有影響到連結串列head?導致head為整個連結串列的最後一個節點?最後返回的head.next還是正確的答案?

你知道為什麼嗎?

測試方法程式碼以及示例原始碼都已經上傳至程式碼庫,有興趣的可以看看。 https://gitee.com/hugogoos/Planner

相關文章