问题描述
2020年有12个月,我们想得到如下格式的所有数据组合集合的结果:
[['2020-01'], ..., ['2020-12'],
['2020-01','2020-02'],...,['2020-11','2020-12'],
...
['2020-01',...,'2020-12']]
仔细观察上述目标结果,其实最终结果是一个二维数组,外层数组的元素正好是组合和 C 12 1 C_{12}^1 C121+…+ C 12 12 C_{12}^{12} C1212的所有可能结果。
解决思路
既然是得到所有可能的组合总和,那么一个很直接的思路是使用回溯算法。
知道思路后,我们进一步将问题进行分解,每一个
C
12
n
C_{12}^n
C12n都是一种回溯,而最外层的求和只是一个for循环而已,所以最终的问题在于如何得到每一个
C
12
n
C_{12}^n
C12n。
求解组合
刷过力扣的朋友可能会遇到这种题,因为力扣上的第77题与此问题完全一致。有兴趣的朋友可以去力扣上查阅更多的解题方案:力扣原题。下面我以
C
12
2
C_{12}^2
C122为例子讲解我自己的解题思路。
首先,需要一个全局的数组来存储所有的满足条件组合情形,然后需要一个递归函数进行回溯。此递归函数的参数有组合下标(12),组合上标(2),动态数组以及遍历的起始坐标。此递归函数的终止条件为动态数组的长度等于组合上标。此递归函数的核心为,从起始坐标开始遍历,每次遍历都把新的元素添入动态数组中,然后进一步进行递归,当进一步递归结束时,删除掉动态数组中新增的元素,而这个过程就构成了回溯。最终当所有的回溯完成时,全局数组中就记录下的所有满足条件的组合情形。
Talk is cheap, show you my code:
const lists = [];
const year = '2020';
function main(n) {
backtrack(n, 2, [], 1);
console.log(lists);
console.log(lists.length);
}
function backtrack(n, k, list, start) {
if (list.length === k) {
lists.push(formatData(list));
return;
}
for (let i = start; i <= n; i++) {
list.push(i);
backtrack(n, k, list, i + 1);
list.pop();
}
}
function formatData(list) {
const res = [];
list.forEach(num => num < 10 ? res.push(`${year}-0${num}`) : res.push(`${year}-${num}`));
return res;
}
main(12)
这里注意一个细节:正是由于所有的递归都是使用同一个动态数组,所以全局数组里的每次添入元素都需要进行深拷贝!
原题解答
把最大的问题解决后,那么原问题也便迎刃而解。
直接上代码:
const lists = [];
const year = '2020';
function main(n) {
for (let i = 1; i <= n; i++) {
backtrack(n, i, [], 1);
}
console.log(lists);
console.log(lists.length);
}
function backtrack(n, k, list, start) {
if (list.length === k) {
lists.push(formatData(list));
return;
}
for (let i = start; i <= n; i++) {
list.push(i);
backtrack(n, k, list, i + 1);
list.pop();
}
}
function formatData(list) {
const res = [];
list.forEach(num => num < 10 ? res.push(`${year}-0${num}`) : res.push(`${year}-${num}`));
return res;
}
main(12)
结语
到此,又一道工作中的算法问题已被解决,虽然工作中很少会用到算法,但是保持持续熟练的算法功底依旧是很有必要,毕竟谁都会害怕书到用时方恨少的醒悟。