代碼隨想錄算法訓練營|第七天|454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和、第三章 雜湊表 學習總結。刷题心得(c++)

目录

讀題

454.四数相加II

自己看到题目的第一想法

看完代码随想录之后的想法

383. 赎金信

自己看到题目的第一想法

看完代码随想录之后的想法

15. 三数之和

自己看到题目的第一想法

看完代码随想录之后的想法

18. 四数之和

自己看到题目的第一想法

看完代码随想录之后的想法

454.四数相加II - 實作

思路

Code

383. 赎金信 - 實作

思路

Code

總結

15. 三数之和 - 實作

思路

Code

18. 四数之和 - 實作

思路

錯誤思路

看完代碼隨想錄後的思路

Code

錯誤代碼

正確代碼

第二章 雜湊表的學習總結

雜湊表的理論基礎

數組

Set

Map

雜湊表的應用 - 算法營推薦題目

數組

Set

Map

雙指針

總結

自己实现过程中遇到哪些困难

今日收获,记录一下自己的学习时长

相關資料

详细布置

454.四数相加II

383. 赎金信

15. 三数之和

18. 四数之和


讀題

454.四数相加II

自己看到题目的第一想法

四數相加,我第一次自己看的時候,原本想說將四個分拆成兩個map,分別進行加總,最後設定一個Map存放結果,但後來時在怎麼想也沒想出規律,也有想過把四個陣列放在四個map當中,但這樣要怎麼進行加總也沒想法,思考了二十幾分鐘沒有想法,決定直接看卡哥是怎麼去釐清這個題目的。

看完代码随想录之后的想法

看完之後,發現跟我第一次想的想法很接近,但我沒想到map的key跟value的對應,以及對於時間複雜度一直想著要最佳,但是思路沒打開之前其實很難想到,自己應該要先去思考最簡單的解法可能長怎麼樣,那如果用hashtable可以怎麼去優化,意識到這一點蠻開新的,接下來解題先試著直覺去想怎麼解,在來看怎麼利用數據結構優化他。

383. 赎金信

自己看到题目的第一想法

看到贖金信第一想法就是跟字母異位詞一樣,只是贖金信的結構是只要magazine 裡面的字有包含ransomNote全部的字元即可,所以對於題意的理解沒有太大問題。

看完代码随想录之后的想法

看完贖金信的代碼隨想錄解釋之後,有一個部份我當時沒有去思考到,的確也可以使用unordered_map來解這道題,使用字母當作key,出現次數存在value當中。遍例ransomNote存入當map,遍歷magazine去做映射,如果有則map[magazine]-- 這樣也可以解題,這個想法沒有思考過,很有趣。

15. 三数之和

自己看到题目的第一想法

自己看到這個題目的時候,在思考如果以暴力解來說,單純枚舉的方式可以找到所有組合,但是考慮到還要有去重的操作,在思考上就有點陷入迷區,不知道該如何去解決。

提示有說到使用雙指針,但我思考定位了第一個之後,我要怎麼使用雙指針遍歷完全沒有想法

看完代码随想录之后的想法

看完之後,豁然開朗,先是排序所有數,讓數是有序的,之後定位第一個數逐步往後,剩下的位置做為雙指針的範圍,進行雙指針的動作,最後進行去重,因為數組是有進行過排序的,用列表舉例

  • 如果是雙指針外面的數值,那就是i - 1,因為如果i + 1 就干涉到雙指針的範圍了,但也是因為是在第一個數值,所以也要對i 進行判斷,當i > 0時就要判斷 i -1 是否等於 i,如果是則continue,跳過此次迴圈
  • 至於left 初始值則是 i + 1,假設 i + left + right < 0, 則left ++ 因為要將值變大,假設相加等於 0 則 將 i, left, right 存放到一個二維陣列當中, left 當然也要進行去重操作,如果left = left ++ 那則持續++到不同
  • 至於left 初始值則是 nums.size() - 1,假設 i + left + right > 0, 則right - - 因為要將值變小,假設相加等於 0 則 將 i, left, right 存放到一個二維陣列當中, right 當然也要進行去重操作,如果right = right - - 那則持續 - - 到不同

18. 四数之和

自己看到题目的第一想法

我在看到這個題目的當下,我想到的是三數之和,只是我們從第一個數固定,變成第一個數跟第二個數固定逐步往中間前進,中間就是雙指針的範圍,等於用到了兩個雙指針(?),並且target也進行了變換,整體思路目前是朝著三數之和的拓展去思考的

看完代码随想录之后的想法

看完之後就發現自己思路錯誤的地方在哪裡了,思路錯誤我寫在題目的思路那裡了,但卡哥有提到的一點就是target有可能是複數這件事情,我的確完全沒有想到,我必須要去對這一點做判斷,只是我之前都沒有思考到這一點,這一次真的是學到了很多

454.四数相加II - 實作

思路

  1. 建立一個map對應數組i, j 相加的數值,以及result計入結果
  2. 因為題目沒有要求我們去除重複的sum,所以裡面的數值存放count,如果有一樣,則count ++.
  3. 接下來遍歷所有k, l的數值
    1. 假設在Map當中有找到 0 - (k + l) 則result+= count

    2. 設為 0 - (k + l) 是因為,這樣可以找到四數相加等於0的結果,以下是數學變化

      i + j + k + l = 0
      i + j = 0 - (k + l)
      

Code

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;
    }
};

383. 赎金信 - 實作

思路

這道題,我將原本的字母異位詞的思路改一下,有兩個思路

一個是遍歷所有randomNote 將26個字母全部計數,之後再用一個迴圈遍歷magazine 全部減減最後確認所有這個count陣列裡面只能小於等於0,如果不是代表magazine 裡面有多餘其他的字無法滿足提議

另一個思路是建立兩個count 分別紀錄randomNote跟magazine 的各個字母數量

假設random Note 大於0的數值跟magazine 比較必須小於等於magazine 否則代表不符合條件

Code

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;
        
    }
};
  1. 第二種思路
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那就會符合條件,所以使用第二種做法沒有比較好。

15. 三数之和 - 實作

思路

  1. 先排序,將nums排序成有序的數組。
  2. 建立一個for迴圈,使第一個數固定往後
    1. 並在第一個數大於0之後,對其做去重的操作,避免第一個數重複
  3. 建立雙指針,left = i + 1 ( i 後面的第一個數,為左邊界),right = nums.size() - 1(數組最後一個數為右邊界)
  4. 建立迴圈(right > left的迴圈) → 為甚麼不是right ≥ left 是因為right 跟left根據題意不能為同一個位置
    1. 假設三數之和小於0,left++ → 讓和變大,趨近於零
    2. 假設三數之和大於0,right--→ 讓和變小,趨近於零
    3. 假設三數之和等於0,則將結果存入result
    4. 對left 以及 right進行去重的操作
      1. right > left → 如果沒有的話,假設數組是1111111,那就會有可能溢位,導致錯誤產生
      2. nums[left] == nums[left + 1] left++
      3. nums[right] == nums[right - 1] right--
    5. 去重結束,收縮範圍 → 要做這個操作主要是因為left 跟 right 在上一步只是做了去重可以想像成他們只是跳到重複數值的最後一個,如果沒有再次收縮範圍,那可能會造成無窮迴圈的產生,因為雙指針就停在了三數之和等於零的位置,但並沒有在收縮,讓程式去判斷下一輪的組合,導致無窮迴圈
      1. left++
      2. right--

Code

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;
    }
};

18. 四数之和 - 實作

思路

錯誤思路

  1. 使用兩個雙指針,分別是外圍跟內圍
  2. 假設第一個數大於target 則 直接return result
  3. 對第一個數以及第二個數進行去重操作
  4. 設定雙指針指向內圍
    1. 假設nums[a] + nums[b] + nums[c] + nums[d] < target, b++
    2. 假設nums[a] + nums[b] + nums[c] + nums[d] > target, c--
    3. 假設nums[a] + nums[b] + nums[c] + nums[d] == target
      1. 儲存值
      2. c、b進行去重操作
      3. 收縮範圍
  5. 收縮外圍
  6. 最後return result;

看完代碼隨想錄後的思路

  1. 在外圈再套一層for迴圈,設值為 k 逐步去檢查每個組合
  2. 假設k大於target 、k > 0 、target > 0則 直接return result → 要加k > 0 , target > 0 是因為 target有可能是負數,所以要加這一層去判斷
  3. 對k 進行去重操作
    1. k > 0
    2. if (nums[k] == nums[k - 1]) break
  4. 第二層for迴圈,設值為 i 逐步去檢查每個組合
    1. i = k + 1;
    2. 假設k+i大於target 、k+i > 0 、target > 0則 直接return result → 要加k+i > 0 , target > 0 是因為 target有可能是負數,所以要加這一層去判斷
    3. 對i 進行去重操作
      1. i > k + 1
      2. if (nums[i] == nums[i - 1]) break
  5. 設定雙指針指向內圍
    1. 假設nums[k] + nums[i] + nums[left] + nums[right] < target, b++
    2. 假設nums[k] + nums[i] + nums[left] + nums[right] > target, c--
    3. 假設nums[k] + nums[i] + nums[left] + nums[right] == target
      1. 儲存值
      2. left、right進行去重操作
      3. 收縮範圍
  6. 最後return result;

Code

錯誤代碼

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

在使用set的狀況下,跟數組狀況很像,但數據量太大,使用array會佔用太多內存空間 所以使用set,在unordered_set中,底層會對數值進行hash運算後,找出對應的下標存入我們的值(?這裡我搞不太 懂set的底層邏輯),因為數值不能重複,類似於數組中我們不存count,是固定存"1"

Map

在使用map的狀況,主要是因為我們需要對兩個值進行索引 所以在使用在unordered_map中,使用狀況是,需要進行key-value的對應,可以設定key值是多少,map會對key值進行hash計算,得出hashvalue,並存入key的對應value到hashvalue的位置,

雜湊表的應用 - 算法營推薦題目

數組

一開始先做242.有效的字母異位詞,在這個題目當中,要求我們去驗證一個詞與另一個詞是不是只有字母順序不一樣,在這裡我們利用一個大小為26的數組進行處理,存放a-z,其實這就是簡單的HashTable應用。

後來做了383. 贖金信,其實思路就跟有效的字母異位詞有些類似,只是題目改成只要一個詞組當中有包含另一個的全部詞組即可,雖然一些判斷條件不同,但整體思路還是不變的

我的總結是數組在hashtable當中,就是當條件很明顯,比如說只有26個字母,就可以用比較簡單的方式,去轉化資料到26個字母的計數,來求出題目想要的答案

Set

在Set相關的題目就有點像是今天假設數值很大,那使用數組來存放就不太適合了,所以在這裡也有兩題,分別是349. 两个数组的交集以及202. 快乐数

在349. 两个数组的交集,就是利用set去除重複的數值,並將num1轉化為set,之後在遍歷num2元素時透過這個set來快速判斷交集結果

在 202. 快乐数,也是利用set存取sum各個個位數平方的數值,假設有重複則直接跳出,如果沒有重複並且沒有為1則繼續執行,值到為1

我的總結是,假設今天我沒辦法明確知道資料的大小,我只要知道這一個數有沒有重複出現過,那我就可以嘗試使用set來解決這個問題

Map

在Map的題目當中就很明顯,當需要知道對應關係時,又需要快速查找就會很適合用Map,以這一次的題目來說就是1. 两数之和以及454.四数相加II

  1. 两数之和,夢開始的地方,這題當想通了就不會太難,就是將數值以及他所在的位置,放入map當中,在後續的循序中,每次都使用taget-curr_value來看map當中有沒有相對應的數值,假設有則可以直接回傳。

454.四数相加II,我一開始以為有沒有甚麼 $O(n)$ 的解法,但後來聽卡哥講解,則是可以將 $O(n^4)$ 降為 $O(n^2)$ 在這一次也讓我學到了先看看最簡單的解法是甚麼,再來思考後續的優化。

我的總結是,假設我今天需要key-value對應,那我就可以使用map,不過就是要定義好key是甚麼,value是要做甚麼

雙指針

最後就是15.三數之和以及18.四數之和,這兩道題目我看著我當初的思路,真的沒有想到就是利用雙指標來解決這道題目,

就算卡哥有提示可以我也沒有想得很明白,直到看完卡哥兩題的解題思路,我突然豁然開朗

15.三數之和重點就是四個

  • 排序 → 為了雙指針的變動以及去重的方便
  • 第一個數位置要放哪
  • 雙指針的範圍要怎麼設定
  • 以及如何去重

18.四數之和重點多一個

  • 排序 → 為了雙指針的變動以及去重的方便
  • target有可能是負數,要記得在剪枝時要進行判斷
  • 第一個數以及第二個數位置要放哪
  • 雙指針的範圍要怎麼設定
  • 以及如何去重

在雙指針的寫法當中,我在這種左右邊界的雙指針,我學到了要記得排序,不然這個雙指針會沒辦法使用,而我一開始沒有想到的就是這點,想到這點之後就比較通透了。

總結

自己实现过程中遇到哪些困难

這一次除了贖金信,其他三題都遇到困難

454.四数相加II,一開始就好高鶩遠,想一下子想出最佳解,但結果搬起石頭砸死自己

15. 三数之和,看的實在沒有想法,後來看卡哥才知道要怎麼去寫,知道之後,寫code就寫的比較順了

18. 四数之和,一開始想成兩個雙指針,結果在一開始的測資還真的對了,想了很久還是沒想出來,看了卡哥才知道是兩層for迴圈來解決。

今日收获,记录一下自己的学习时长

今天大概學了五個小時左右,主要是要把自己的思路以及想法記錄下來花費比較多時間,但在這個過程中,也發現了寫博客的好處,我今天在做總結時,發現我有些東西突然想不起來,但是看著過去的自己寫的思路,其實就比較清晰了。

堅持下去!

相關資料

454.四数相加II

题目链接/文章讲解/视频讲解:https://programmercarl.com/0454.四数相加II.html

383. 赎金信

题目链接/文章讲解:https://programmercarl.com/0383.赎金信.html

15. 三数之和

题目链接/文章讲解/视频讲解:https://programmercarl.com/0015.三数之和.html

18. 四数之和

题目链接/文章讲解/视频讲解:https://programmercarl.com/0018.四数之和.html

你可能感兴趣的:(c++,leetcode,算法,散列表,数据结构)