切割问题
一、问题特点
这类问题通常会给出一个数组(或字符串),要求列举出所有满足特定条件的划分 。其特点如下:
通常不允许改变原集合中元素的相对顺序
所求答案通常是若干组满足特定条件的划分
划分中的所有元素必须在原集合中连续取值
二、相关例题
(一)分割回文串
131.分割回文串(LeetCode)
题目要求:
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文串 。返回 s 所有可能的分割方案。
回文串是正着读和反着读都一样的字符串。
示例:
输入:s = “aab”
输出:[[“a”,“a”,“b”],[“aa”,“b”]]
提示:
1 <= s.length <= 16
s 仅由小写英文字母组成
解题思路:
切割问题同样是回溯算法的典型应用,其解题思路和组合问题大致相同,同样需要用for循环加递归的方法遍历搜索树查找满足条件的解,又因为切割问题中不允许重复取值,所以仍需要startIndex标明切割的起始位置。和组合问题不同的是,切割问题每次添加到路径中的往往不是单个元素s[i],而是从[startIndex, i]索引范围内的子串。
本题中需要找出所有元素均为回文子串的划分。所以在进行添加操作前,需要先判断该段子串是否为回文串,该步骤需要重复许多次,可以通过设置一个函数bool isPalindrome(string s){}利用双指针法实现判断,但由于问题中每一次取子串都需要检查,故调用次数过多,影响程序运行效率。为了进行优化,可以设一个二维数组vector<vector> isP先将字符串s中所有可能的子串是否为回文串的情况记录下来,当需要判断时查找二维数组即可。后者只需要遍历判断一次,效率更高。
代码示例:
// 优化后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 class Solution {public : void isPalindrome (string s) { isP.resize (s.size (), vector <bool >(s.size (), false )); for (int i = s.size () - 1 ; i >= 0 ; i--) { for (int j = i; j < s.size (); j++) { if (i == j) isP[i][j] = true ; else if (j - i == 1 ) isP[i][j] = (s[i] == s[j]); else { isP[i][j] = (isP[i+1 ][j-1 ] && s[i] == s[j]); } } } return ; } void backtracking (const string s, int startIndex) { if (startIndex >= s.size ()) { ans.push_back (path); return ; } for (int i = startIndex; i < s.size (); ++i) { if (!isP[startIndex][i]) continue ; string temp = s.substr (startIndex, i - startIndex + 1 ); path.push_back (temp); backtracking (s, i + 1 ); path.pop_back (); } return ; } vector<vector<string>> partition (string s) { isPalindrome (s); backtracking (s, 0 ); return ans; } private : vector<string> path; vector<vector<string>> ans; vector<vector<bool >> isP; }; class Solution {public : bool isPalindrome (string s, int m, int n) { if (m > n || n >= s.size ()) return false ; for (int i = m; i < n; ++i , --n) { if (s[i] != s[n]) return false ; } return true ; } void backtracking (const string s, int startIndex) { if (startIndex >= s.size ()) { ans.push_back (path); return ; } for (int i = startIndex; i < s.size (); ++i) { if (!isPalindrome (s, startIndex, i)) continue ; string temp = s.substr (startIndex, i - startIndex + 1 ); path.push_back (temp); backtracking (s, i + 1 ); path.pop_back (); } return ; } vector<vector<string>> partition (string s) { backtracking (s, 0 ); return ans; } private : vector<string> path; vector<vector<string>> ans; };
(二)复原IP地址
93.复原IP地址(LeetCode)
题目要求:
有效 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是无效 IP 地址。
给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 ‘.’ 来形成。你不能重新排序或删除 s 中的任何数字。你可以按任何顺序返回答案。
示例:
输入:s = “25525511135”
输出:[“255.255.11.135”,“255.255.111.35”]
解题思路:
因为不能重复取值,所以需要startIndex计数
因为一定是分成四份,所以需要一个int型参数count用于纪录当前深度,当深度不等于4时结果无效,据此可以直接剪去深度大于4的分支
因为每一次分割出来的子串长度为[1, 3]所以可以剪去长度大于3的分支
要求分割后的子串不能有前导0,范围在[0, 255],可以设置一个函数判断特定子串是否满足条件。
注意除了第一次向path中添加子串,每一次添加前都需要先向path中添加一个"."
注意回溯时需要删除添加的子串和"."
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Solution {public : bool isValid (string num) { if (num.size () > 1 && num[0 ] == '0' ) return false ; int n = stoi (num); if (n > 255 || n < 0 ) return false ; return true ; } void backtracking (string s, int startIndex, int count) { if (startIndex >= s.size ()) { if (count == 4 ) ans.push_back (path); return ; }else if (count == 4 ) return ; for (int i = startIndex; i < s.size (); ++i) { if (i - startIndex >= 3 ) break ; string temp = s.substr (startIndex, i - startIndex + 1 ); if (!isValid (temp)) break ; if (startIndex != 0 ) path.append ("." ); path.append (temp); backtracking (s, i + 1 , count + 1 ); if (startIndex == 0 ) path.erase (path.size () - temp.size (), temp.size ()); else path.erase (path.size () - temp.size () - 1 , temp.size () + 1 ); } return ; } vector<string> restoreIpAddresses (string s) { backtracking (s, 0 , 0 ); return ans; } private : vector<string> ans; string path = "" ; };