代碼隨想錄算法訓練營|第二十九天|39. 组合总和、40.组合总和II、131.分割回文串。刷题心得(c++)

文章讲述了作者在解决LeetCode题目中的三个问题——组合总和、组合总和II和分割回文串时,从初始想法、代码随想录的启发到最终正确解法的转变过程,涉及回溯法、去重策略和剪枝优化。
摘要由CSDN通过智能技术生成

目录

讀題

39. 组合总和

自己看到题目的第一想法

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

40.组合总和II

自己看到题目的第一想法

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

131.分割回文串

自己看到题目的第一想法

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

39. 组合总和 - 實作

思路

錯誤思路

正確思路

剪枝思路

Code

錯誤代碼

正確代碼

剪枝代碼

40.组合总和II - 實作

思路

錯誤思路

正確思路

使用used的思路

Code

錯誤思路

正確思路

使用used 的思路

131.分割回文串 - 實作

思路

Code

總結

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

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

相關資料

39. 组合总和

40.组合总和II

131.分割回文串


讀題

39. 组合总和

自己看到题目的第一想法

看到的想法其實跟昨天電話號碼的思路類似,可以想像每一層的集合都一樣,這個題目是由與數組長度n相等的n個集合,而集合內的元素都一致,終止這裡加入到result的結果要大於等於target並且如果是等於則將結果加入到result 當中, 這裏就可以藉由回溯法窮舉。

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

看完對於startIndex的想法要記住以下在代碼隨想錄有提到的:

如果是一个集合求组合的话,就需要startIndex,例如:77.组合 (opens new window),216.组合总和III (opens new window)。

如果是多个集合取组合,各个集合之間互不影響,就不用startIndex,例如:17.电话号码的字母组合

一開始想成多個集合取組合但是雖然是這樣,但是集合之間會互相影響,就需要使用index了

自己對於這道題目沒有到非常透徹,看完之後才發現原來要排序,因為照原本的解法也可以通過,但是假設先進行排序,就可以在後續的過程中進行剪枝的操作,讓整體的速度再次提升,這點是我沒想到的。

40.组合总和II

自己看到题目的第一想法

看到題目的第一想法是使用set, 但我對set的作法實在不熟悉,整體思路想的是先進行排序,排序完後將數組傳遞到回溯函數當中,終止條件大致跟組合總和一樣,但加上一個index == candidates.size一致的狀況,至於去重我在想會不會可以使用一個機制是當我回溯完之後,因為數組已經排序過,那我們要做的事情其實就是不再用跟第一個相同的值,因為實際上在第一次回溯時都遍歷過了,那就下來遍歷就直接忽略相同值,讓index直接指向下一個不同的數值

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

看完卡哥的想法之後,我發現我一開始就是直接用startIndex來進行去重,應該說我是使用i 來去重比較正確,在看卡哥的used去重時,需要花點時間來理解,而我的思考角度是從假設有很多個1 組成的集合 那我相同的元素在第一個一時都有遍歷過,就不需要再遍歷一次了,不過我還是需要再理解一下卡哥的思維。

反覆看了代碼隨想錄,大概能夠掌握used這個概念的思路,主要就是判斷目前這個重複是出現在樹層還是樹枝,如果是樹枝則繼續,如果重複是出現在樹層則跳過,理解完之後,在去寫code也很快能夠寫出來了。

131.分割回文串

自己看到题目的第一想法

分割我在思考的時候就有點像是我有很多不同的排列組合,整體感覺跟總合總合有點像,但是組合總合中間可以跳過某些節點,但是在分割時,不能跳過,以這個字串為例 aca 在組合裡可能會有三個回文組合 [“a” “c” “a” ] [“aca”] [”aa”] 但是在分割”aa”是不合法的。所以我要想的是,要怎麼去記錄分割,我在想會不會是迴圈的部分其實就是分割的長度,回溯就是進行分割,假設子字符串都符合條件,則將結果存入result,但還是想不通這段要怎麼操作

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

看完之後,並且一步一步造著寫,有粗略的了解了概念,但至於是否有懂,可能還需要更多的題目來考驗,但整體思路很清晰,裡面有兩個重點如下,與自己第一次的想法有點類似,但我沒有想到startIndex到i之間就是子串區間,並且對於切割的思路也不清晰,還需要再多複習

  • startIndex = 切割線
  • startIndex i 左閉右閉的區間

39. 组合总和 - 實作

思路

錯誤思路

  1. 建立一個result 函數以及path 函數
  2. 建立一個回溯函數
  3. 傳入值 candidates and target and pathSum
  4. 終止條件: if pathSum ≥ target and if pathSum == target result.push_back(path) return
  5. 單遞迴
    1. for I = 0 ; i < candidate.size(); i ++)
      1. Path.push_back(candidates[i])
      2. backtracking(candidates, target, pathSum + candidates[i])
      3. Path.pop_back()
  6. return

主函式

backtracking( candidates, target, 0)

return result

錯誤點

  • 沒有一個startIndex 控制下一個集合出現的位置,在題意中我們要找尋唯一的組合,但一個數值又可以重複使用,仍然把全部的集合都當作不同的集合處理,但是因為內部數值都一樣,透過原本i + 1 控制startIndex 找下一個,我們透過傳入i 讓下一個數值由i 開始,而不是i + 1 也不是 0 ,如果是0的話會導致集合可能會出現一樣的組合,因為從0 開始,每個數組又都一樣,以example1 來說可能就會出現 [223] [232] [322]這三種組合,但題意是要我們找出不同的組合,所以我們透過startIndex來控制下一個遞迴從哪開始。

正確思路

  1. 建立一個result 函數以及path 函數
  2. 建立一個回溯函數
  3. 傳入值 candidates and target and pathSum and startIndex
  4. 終止條件: if pathSum ≥ target and if pathSum == target result.push_back(path) return
  5. 單遞迴
    1. for I = startIndex; i < candidate.size(); i ++)
      1. Path.push_back(candidates[i])
      2. backtracking(candidates, target, pathSum + candidates[i], i)
      3. Path.pop_back()
  6. return

主函式

backtracking( candidates, target, 0, 0)

return result

剪枝思路

多加兩個動作

  1. 排序candidates
  2. 假設當前的sum + candidates[i] > target則不用進去遞迴

Code

錯誤代碼

class Solution {
public:
    vector<vector<int>> results;
    vector<int> path;
    void backtracking(vector<int> candidates, int target, int pathSum) {
        if(pathSum >= target) {
            if(pathSum == target) results.push_back(path);
            return;
        }

        for(int i = 0; i < candidates.size(); i++) {
            path.push_back(candidates[i]);
            backtracking(candidates, target, pathSum + candidates[i]);
            path.pop_back();
        }
        
        return;
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates, target, 0);
        return results;
    }
};

正確代碼

class Solution {
public:
    vector<vector<int>> results;
    vector<int> path;
    void backtracking(vector<int> candidates, int target, int pathSum, int startIndex) {
        if(pathSum >= target) {
            if(pathSum == target) results.push_back(path);
            return;
        }

        for(int i = startIndex; i < candidates.size(); i++) {
            path.push_back(candidates[i]);
            backtracking(candidates, target, pathSum + candidates[i], i);
            path.pop_back();
        }
        
        return;
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates, target, 0, 0);
        return results;
    }
};

剪枝代碼

class Solution {
public:
    vector<vector<int>> results;
    vector<int> path;
    void backtracking(vector<int> candidates, int target, int pathSum, int startIndex) {
        if(pathSum >= target) {
            if(pathSum == target) results.push_back(path);
            return;
        }

        for(int i = startIndex; i < candidates.size() && pathSum + candidates[i] <= target; i++) {
            path.push_back(candidates[i]);
            backtracking(candidates, target, pathSum + candidates[i], i);
            path.pop_back();
        }
        
        return;
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end()); // 需要排序
        backtracking(candidates, target, 0, 0);
        return results;
    }
};

 

40.组合总和II - 實作

思路

錯誤思路

  1. 建立一個result and path 儲存結果
  2. 建立一個回溯函數
    1. 傳入值 candidates target pathSum index
    2. 終止條件
      1. pathSum == target
      2. pathSum > target && index == candidates.size()
    3. 單遞迴
      1. i = index i < candidates.size I ++
      2. path.push_back(candidates[i])
      3. backtracking ( candidates, target, pathSum + candidates[i], i + 1)
      4. path.pop_back
      5. while ( candidates[i] == candidates[i+1] && i < candidates.size())
        1. i++

主函式

  1. qsort candidates
  2. backtracking (candidates, target, 0, 0)
  3. return result

錯誤點

  • pathSum > target && index == candidates.size() 應該為||
  • candidates[i] == candidates[i + 1] && i < candidates.size() 順序應相反,不然有可能出現操作空數組

正確思路

  1. 建立一個result and path 儲存結果
  2. 建立一個回溯函數
    1. 傳入值 candidates target pathSum index
    2. 終止條件
      1. pathSum == target
      2. pathSum > target || index == candidates.size()
    3. 單遞迴
      1. i = index i < candidates.size I ++
      2. path.push_back(candidates[i])
      3. backtracking ( candidates, target, pathSum + candidates[i], i + 1)
      4. path.pop_back
      5. while ( i < candidates.size() && candidates[i] == candidates[i+1] )
        1. i++

主函式

  1. sort candidates
  2. backtracking (candidates, target, 0, 0)
  3. return result

使用used的思路

藉由判斷used[i - 1]是 true 還是 false 判斷這個元素在同一層中是否重複

而要使用這個思路也必須要進行排序,才能夠避免這個元素在後面再次出現時,沒有被包含到前面的去重過程。

  1. 建立一個result and path 儲存結果
  2. 建立一個回溯函數
    1. 傳入值 candidates target pathSum index used
    2. 終止條件
      1. pathSum == target
      2. pathSum > target || index == candidates.size()
    3. 單遞迴
      1. i = index i < candidates.size I ++
      2. if( i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) conntinue;
      3. path.push_back(candidates[i])
      4. used[i] = true \\ 接下來在遞迴遇到相同的就是樹枝重複而不是樹層重複
      5. backtracking ( candidates, target, pathSum + candidates[i], i + 1)
      6. used[i] = false \\ 回溯操作,接下來的for loop就是樹層的操作,樹層不能重複,所以要返回false
      7. path.pop_back

主函式

  1. 建立一個bool 的used 數組
  2. sort candidates
  3. backtracking (candidates, target, 0, 0)
  4. return result

Code

錯誤思路

class Solution {
public:
    vector<vector<int>> results;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int pathSum, int index) {
        if(pathSum == target) {
            results.push_back(path);
            return;
        }
        if(pathSum > target && index == candidates.size()) return;

        for(int i = index; i < candidates.size(); i++) {
            path.push_back(candidates[i]);
            backtracking(candidates, target, pathSum + candidates[i], i + 1);
            path.pop_back();
            while(candidates[i] == candidates[i + 1] && i < candidates.size()) i++;
        }
        return;
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        if(candidates.size() == 0) return results;
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, 0);
        return results;
    }
};

正確思路

class Solution {
public:
    vector<vector<int>> results;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int pathSum, int index) {
        if(pathSum == target) {
            results.push_back(path);
            return;
        }
        if(pathSum > target || index == candidates.size()) return;

        for(int i = index; i < candidates.size(); i++) {
            path.push_back(candidates[i]);
            backtracking(candidates, target, pathSum + candidates[i], i + 1);
            path.pop_back();
            while( i + 1 < candidates.size() && candidates[i] == candidates[i + 1] ) i++;
        }
        return;
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        if(candidates.size() == 0) return results;
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, 0);
        return results;
    }
};

使用used 的思路

class Solution {
public:
    vector<vector<int>> results;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int pathSum, int index, vector<bool>& used) {
        if(pathSum == target) {
            results.push_back(path);
            return;
        }
        if(pathSum > target || index == candidates.size()) return;

        for(int i = index; i < candidates.size() && pathSum + candidates[i] <= target; i++) {
            if(i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) continue;
            path.push_back(candidates[i]);
            used[i] = true;
            backtracking(candidates, target, pathSum + candidates[i], i + 1, used);
            used[i] = false;
            path.pop_back();
        }
        return;
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        if(candidates.size() == 0) return results;
        vector<bool> used(candidates.size(), false);
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, 0, used);
        return results;
    }
};

 

131.分割回文串 - 實作

思路

  1. 建立一個results 以及 path 儲存結果
  2. 回溯函數
    1. 傳入值 s, startIndex
    2. 終止條件
      1. startIndex大於s.size
      2. 如果startIndex大於s.size 代表已經找到一組切割方案,因為startIndex可以想像成切割線,如果切割線大於等於s的大小,代表已經沒有要切割的部分了
      3. 並且因為在單遞迴都有判斷是否為回文,所以如果到達終止條件的path裡的子字串都是回文
    3. 單遞迴
      1. i = startIndex; i < s.size(); i++
      2. 獲取startIndex 到 i 之間的區間→ 代表著s中的子串
        1. 以aab來說 如果startIndex = 0 則是從a 開始切割,遞回到下一層startIndex = 1 切割第二個a 、第三層startIndex = 2 切割b ,將結果存入path
        2. 第三層回到第二層 i 則++為 2 startIndex = 1 代表的是[1, 2 ]也就是ab這個區間,而這個區間不是回文則直接跳過
      3. 判斷startIndex 到 i之間是否為回文
        1. 如果是,則將這個子串存入path
        2. 不是則跳過這次迴圈
  3. 判斷回文函數
    1. 使用雙指針,假設前後不一致,則return false 否則return true;
  4. 主函式
    1. 傳入s以及0
    2. return results.

Code

class Solution {
public:
    vector<vector<string>> results;
    vector<string> path;
    void backtracking(const string& s, int startIndex) {
        // 如果startIndex大於s.size 代表已經找到一組切割方案,因為startIndex可以想像成切割線,如果切割線大於等於s的大小,代表已經沒有要切割的部分了
        if(startIndex >= s.size()) {
            results.push_back(path);
            return;
        }

        for(int i = startIndex; i < s.size(); i++) {
                //獲取[startIndex,i] 在s中的子串
            if(isPalindrome(s, startIndex, i)) {
                string str = s.substr(startIndex, i - startIndex + 1);
                path.push_back(str);
            } else {
                continue; // 如果不是代表這個子串已經不是回文,等同這個切割方案不符合條件,則跳過這一次的處理
            }
            backtracking(s, i + 1);
            path.pop_back();
        }
    }
     bool isPalindrome(const string& s, int start, int end) {
     for (int i = start, j = end; i < j; i++, j--) {
         if (s[i] != s[j]) {
             return false;
         }
     }
     return true;
    }
    vector<vector<string>> partition(string s) {
        backtracking(s, 0);
        return results;
    }
};

 

總結

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

今天最大的困難點是在分割回文串的部分,自己花了蠻長時間去理解,但看完卡哥的講解後清晰很多,其次就是startIndex的使用,自己沒有到非常理解,不過看到代碼隨想錄當中組合總合裡面的總結,就清晰了很多,另外就是used數組的使用,這部分還需要一點時間來理解。

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

今天大概學了2.5hr,整體學起來,包含自己想辦法做出來的題目都能夠AC真的很爽,另外在學習的過程中可以了解到不同的操作,也讓我的思考更廣了些。

相關資料

● 今日学习的文章链接和视频链接

39. 组合总和

题目链接/文章讲解:https://programmercarl.com/0039.组合总和.html

视频讲解:带你学透回溯算法-组合总和(对应「leetcode」力扣题目:39.组合总和)| 回溯法精讲!_哔哩哔哩_bilibili

40.组合总和II

题目链接/文章讲解:https://programmercarl.com/0040.组合总和II.html

视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II_哔哩哔哩_bilibili

131.分割回文串

https://programmercarl.com/0131.分割回文串.html

视频讲解:带你学透回溯算法-分割回文串(对应力扣题目:131.分割回文串)| 回溯法精讲!_哔哩哔哩_bilibili

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值