題目敘述
題目描述給定整數的陣列以及整數 target
值,請在陣列找到任兩元素相加等於 target,並且回傳元素的索引,另外對於每個輸入陣列只會有一組輸出答案。
解法-1: 暴力解 首先一樣從暴力解開始,先有解法,後續再看要怎麼優化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int * twoSum (int * nums, int numsSize, int target, int * returnSize) { int i, j; int * results = (int *)malloc (2 * sizeof (int )); for (i=0 ; i< numsSize-1 ; i++){ for (j = i+1 ; j < numsSize; j++){ if (nums[i] + nums[j] == target){ results[0 ] = i; results[1 ] = j; *returnSize = 2 ; return results; } } } free (results); *returnSize = 0 ; return NULL ; }
這裡的核心做法為: 選擇一個元素,去比較另一個元素,看相加是否為 target
但這麼做的時間複雜度為 $O(n^2)$
另外因為題目要求回傳陣列需要動態宣告,因此在結尾必須釋放記憶體位址 *returnSize
是用來告訴呼叫者返回的陣列的大小。由於 C 語言不支援直接返回多個值,題目需要指標來修改外部變數,這樣可以傳遞更多的資訊。
執行結果:
解法-2: 使用雜湊表(Hash Table) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 int hash (int key, int size) { return abs (key) % size; } int * twoSum (int * nums, int numsSize, int target, int * returnSize) { typedef struct { int key; int value; bool hasData; } twosum; int hashtableSize = numsSize * 2 ; twosum* hashtable = (twosum*)calloc (hashtableSize, sizeof (twosum)); int * results = (int *)malloc (2 * sizeof (int )); if (!results) return NULL ; for (int i=0 ;i<hashtableSize;i++){ hashtable[i].key=0 ; hashtable[i].value=0 ; hashtable[i].hasData = false ; } for (int i=0 ; i < numsSize; i++){ int complement = target - nums[i]; int index = hash(complement, hashtableSize); while (hashtable[index].hasData ){ if ( hashtable[index].key == complement){ results[0 ] = hashtable[index].value; results[1 ] = i; *returnSize = 2 ; free (hashtable); return results; } index = hash(index+1 , hashtableSize); } index = hash(nums[i], hashtableSize); while (hashtable[index].hasData) { index = hash(index+1 , hashtableSize); } hashtable[index].key = nums[i]; hashtable[index].value = i; hashtable[index].hasData = true ; } free (hashtable); free (results); *returnSize = 0 ; return NULL ; }
程式的主要的流程為:
定義 hashtable 結構以及 hashtable 大小
定義 hash function
初始化回傳陣列
初始化 hashtable
插入元素,需要先判斷 index 內是否已經有元素存在
發生碰撞時,透過 Linear Probing 來處理碰撞,找到空位址
若無元素存在則直接插入元素
釋放記憶體,返回回傳陣列
這種作法又被稱為 Two-pass Hash Table ,主要原因是他經過兩次迭代,一次將元素的值和索引填到 hashtable的 key跟value,另一次檢查每個元素的 complement 是否存在於 hash table 當中。
這樣透過雜湊表的執行時間複雜度可以降到 $O(n)$,其實查找Hash Table的時間複雜度會是 $O(1)$,但考量到碰撞可能發生,進行 Linear Probing,這會讓整體的時間複雜度變成 $O(n)$。
心得 原本在實作的時候,我原本的 hashtable 結構只有定義
1 2 3 4 typedef struct { int key; int value; } twosum;
並且在判斷式是使用 while( hashtable[index].key != 0 || hashtable[index].value != 0 )
但後面發現對特定的測資會出現問題,像是 nums = [0,4,3,0]
並且 target= 0
的時候,並不會有任何輸出出現
問題分析 原因是在插入hash table 的時候,以第一個元素為例,插入至hashtable的key會是 0,但我們在初始化將沒有資料的狀況定義成0,並且在中間判斷式也以0作為有無值的判斷依據 ,但在想要出儲存的值本身為 0 時,這樣會發生衝突,因此後續解法就是在 hashtable 的結構中定義一個 hasData
來判斷是否有資料存在。
但這麼做也有缺點,那就是增加了空間複雜度。
其他解法 - One Pass Hash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int * twoSum (int * nums, int numsSize, int target, int * returnSize) { struct hashTable { int key; int value; UT_hash_handle hh; } *hashTable = NULL , *item; for (int i = 0 ; i < numsSize; i++) { int complement = target - nums[i]; HASH_FIND_INT(hashTable, &complement, item); if (item) { int * result = malloc (sizeof (int ) * 2 ); result[0 ] = item->value; result[1 ] = i; *returnSize = 2 ; HASH_CLEAR(hh, hashTable); return result; } item = malloc (sizeof (struct hashTable)); item->key = nums[i]; item->value = i; HASH_ADD_INT(hashTable, key, item); } *returnSize = 0 ; HASH_CLEAR(hh, hashTable); return NULL ; }
這裡就是將插入 hashtable 和檢查 complement 併入同一個迭代中,但由於還是迭代了 n 個元素,因此時間複雜度一樣是 $O(n)$
這裡我也開始考慮改成學習使用 C++ 來刷題XD,C++ 像這種題目就有很多好用的 STL unordered_map
能夠直接使用,不需要重新設計太多東西。
C++ 的解法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : vector<int > twoSum (vector<int > &nums, int target) { unordered_map<int , int > hash; for (int i = 0 ; i < nums.size (); i++) { hash[nums[i]] = i; } for (int i = 0 ; i < nums.size (); i++) { int complement = target - nums[i]; if (hash.find (complement) != hash.end () && hash[complement] != i) { return {i, hash[complement]}; } } return {}; } };