[算法数据结构] 回溯算法中的判断和子集问题

81 阅读2分钟

回溯算法中的判断是指如回文串、IP地址这类题目,需要对划分的子集(子字符串,子数字)进行一个是否有效的判断。当子符串都为回文串、满足IP规则才有效。这和之前组合总和有区别,组合总和的判断相对简单,只要判断总和是否为target值即可。

  • 回文串

      void backtracking(const string& s, int startIndex) {
          if (startIndex >= s.size()) {
              result.push_back(path);
              return ;
          }
    
      for (int i = startIndex; i < s.size(); i++) {
          if (isPalindrome(s, startIndex, i)) {
              string str = s.substr(startIndex, i - startIndex + 1);
              path.push_back(str);
              backtracking(s, i + 1);
              path.pop_back();
          } 
          else {
              continue;
          }
          }
      }
    
      vector<vector<string>> partition(string s) {
          backtracking(s, 0);
          return result;        
      } 
    

这里需要思考的一个点,就是程序如何保证每一个划分的子串都是符合要求的,因此,需要在将某个节点的值放进path之前做判断,如上述代码中,for 语句中添加了判断if (isPalindrome(s, startIndex, i)),只有当,当前子串为回文串时,才将其放入,否则,continue。注意,是continue,而不是break.因为有可能'ab',不是回文,但是下一个子串'aba'是回文串。

  • IP地址 IP地址难点在于如何设计逻辑,确保分割的数字都是符合要求的。这里思考是否可以结合回文串的逻辑框架。答案是可以的。代码如下

      void backtracking(string& s, int startIndex, int count) {
          if (count == 3) {
              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)) {
              s.insert(s.begin() + i + 1, '.');
              count++;
              backtracking(s, i + 2, count);
              count--;
              s.erase(s.begin() + i + 1);
          } else break;
      }}
    
    
      bool isValid(const string& s, int start, int end) {
          if (start > end) {
              return false;
          }
          if (s[start] == '0' && start != end) {
              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) {
                  return false;
              }
          }
          return true;
      }
    
      vector<string> restoreIpAddresses(string s) {
          backtracking(s, 0, 0);
          return result;
      }
    

大体的逻辑就是在for中加了一个if else 的判断。有些细节上的区别,IP地址是直接break.这个比较好理解,结合IP地址的规则,如果当前子串已经违规那么再往后多加一位也一定违规,因此,直接break。这里的子字符串是直接在S上进行操作,S.insert(),和s.erase()的操作,而不是新建一个子字符串。判断isValid()的函数比较复杂,规则比较多。

  • 子集问题 子集问题,搜集叶子节点,和路径有所不同,子集问题是将出现过的叶子节点搜集起来。当没有重复元素时比较简单,有重复节点该怎么操作?避免结果中出现重复的组合。