39. Combination Sum + 40. Combination Sum II + 216. Combination Sum III + 377. Combination Sum IV

时间:2022-03-18 08:15:17

▶ 给定一个数组 和一个目标值。从该数组中选出若干项(项数不定),使他们的和等于目标值。

▶ 36. 数组元素无重复

● 代码,初版,19 ms 。从底向上的动态规划,但是转移方程比较智障(将待求数分解为左右两个半段,分别找解,拼在一起,再在接缝上检查是否是重复解)。

 class Solution
{
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target)
{
sort(candidates.begin(), candidates.end());
int n, i, j, k;
vector<int> temp;
vector<int>& cand = candidates;
vector<vector<int>> left, right;
vector<vector<vector<int>>> table(target - cand[] + );
unordered_map<int, int> map; if (target < cand[])
return vector<vector<int>>{};
for (i = ; i < candidates.size(); i++)
map.emplace(candidates[i], i);
for (n = cand[]; n <= target; n++) // 每次循环计算一个数 n
{
if (map.find(n) != map.end()) // 原数组中有 n,本身就是一个解
table[n - cand[]].push_back(vector<int>{n});
if (!(n % ) && map.find(n / ) != map.end()) // 将偶数 n 分拆为两个 n/2 的和的解
table[n - cand[]].push_back(vector<int>{n/, n/});
for (i = cand[]; i <= (n - ) / ; i++) // 将 n 分解为左右两个半段,将各自的解进行笛卡尔积
{
left = table[i - cand[]];
right = table[n - i - cand[]];
if (left.size() == || right.size() == ) // 左右半段至少一个为空,不可照此分解
continue;
for (j = ; j < left.size(); j++)
{
for (k = ; k < right.size(); k++)
{
if (left[j][left[j].size() - ] > right[k][] ||
left[j].size() > && table[i - left[j][left[j].size() - ] - cand[]].size() > && table[n - i + left[j][left[j].size() - ] - cand[]].size() > )
continue;// 删除解的条件:左半段最后一个元素比右半段第一个元素大(不保持单调);将左半段最后一个元素转移给右半段后仍是一个解(该解一定在之前的搜索中出现过)
temp = left[j];
temp.insert(temp.end(), right[k].begin(), right[k].end());
table[n - cand[]].push_back(temp);
}
}
}
}
return table[target - cand[]];
}
};

● 代码,成套方法系列,13 ms 。从顶向下的递归,向解向量集中逐一添加元素。最快的解法算法与之相同。

 class Solution
{
public:
vector<vector<int> > combinationSum(vector<int> &candidates, int target)
{
sort(candidates.begin(), candidates.end());
vector<vector<int>> res;
vector<int> combination;
sum(candidates, target, res, combination, );
return res;
}
void sum(vector<int> &candidates, int target, vector<vector<int>> &res, vector<int> &combination, int begin)
{
if (!target)
{
res.push_back(combination);
return;
}
for (int i = begin; i < candidates.size() && target >= candidates[i]; i++)// 每次向现有组合中添加一项,使用剩下的项来求解一个更小的和
{
combination.push_back(candidates[i]);
sum(candidates, target - candidates[i], res, combination, i);
combination.pop_back();
}
}
};

● 代码,大佬的版本,9 ms 。与上面的成套方法基本相同。

 class Solution
{
public:
vector<vector<int>> sum(vector<int>& candidates, int m, int target)
{
vector<vector<int>> output, x;
int c, rep;
for (int i = m; i < candidates.size(); i++)
{
if (candidates[i] > target)
break;
for (c = ; target - c * candidates[i] >= ; c++)
{
if (target - c * candidates[i] == )
{
output.push_back(vector<int>(c, candidates[i]));
break;
}
x = sum(candidates, i + , target - c * candidates[i]);
for (auto & k : x)
{
for(rep = c;rep > ;rep--)
k.push_back(candidates[i]);
output.push_back(k);
}
}
}
return output;
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target)
{
sort(candidates.begin(), candidates.end());
return sum(candidates, , target);
}
};

▶ 39.数组元素有重复(给重复解检查造成困难)

● 代码,成套方法系列,12 ms 。添加一个跳过相同元素的判断。

 class Solution
{
public:
vector<vector<int> > combinationSum2(vector<int> &candidates, int target)
{
sort(candidates.begin(), candidates.end());
vector<vector<int>> res;
vector<int> combination;
sum(candidates, target, res, combination, );
return res;
}
void sum(vector<int> &candidates, int target, vector<vector<int>> &res, vector<int> &combination, int begin)
{
if (!target)
{
res.push_back(combination);
return;
}
for (int i = begin; i < candidates.size() && target >= candidates[i]; i++)// 每次向现有组合中添加一项,使用剩下的项来求解一个更小的和
{ if (i != begin && candidates[i] == candidates[i - ])// 跳过相同的元素,否则会出现重复解
continue;
combination.push_back(candidates[i]);
sum(candidates, target - candidates[i], res, combination, i + );
combination.pop_back();
}
}
};

● 大佬的代码,12 ms,算法与上面相同。

 class Solution
{
public:
vector<vector<int>> sum(vector<int>& candidates, int m, int target)
{
vector<vector<int>> output, x;
int i, j, k, c;
for (i = m; i < candidates.size();i++)
{
if (candidates[i] > target)// 最小元素过大
break;
for (j = i; i < candidates.size() - && candidates[i] == candidates[i + ]; i++);// 跳过重复元素
for (k = , c = i - j + ; k <= c; k++) // 尽量多的选取 candidates[j]
{
if (k * candidates[j] == target)
{
output.push_back(vector<int>(k, candidates[j]));
break;
}
else if (k * candidates[j] > target)
break;
vector<int> first(k, candidates[j]); // 选定 k 个 candidates[j]
x = sum(candidates, i + , target - k*candidates[j]); // 剩下的工作由递归完成
for (auto& k : x)
{
vector<int> second = first;
for (auto p : k) second.push_back(p);
output.push_back(second);
}
}
}
return output;
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target)
{
sort(candidates.begin(), candidates.end());
return sum(candidates, , target);
}
};

▶ 216. 没有给出确定的数组,而是指定了加数个数和目标值,且要求各加数不相等。

● 代码,成套方法系列,2 ms 。

 class Solution
{
public:
std::vector<std::vector<int> > combinationSum3(int k, int n)
{
std::vector<std::vector<int> > res;
std::vector<int> combination;
sum(n, res, combination, , k);
return res;
}
void sum(int target, std::vector<std::vector<int> > &res, std::vector<int> &combination, int begin, int need)
{
if (!target)
{
res.push_back(combination);
return;
}
else if (!need)
return;
for (int i = begin; i < && target >= i * need + need * (need - ) / ; i++)
// 要求 need 个数和为 target,最小的加数 i 满足 target >= i * need(不大于平均数),超过 target / need 的选择被剪枝
// 剩余 (need - 1) 个数的和最小为 1 + 2 + ... + (need-1) = need * (need - 1) / 2,阻止 i 选择过大
{
combination.push_back(i);
sum(target - i, res, combination, i + , need - );
combination.pop_back();
}
}
};

● 大佬的代码,4 ms,算法与上面相同。

 class Solution
{
public:
vector<vector<int>> combinationSum3(int k, int n)
{
vector<vector<int>> res;
vector<int> solu;
sum(res, solu, k, n);
return res;
}
void sum(vector<vector<int>> & res, vector<int> solu, int k, int n)
{
if (solu.size() == k && n == )
res.push_back(solu);
if (solu.size() < k)
{
for (int i = (solu.size() == ? : solu.back() + ); i <= ; i++)
{
if (i > n) break;
solu.push_back(i);
sum(res, solu, k, n - i);
solu.pop_back();
}
}
}
};

▶ 377. 数组元素可以重复使用,要求计算解的个数(包括交换两个不相等元素构成的新解)

● 代码,初版,476 ms 。智障的动态规划,使用两个向量组交替求解从数组最小元素到目标值之间各整数的拆分方法数。

 class Solution
{
public:
int combinationSum4(vector<int> &candidates, int target)
{
if (candidates.size() == )
return ;
sort(candidates.begin(), candidates.end());
if (target < candidates[])
return ; vector<vector<int>> table;
table.push_back(vector<int>(target + , ));
table.push_back(vector<int>(target + , ));
int i, j, k, count;
for (i = count = ; i < candidates.size() && candidates[i] <= target; i++)
table[][candidates[i]] = ;
count += table[][target]; // candidates 本身包含了 target
for (i = ; i <= (target - ) / candidates[] + ; i++) // 填表,循环次数控制
{
table[(i + ) % ].assign(target + , ); // 擦除目标行
for (j = candidates[] * i; j <= target; j++)
{
for (k = ; k < candidates.size() && candidates[k] <= target; k++)
{
if (j - candidates[k] >= && j - candidates[k] <= target)
table[(i + ) % ][j] += table[i % ][j - candidates[k]];
}
}
count += table[(i + ) % ][target];
}
return count;
}
};

● 大佬的代码,3 ms,正确的动态规划姿势,仅在一个向量中进行递推。

 class Solution
{
public:
int combinationSum4(vector<int>& nums, int target)
{
vector<int> t(target + , );
for (int i = ; i <= target; i++)
{
for (auto n : nums)
{
if (n == i)
t[i]++;
else if (n < i)
t[i] += t[i - n];
}
}
return t[target];
}
};