【LeetCode】1. Two Sum + 雜湊演算法
傳送門:https://leetcode.com/problems/two-sum/#/description
一、題目描述
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
二、示例
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
三、問題描述
給定一個數字和一個目標數字,求出陣列中兩個數字之和等於給定的目標數字。
注意:假定每個陣列中有且只有一組解,不能使用同一個陣列元素兩次
例如:陣列[2,7,11,15], 目標數字9,因為nums[0] + nums[1] = 9,所以返回[0,1]
四、分析
1、暴力解法
遍歷陣列法,直接遍歷整個陣列,找到兩個陣列元素和等於target即可。
2、雜湊思想
通過建立 <數值,下標>
的雜湊表,每遍歷到一個元素 i
,就查詢所對應的元素 target-i
i
和 數值 target - i
不能是同一個下標。
五、實現
1、暴力解法
==》時間複雜度:O(n2)
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> res;
for(int i = 0; i < nums.size(); i++)
{
for(int j = i + 1 ; j < nums.size(); j++)
{
if((nums[i] + nums[j]) == target)
{
res.push_back(i);
res.push_back(j);
break;
}
}
if(!res.empty())
break;
}
return res;
}
};
提交結果
2、雜湊法
==》時間複雜度:O(n)
==》空間複雜度:O(n)——雜湊表
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int i,sum;
vector<int> results;
map<int, int> hmap;
for(i = 0; i < nums.size(); i++){
hmap.insert(pair<int, int>(nums[i], i));
}
for(i = 0; i < nums.size(); i++){
if(hmap.count(target - nums[i]) && hmap[target - nums[i]] != i)
{
int n = hmap[target - nums[i]];
results.push_back(i);
results.push_back(n);
break;
}
}
return results;
}
};
注:使用count,返回的是被查詢元素的個數。如果有,返回1;否則,返回0。注意,map中不存在相同元素,所以返回值只能是1或0。
提交結果
六、雜湊演算法
1、基本概念
雜湊演算法(也稱雜湊演算法)
將任意長度的輸入(又叫做預對映, pre-image)對映為固定長度的輸出的對映規則。
==》雜湊值的空間通常遠小於輸入的空間
雜湊值(也稱雜湊值)
通過對原始資料對映後得到的輸出。
雜湊表
以 鍵-值(key-value)
儲存資料的結構,只需輸入待查詢的 key,就可以查詢其對應的值。
負載因子
例如要儲存80個元素,但可能為這80個元素申請了100個元素的空間。80/100=0.8,這個數字稱為負載因子。我們之所以這樣做,也是為了“高速存取”的目的。
衝突
兩個元素通過 雜湊函式 得到同樣的結果。
2、雜湊演算法的設計要求
- 從雜湊值不能反向推導出原始資料(所以雜湊演算法也叫單向雜湊演算法);
- 對輸入資料非常敏感,哪怕原始資料只修改了一個 Bit,最後得到的雜湊值也大不相同;
- 雜湊衝突的概率要很小,對於不同的原始資料,雜湊值相同的概率非常小;
- 雜湊演算法的執行效率要儘量高效,針對較長的文字,也能快速地計算出雜湊值。
==》無論雜湊文字長度,都可得到長度相同的雜湊值;
==》兩個相似的文字,得到的雜湊值完全不同。
3、雜湊函式設計原則
- 雜湊函式的設計不能太複雜。否則,消耗很多計算時間,間接影響散列表效能。
- 雜湊函式生成的值儘可能隨機且均勻分佈==》避免或最小化雜湊衝突,即便衝突,也比較平均。
雜湊函式設計方法:資料分析法(從原始資料中擷取部分作為雜湊函式)、直接定址法、平方取中法、摺疊法、隨機數法等
4、常見雜湊函式的構建方法
- 直接定址法:取keyword或keyword的某個線性函式值為雜湊地址。即H(key)=key或H(key) = a•key + b,當中a和b為常數(這樣的雜湊函式叫做自身函式)
- 數字分析法:分析一組資料,比方一組員工的出生年月日,這時我們發現出生年月日的前幾位數字大體同樣,這種話,出現衝突的機率就會非常大,可是我們發現年月日的後幾位表示月份和詳細日期的數字區別非常大,假設用後面的數字來構成雜湊地址,則衝突的機率會明顯減少。因此數字分析法就是找出數字的規律,儘可能利用這些資料來構造衝突機率較低的雜湊地址。
- 平方取中法:取keyword平方後的中間幾位作為雜湊地址
- 摺疊法:將keyword切割成位數同樣的幾部分,最後一部分位數能夠不同,然後取這幾部分的疊加和(去除進位)作為雜湊地址。
- 隨機數法:選擇一隨機函式,取keyword的隨機值作為雜湊地址,通經常使用於keyword長度不同的場合。
- 除留餘數法:取keyword被某個不大於散列表表長m的數p除後所得的餘數為雜湊地址。即 H(key) = key MOD p, p<=m。不僅能夠對keyword直接取模,也可在摺疊、平方取中等運算之後取模。對p的選擇非常重要,一般取素數或m,若p選的不好,easy產生同義詞。
5、雜湊衝突
兩類方法:開放定址法(open addressing)和連結串列法(chaining)
(1)開放定址法
基本思想: 若存在雜湊衝突,則探測一個空閒位置,將其插入。
A、探測方法
① 線性探測(Linear Probing)
從當前位置開始,依次往後(若不夠則從頭繼續)查詢,看是否有空閒位置,直到找到為止。
② 二次探測(Quadratic Probing)
區別: 步長變成了原來的“二次方“,即:探測的下標序列
hash(key)+0,hash(key)+12,hash(key)+22,hash(key)+32,……
③ 雙重雜湊(Double hashing)
使用一組雜湊函式 hash1(key),hash2(key),hash3(key)……先用第一個雜湊函式,如果計算得到的儲存位置已經被佔用,再用第二個雜湊函式,依次類推,直到找找到空閒的儲存位置。
B、存在問題
當資料越來越多時,雜湊衝突發生的可能性就會越來越大,空閒位置會越來越少,線性探測的時間就會越來越久。極端情況下,最壞情況下的時間複雜度為 O(n)。同理,在刪除和查詢時,,也有可能會線性探測整張散列表,才能找到要查詢或者刪除的資料。
C、裝載因子(load factor)
為了儘可能保證散列表的操作效率,利用裝載因子(load factor)來表示空閒槽位的多少。
散列表的裝載因子 = 填入表中的元素個數 / 散列表的長度
裝載因子越大,說明空閒位置越少,衝突越多,散列表的效能會下降。
(2)連結串列法——更常用
所有雜湊值相同的元素我們都放到相同槽位對應的連結串列中。
插入:通過雜湊函式計算出對應的雜湊槽位,將其插入到對應連結串列中即可
==》時間複雜度:O(1)
查詢、刪除:需要遍歷,時間複雜度跟連結串列的長度 k 成正比,也就是 O(k)。
對於雜湊比較均勻的雜湊函式來說,理論上講,k=n/m,其中 n 表示雜湊中資料的個數,m 表示散列表中“槽”的個數。
6、應用
最常見的幾種應用:安全加密、唯一標識、資料校驗、雜湊函式、負載均衡、資料分片、分散式儲存。