k组合生成问题,是一种非常常见的问题。例如,我们在玩游戏当中,需要从 n 种宝石当中选择 k 种不同的宝石佩戴,这就是一个 k 组合的生成问题。
当 n = 6,k = 4时,按照字典序生成的 k 组合如下:
1 2 3 4
1 2 3 5
1 2 3 6
1 2 4 5
...
3 4 5 6
1.分析
看第一个 k 组合,是以 1 开始的一个序列,包含了 1 到 n 中最小的 k 个数;而最后一个 k 组合则以 3 开头,包含了 1 到 n 中最大的 k 个数。
每个位置上,都有允许的最大值。分别是最后一个 k 组合中对应位指定的值。
利用以上特征,每次求下一个 k 组合时,可以给“最低位” + 1,根据是否达到最大值决定是否进位。
以1 2 3 6 为例,给“最低位”6 加 1,但最低位已达到最大值,需要进位。于是给3加上1,变成 1 2 4 6。而以1 2 4开头的组合,第一个是 1 2 4 5.
以1 2 5 6为例,给“最低位” 6 加 1 ,最终进位到 2 ,变成 1 3 5 6。而以1 3 开头的组合,第一个是1 3 4 5。
2.算法
写成C++代码如下:
vector<vector<int>> combine(int n, int k)
{
vector<vector<int>> ret{};
if(n < 0 || k < 0 || k > n)
return ret;
//c[0],c[1],...,c[k-1]
//max of c[i] == n - k + 1 + i
//eg. max of c[k-1] == n
//初始化
vector<int> comb;
for(size_t i = 0;i < k;++i)
comb.push_back(i + 1);
ret.push_back(comb);
bool cf;
while( comb[0] != (n - k + 1) )
{
cf = false;
for(int j = k - 1;j >= 0;--j)
{
int max_val = n - k + 1 + j;
if(comb[j] == max_val)
cf = true;
else if(comb[j] < max_val)
{
++comb[j];
if(cf)
{
for(int fw = j + 1;fw < k;++fw)
comb[fw] = comb[fw - 1] + 1;
cf = false;
}
ret.push_back(comb);
break;
}
}
}
这也是leetcode上的题,目前运行时间虽然不是最快,但是排在93.68%。排名第一的依然是递归算法。下一篇决定研究全排列和k组合生成的递归解法。
3.总结
全排列和k组合问题,都可以利用相邻字典序之间的规律,根据当前序列求解下一个序列。把这些规律写成算法,也就解决了这些问题。