数字总和排列二叉树模型求解
题目的原型来自于前段时间在论坛上看到的一道水友分享的一道卓越亚马逊的笔试算法题,题目如下:
每个大于1的数字都可以由其他数字相加组合而成,例如:
5=1+4, 5=2+3;
7=1+6, 7=2+5, 7=3+4;
现要求输入正整数n(n<50),输出所有除n本身外正数相加的不重复组合
【例如】
输入:
6
输出:
6=1+5
6=2+4
【分析:】
拿到这个题首先想到的方法通常是1~n数字的全排列组合,比如1+2+3...、1+2+4...,然后尝试每种排列结果是否等于n。这个思路没有错,但是代码写起来较麻烦,组合的过程也要考虑很多东西。刚开始我也是这么尝试,用循环加递归的方法,从1开始每次换一个数作为开始然后进行排列,递归代码比较繁琐而且总有遗漏。
后来想到其实这个遍历的过程就是一个二叉树。为什么这么说呢,比如对于1来说,最后的结果只有两种情况,有1或者没有1,2也是如此。那么由此我们可以假想一个头结点,左边第一个孩子为有1的情况,右边第一个孩子为不加1的情况(用0代替),以n=4为例图示如下(图片画的很烂,请多包涵^_^)
index为当前遍历的数字 同时也是树的层次,同中可以看到每一个结点就是一个遍历的过程,那么我们只用找到遍历的结果等于n的结点即可,遍历的过程很容易想到用数据结构中的递归遍历方法,这样一来代码思路就很清晰了。
1 vector<int> v; //保存符合条件的数字 2 int main() 3 { 4 void digitCombin (int len, int start, int sum); 5 int n; 6 cin >> n; 7 digitCombin(n,1,0); 8 9 printf("Time used:%.2fs\n",(double)clock() / CLOCKS_PER_SEC);//显示程序运行时间 10 return 0; 11 } 12 /* 13 len 为所求的数字 14 index 为当前索引值 也即树的层数 15 sum 为当前总和 16 */ 17 void digitCombin (int len, int index, int sum) 18 { 19 //如果当前总和满足要求则打印数组v中的数字 20 if (sum == len) { 21 cout << len << "="; 22 for (vector<int>::iterator i=v.begin(); i != v.end()-1; ++i) 23 cout << *i << "+"; 24 cout << *i << endl; 25 return; 26 } 27 //如果到达树的叶子结点 或者当前总和已经超出了len则返回 28 if (index==len || sum>len) 29 return; 30 //1.加入当前层号index 31 v.push_back(index); 32 digitCombin(len, index+1, sum+index); //递归求解下一层 33 //2.不加入当前层号 视index为0 34 v.pop_back(); 35 digitCombin(len, index+1, sum); //递归求解下一层 36 }
输入12 得到结果如下:
【总结:】
由此可以想到很多算法模型可以转化为树,对于每一个数字或者情形通常只有几种固定的情况,比如数字1是否在结果中、问题回答对或错。当转化为树模型后进行各种操作就较容易了,比如遍历每种情况或者统计叶子结点数量,可以直接使用数据结构的代码。
【附:奇怪的比赛】
某电视台举办了低碳生活大奖赛。题目的计分规则相当奇怪:
每位选手需要回答10个问题(其编号为1到10),越后面越有难度。答对的,当前分数翻倍;答错了则扣掉与题号相同的分数(选手必须回答问题,不回答按错误处理)。
每位选手都有一个起步的分数为10分。
某获胜选手最终得分刚好是100分,如果不让你看比赛过程,你能推断出他(她)哪个题目答对了,哪个题目答错了吗?
如果把答对的记为1,答错的记为0,则10个题目的回答情况可以用仅含有1和0的串来表示。例如:0010110011 就是可能的情况。
你的任务是算出所有可能情况。每个答案占一行。

浙公网安备 33010602011771号