代碼隨想錄算法訓練營|第三十天|93.复原IP地址、78.子集、90.子集II。刷题心得(c++)

目录

讀題

93.复原IP地址

自己看到题目的第一想法

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

78.子集

自己看到题目的第一想法

90.子集II

自己看到题目的第一想法

93.复原IP地址 - 實作

思路

錯誤思路

正確思路

不使用path的思路

Code

錯誤思路

正確思路

不使用path的思路(代碼來源 代碼隨想錄)

78.子集 - 實作

思路

Code

90.子集II - 實作

思路

Used 錯誤思路

Used 正確思路

使用i 跳過相同元素 思路

Code

Used 錯誤思路

Used 正確思路

使用i 跳過相同元素 思路

總結

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

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

相關資料

93.复原IP地址

78.子集

90.子集II


讀題

93.复原IP地址

自己看到题目的第一想法

  1. startIndex 是切割點
  2. startIndex, i 之間是切割點
  3. path大小為一 path[0] 可以是0,path大小大於1 path[0] ≠ 0
  4. ip 長度 需介於 0~255之間
  5. 建立函數判斷子字符串是否合法
  6. 最多切割四次

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

看完之後,腦袋對於這道題目的想法清晰很多,自己的做法比較偏向是加一個path,而卡哥則是在原字串上進行操作,但兩種方法都實現出來了,一開始主要錯誤點在終止條件的錯誤設置,判斷Valid IP 的函數處理錯誤,以及回溯操作的錯誤,這幾個錯誤點導致我的程式無法正常值行,但整體的想法是有,但實際執行上在思考上自己有很多部分需要去釐清。

78.子集

自己看到题目的第一想法

在組合以及分割的時候都是有符合某個條件才收集結果,但在這一題當中可以想像就是每個節點收集結果,並且這個是一個集合,彼此不能相互干擾,需要使用startIndex 來進行控制

90.子集II

自己看到题目的第一想法

這題的第一個想法是其實就是跟組合總和II 一樣,需要進行去重,如果用卡哥的做法used 或者是跳過重複元素都可以蠻清楚的去解決這個問題

93.复原IP地址 - 實作

思路

錯誤思路

  1. 建立一個results 以及 path 儲存結果
  2. 回溯函數
    1. 傳入值 s, startIndex
    2. 終止條件
      1. startIndex大於s.size
      2. 如果startIndex大於s.size 代表已經找到一組切割方案,因為startIndex可以想像成切割線,如果切割線大於等於s的大小,代表已經沒有要切割的部分了
      3. 並且因為在單遞迴都有判斷是否為有效IP,所以如果到達終止條件的path裡的子字串都是有效IP
    3. 單遞迴
      1. i = startIndex; i < s.size() + 3 ; i++ 每個子字符串最多三個數
      2. 獲取startIndex 到 i 之間的區間→ 代表著s中的子串
      3. 判斷startIndex 到 i之間是否為有效IP
        1. 如果是,則將這個子串存入path
        2. 不是則跳過這次迴圈
  3. 判斷有效IP函數
    1. path大小為一 path[0] 可以是0,path大小大於1 path[0] ≠ 0
    2. ip 長度 需介於 0~255之間
  4. 主函式
    1. 傳入s以及0
    2. return results.

錯誤點:

  1. 終止條件設置錯誤,導致遞迴並未在深度為四的時候停止,而是繼續執行,新增pointSum來記錄目前的深度(已分割幾次),並對最後的字串進行判斷與處理
  2. for迴圈的終止條件設錯,有可能會讓startIndex超出i的範圍
  3. 回溯操作錯誤,導致path字串沒有正常回溯,改為紀錄原本的size,對path字串進行回溯操作
  4. 判斷Valid IP 函數中
    1. 未Filter s.size > 3 && < 1 的狀況
    2. 計算數值的部分,邊界條件設錯,應該是i ≥ 0 而不是 i > 0 這樣會少計算一次,導致錯誤結果

正確思路

  1. 建立一個results 以及 path 儲存結果
  2. 回溯函數
    1. 傳入值 s, startIndex, pointSum
    2. 終止條件
      1. pointSum == 3
      2. 如果pointSum == 3代表已經切割到第三次,代表已經沒有要切割的部分了
      3. 判斷最後的子字串是否Valid,如果有效則將結果放入results當中
    3. 單遞迴
      1. i = startIndex; i < s.size() ; i++
      2. 獲取startIndex 到 i 之間的區間→ 代表著s中的子串
      3. 紀錄目前的path大小 → 供字串回溯操作使用
      4. 判斷startIndex 到 i之間是否為有效IP
        1. 如果是,則將這個子串存入path,並加入’.’
        2. 不是則跳過這次迴圈
      5. 遞迴操作 backtracking(s, i + 1, pointSum + 1)
      6. 回溯操作 path.resize(oldSize)
  3. 判斷有效IP函數
    1. path大小為一 path[0] 可以是0,path大小大於1 path[0] ≠ 0
    2. path長度需介於 1 ~ 3 之
    3. ip 數值 需介於 0~255之間
  4. 主函式
    1. 傳入s, 0, 0
    2. return results.

不使用path的思路

  1. 建立一個results 儲存結果
  2. 回溯函數
    1. 傳入值 s, startIndex, pointSum
    2. 終止條件
      1. pointSum == 3
      2. 如果pointSum == 3代表已經切割到第三次,代表已經沒有要切割的部分了
      3. 判斷最後的子字串是否Valid,如果有效則將結果放入results當中
    3. 單遞迴
      1. i = startIndex; i < s.size() ; i++
      2. 判斷startIndex 到 i之間是否為有效IP
        1. 如果是
          1. 在切割點加入一個逗號
          2. 遞迴操作
          3. 回溯刪除逗號
        2. 不是則結束本層循環,因為假設當前的切割是 01.0.0.01 就算第一個切割點在往後仍然都不是合法的IP 數值。 → 剪枝操作
      3. 遞迴操作 backtracking(s, i + 1, pointSum + 1)
      4. 回溯操作 path.resize(oldSize)
  3. 判斷有效IP函數
    1. path大小為一 path[0] 可以是0,path大小大於1 path[0] ≠ 0
    2. path長度需介於 1 ~ 3 之
    3. ip 數值 需介於 0~255之間
    4. 非數字字符不合法
  4. 主函式
    1. 傳入s, 0, 0
    2. return results.

Code

錯誤思路

class Solution {
public:
    vector results;
    string path;
    void backtracking(const string& s, int startIndex) {
        if(startIndex >= s.size()) {
            results.push_back(path);
            return;
        }

        for(int i = startIndex; i < startIndex + 3; i++) {
            string str = s.substr(startIndex, i - startIndex + 1);
            if(isValidIP(str)) {
                path += str;
                path += '.';
            } else {
                continue; 
            }
            backtracking(s, i + 1);
            path.pop_back();
        }
    }
     bool isValidIP(const string& s) {
        int count = 1;
        int sum = 0;
        if(s.size() != 1 && (s[0] - '0') == 0) return false;
        for (int i = s.size() - 1; i > 0; i--) {
            sum += (s[i] - '0') * count;
            count *= 10;
        }
        if(sum > 255 || sum < 0) return false;
        return true;
    }
    vector restoreIpAddresses(string s) {
        backtracking(s, 0);
        return results;
    }
};

正確思路

class Solution {
public:
    vector results;
    string path;
    void backtracking(const string& s, int startIndex, int pointSum) {
        if(pointSum == 3) {
            string str = s.substr(startIndex, s.size());
            if(isValidIP(str)) {
                results.push_back(path + str);
            }
            return;
        }

        for(int i = startIndex; i < s.size(); i++) {
            string str = s.substr(startIndex, i - startIndex + 1);
            int oldSize = path.size();
            if(isValidIP(str)) {
                path += str;
                path += '.';
            } else {
                continue; 
            }
            backtracking(s, i + 1, pointSum + 1);
            path.resize(oldSize);
        }
    }
     bool isValidIP(const string& s) {
			  int count = 1;
        int sum = 0;
        if(s.size() != 1 && (s[0] - '0') == 0 ) return false;
        if(s.size() > 3 || s.size() < 1) return false;
				for (int i = s.size() - 1; i >= 0; i--) {
            sum += (s[i] - '0') * count;
            count *= 10;
        }
        for (int i = 0; i < s.size(); i++) {
            sum = (s[i] - '0') + sum * 10;
        }
        if(sum > 255) return false;
        return true;
    }
    vector restoreIpAddresses(string s) {
        backtracking(s, 0, 0);
        return results;
    }
};

不使用path的思路(代碼來源 代碼隨想錄)

class Solution {
private:
    vector result;// 记录结果
    // startIndex: 搜索的起始位置,pointNum:添加逗点的数量
    void backtracking(string& s, int startIndex, int pointNum) {
        if (pointNum == 3) { // 逗点数量为3时,分隔结束
            // 判断第四段子字符串是否合法,如果合法就放进result中
            if (isValid(s, startIndex, s.size() - 1)) {
                result.push_back(s);
            }
            return;
        }
        for (int i = startIndex; i < s.size(); i++) {
            if (isValid(s, startIndex, i)) { // 判断 [startIndex,i] 这个区间的子串是否合法
                s.insert(s.begin() + i + 1 , '.');  // 在i的后面插入一个逗点
                backtracking(s, i + 2, pointNum + 1);   // 插入逗点之后下一个子串的起始位置为i+2
                s.erase(s.begin() + i + 1);         // 回溯删掉逗点
            } else break; // 不合法,直接结束本层循环
        }
    }
    // 判断字符串s在左闭又闭区间[start, end]所组成的数字是否合法
    bool isValid(const string& s, int start, int end) {
        if (start > end) {
            return false;
        }
        if (s[start] == '0' && start != end) { // 0开头的数字不合法
                return false;
        }
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
                return false;
            }
            num = num * 10 + (s[i] - '0');
            if (num > 255) { // 如果大于255了不合法
                return false;
            }
        }
        return true;
    }
public:
    vector restoreIpAddresses(string s) {
        result.clear();
        if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了
        backtracking(s, 0, 0);
        return result;
    }
};

78.子集 - 實作

思路

  1. 建立一個path 以及 results 收集結果
  2. 回溯函數 需傳入 nums 以及 startIndex
  3. 終止條件 if startIndex >= nums.size()
  4. 單遞迴
    1. I = startIndex I < nums.size() I ++
    2. path.push_back(nums[i])
    3. results.push_back(path)
    4. backtracking (nums, i + 1)
    5. path.pop_back()
  5. return

Code

class Solution {
public:
    vector> results;
    vector path;
    void backtracking(vector& nums, int startIndex) {
        if(startIndex >= nums.size()) return;

        for(int i = startIndex; i < nums.size(); i++) {
            path.push_back(nums[i]);
            results.push_back(path);
            backtracking(nums, i + 1);
            path.pop_back();
        }
        return;
    }
    vector> subsets(vector& nums) {
        results.push_back(path);
        backtracking(nums, 0);
        return results;
    }
};

90.子集II - 實作

思路

Used 錯誤思路

  1. 建立一個path, results 數組儲存結果
  2. 回溯函數
    1. 傳入值 nums, startIndex, used
    2. 終止條件
      1. if startIndex ≥ nums.size()
    3. 單遞迴
      1. I = startIndex i < nums.size I ++
      2. if( i > 0 && nums[i] == nums[i-1] && used[i - 1] == false)
        1. continue;
      3. else
        1. path.push_back(nums[i])
        2. results.push_back(path)
      4. used[i] == true
      5. backtracking(nums, i + 1, used)
      6. used[i] == false
      7. path.pop_back()
    4. return
  3. 主函式
    1. created used
    2. backtracking
    3. Return results

錯誤點: 忘記排序

Used 正確思路

  1. 建立一個path, results 數組儲存結果
  2. 回溯函數
    1. 傳入值 nums, startIndex, used
    2. 終止條件
      1. if startIndex ≥ nums.size()
    3. 單遞迴
      1. I = startIndex i < nums.size I ++
      2. if( i > 0 && nums[i] == nums[i-1] && used[i - 1] == false)
        1. continue;
      3. else
        1. path.push_back(nums[i])
        2. results.push_back(path)
      4. used[i] == true
      5. backtracking(nums, i + 1, used)
      6. used[i] == false
      7. path.pop_back()
    4. return
  3. 主函式
    1. created used
    2. sort nums;
    3. backtracking
    4. Return results

使用i 跳過相同元素 思路

  1. 建立一個path, results 數組儲存結果
  2. 回溯函數
    1. 傳入值 nums, startIndex, used
    2. 終止條件
      1. if startIndex ≥ nums.size()
    3. 單遞迴
      1. I = startIndex i < nums.size I ++
      2. path.push_back(nums[i])
      3. results.push_back(path)
      4. backtracking(nums, i + 1, used)
      5. path.pop_back()
      6. while ( i + 1 < nums.size() && nums[i] == nums[i+1] ) i ++
    4. return
  3. 主函式
    1. sort nums;
    2. backtracking
    3. Return results

Code

Used 錯誤思路

class Solution {
public:
    vector> results;
    vector path;
    
    void backtracking(vector& nums, int startIndex, vector& used) {
        if(startIndex >= nums.size()) return;

        for(int i = startIndex; i < nums.size(); i++) {
            if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue;
            else {
                path.push_back(nums[i]);
                results.push_back(path);
            }
            used[i] = true;
            backtracking(nums, i + 1, used);
            used[i] = false;
            path.pop_back();
        }
        return;
    }
    vector> subsetsWithDup(vector& nums) {
        vector used(nums.size(), false);
        results.push_back(path);
        backtracking(nums, 0, used);
        return results;      
    }
};

Used 正確思路

class Solution {
public:
    vector> results;
    vector path;
    
    void backtracking(vector& nums, int startIndex, vector& used) {
        if(startIndex >= nums.size()) return;

        for(int i = startIndex; i < nums.size(); i++) {
            if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue;
            else {
                path.push_back(nums[i]);
                results.push_back(path);
            }
            used[i] = true;
            backtracking(nums, i + 1, used);
            used[i] = false;
            path.pop_back();
        }
        return;
    }
    vector> subsetsWithDup(vector& nums) {
        vector used(nums.size(), false);
        sort(nums.begin(), nums.end());
        results.push_back(path);
        backtracking(nums, 0, used);
        return results;      
    }
};

使用i 跳過相同元素 思路

class Solution {
public:
    vector> results;
    vector path;
    
    void backtracking(vector& nums, int startIndex) {
        if(startIndex >= nums.size()) return;

        for(int i = startIndex; i < nums.size(); i++) {
            path.push_back(nums[i]);
            results.push_back(path);
            backtracking(nums, i + 1);
            path.pop_back();
            while(i + 1 < nums.size() && nums[i] == nums[i + 1]) i++;
        }
        return;
    }
    vector> subsetsWithDup(vector& nums) {
        sort(nums.begin(), nums.end());
        results.push_back(path);
        backtracking(nums, 0);
        return results;      
    }
};

總結

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

今天主要遇到的問題仍然是切割,在處理IP 的過程中,實作上沒有考慮清楚條件,導致各種問題產生。

但後續也通過代碼隨想錄的文字版以及卡哥的講解,慢慢釐清自己的錯誤點,並加已修正後就將問題解決了。

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

今天大概學了2.5 hr , IP 大概佔了2hr ,另外兩題很直覺地寫出來了,但這兩個小時也讓我對切割的問題有比較深的理解,也有將我的錯誤點給列點出來,將問題點加以修正後,程式就沒有問題了

相關資料

93.复原IP地址

题目链接/文章讲解:https://programmercarl.com/0093.复原IP地址.html

视频讲解:回溯算法如何分割字符串并判断是合法IP?| LeetCode:93.复原IP地址_哔哩哔哩_bilibili

78.子集

题目链接/文章讲解:https://programmercarl.com/0078.子集.html

视频讲解:回溯算法解决子集问题,树上节点都是目标集和! | LeetCode:78.子集_哔哩哔哩_bilibili

90.子集II

题目链接/文章讲解:https://programmercarl.com/0090.子集II.html

视频讲解:回溯算法解决子集问题,如何去重?| LeetCode:90.子集II_哔哩哔哩_bilibili

你可能感兴趣的:(算法,c++,开发语言,leetcode,数据结构)