131. 分割回文串
思路:
- 回溯分割,利用回溯枚举对字符串的所有分割方案
- 双指针法判断回文串
class Solution {
private:vector> result;vector path;void backtracking(const string& s, int startIndex) {if (startIndex >= s.size()) {result.push_back(path);return ;}int size = s.size();for (int i = startIndex; i < size; i++) {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;}public:vector> partition(string s) {result.clear();path.clear();backtracking(s, 0);return result;}
};
优化:
每次划分出区间重新判断回文串,出现了大量重复判断,可以利用一个数组直接将所有回文串判断出来,下次直接 O(1)O(1)O(1) 访问某区间内的字符串是否是回文串即可。
class Solution {
private:vector> result;vector path;vector> isPalindrome;void backtracking(const string& s, int startIndex) {if (startIndex >= s.size()) {result.push_back(path);return ;}int size = s.size();for (int i = startIndex; i < size; i++) {if (isPalindrome[startIndex][i]) {string str = s.substr(startIndex, i - startIndex + 1);path.push_back(str);} else continue;backtracking(s, i + 1);path.pop_back();}}void computePalindrome(const string& s) {int size = s.size();isPalindrome.resize(size, vector(size, false));for (int i = size - 1; i >= 0; i--) {for (int j = i; j < size; j++) {if (i == j) isPalindrome[i][j] = true;else if (j - i == 1 && s[i] == s[j]) isPalindrome[i][j] = true;else if (s[i] == s[j] && isPalindrome[i + 1][j - 1]) isPalindrome[i][j] = true;}}}public:vector> partition(string s) {result.clear();path.clear();computePalindrome(s);backtracking(s, 0);return result;}
};
93. 复原 IP 地址
思路:
还是枚举分割的区间,但是回溯出口是划分区间数为 4,而不是最后一次划分。
class Solution {
private:vector result;void backtracking(string& s, int startIndex, int pointNum) {if (pointNum == 3) { // 当出现三个断点,即分为 4 段if (isVaild(s, startIndex, s.length() - 1)) {result.push_back(s);}return ;}for (int i = startIndex; i < s.length(); i++) {if (isVaild(s, startIndex, i)) {s.insert(s.begin() + i + 1, '.'); // 在i的后面插入一个逗点backtracking(s, i + 2, pointNum + 1); // 插入 '.' 后,起始位置为 i + 2s.erase(s.begin() + i + 1); // 回溯删掉逗点} else break;}}bool isVaild(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] < '0' || s[i] > '9') return false;num = num * 10 + (s[i] - '0');if (num > 255) return false;}return true;}public:vector restoreIpAddresses(string s) {result.clear();if (s.length() < 4 || s.length() > 12) return result;backtracking(s, 0, 0);return result;}
};
78. 子集
思路:
如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!
其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
直接利用回溯找组合的模板,区别就是
- 找子集的过程是组合问题,没有重复的
- 不需要结束条件,因为此过程需要枚举所有的可能,即递归所有情况后递归自动结束
class Solution {
private:vector> result;vector path;void backtracking(vector& nums, int startIndex) {result.push_back(path);for (int i = startIndex; i < nums.size(); i++) {path.push_back(nums[i]);backtracking(nums, i + 1);path.pop_back();}}public:vector> subsets(vector& nums) {result.clear();path.clear();backtracking(nums, 0);return result;}
};