寫在前面:基本全部參考大神“Grandyang”的博客,附上網址:http://www.cnblogs.com/grandyang/p/4130379.html
寫在這里,是為了做筆記,同時加深理解,希望有識之士一起加油。
Easy!
題目描述:
給定一個整數數組和一個目標值,找出數組中和為目標值的兩個數。
你可以假設每個輸入只對應一種答案,且同樣的元素不能被重復利用。
示例:
給定 nums = [2, 7, 11, 15], target = 9 因為 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]
這道題給了我們一個數組,還有一個目標數target,讓我們找到兩個數字,使其和為target,乍一看就感覺可以用暴力搜索,但是猜到OJ肯定不會允許用暴力搜索這么簡單的方法,於是去試了一下,果然是Time Limit Exceeded,這個算法的時間復雜度是O(n^2)。那么只能想個O(n)的算法來實現,由於暴力搜索的方法是遍歷所有的兩個數字的組合,然后算其和,這樣雖然節省了空間,但是時間復雜度高。一般來說,我們為了提高時間的復雜度,需要用空間來換,這算是一個trade off吧,我們只想用線性的時間復雜度來解決問題,那么就是說只能遍歷一個數字,那么另一個數字呢,我們可以事先將其存儲起來,使用一個HashMap,來建立數字和其坐標位置之間的映射,我們都知道HashMap是常數級的查找效率,這樣,我們在遍歷數組的時候,用target減去遍歷到的數字,就是另一個需要的數字了,直接在HashMap中查找其是否存在即可,注意要判斷查找到的數字不是第一個數字,比如target是4,遍歷到了一個2,那么另外一個2不能是之前那個2,整個實現步驟為:先遍歷一遍數組,建立HashMap映射,然后再遍歷一遍,開始查找,找到則記錄index。
C++參考答案一:
1 class Solution { 2 public: 3 vector<int> twoSum(vector<int>& nums, int target) { 4 unordered_map<int, int> m; 5 vector<int> res; 6 for (int i = 0; i < nums.size(); ++i) { 7 m[nums[i]] = i; //先遍歷一遍數組,建立HashMap映射 8 }
//然后再遍歷一遍,開始查找,找到則記錄index 9 for (int i = 0; i < nums.size(); ++i) { 10 int t = target - nums[i]; 11 if (m.count(t) && m[t] != i) {//if里面的條件用於判斷查找到的數字不是第一個數字 12 res.push_back(i); 13 res.push_back(m[t]); 14 break; 15 } 16 } 17 return res; 18 } 19 };
或者可以寫得更簡潔一些,把兩個for循環合並成一個(注意到答案一中兩個for循環的循環條件是一模一樣的):
C++參考答案二:
1 class Solution { 2 public: 3 vector<int> twoSum(vector<int>& nums, int target) { 4 unordered_map<int, int> m; 5 for (int i = 0; i < nums.size(); ++i) { 6 if (m.count(target - nums[i])) { 7 return {i, m[target - nums[i]]}; 8 } 9 m[nums[i]] = i; 10 } 11 return {}; 12 } 13 };
解決思路:
第一個for循環把數組遍歷一遍,建立map數據,第二個for循環進行查找,找到符合條件的則記錄index。
知識點回顧:(注:下面知識點的頁碼標注,均依據《C++ primer plus》第六版)
容器(P695)
容器概念:容器是存儲在其他對象的對象。被存儲的對象必須是同一種類型的。不能將任何類型的對象存儲在容器中,具體的說,類型必須是可賦值構造的和可賦值的。
vector(P195)
vector是C++眾多容器類型中的一個,是一個十分有用的容器。是數組的一種類表示,它能夠像容器一樣存放各種類型的對象,簡單地說,vector是一個能夠存放多種類型的動態數組,能夠增加和壓縮數據。vector在C++標准模板庫中的部分內容,它是一個多功能的,能夠操作多種數據結構和算法的模板類和函數庫。
使用vector需要注意以下幾點:
1、如果你要表示的向量長度較長(需要為向量內部保存很多數),容易導致內存泄漏,而且效率會很低;
2、Vector作為函數的參數或者返回值時,需要注意它的寫法:
double Distance(vector<int>&a, vector<int>&b) 其中的“&”絕對不能少!!!
實例:vector<int>test;
//建立一個vector,int為數組元素的數據類型,test為動態數組名
簡單的使用方法如下:
vector<int>test;//建立一個vector
test.push_back(1);
test.push_back(2);//把1和2壓入vector,這樣test[0]就是1,test[1]就是2。
1 、基本操作
(1)頭文件#include<vector>.
(2)創建vector對象,vector<int> vec;
(3)尾部插入數字:vec.push_back(a);
(4)使用下標訪問元素,cout<<vec[0]<<endl;記住下標是從0開始的。
(5)使用迭代器訪問元素.
vector<int>::iterator it;
for(it=vec.begin();it!=vec.end();it++)
cout<<*it<<endl;
(6)插入元素: vec.insert(vec.begin()+i,a);在第i+1個元素前面插入a;
(7)刪除元素: vec.erase(vec.begin()+2);刪除第3個元素
vec.erase(vec.begin()+i,vec.end()+j);刪除區間[i,j-1];區間從0開始
(8)向量大小:vec.size();
(9)清空:vec.clear();
特別提示:這里有begin()與end()函數、front()與back()的差別
2、重要說明
vector的元素不僅僅可以是int,double,string,還可以是結構體,但是要注意:結構體要定義為全局的,否則會出錯。
1 #include<stdio.h> 2 #include<algorithm> 3 #include<vector> 4 #include<iostream> 5 using namespace std; 6 7 typedef struct rect 8 { 9 int id; 10 int length; 11 int width; 12 13 //對於向量元素是結構體的,可在結構體內部定義比較函數,下面按照id,length,width升序排序。 14 bool operator< (const rect &a) const 15 { 16 if (id != a.id) 17 return id<a.id; 18 else 19 { 20 if (length != a.length) 21 return length<a.length; 22 else 23 return width<a.width; 24 } 25 } 26 }Rect; 27 28 int main() 29 { 30 vector<Rect> vec; 31 Rect rect; 32 rect.id = 1; 33 rect.length = 2; 34 rect.width = 3; 35 vec.push_back(rect); 36 vector<Rect>::iterator it = vec.begin(); 37 cout << (*it).id << ' ' << (*it).length << ' ' << (*it).width << endl; 38 39 return 0; 40 cin.get(); 41 42 }
3、算法
(1) 使用reverse將元素翻轉:需要頭文件#include<algorithm>
reverse(vec.begin(),vec.end());將元素翻轉,即逆序排列!
(在vector中,如果一個函數中需要兩個迭代器,一般后一個都不包含)
(2)使用sort排序:需要頭文件#include<algorithm>,
sort(vec.begin(),vec.end());(默認是按升序排列,即從小到大).
可以通過重寫排序比較函數按照降序比較,如下:
定義排序比較函數:
bool Comp(const int &a,const int &b)
{
return a>b;
}
調用時:sort(vec.begin(),vec.end(),Comp),這樣就降序排序。
輸出Vector的中的元素
vector<float> vecClass;
int nSize = vecClass.size();
//打印vecClass,方法一:
1 for(int i=0;i<nSize;i++) 2 { 3 cout<<vecClass[i]<<" "; 4 } 5 cout<<endl;
需要注意的是:以方法一進行輸出時,數組的下標必須保證是整數。
//打印vecClass,方法二:
1 for(int i=0;i<nSize;i++) 2 { 3 cout<<vecClass.at(i)<<" "; 4 } 5 cout<<endl;
//打印vecClass,方法三:輸出某一指定的數值時不方便
1 for(vector<float>::iterator it = vecClass.begin();it!=vecClass.end();it++) 2 { 3 cout<<*it<<" "; 4 } 5 cout<<endl;
二維數組的使用:
1 #include "stdafx.h" 2 #include <cv.h> 3 #include <vector> 4 #include <iostream> 5 using namespace std; 6 int main() 7 { 8 using namespace std; 9 int out[3][2] = { 1, 2, 10 3, 4, 11 5, 6 }; 12 vector <int*> v1; 13 14 v1.push_back(out[0]); 15 v1.push_back(out[1]); 16 v1.push_back(out[2]); 17 18 cout << v1[0][0] << endl;//1 19 cout << v1[0][1] << endl;//2 20 cout << v1[1][0] << endl;//3 21 cout << v1[1][1] << endl;//4 22 cout << v1[2][0] << endl;//5 23 cout << v1[2][1] << endl;//6 24 25 return 0; 26 }
vector中insert()的用法詳解
insert() 函數有以下三種用法:
1、在指定位置loc前插入值為val的元素,返回指向這個元素的迭代器
2、在指定位置loc前插入num個值為val的元素
3、在指定位置loc前插入區間[start, end)的所有元素
1 //創建一個vector,置入字母表的前十個字符 2 vector <char> Avector; 3 for( int i=0; i < 10; i++ ) 4 Avector.push_back( i + 65 ); 5 6 7 //插入四個C到vector中 8 vector <char>::iterator theIterator = Avector.begin(); 9 Avector.insert( theIterator, 4, 'C' ); 10 11 12 //顯示vector的內容 13 for( theIterator = Avector.begin(); theIterator != Avector.end(); theIterator++ ) 14 cout < < *theIterator;
這段代碼需要再完善方可運行,運行結果將顯示:CCCCABCDEFGHIJ
unordered_map的定義
詳見原網頁:http://www.cplusplus.com/reference/unordered_map/unordered_map/
1 template < class Key, 2 class T, 3 class Hash = hash<Key>, 4 class Pred = equal_to<Key>, 5 class Alloc = allocator< pair<const Key,T> >> class unordered_map;
模版參數說明:
Key
主鍵的類型。
在類模板內部,使用其別名為 key_type 的成員類型。
T
被映射的值的類型。
在類模板內部,使用其別名為 mapped_type 的成員類型。
Hash
一元謂詞,以一個 Key 類型的對象為參數,返回一個基於該對象的 size_t 類型的唯一值。可以是函數指針(Function pointer)類型或函數對象(Function object)類型。在類模板內部,使用其別名為 hasher 的成員類型。
Pred
二元謂詞,以兩個 Key 類型的對象為參數,返回一個 bool 值,如果第一個參數等價於第二個參數,該 bool 值為 true,否則為 false。默認為 std::equal_to.可以是函數指針類型(Function pointer)類型或函數對象(Function object)類型.在類模板內部,使用其別名為 key_equal 的成員類型。
Alloc
容器內部用來管理內存分配及釋放的內存分配器的類型。這個參數是可選的,它的默認值是 std::allocator,這個是一個最簡單的非值依賴的(Value-independent)內存分配器。在類模板內部,使用其別名為 allocator_type 的成員類型。
unordered_map是一個關聯容器,存儲key,value.其中元素並沒有特別的次序關系 。
特點:
1. 關聯容器中的元素是通過主鍵(Key)而不是它們在容器中的絕對位置來引用的。
2. 無序(Unordered)無序容器通過 hash 表來組織它們的元素,允許通過主鍵快速地訪問元素。
3. 映射(Map)每個元素為一個值(Mapped value)綁定一個鍵(Key):以主鍵來標志主要內容等於被映射值的元素。
4. 鍵唯一(Unique keys)容器中不存在兩個元素有相同的主鍵。
5. 能夠感知內存分配器的(Allocator-aware)容器使用一個內存分配器對象來動態地處理它的存儲需求。
在 unordered_map 內部,元素不會按任何順序排序,而是通過主鍵的 hash 值將元素分組放置到
各個槽(Bucket,也可譯成“桶”)中,這樣就能通過主鍵快速地訪問各個對應的元素
(平均耗時為一個常量,即時間復雜度為 O(1))。
成員函數
=================迭代器=========================
begin 返回指向容器起始位置的迭代器(iterator)
end 返回指向容器末尾位置的迭代器
cbegin 返回指向容器起始位置的常迭代器(const_iterator)
cend 返回指向容器末尾位置的常迭代器
=================Capacity================
size 返回有效元素個數
max_size 返回 unordered_map 支持的最大元素個數
empty 判斷是否為空
=================元素訪問=================
operator[] 訪問元素
at 訪問元素
=================元素修改=================
insert 插入元素
erase 刪除元素
swap 交換內容
clear 清空內容
emplace 構造及插入一個元素
emplace_hint 按提示構造及插入一個元素
================操作=========================
find 通過給定主鍵查找元素
count 返回匹配給定主鍵的元素的個數
equal_range 返回值匹配給定搜索值的元素組成的范圍
================Buckets======================
bucket_count 返回槽(Bucket)數
max_bucket_count 返回最大槽數
bucket_size 返回槽大小
bucket 返回元素所在槽的序號
load_factor 返回載入因子,即一個元素槽(Bucket)的最大元素數
max_load_factor 返回或設置最大載入因子
rehash 設置槽數
reserve 請求改變容器容量
測試代碼:
1 #include <unordered_map> 2 #include <iostream> 3 #include <string> 4 5 using namespace std; 6 7 void PrintIntDoubleUnOrderedMap(unordered_map<int, double>& m, char* pre) 8 { 9 unordered_map<int, double>::iterator iter; 10 cout << pre; 11 for (iter = m.begin(); iter != m.end(); ++iter) 12 cout << "(" << iter->first << ", " << iter->second << ")" << " "; 13 cout << endl; 14 } 15 16 void UnOrderedMapExp1() 17 { 18 unordered_map<int, double> m; 19 //沒有key,就自動創建 20 m[0] = 1.11; 21 //普通插入,使用類型轉換 22 m.insert(unordered_map<int, double>::value_type(1, 2.22)); 23 //帶暗示的插入,pair<int, double>就相當於上面的unordered_map<int ,double> 24 m.insert(m.end(), pair<int, double>(2, 3.33)); 25 PrintIntDoubleUnOrderedMap(m, "插入元素之前的m:"); 26 27 //插入一個范圍 28 unordered_map<int, double> m2; 29 m2.insert(unordered_map<int, double>::value_type(3, 4.44)); 30 m2.insert(unordered_map<int, double>::value_type(4, 5.44)); 31 m2.insert(unordered_map<int, double>::value_type(5, 6.44)); 32 m.insert(m2.begin(), m2.end()); 33 34 m.emplace(6, 5.55); 35 m.emplace_hint(m.end(), 7, 3.09); 36 m.at(5) = 3.333333; 37 38 PrintIntDoubleUnOrderedMap(m, "插入元素之后m:"); 39 40 unordered_map<int, double>::iterator iter; 41 iter = m.find(4); 42 if (iter != m.end()) 43 { 44 cout << "m.find(4): "; 45 cout << "(" << iter->first << ", " << iter->second << ")" << endl; 46 } 47 48 if (iter != m.end()) 49 { 50 m.erase(iter); 51 } 52 PrintIntDoubleUnOrderedMap(m, "刪除主鍵為4的元素之后m:"); 53 54 //遍歷刪除 55 for (iter = m.begin(); iter != m.end(); ++iter) 56 { 57 if (iter->first == 2) 58 { 59 m.erase(iter); 60 break; 61 } 62 } 63 64 //內部數據 65 cout << "bucket_count:" << m.bucket_count() << endl; 66 cout << "max_bucket_count:" << m.max_bucket_count() << endl; 67 cout << "bucket_size:" << m.bucket_size(0) << endl; 68 std::cout << "load_factor:" << m.load_factor() << std::endl; 69 std::cout << "max_load_factor:" << m.max_load_factor() << std::endl; 70 PrintIntDoubleUnOrderedMap(m, "刪除主鍵為2的元素后的foo1:"); 71 m.clear(); 72 PrintIntDoubleUnOrderedMap(m, "清空后的foo1:"); 73 } 74 75 int main() 76 { 77 UnOrderedMapExp1(); 78 79 return 0; 80 }
以類作為key,value
只是用STL提供的基本類型int, char, long等和stringz作為key,value,STL提供了哈希函數和比較函數。但是用自己定義的類時,需要自己定義哈希函數和比較函數
1 //hash函數 2 template <typename T> 3 class hash 4 { 5 public: 6 size_t operator()(const T& o) const { return 0; } 7 }; 8 9 //compare函數 10 template <typename T> 11 class equal_to 12 { 13 public: 14 bool operator()(const T& a, const T& b) const { return a == b; } 15 };
下面以一個學號姓名為例子來實現
1 #include <unordered_map> 2 #include <iostream> 3 #include <string> 4 5 using namespace std; 6 7 //自己設計類,作為key和value 8 9 10 //學號 11 class Number 12 { 13 string str; 14 public: 15 Number() { } 16 Number(string s) { str = s; } 17 18 const string& get() const 19 { 20 return str; 21 } 22 }; 23 24 //姓名 25 class Name 26 { 27 string str; 28 public: 29 Name() {} 30 Name(string s) { str = s; } 31 32 const string& get() const 33 { 34 return str; 35 } 36 }; 37 38 39 //哈希函數對象實現 40 // 必須為const 41 class MyHash 42 { 43 public: 44 45 size_t operator()(const Number& num) const 46 { 47 hash<string> sh; //使用STL中hash<string> 48 return sh(num.get()); 49 } 50 }; 51 52 //實現equal_to對象 53 //必須為const 54 class MyEqualTo { 55 public: 56 57 bool operator()(const Number& n1, const Number& n2) const 58 { 59 return n1.get() == n2.get(); 60 } 61 }; 62 63 int main() 64 { 65 unordered_map<Number, Name, MyHash, MyEqualTo> map; 66 map.emplace(Number("1000"), Name("A")); 67 map.emplace(Number("1001"), Name("G")); 68 map.emplace(Number("1002"), Name("E")); 69 map.emplace(Number("1003"), Name("D")); 70 71 72 unordered_map<Number, Name, MyHash, MyEqualTo>::iterator iter; 73 Number num("1001"); 74 iter = map.find(num); 75 76 if (iter != map.end()) 77 cout << "Number: " << iter->first.get() << "," << "Name: " << iter->second.get() << endl; 78 79 else 80 cout << "Not found!" << endl; 81 82 return 0; 83 }
官方解答:
方法一:暴力法
暴力法很簡單。遍歷每個元素x,並查找是否存在一個值與target-x相等的目標元素。Java代碼:
1 public int[] twoSum(int[] nums, int target) { 2 for (int i = 0; i < nums.length; i++) { 3 for (int j = i + 1; j < nums.length; j++) { 4 if (nums[j] == target - nums[i]) { 5 return new int[] { i, j }; 6 } 7 } 8 } 9 throw new IllegalArgumentException("No two sum solution"); 10 }
復雜度分析:
-
時間復雜度:O(n2), 對於每個元素,我們試圖通過遍歷數組的其余部分來尋找它所對應的目標元素,這將耗費O(n) 的時間。因此時間復雜度為 O(n2)。
-
空間復雜度:O(1)。
方法二:兩遍哈希表
為了對運行時間復雜度進行優化,我們需要一種更有效的方法來檢查數組中是否存在目標元素。如果存在,我們需要找出它的索引。保持數組中的每個元素與其索引相互對應的最好方法是什么?哈希表。
通過以空間換取速度的方式,我們可以將查找時間從 O(n)O(n) 降低到 O(1)O(1)。哈希表正是為此目的而構建的,它支持以 近似 恆定的時間進行快速查找。我用“近似”來描述,是因為一旦出現沖突,查找用時可能會退化到 O(n)O(n)。但只要你仔細地挑選哈希函數,在哈希表中進行查找的用時應當被攤銷為 O(1)O(1)。
一個簡單的實現使用了兩次迭代。在第一次迭代中,我們將每個元素的值和它的索引添加到表中。然后,在第二次迭代中,我們將檢查每個元素所對應的目標元素(target - nums[i]target−nums[i])是否存在於表中。注意,該目標元素不能是 nums[i]nums[i] 本身!
1 public int[] twoSum(int[] nums, int target) { 2 Map<Integer, Integer> map = new HashMap<>(); 3 for (int i = 0; i < nums.length; i++) { 4 map.put(nums[i], i); 5 } 6 for (int i = 0; i < nums.length; i++) { 7 int complement = target - nums[i]; 8 if (map.containsKey(complement) && map.get(complement) != i) { 9 return new int[] { i, map.get(complement) }; 10 } 11 } 12 throw new IllegalArgumentException("No two sum solution"); 13 }
復雜度分析:
-
時間復雜度:O(n), 我們把包含有 n 個元素的列表遍歷兩次。由於哈希表將查找時間縮短到 O(1) ,所以時間復雜度為 O(n)。
-
空間復雜度:O(n), 所需的額外空間取決於哈希表中存儲的元素數量,該表中存儲了 n 個元素。
方法三:一遍哈希表
事實證明,我們可以一次完成。在進行迭代並將元素插入到表中的同時,我們還會回過頭來檢查表中是否已經存在當前元素所對應的目標元素。如果它存在,那我們已經找到了對應解,並立即將其返回。
1 public int[] twoSum(int[] nums, int target) { 2 Map<Integer, Integer> map = new HashMap<>(); 3 for (int i = 0; i < nums.length; i++) { 4 int complement = target - nums[i]; 5 if (map.containsKey(complement)) { 6 return new int[] { map.get(complement), i }; 7 } 8 map.put(nums[i], i); 9 } 10 throw new IllegalArgumentException("No two sum solution"); 11 }
復雜度分析:
-
時間復雜度:O(n), 我們只遍歷了包含有 n個元素的列表一次。在表中進行的每次查找只花費 O(1)的時間。
-
空間復雜度:O(n), 所需的額外空間取決於哈希表中存儲的元素數量,該表最多需要存儲 n 個元素。