题目描述:给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。
(题目及图片复制于Leetcode)
思考:这图一看脑子大概就有了个解决过程。从杨辉三角的生成规律可以知道,第n+1行的结果是从第n行产生的,所以很容易就能想到动态规划的解法。
动态方程:dp[i][j] = dp[i-1][j-1]+dp[i-1][j];
(但是具体的操作还有更加妙的处理方式,主要得益于发现者敏锐地观察力)
不过作为一名高中生,被组合数统治的恐惧依旧未散,一看到”1,3,3,1“”1,4,6,4,1“脑子里就蹦出了$C_{3}^{k}和C_{4}^{k}\dots$
下面开始实现:
方法零:打表(oier都懂得~)
方法一:动态规划
思路:一个是刚开始就构建一个包含所有前numRows行的数组数组,然后循环每个数组进行更新。
但是这样浪费空间,实际上每次操作只需要两个数组,一个是上一行,一个是本行。
然后再思考思考,是不是一行也可以呢?
可以!
这里就提到一个发现的规律(当然,是可以证明的):
比如上一行是 1 3 3 1
下一行就是 1 4 6 4 1
这两行有什么关系呢?
看: 1 3 3 1 0
+ 0 1 3 3 1
_________________________________
=> 1 4 6 4 1 (数组向右偏移一个,然后对应数字相加)
于是一个数组就可以完美解决。(具体细节自己阅读代码哦)
(注意: 由于数组大小变动,用vector数据结构比较方便)
<c++>
1 #include<iostream>
2 #include<vector>
3 using namespace std;
4 void showcase(vector <int> &outp);//显示一行
5 int main()
6 {
7 int numRows;
8 vector <int>outp(1, 1);//期初数组是{1}
9
10 cout << "请输入行数:";
11 cin >> numRows;
12
13 showcase(outp); // 先把第一行输出了
14 for (int i = 1; i < numRows; ++i)
15 {
16 outp.push_back(1); //最后一个一定是1了
17 for (auto elm = outp.end()-2; elm != outp.begin(); elm--)//大细节!若从前往后就会出问题,建议自己尝试
18 { //从第二项处理到倒数第二项 因为两边的都已经是1了
19 *elm = *(elm - 1) + *(elm);//此时前一项相当于上一行的对应数,
20 } //本项相当于上一行偏移后的对应数
21 showcase(outp);
22 }
23 system("pause");
24 return 0;
25 }
26 void showcase(vector<int> &outp)//显示本行
27 {
28 int siz = outp.size();
29 for (int i = 0; i < siz; ++i)
30 {
31 cout << outp[i] << " ";
32 }
33 cout << endl;
34 }
方法二:构造组合数函数
思路:构造组合数函数
杨辉三角第n行个数分别为$C_{n-1}^{0},C_{n-1}^{1},\cdots ,C_{n-1}^{n-1} n\geq 2$
所以我们只需要构造一个Combine组合数函数(组合数不懂得可以百度哦网上教程可比我讲得全面)
遇到的问题及解决办法:
1.如果单单构建阶乘函数,那么等numRows够大后 unsigned long long都不够
解决办法:于是就直接构建了组合数函数(比如$C_{7}^{2}$只是7*6/2,阶乘的话就得算出7!)
2.但是构造阶乘函数unsigned long long还是不够用
解决办法:函数内部计算时用double 最后再转换成int;
代码:
<c++>
1 #include<iostream>
2 #include<vector>
3 using namespace std;
4 int Combine(double a, double b);// 求组合数的函数
5 int main()
6 {
7 int numRows;
8 cout << "请输入行数:";
9 cin >> numRows;
10 // ans[0][0]=1;
11 if (numRows == 1){ cout << 1 << endl; system("pause"); return 0; }//特判
12 for (int a = 0; a<numRows; ++a) //numRows >=2
13 {
14 for (int b = 0; b <= a; ++b)
15 {
16 cout << Combine(a, b) << " ";
17 }
18 cout << endl;
19 }
20 system("pause");
21 return 0;
22 }
23 /*unsigned long long Factorial(int n) //阶乘 but it turned out to be a failure
24 {
25 if(n == 0)return 1;
26 n++;
27 unsigned long long ans=1;
28 for (int i = 1;i<n;++i)
29 {
30 ans *= i;
31 }
32 return ans;
33 }*/
34 int Combine(double a, double b)//组合数函数
35 {
36 double ans = 1;
37 if (b == 0 || a == b)return 1; //组合数定义
38 int terminal = b>(a - b) ? b : (a - b); //多算算组合数题目就懂了
39 for (double i = 1; a>terminal; ans *= a / i, --a, ++i);
40 return int(ans + 0.5); //小细节
41 }
总结:使用组合数的优势似乎并不是很明显(而且有点吃数学知识)。不过这个思想却是从这个例子中最大的收获。很多算法问题,顺水推舟去解决往往总会止步在效率的上限,但是用上数学的思想和工具就会有意想不到的效果(有时对效率的改进是颠覆性的)。