散列表(Hash table,也叫哈希表),是根據鍵(Key)而直接訪問在內存存儲位置的數據結構。也就是說,它經過計算一個關於鍵值的函數,將所需查詢的數據映射到表中一個位置來訪問記錄,這加快了查找速度。這個映射函數稱作散列函數,存放記錄的數組稱作散列表。c++
更加詳細的介紹請戳這:算法
題目來源於 LeetCode 上第 1 號問題: Two Sum。數組
給定一個整數數組 nums
和一個目標值 target
,請你在該數組中找出和爲目標值的那 兩個 整數,並返回他們的數組下標。數據結構
你能夠假設每種輸入只會對應一個答案。可是,你不能重複利用這個數組中一樣的元素。函數
示例:動畫
給定 nums = [2, 7, 11, 15], target = 9
由於 nums[0] + nums[1] = 2 + 7 = 9
因此返回 [0, 1]
使用散列表來解決該問題。ui
首先設置一個 map 容器 record 用來記錄元素的值與索引,而後遍歷數組 nums 。spa
兩數之和指針
// 1. Two Sum
// 時間複雜度:O(n)
// 空間複雜度:O(n)
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> record;
for(int i = 0 ; i < nums.size() ; i ++){
int complement = target - nums[i];
if(record.find(complement) != record.end()){
int res[] = {i, record[complement]};
return vector<int>(res, res + 2);
}
record[nums[i]] = i;
}
}
};
題目來源於 LeetCode 上第 3 號問題: Longest Substring Without Repeating Characters 。code
給定一個字符串,請你找出其中不含有重複字符的 最長子串 的長度。
創建一個 HashMap ,創建每一個字符和其最後出現位置之間的映射,而後再定義兩個變量 res 和 left ,其中 res 用來記錄最長無重複子串的長度,left 指向該無重複子串左邊的起始位置的前一個,一開始因爲是前一個,因此在初始化時就是 -1。
接下來遍歷整個字符串,對於每個遍歷到的字符,若是該字符已經在 HashMap 中存在了,而且若是其映射值大於 left 的話,那麼更新 left 爲當前映射值,而後映射值更新爲當前座標i,這樣保證了left始終爲當前邊界的前一個位置,而後計算窗口長度的時候,直接用 i-left 便可,用來更新結果 res 。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int res = 0, left = -1, n = s.size();
unordered_map<int, int> m;
for (int i = 0; i < n; ++i) {
if (m.count(s[i]) && m[s[i]] > left) {
left = m[s[i]];
}
m[s[i]] = i;
res = max(res, i - left);
}
return res;
}
};
此題也可使用滑動窗口的概念來處理。
創建一個 256 位大小的整型數組 freg ,用來創建字符和其出現位置之間的映射。
維護一個滑動窗口,窗口內的都是沒有重複的字符,去儘量的擴大窗口的大小,窗口不停的向右滑動。
無重複字符的最長子串
// 3. Longest Substring Without Repeating Characters
// 滑動窗口
// 時間複雜度: O(len(s))
// 空間複雜度: O(len(charset))
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int freq[256] = {0};
int l = 0, r = -1; //滑動窗口爲s[l...r]
int res = 0;
// 整個循環從 l == 0; r == -1 這個空窗口開始
// 到l == s.size(); r == s.size()-1 這個空窗口截止
// 在每次循環裏逐漸改變窗口, 維護freq, 並記錄當前窗口中是否找到了一個新的最優值
while(l < s.size()){
if(r + 1 < s.size() && freq[s[r+1]] == 0){
r++;
freq[s[r]]++;
}else { //r已經到頭 || freq[s[r+1]] == 1
freq[s[l]]--;
l++;
}
res = max(res, r-l+1);
}
return res;
}
};
題目來源於 LeetCode 上第 15 號問題: 3Sum 。
給定一個包含 n 個整數的數組 nums
,判斷 nums
中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?找出全部知足條件且不重複的三元組。
題目須要咱們找出三個數且和爲 0 ,那麼除了三個數全是 0 的狀況以外,確定會有負數和正數,因此一開始能夠先選擇一個數,而後再去找另外兩個數,這樣只要找到兩個數且和爲第一個選擇的數的相反數就好了。也就是說須要枚舉 a 和 b ,將 c 的存入 map 便可。
須要注意的是返回的結果中,不能有有重複的結果。這樣的代碼時間複雜度是 O(n^2)。在這裏能夠先將原數組進行排序,而後再遍歷排序後的數組,這樣就可使用雙指針以線性時間複雜度來遍歷全部知足題意的兩個數組合。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
if (nums.empty() || nums.back() < 0 || nums.front() > 0) return {};
for (int k = 0; k < nums.size(); ++k) {
if (nums[k] > 0) break;
if (k > 0 && nums[k] == nums[k - 1]) continue;
int target = 0 - nums[k];
int i = k + 1, j = nums.size() - 1;
while (i < j) {
if (nums[i] + nums[j] == target) {
res.push_back({nums[k], nums[i], nums[j]});
while (i < j && nums[i] == nums[i + 1]) ++i;
while (i < j && nums[j] == nums[j - 1]) --j;
++i; --j;
} else if (nums[i] + nums[j] < target) ++i;
else --j;
}
}
return res;
}
};
題目來源於 LeetCode 上第 187 號問題: Repeated DNA Sequences 。
全部 DNA 由一系列縮寫爲 A,C,G 和 T 的核苷酸組成,例如:「ACGAATTCCG」。在研究 DNA 時,識別 DNA 中的重複序列有時會對研究很是有幫助。
編寫一個函數來查找 DNA 分子中全部出現超過一次的 10 個字母長的序列(子串)。
首先,先將 A , C , G , T 的 ASCII 碼用二進制來表示:
A: 0100 0001 C: 0100 0011 G: 0100 0111 T: 0101 0100
經過觀察發現每一個字符的後三位都不相同,所以能夠用末尾的三位來區分這四個字符。
題目要求是查找 10 個字母長的序列,這裏咱們將每一個字符用三位來區分的話,10 個字符就須要 30 位 ,在32位機上也 OK 。
爲了提取出後 30 位,須要使用 mask ,取值爲 0x7ffffff(二進制表示含有 27 個 1) ,先用此 mask 可取出整個序列的後 27 位,而後再向左平移三位可取出 10 個字母長的序列 ( 30 位)。
爲了保存子串的頻率,這裏使用哈希表。
首先當取出第十個字符時,將其存在哈希表裏,和該字符串出現頻率映射,以後每向左移三位替換一個字符,查找新字符串在哈希表裏出現次數,若是以前恰好出現過一次,則將當前字符串存入返回值的數組並將其出現次數加一,若是從未出現過,則將其映射到 1。
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
vector<string> res;
if (s.size() <= 10) return res;
int mask = 0x7ffffff, cur = 0;
unordered_map<int, int> m;
for (int i = 0; i < 9; ++i) {
cur = (cur << 3) | (s[i] & 7);
}
for (int i = 9; i < s.size(); ++i) {
cur = ((cur & mask) << 3) | (s[i] & 7);
if (m.count(cur)) {
if (m[cur] == 1) res.push_back(s.substr(i - 9, 10));
++m[cur];
} else {
m[cur] = 1;
}
}
return res;
}
};
題目來源於 LeetCode 上第 349 號問題: Intersection of Two Arrays。
給定兩個數組,編寫一個函數來計算它們的交集。
容器類 set 的使用。
兩個數組的交集
// 時間複雜度: O(nlogn)
// 空間複雜度: O(n)
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
set<int> record;
for( int i = 0 ; i < nums1.size() ; i ++ ){
record.insert(nums1[i]);
}
set<int> resultSet;
for( int i = 0 ; i < nums2.size() ; i ++ ){
if(record.find(nums2[i]) != record.end()){
resultSet.insert(nums2[i]);
}
}
vector<int> resultVector;
for(set<int>::iterator iter = resultSet.begin(); iter != resultSet.end(); iter ++ ){
resultVector.push_back(*iter);
}
return resultVector;
}
};
題目來源於 LeetCode 上第 350 號問題: Intersection of Two Arrays II。
給定兩個數組,編寫一個函數來計算它們的交集。
示例 1:
輸入: nums1 = [1,2,2,1], nums2 = [2,2]
輸出: [2,2]
示例 2:
輸入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
輸出: [4,9]
與上題 兩個數組的交集 相似。只不過這裏使用的是 map 。
兩個數組的交集 II
// 時間複雜度: O(nlogn)
// 空間複雜度: O(n)
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
map<int, int> record;
for(int i = 0 ; i < nums1.size() ; i ++){
record[nums1[i]] += 1;
}
vector<int> resultVector;
for(int i = 0 ; i < nums2.size() ; i ++){
if(record[nums2[i]] > 0){
resultVector.push_back(nums2[i]);
record[nums2[i]] --;
}
}
return resultVector;
}
};
題目來源於 LeetCode 上第 447 號問題: Number of Boomerangs 。
給定平面上 n 對不一樣的點,「迴旋鏢」 是由點表示的元組 (i, j, k)
,其中 i
和 j
之間的距離和 i
和 k
之間的距離相等(須要考慮元組的順序)。
找到全部迴旋鏢的數量。你能夠假設 n 最大爲 500,全部點的座標在閉區間 [-10000, 10000] 中。
n 最大爲 500,可使用時間複雜度爲 O(n^2)的算法。
注意點:
若是有一個點a,還有兩個點 b 和 c ,若是 ab 和 ac 之間的距離相等,那麼就有兩種排列方法 abc 和 acb ;
若是有三個點b,c,d 都分別和 a 之間的距離相等,那麼有六種排列方法,abc, acb, acd, adc, abd, adb;
若是有 n 個點和點 a 距離相等,那麼排列方式爲 n(n-1);
計算距離時不進行開根運算, 以保證精度;
只有當 n 大於等於 2 時,res 值纔會真正增長,由於當n=1時,增長量爲1 * ( 1 - 1 ) = 0
。
迴旋鏢的數量
// 時間複雜度: O(n^2)
// 空間複雜度: O(n)
class Solution {
public:
int numberOfBoomerangs(vector<pair<int, int>>& points) {
int res = 0;
for( int i = 0 ; i < points.size() ; i ++ ){
// record中存儲 點i 到全部其餘點的距離出現的頻次
unordered_map<int, int> record;
for(int j = 0 ; j < points.size() ; j ++){
if(j != i){
// 計算距離時不進行開根運算, 以保證精度
record[dis(points[i], points[j])] += 1;
}
}
for(unordered_map<int, int>::iterator iter = record.begin() ; iter != record.end() ; iter ++){
res += (iter->second) * (iter->second - 1);
}
}
return res;
}
private:
int dis(const pair<int,int> &pa, const pair<int,int> &pb){
return (pa.first - pb.first) * (pa.first - pb.first) +
(pa.second - pb.second) * (pa.second - pb.second);
}
};
題目來源於 LeetCode 上第 454 號問題: 4Sum II 。
給定四個包含整數的數組列表 A , B , C , D ,計算有多少個元組 (i, j, k, l)
,使得 A[i] + B[j] + C[k] + D[l] = 0
。
爲了使問題簡單化,全部的 A, B, C, D 具備相同的長度 N,且 0 ≤ N ≤ 500 。全部整數的範圍在 -2^28 到 2^28- 1 之間,最終結果不會超過 2^31 - 1 。
與 Two Sum 極其相似,使用哈希表來解決問題。
四數相加 II
// 時間複雜度: O(n^2)
// 空間複雜度: O(n^2)
class Solution {
public:
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
unordered_map<int,int> hashtable;
for(int i = 0 ; i < A.size() ; i ++){
for(int j = 0 ; j < B.size() ; j ++){
hashtable[A[i]+B[j]] += 1;
}
}
int res = 0;
for(int i = 0 ; i < C.size() ; i ++){
for(int j = 0 ; j < D.size() ; j ++){
if(hashtable.find(-C[i]-D[j]) != hashtable.end()){
res += hashtable[-C[i]-D[j]];
}
}
}
return res;
}
};