LeetCode :1.兩數之和 解題報告及算法優化思路

最近開始重拾算法,在 LeetCode上刷題。順便也記錄下解題報告以及優化思路。算法

題目連接:1.兩數之和c#

題意

給定一個整數數組 nums 和一個目標值 target,請你在該數組中找出和爲目標值的那 兩個 整數,並返回他們的數組下標。數組

你能夠假設每種輸入只會對應一個答案。可是,你不能重複利用這個數組中一樣的元素。優化

示例:設計

nums = [2, 7, 11, 15], target = 9

返回 [0, 1]

題意很簡單,就是尋找兩個數,這兩個數相加的值等於 target。且保證每組輸入必定會有答案。code

解題思路

從題意上看, 只須要找到那兩個數便可。那麼首先能夠想到的就是枚舉組合兩個數的和,可是 組合數 的數量是很是大的,這個思路就能夠做罷。內存

兩個數相加的和等於 target, 反過來,對於一個數 nums[i],可否在數組裏找到另一個數 nums[j] 使得 nums[j] = target - nums[i]。這樣咱們只須要關心一個數便可。leetcode

暴力枚舉

簡單粗暴,一重循環用來枚舉 nums[i], 另外一重用來尋找 nums[j]get

代碼:源碼

public class Solution {
    public int[] TwoSum(int[] nums, int target) {
        for(int i = 0; i < nums.Length; i ++) {
            int res = target - nums[i];
            for(int j = 0; j < nums.Length; j ++) {
                if(i == j) continue;
                if(res == nums[j]) return new int[] {i, j};
            }
        }
        return new int[] {};
    }
}

執行用時:904ms
內存消耗:29.6MB
用時排名:超過21.29%

29個案例,耗時近 1 秒。 因爲這裏僅有循環輔助變量,內存消耗其實不大。
耗時排名是排在比較後面的,這也說明了還有更優的解法。

空間換時間

此題關鍵的地方在於:如何快速的找到 j,暴力枚舉在最壞的狀況下會找遍整個數組,直到最後一個才找到,時間複雜度也就是 O(n)

那麼,在這裏咱們能夠利用 哈希算法 進行映射,從而達到更快查找效率。理論上 哈希算法 設計良好的狀況下能夠達到 常數級 O(1) 的複雜度。

一個例子:在值不大的狀況下, 能夠用值當作數組下標,而數組的值做爲原來數組的下標。即:

對於 x = nums[i],存在 hash[x] = i。這樣在犧牲大量空間的狀況下可使得查詢效率達到極致的常數級 O(1)

可是很遺憾, 這道題並無辦法直接使用這個方法,由於 int.MaxValue 是遠超過了數組能夠定義的範圍。編譯時會報錯,內存溢出。

既然暫時沒有辦法達到 O(1) 的地步, 那麼能夠考慮使用實現了 哈希算法 (這裏保留說法,若羽源碼沒有閱讀完,在看到index的取法有着很明顯的哈希痕跡進行猜想的)的 Dictionary<TKey, TValue>

代碼:

public class Solution 
{
    public int[] TwoSum(int[] nums, int target) 
    {
        var dic = new Dictionary<int, List<int>>();
        for(int i = 0; i < nums.Length; i ++) 
        {
            int num = nums[i];
            int res = target - num;
            if(dic.ContainsKey(res)) 
            {
                return new int[] {i, dic[res][0]};
            } 
            
            if(!dic.ContainsKey(num)) 
            {
                dic.Add(num, new List<int>(){});
                
            }
            
            dic[num].Add(i);
        }
        return new int[] {};
    }
}

執行用時:468 ms
內存消耗:30.5MB
用時排名:超過78.61%

改進後的算法排名與以前可謂是天差地別,已經到了前 30%

僅僅是達到了前三分之一,說明這個算法還有能夠更進一步的優化。

進一步優化查詢

這裏用了 Dictionary, 但這裏的 TValue 是一個列表。仔細想一想,這裏咱們是不須要保存列表的,只須要保存一個值便可,由於只須要找到一組答案便可!

其次能夠減去第二個判斷,並不須要判斷是否存在,直接覆蓋/新建便可。

最後能夠反向查詢,查以前的數值中是否缺一個 nums[i],對應存進去的就是差值,這樣能夠減去兩個臨時變量,順帶優化一點點的空間。

代碼:

public class Solution 
{
    Dictionary<int, int> dic = new Dictionary<int, int>();
    public int[] TwoSum(int[] nums, int target) 
    {
        if(nums.Length == 2) return new int[] {0, 1};
        dic.Clear();
        for (int i = 0; i < nums.Length; i++)
        {
            if(dic.ContainsKey(nums[i])) return new int[] {dic[nums[i]], i};
            dic[target - nums[i]] = i;
        }
        return new int[] { };
    }
}

執行用時:360 ms
內存消耗:30MB
用時排名:超過98.83%

改進後的算法相比以前的差距並非很是的大,但就是這百來毫秒的差距,排名上升到了前 3%

這個算法仍是有能夠改進的地方,可是若羽如今暫時尚未思路如何再進一步將查詢複雜度降下去,這裏可能須要本身實現一個更高效的哈希算法。

寫在最後

許久沒有接觸算法了,有些生疏,思路上也有些堵塞。這裏若羽對於接下來進一步優化有一些初步的想法,待有空實驗後再加一篇解題報告。

  1. 分段策略,當數據量達到必定程度時使用更高效的算法,當數據量較小時,可能哈希耗時會更長一些,這個須要實驗。大意即是尋找多個算法的耗時閾值,利用閾值進行策略選擇。

  2. 本身實現針對題目更高效的哈希算法。

相關文章
相關標籤/搜索