回溯.专题
一、入门回溯
二、子集型回溯
三、划分型回溯
核心思路:把分割线(逗号)看成是可以「选或不选」的东西,本质是子集型回溯。
Leetcode 131. 分割回文串
思路:枚举子串结束位置(同下一题93的回溯法二)
Code:
class Solution { public:bool jdg(string s) { // 判断是否是回文串int n = s.length();int i = 0, j = n - 1;while (i < j) {if (s[i] != s[j]) return false;i ++, j --;}return true;} vector<vector<string>> partition(string s) {vector<vector<string>> res;vector<string> tmp;int n = s.length();auto dfs = [&](this auto&& dfs, int i) -> void{if (i == n) {res.push_back(tmp);return;}for (int k = 1; k <= n; k ++) {if (i + k <= n && jdg(s.substr(i, k))){tmp.push_back(s.substr(i, k));dfs(i + k);tmp.pop_back();} }};dfs(0);return res;} };
Leetcode 93. 复原 IP 地址
思路一:三重循环枚举
注意判断的时候额外关注一下前导零的判断。
class Solution { public:vector<string> restoreIpAddresses(string s) {// 判断子串[i, j)是否合法auto jdg = [&](int i, int j) -> bool {// len <= 3 && 不能有前导零if (j - i > 3 || j - i > 1 && s[i] == '0')return false; // 没有前导零才能用stoi->否则会自动筛掉前导0return stoi(s.substr(i, j - i)) <= 255; // 数字范围};int n = s.size();vector<string> res;for (int i = 1; i < n && jdg(0, i); i ++)for (int j = i + 1; j < n && jdg(i, j); j ++) for (int k = j + 1; k < n && jdg(j, k); k ++)if (jdg(k, n))res.push_back(s.substr(0,i)+'.'+s.substr(i,j-i)+'.'+s.substr(j,k-j)+'.'+s.substr(k));return res;} };
思路二:回溯法 之 选或不选(是否分割)
class Solution { public:vector<string> restoreIpAddresses(string s) {int n = s.size();vector<string> res;int path[4]; // path[i]表示第i段结束位置 + 1(右开区间)auto dfs = [&](this auto&& dfs, int i, int j, int ip_val) -> void {if (i == n) { // s 分割完毕if (j == 4) // 必须有4段{auto [a, b, c, _] = path;res.emplace_back(s.substr(0,a)+"."+s.substr(a,b-a)+"."+s.substr(b,c-b)+"."+s.substr(c));}return;}if (j == 4) return;// j==4的时候必须分割完毕,不能有剩余的字符(j从0开始的)// 手动str->int, 严格o(1)ip_val = ip_val * 10 + (s[i] - '0');if (ip_val > 255) return; // 不合法// ①不分割,不以s[i]为这一段的结尾if (ip_val > 0) dfs(i + 1, j, ip_val);// ②分割 -> 以s[i]为这一段的结尾path[j] = i + 1; // 记录下一段的开始位置dfs(i + 1, j + 1, 0); // ipval清零};dfs(0, 0, 0); return res;} };
思路三:回溯法 之 (枚举子串右端点)
class Solution { public:vector<string> restoreIpAddresses(string s) {int n = s.size();vector<string> res;string path[4];// 分割s[i]到s[n-i],当前从第j段开始(j从0开始)auto dfs = [&](this auto&& dfs, int i, int j) -> void{// 剪枝// 还剩下n-i个字符,需要分成4-j 段,每段至少1个字符,至多3个字符,所以4-j<=n-i <=(4-j)*3if (n - i < 4 - j || n - i > (4 - j) * 3) return;if (i == n) {res.emplace_back(path[0]+'.'+path[1]+'.'+path[2]+'.'+path[3]);return;}// 子串左端点是i, 枚举子串的右端点rightint ip_val = 0;for (int right = i; right < n; right ++) {ip_val = ip_val * 10 + (s[right] - '0');if (ip_val > 255) break; // illegalpath[j] = s.substr(i, right - i + 1); // 直接覆盖,无需恢复现场dfs(right + 1, j + 1);if (ip_val == 0) break; // 如果当前数是0,对于后续for都不合法(排除前导零)}};dfs(0, 0);return res;} };