LeetCode :2.兩數相加 解題報告及算法優化思路

題目鏈接:2.兩數相加算法

題意

題目難度標爲 中等, 由於題意上有一部分理解難度,以及須要數據結構的鏈表基礎。c#

還不知道到鏈表的童鞋能夠粗略的看下百度百科或者是翻出數據結構的書看一看,通俗一點的語言來解釋鏈表就是:上線和下線數據結構

上線知道本身的下線,但不知道本身下線的下線,同時也不知道本身的上線是誰。優化

這就是單向鏈表設計

這道題的題意就是將兩個數字變成了兩個單向鏈表,其中每個節點存儲一位數字,且是逆序存放,也就是倒過來存了。code

解題思路

首先來想一下不一樣狀況和對應的案例:內存

  1. 兩個鏈表長度相等。

輸入:(2 -> 4 -> 3) + (5 ->6 -> 4)
輸出:(7 -> 0 -> 8)leetcode

  1. 兩個鏈表長度不等。

輸入:(2 -> 4) + (5 -> 6 -> 4)
輸出:(7 -> 0 -> 5)get

  1. 最終結果的最高位存在進位的狀況。

輸入:(2 -> 4 -> 5) + (5 -> 6 -> 4)
輸出:(7 -> 0 -> 0 -> 1)io

解題的關鍵即是:合併鏈表而且保證進位正確

模擬進位

首先咱們按位相加,而後再對每一位進行進位處理,滿 10 則進 1

代碼:

public class Solution
    {
        public ListNode AddTwoNumbers(ListNode l1, ListNode l2)
        {
            ListNode first = null;
            ListNode current = null;
            IList<int> sums = new List<int>();
            int sum = 0;
            int res = 0;

            while (l1 != null || l2 != null)
            {
                sum = res;
                if (l1 != null)
                {
                    sum += l1.val;
                    l1 = l1.next;
                }

                if (l2 != null)
                {
                    sum += l2.val;
                    l2 = l2.next;
                }

                res = sum / 10;
                sum %= 10;
                sums.Add(sum);
            }

            if(res > 0) sums.Add(res);

            if(sums.Any()) first = new ListNode(sums[0]);

            current = first;

            for (int i = 1; i < sums.Count; i++)
            {
                current.next = new ListNode(sums[i]);
                current = current.next;
            }

            return first;
        }
    }

執行用時: 252 ms, 在全部 C# 提交中擊敗了13.33%的用戶
內存消耗: 26.7 MB

這個耗時有點悽慘,接近墊底了。那也說明了還有很大的優化空間。

優化常量

上面咱們在循環時使用到了 IListCount,這裏咱們能夠提早將其存儲起來。

代碼以下:

public class Solution
    {
        public ListNode AddTwoNumbers(ListNode l1, ListNode l2)
        {
            ListNode first = null;
            ListNode current = null;
            IList<int> sums = new List<int>();
            int sum = 0;
            int res = 0;
            int count = 0;

            while (l1 != null || l2 != null)
            {
                sum = res;
                if (l1 != null)
                {
                    sum += l1.val;
                    l1 = l1.next;
                }

                if (l2 != null)
                {
                    sum += l2.val;
                    l2 = l2.next;
                }

                res = sum / 10;
                sum %= 10;
                sums.Add(sum);
            }

            if (res > 0) sums.Add(res);

            count = sums.Count;

            if (count > 0) first = new ListNode(sums[0]);

            current = first;

            for (int i = 1; i < count; i++)
            {
                current.next = new ListNode(sums[i]);
                current = current.next;
            }

            return first;
        }
    }

執行用時: 164 ms, 在全部 C# 提交中擊敗了85.62%的用戶
內存消耗: 26.8 MB

僅僅是替換了一個變量,執行用時就優化了近 100ms! 這 100ms 就超過了 70% 的提交。

前面還有近 15% 說明還有優化空間。

優化循環數

上面的算法,若是按照大O表示法來計算複雜度的話,它的複雜度是:O(max(l1.Length, l2.Length)) ,再精簡一下也就是 O(n),即單重循環的程度。

但真正的複雜度是(一樣也是估算,循環內的操做數沒有算進來,這裏只算了循環的):2 * max(l1.Length, l2.Length) + 1。由於這裏其實是使用了兩個循環。那麼咱們能夠將這個複雜度表達式中的倍數 : 2 去掉,即只用一重循環。

代碼以下:

public class Solution
    {
        public ListNode AddTwoNumbers(ListNode l1, ListNode l2)
        {
            ListNode first = null;
            ListNode current = null;
            int sum = 0;
            int res = 0;

            while (l1 != null || l2 != null)
            {
                sum = res;
                if (l1 != null)
                {
                    sum += l1.val;
                    l1 = l1.next;
                }

                if (l2 != null)
                {
                    sum += l2.val;
                    l2 = l2.next;
                }

                res = sum / 10;
                sum %= 10;

                if (current == null)
                {
                    first = new ListNode(sum);
                    current = first;
                }
                else
                {
                    current.next = new ListNode(sum);
                    current = current.next;
                }
            }

            if (res > 0) current.next = new ListNode(res);

            return first;
        }
    }

執行用時: 136 ms, 在全部 C# 提交中擊敗了98.85%的用戶
內存消耗: 26.5 MB

在咱們移除掉一重循環以後,執行用時優化了 20 多ms(爲何不是優化了近一半的時間?),而這 20 多ms就又超過了 13% 的提交。

對於Leetcode判題耗時的思考

處於對連續兩道題目都沒有辦法達到最優(至少前 1% )的狀況下,若羽今天寫到這裏時,特地去看了一下耗時最短的代碼(104ms),複製下來以後直接提交,結果顯示是 248ms !??

再次提交以後結果顯示是 160 ms !??

同一份代碼, 上下浮動的區間未免也太大了!若羽不由思考起 LeetCode 的判題核心是如何進行計時的。這個浮動區間已經達到能夠破壞排名公平性的程度了,也許有人會以爲若羽危言聳聽,誇大其詞。

事實是:確實已經達到破壞公平的程度了!!!,這一類在線運行代碼而且自動輸入案例比對結果的系統其實很早就已經出現,在 信息學競賽 以及 ACM大學生程序設計競賽 中一般被稱爲 OJ(Online Judge System) 在線判題系統。而在競賽場上,差距達到 160ms 意味着什麼?意味着兩隊選手同時甚至略遲一點點提交代碼,最終誰的排名在前還得聽天命

拋開競賽的立場,浮動如此大的排名,在必定程度上已經沒法再客觀的去評價時間複雜度相近算法的優劣了。排名第一的算法再運行一次也有可能會排到末尾去。

可是算法優化的思路仍是能夠繼續,擼碼不停,優化不止~(好想本身實現一個 OJ 了!)

相關文章
相關標籤/搜索