目录
讀題
454.四数相加II
自己看到题目的第一想法
看完代码随想录之后的想法
383. 赎金信
自己看到题目的第一想法
看完代码随想录之后的想法
15. 三数之和
自己看到题目的第一想法
看完代码随想录之后的想法
18. 四数之和
自己看到题目的第一想法
看完代码随想录之后的想法
454.四数相加II - 實作
思路
Code
383. 赎金信 - 實作
思路
Code
總結
15. 三数之和 - 實作
思路
Code
18. 四数之和 - 實作
思路
錯誤思路
看完代碼隨想錄後的思路
Code
錯誤代碼
正確代碼
第二章 雜湊表的學習總結
雜湊表的理論基礎
數組
Set
Map
雜湊表的應用 - 算法營推薦題目
數組
Set
Map
雙指針
總結
自己实现过程中遇到哪些困难
今日收获,记录一下自己的学习时长
相關資料
详细布置
454.四数相加II
383. 赎金信
15. 三数之和
18. 四数之和
四數相加,我第一次自己看的時候,原本想說將四個分拆成兩個map,分別進行加總,最後設定一個Map存放結果,但後來時在怎麼想也沒想出規律,也有想過把四個陣列放在四個map當中,但這樣要怎麼進行加總也沒想法,思考了二十幾分鐘沒有想法,決定直接看卡哥是怎麼去釐清這個題目的。
看完之後,發現跟我第一次想的想法很接近,但我沒想到map的key跟value的對應,以及對於時間複雜度一直想著要最佳,但是思路沒打開之前其實很難想到,自己應該要先去思考最簡單的解法可能長怎麼樣,那如果用hashtable可以怎麼去優化,意識到這一點蠻開新的,接下來解題先試著直覺去想怎麼解,在來看怎麼利用數據結構優化他。
看到贖金信第一想法就是跟字母異位詞一樣,只是贖金信的結構是只要magazine 裡面的字有包含ransomNote全部的字元即可,所以對於題意的理解沒有太大問題。
看完贖金信的代碼隨想錄解釋之後,有一個部份我當時沒有去思考到,的確也可以使用unordered_map來解這道題,使用字母當作key,出現次數存在value當中。遍例ransomNote存入當map,遍歷magazine去做映射,如果有則map[magazine]--
這樣也可以解題,這個想法沒有思考過,很有趣。
自己看到這個題目的時候,在思考如果以暴力解來說,單純枚舉的方式可以找到所有組合,但是考慮到還要有去重的操作,在思考上就有點陷入迷區,不知道該如何去解決。
提示有說到使用雙指針,但我思考定位了第一個之後,我要怎麼使用雙指針遍歷完全沒有想法
看完之後,豁然開朗,先是排序所有數,讓數是有序的,之後定位第一個數逐步往後,剩下的位置做為雙指針的範圍,進行雙指針的動作,最後進行去重,因為數組是有進行過排序的,用列表舉例
我在看到這個題目的當下,我想到的是三數之和,只是我們從第一個數固定,變成第一個數跟第二個數固定逐步往中間前進,中間就是雙指針的範圍,等於用到了兩個雙指針(?),並且target也進行了變換,整體思路目前是朝著三數之和的拓展去思考的
看完之後就發現自己思路錯誤的地方在哪裡了,思路錯誤我寫在題目的思路那裡了,但卡哥有提到的一點就是target有可能是複數這件事情,我的確完全沒有想到,我必須要去對這一點做判斷,只是我之前都沒有思考到這一點,這一次真的是學到了很多
假設在Map當中有找到 0 - (k + l) 則result+= count
設為 0 - (k + l) 是因為,這樣可以找到四數相加等於0的結果,以下是數學變化
i + j + k + l = 0
i + j = 0 - (k + l)
class Solution {
public:
int fourSumCount(vector& nums1, vector& nums2, vector& nums3, vector& nums4) {
unordered_map sum;
int result = 0;
for(int i = 0; i < nums1.size(); i++) {
for(int j = 0; j < nums2.size(); j++){
sum[nums1[i] + nums2[j]]++;
}
}
for(int k = 0; k < nums3.size(); k++) {
for(int l = 0; l < nums4.size(); l++){
if(sum.find(0 - (nums3[k] + nums4[l])) != sum.end()){
result += sum[0 - (nums3[k] + nums4[l])];
}
}
}
return result;
}
};
這道題,我將原本的字母異位詞的思路改一下,有兩個思路
一個是遍歷所有randomNote 將26個字母全部計數,之後再用一個迴圈遍歷magazine 全部減減最後確認所有這個count陣列裡面只能小於等於0,如果不是代表magazine 裡面有多餘其他的字無法滿足提議
另一個思路是建立兩個count 分別紀錄randomNote跟magazine 的各個字母數量
假設random Note 大於0的數值跟magazine 比較必須小於等於magazine 否則代表不符合條件
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int count[26] = {0};
for(int i = 0; i < ransomNote.size(); i++) {
count[ransomNote[i] - 'a']++;
}
for(int i = 0; i < magazine.size(); i++) {
count[magazine[i] - 'a']--;
}
for(int i = 0; i < 26; i++) {
if(count[i] > 0){
return false;
}
}
return true;
}
};
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int count1[26] = {0};
int count2[26] = {0};
for(int i = 0; i < ransomNote.size(); i++) {
count1[ransomNote[i] - 'a']++;
}
for(int i = 0; i < magazine.size(); i++) {
count2[magazine[i] - 'a']++;
}
for(int i = 0; i < 26; i++) {
if(count1[i] > count2[i]){
return false;
}
}
return true;
}
};
這兩個解法時間複雜度一樣,但空間複雜度第二種比較高,想到因為在字母異位詞中長度也會一致才會符合條件,但是在贖金信中,如果長度不一致,但只要magazine中有包含全部的ransomNote那就會符合條件,所以使用第二種做法沒有比較好。
--
→ 讓和變小,趨近於零class Solution {
public:
vector> threeSum(vector& nums) {
vector> result;
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size(); i++) {
if(nums[i] > 0) {
// continue; 寫成continue了
return result;
}
if(i > 0 && nums[i] == nums[i - 1]){
continue;
}
int left = i + 1;
int right = nums.size() - 1;
// 因為right == left 位置不合法
while(right > left) {
if(nums[i] + nums[left] + nums[right] < 0) {
left++;
}
else if (nums[i] + nums[left] + nums[right] > 0) {
right--;
} else {
result.push_back(vector{nums[i], nums[left], nums[right]});
//沒有思考到這裡也需要right > left ,這裡是去重的操作
while(right > left && nums[left] == nums[left + 1]) left++;
while(right > left && nums[right] == nums[right - 1]) right--;
// 找到答案,收縮範圍
right--;
left++;
}
}
}
return result;
}
};
class Solution {
public:
vector> fourSum(vector& nums, int target) {
int a = 0;
int d = nums.size() - 1;
vector> result;
sort(nums.begin(), nums.end());
while(d > a){
if(nums[a] > target) {
return result;
}
if(a > 0 && nums[a] == nums[a - 1]){
a++;
continue;
}
if(d < nums.size() - 1 && nums[d] == nums[d + 1]){
d--;
continue;
}
int b = a + 1;
int c = d - 1;
// 因為right == left 位置不合法
while(c > b) {
if(nums[a] + nums[b] + nums[c] + nums[d] < target) {
b++;
}
else if (nums[a] + nums[b] + nums[c] + nums[d] > target) {
c--;
} else {
result.push_back(vector{nums[a], nums[b], nums[c], nums[d]});
while(c > b && nums[b] == nums[b + 1]) b++;
while(c > b && nums[c] == nums[c - 1]) c--;
// 找到答案,收縮範圍
c--;
b++;
}
}
a++;
d--;
}
return result;
}
};
class Solution {
public:
vector> fourSum(vector& nums, int target) {
vector> result;
sort(nums.begin(), nums.end());
for(int k = 0; k < nums.size(); k++) {
if(nums[k] > target && nums[k] >= 0) {
break;
}
if(k > 0 && nums[k] == nums[k - 1]){
continue;
}
for(int i = k + 1; i < nums.size(); i++) {
if((nums[k]+nums[i]) > target && (nums[k]+nums[i]) > 0) {
break;
}
if(i > k + 1 && nums[i] == nums[i - 1]){
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while(right > left) {
// nums[k] + nums[i] + nums[left] + nums[right] > target 会溢出
if((long) nums[k] + nums[i] + nums[left] + nums[right] < target) {
left++;
}
else if ((long) nums[k] + nums[i] + nums[left] + nums[right] > target) {
right--;
} else {
result.push_back(vector{nums[k], nums[i], nums[left], nums[right]});
while(right > left && nums[left] == nums[left + 1]) left++;
while(right > left && nums[right] == nums[right - 1]) right--;
right--;
left++;
}
}
}
}
return result;
}
};
使用雜湊表的目的就是可以快速判斷一個元素是否存在在集合裡
至於雜湊表中的雜湊函數與碰撞的處理,我在之前的這篇文章中就有做詳細介紹了,就不多做贅述。
主要針對雜湊表三個常見的數據結構來做總結
在數據量比較小的狀態下,我可以使用數組來實現hashtable 而裡面的hashfunction我可以自己設計,就像是有效的字母异位词 可以用"-'a'" (類似hashfunction)來找到對應的hashvalue,並存入count值
在使用set的狀況下,跟數組狀況很像,但數據量太大,使用array會佔用太多內存空間 所以使用set,在unordered_set中,底層會對數值進行hash運算後,找出對應的下標存入我們的值(?這裡我搞不太 懂set的底層邏輯),因為數值不能重複,類似於數組中我們不存count,是固定存"1"
在使用map的狀況,主要是因為我們需要對兩個值進行索引 所以在使用在unordered_map中,使用狀況是,需要進行key-value的對應,可以設定key值是多少,map會對key值進行hash計算,得出hashvalue,並存入key的對應value到hashvalue的位置,
一開始先做242.有效的字母異位詞,在這個題目當中,要求我們去驗證一個詞與另一個詞是不是只有字母順序不一樣,在這裡我們利用一個大小為26的數組進行處理,存放a-z,其實這就是簡單的HashTable應用。
後來做了383. 贖金信,其實思路就跟有效的字母異位詞有些類似,只是題目改成只要一個詞組當中有包含另一個的全部詞組即可,雖然一些判斷條件不同,但整體思路還是不變的
我的總結是數組在hashtable當中,就是當條件很明顯,比如說只有26個字母,就可以用比較簡單的方式,去轉化資料到26個字母的計數,來求出題目想要的答案
在Set相關的題目就有點像是今天假設數值很大,那使用數組來存放就不太適合了,所以在這裡也有兩題,分別是349. 两个数组的交集以及202. 快乐数
在349. 两个数组的交集,就是利用set去除重複的數值,並將num1轉化為set,之後在遍歷num2元素時透過這個set來快速判斷交集結果
在 202. 快乐数,也是利用set存取sum各個個位數平方的數值,假設有重複則直接跳出,如果沒有重複並且沒有為1則繼續執行,值到為1
我的總結是,假設今天我沒辦法明確知道資料的大小,我只要知道這一個數有沒有重複出現過,那我就可以嘗試使用set來解決這個問題
在Map的題目當中就很明顯,當需要知道對應關係時,又需要快速查找就會很適合用Map,以這一次的題目來說就是1. 两数之和以及454.四数相加II
454.四数相加II,我一開始以為有沒有甚麼 $O(n)$ 的解法,但後來聽卡哥講解,則是可以將 $O(n^4)$ 降為 $O(n^2)$ 在這一次也讓我學到了先看看最簡單的解法是甚麼,再來思考後續的優化。
我的總結是,假設我今天需要key-value對應,那我就可以使用map,不過就是要定義好key是甚麼,value是要做甚麼
最後就是15.三數之和以及18.四數之和,這兩道題目我看著我當初的思路,真的沒有想到就是利用雙指標來解決這道題目,
就算卡哥有提示可以我也沒有想得很明白,直到看完卡哥兩題的解題思路,我突然豁然開朗
15.三數之和重點就是四個
18.四數之和重點多一個
在雙指針的寫法當中,我在這種左右邊界的雙指針,我學到了要記得排序,不然這個雙指針會沒辦法使用,而我一開始沒有想到的就是這點,想到這點之後就比較通透了。
這一次除了贖金信,其他三題都遇到困難
454.四数相加II,一開始就好高鶩遠,想一下子想出最佳解,但結果搬起石頭砸死自己
15. 三数之和,看的實在沒有想法,後來看卡哥才知道要怎麼去寫,知道之後,寫code就寫的比較順了
18. 四数之和,一開始想成兩個雙指針,結果在一開始的測資還真的對了,想了很久還是沒想出來,看了卡哥才知道是兩層for迴圈來解決。
今天大概學了五個小時左右,主要是要把自己的思路以及想法記錄下來花費比較多時間,但在這個過程中,也發現了寫博客的好處,我今天在做總結時,發現我有些東西突然想不起來,但是看著過去的自己寫的思路,其實就比較清晰了。
堅持下去!
题目链接/文章讲解/视频讲解:https://programmercarl.com/0454.四数相加II.html
题目链接/文章讲解:https://programmercarl.com/0383.赎金信.html
题目链接/文章讲解/视频讲解:https://programmercarl.com/0015.三数之和.html
题目链接/文章讲解/视频讲解:https://programmercarl.com/0018.四数之和.html