思路:这道题与前两个的不同点:组合没有数量要求;元素可无限重复选取。
但是有个点:最开始我看没有重复元素,所以我没有设置start那一项,发现就没法去重了。然后我发现2,2,3和3,2,2是一个,所以我发现得到的结果要单调递增,这样才能去重。
vector<vector<int>> res;
vector<int> path;
void backtrack(vector<int> &candidates, int cnt,int start)
{
if (cnt == 0)
{
res.push_back(path);
return;
}
if (cnt < 0)
return;
for (int i = start; i < candidates.size(); i++)
{
path.push_back(candidates[i]);
backtrack(candidates, cnt - candidates[i], i);
path.pop_back();
}
return;
}
vector<vector<int>> combinationSum(vector<int> &candidates, int target)
{
backtrack(candidates, target,0);
return res;
}
减枝后:
for (int i = start; i < candidates.size() && cnt - candidates[i] >= 0; i++)
⚠️:这个for循环后半部分的剪枝,要求初始数组是递增的。
vector<vector<int>> res;
vector<int> path;
void backtrack(vector<int> &candidates, int cnt, int start)
{
if (cnt == 0)
{
res.push_back(path);
return;
}
if (cnt < 0)
return;
for (int i = start; i < candidates.size() && cnt - candidates[i] >= 0; i++)
{
path.push_back(candidates[i]);
backtrack(candidates, cnt - candidates[i], i);
path.pop_back();
}
return;
}
vector<vector<int>> combinationSum(vector<int> &candidates, int target)
{
sort(candidates.begin(), candidates.end());
backtrack(candidates, target, 0);
return res;
}
思路:但是我去重打逻辑和代码随想录里的不太一样,我加了下面这行代码
while (i < candidates.size() - 1 && candidates[i + 1] == candidates[i])
{
i++;
}
我放在了没层for循环的的结尾,如果该层的candidates[i]与下层对应的candidates[i+1]相等,那就i++;因为如下图所示,排序后的数组连续的两个1连一起会造成数组的重复。
vector<vector<int>> res;
vector<int> path;
void dfs(vector<int> &candidates, int cnt, int start)
{
if (cnt == 0)
{
res.push_back(path);
return;
}
if (cnt < 0 || start > candidates.size() - 1)
return;
for (int i = start; i < candidates.size() && cnt - candidates[i] >= 0; i++)
{
path.push_back(candidates[i]);
dfs(candidates, cnt - candidates[i], i + 1); // 每个数字只能使用一次
path.pop_back();
while (i < candidates.size() - 1 && candidates[i + 1] == candidates[i])
{
i++;
}
}
}
vector<vector<int>> combinationSum2(vector<int> &candidates, int target)
{
sort(candidates.begin(), candidates.end()); // 先排序,很重要,否则会出现1,7和7,1的问题
dfs(candidates, target, 0);
return res;
}
思路:其实这道题就是遍历切割位置。如果startindex>=size()说明已经切割完毕了,即遍历完了。然后运用for循环去遍历从切割线到字符串末尾的位置,然后一直截取子串判断是不是回文数。如果是就存入path中,如果不是就continue(跳过)。
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
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;
}
void backtracking(const string &s, int startIndex)
{ // 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
// startIndex就是切割线
if (startIndex >= s.size())
{
result.push_back(path);
return;
}
for (int i = startIndex; i < s.size(); i++)
{ //[startIndex,i]是要截取的子串
if (isPalindrome(s, startIndex, i))
{ // 是回文子串
// 获取[startIndex,i]在s中的子串
string str = s.substr(startIndex, i - startIndex + 1);
path.push_back(str);
}
else
{ // 如果不是则直接跳过
continue;
}
backtracking(s, i + 1); // 寻找i+1为起始位置的子串
path.pop_back(); // 回溯过程,弹出本次已经添加的子串
}
}
vector<vector<string>> partition(string s)
{
if (s.size() == 0)
return {};
if (s.size() == 1)
return {{s}};
backtracking(s, 0);
return result;
}
但是上述代码是可以优化的,如下所示。上述代码isPalindrome
函数运用双指针的方法来判定对于一个字符串s
, 给定起始下标和终止下标, 截取出的子字符串是否是回文字串。但是其中有一定的重复计算存在:例如给定字符串"abcde"
, 在已知"bcd"
不是回文字串时, 不再需要去双指针操作"abcde"
而可以直接判定它一定不是回文字串。具体来说, 给定一个字符串s
, 长度为n
, 它成为回文字串的充分必要条件是s[0] == s[n-1]
且s[1:n-1]
是回文字串。
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
vector<vector<bool>> isPalindrome; // 放事先计算好的是否回文子串的结果
void backtracking(const string &s, int startIndex)
{ // 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
// startIndex就是切割线
if (startIndex >= s.size())
{
result.push_back(path);
return;
}
for (int i = startIndex; i < s.size(); i++)
{ //[startIndex,i]是要截取的子串
if (isPalindrome[startIndex][i])
{ // 是回文子串
// 获取[startIndex,i]在s中的子串
string str = s.substr(startIndex, i - startIndex + 1);
path.push_back(str);
}
else
{ // 如果不是则直接跳过
continue;
}
backtracking(s, i + 1); // 寻找i+1为起始位置的子串
path.pop_back(); // 回溯过程,弹出本次已经添加的子串
}
}
void computePalindrome(const string &s)
{
// isPalindrome[i][j] 代表 s[i:j](双边包括)是否是回文字串
isPalindrome.resize(s.size(), vector<bool>(s.size(), false)); // 根据字符串s, 刷新布尔矩阵的大小
for (int i = s.size() - 1; i >= 0; i--)
{
// 需要倒序计算, 保证在i行时, i+1行已经计算好了
for (int j = i; j < s.size(); j++)
{
if (j == i) // 对角线
{
isPalindrome[i][j] = true;
}
else if (j - i == 1) // 如果是相邻字符
{
isPalindrome[i][j] = (s[i] == s[j]);
}
else // 如果是不相邻字符,充要条件是s[0] == s[n-1]且s[1:n-1]是回文字串。
{
isPalindrome[i][j] = (s[i] == s[j] && isPalindrome[i + 1][j - 1]);
}
}
}
}
vector<vector<string>> partition(string s)
{
if (s.size() == 0)
return {};
if (s.size() == 1)
return {{s}};
computePalindrome(s);
backtracking(s, 0);
return result;
}