关于leetcode 39题的思考:
超链接在此:https://leetcode-cn.com/problems/combination-sum/
First: 首先我们按解题的思路来:
我们需要把给定的数据排序一下,C++中自带algorithm函数中sort一下即可。
然后:
我们需要建立一个搜寻函数,我们采用指针进行搜索,但是按照题意成立的答案多的数不过来,他们有的只有几个元素,有的却可以有几十个。所以我们要区分讨论。这里我们创造的搜查函数不妨给它设定一个功能:搜索全部含有特定数量元素的方案。我们需要n个指针(n为我们设定的数量)。从数学方向角度看n的最大值为我们的target除以所给数据中最小数据取整值。
接着:
我们需要分析如何不重复的找到所有的组合。我们一个可行的解决方案就是穷举法,穷举所有的组合,找出符合条件的组合并记录下来。我们可以构造一个比较的函数:int compare(int **m, int num,int target){//比较函数 int total = 0; for(int i = 0;i<num;i++){ total += **(m+i); } if(total == target){ return 1; } else if(total > target){ return -1; } else{ return 0; } }
这个函数可以帮助我们讨论当前指针数组所指向的元素之和是否满足target的条件。int **m是一个指针数组。
现在我们考虑:如何不重复的穷举所有类型。现在提供一种解决方案:定义一个含有n个指针的数组,并且都让它们指向从小到大排过序的数组的第一位。判断当前指针组所指对象之和是否满足target。如果满足条件,则移动最小指针,我们把数组中第一个指针称作最小指针,按此道理我们可以定义第二指针,第三指针,一直到第n指针,最小指针向着数组元素增大的位置移动一位,再次判断,满足条件则进行记录,移动指针。不满足条件则直接移动指针。当第一指针(最小指针)移动到数组最大值时,移动第二指针,向右移动一位,随后第一指针退回到第二指针的位置。以此类推,第二指针指到最大元素后,再次移动则让第三指针向右移动一位,第一指针,第二指针回到第三指针位置。到最后所有指针均指到了元素最大值处,此时我们可以退出循环了。但是这和杨辉三角有什么联系呢?我们稍后再谈。
优化:
我们发现,这个是可以优化的,因为当我们若在一次target的比较中满足了条件,则再移动第一指针,得到的结果或许是不满足条件的。那到底是什么情况下它移动指针肯定不满足条件呢?我们会发现,假设第一指针和第二指针此时并排,或者指向同一个元素(不同时指向最后一个元素),我们发现,移动第一指针,必然导致所求值不满足target的条件,那么把这一种情况通过if来省略掉就可以节省很多的空间,也会提高程序运行速度。那我们就寻找,就寻找和第一指针指向一直相同的到m(第二,第三,第四,第。。。m都与第一指针指向相同,我们不再移动这些指针,而是把m+1个指针向右移动一位,然后第一到第m指针与m+1指针指向相同。这样可以大大提高程序运行速度。
代码如下:
bool move(int **m,int length,int *data,int num){//移动函数
int flag = 0;
if(m[0] < data+length-1){
m[0] += 1;
}
else{
while(m[flag] == data+length-1){
flag++;
if(flag == num){
return 0;//移动失败
}
}
if(flag < num){
m[flag]++;
for(int j =0;j<flag;j++){
m[j] = m[flag];
}
return 1;//移动成功
}
}
}
bool move_two(int **m,int num,int *data,int length){//移动函数
int flag = 0;
int check_an = check(m,num);
if(check_an == num-1)
return 0;
else{
m[check_an+1]++;
for(int i = 0;i<=check_an;i++){
m[i] = m[check_an+1];
}
return 1;
}
}
//因为有两种指针移动模式,所以我们定义两个方法
通过此我们就通过不断地移动指针来达到比较的目的(注意退出比较循环的条件:当第n级指针到达数组最后一位,此时所有的指针都指向了最后一位,可退出循环。然后就有了一个问题,我们每个元素可以选的都是不限数量的,那么我们是否要设置一个最大值呢,即n的最大值。倘若元素里有元素1,target = 100,那么100个1的情况也要考虑进去,那么我们就应该在主函数中,设置一个循环,从1到(target/元素中最小元素)即可。
其中发现的数学道理
这里面如果不看题目,是蕴含着一种排序思想的。
首先,给你一个问题,在一个班里要竞选班长。一共有五名学生竞选班长,需要说明,这五名学生互不相同(自行注释废话)。现在如果只有一个学生投票,我们会有几种情况呢?
C
5
1
C^1_5
C51种对吗?
那我们假设某种情况。
倘若在竞选班长时,每个竞选者的票数至少为一,那么有意思的来了。如果是有两个投票者,则最多有两种数量的竞选班长人数,1和2。假如竞选班长数为1,则有一种情况;竞选班长数为2,则有1种情况。假如有三名投票者,如果有1位竞选班长人数,则只有一种情况。如果有两位竞选班长,则会有2种情况,如果有三位竞选班长,则会有1种情况。以此类推,我们会发现
C
1
0
C^0_1
C10,
C
1
1
C^1_1
C11,
C
2
0
C^0_2
C20,
C
2
1
C^1_2
C21,
C
2
2
C^2_2
C22,的规律开始增加,那么我们可以推断出继续向下,他们仍然满足这个规律,即满足杨辉三角!
我们由可以主观的写出表达式,假如有m个人竞选班长,n个人进行投票,m个人每个人最少为一票,那么则会有
C
n
−
1
m
−
1
C^{m-1}_{n-1}
Cn−1m−1种情况。那么我们换种方法进行思考,假如有k个空余票数,即这k个空余票数是可以随便投的,即k=n-m,那公式
C
n
−
1
m
−
1
C^{m-1}_{n-1}
Cn−1m−1就可以改写为:
C
k
+
m
−
1
m
−
1
C^{m-1}_{k+m-1}
Ck+m−1m−1种情况。
我们需要换种思路继续思考这个问题。假如有k个空余票数可以随便投的话(并且把这空余票数投给m个人),我们可以给票数分组。
我们这么假设,把票分成n组,有m张票。我们假设一个符号
F
m
n
F^n_m
Fmn,这个符号就代表了有多少种分法。倘若我们把他展开,即控制第一个为1,那么剩下(m-1)张票分成n-1堆则有
F
m
−
1
n
−
1
F^{n-1}_{m-1}
Fm−1n−1种,如果第二个为2,则剩下有
F
m
−
2
n
−
1
F^{n-1}_{m-2}
Fm−2n−1种,以此类推,我们可以发现:
F
m
n
F^n_m
Fmn=
F
m
−
1
n
−
1
F^{n-1}_{m-1}
Fm−1n−1+
F
m
−
2
n
−
1
F^{n-1}_{m-2}
Fm−2n−1+
F
m
−
3
n
−
1
F^{n-1}_{m-3}
Fm−3n−1+
⋯
\cdots
⋯+
F
n
n
−
1
F^{n-1}_{n}
Fnn−1+
F
n
−
1
n
−
1
F^{n-1}_{n-1}
Fn−1n−1。我们就找到了它的递推公式。通过观察我们发现
F
m
1
F^{1}_{m}
Fm1(m>1)均为1,那么有意思的就来了,我们把上面的递推公式逐层展开,展到最后,使每一项的上标都变成1,那么这就可以求出
F
m
n
F^{n}_{m}
Fmn的具体值了。
省去递推过程直接看结果
其结果是一个连加的形式,可以理解为多层连加。形式如下:
∑
a
n
−
1
=
1
m
−
n
+
1
∑
a
n
−
2
=
1
a
n
−
1
∑
a
n
−
3
=
1
a
n
−
2
⋯
∑
a
1
=
1
a
2
⋅
1
\sum^{m-n+1}_{a_{n-1}=1}\sum^{a_{n-1}}_{a_{n-2}=1}\sum^{a_{n-2}}_{a_{n-3}=1}\cdots\sum^{a_2}_{a_1=1}\cdot1
∑an−1=1m−n+1∑an−2=1an−1∑an−3=1an−2⋯∑a1=1a2⋅1=
C
m
−
1
n
−
1
C^{n-1}_{m-1}
Cm−1n−1。其代表的结果就是给定m个指针,有n个元素可选,但是每个元素至少选择一次。
既然每个元素选择一次的公式我们已经发现了,我们继续寻找,如果在n个元素中选择出来m个,每个元素可选次数可以为0,那么会有多少种情况呢?这个问题我们只要递推一下就能知道。假如我们有一个元素可选并且至每个元素至少选一次,我们要选m次,我们通过上面的公式可以轻易地写出
C
m
−
1
0
C^0_{m-1}
Cm−10次,然后这个有多少种选法呢?n个元素中每个元素都可能成为那可选的元素之一,所以我们还要再乘上
C
n
1
C^1_n
Cn1,一次类推的话,我们可以得到,如果有n个元素可选,并且不限选择次数,我们应该有
C
n
1
C^1_n
Cn1
C
m
−
1
0
C^0_{m-1}
Cm−10+
C
n
2
C^2_n
Cn2
C
m
−
1
1
C^1_{m-1}
Cm−11+
C
n
3
C^3_n
Cn3
C
m
−
1
2
C^2_{m-1}
Cm−12+
C
n
4
C^4_n
Cn4
C
m
−
1
3
C^3_{m-1}
Cm−13+
⋯
\cdots
⋯+
C
n
n
C^n_n
Cnn
C
m
−
1
n
−
1
C^{n-1}_{m-1}
Cm−1n−1(①),那么这么多种到底是多少呢?我们通过计算(盲猜)可以得出,这个式子的结果是
C
m
+
n
−
1
m
C^m_{m+n-1}
Cm+n−1m。那为什么呢?实际上,我们看这个公式:
∑
a
n
−
1
=
1
m
−
n
+
1
∑
a
n
−
2
=
1
a
n
−
1
∑
a
n
−
3
=
1
a
n
−
2
⋯
∑
a
1
=
1
a
2
⋅
1
\sum^{m-n+1}_{a_{n-1}=1}\sum^{a_{n-1}}_{a_{n-2}=1}\sum^{a_{n-2}}_{a_{n-3}=1}\cdots\sum^{a_2}_{a_1=1}\cdot1
∑an−1=1m−n+1∑an−2=1an−1∑an−3=1an−2⋯∑a1=1a2⋅1,我们可以理解它的深层含义,当所有值都为1,时,我们可以假设现在有n-1个指针,都指向了第一个元素。这时开始进位,它是这样的进位规则,即最小的指针不能超过比它大一级的指针(和我们刚开始定义的最小的指针不能小于比它大的指针,是不是很类似?)事实证明,它们是等价的,只不过一个是正过程,一个是逆过程而已。那么就好,求在n个元素里选出来m个的问题,不久恰好是指针的排列组合问题吗。那么不久恰好可以用连加的形式来解决吗?受此启发,我们可以轻易地写出n个元素里选m个的问题的解
⇒
\Rightarrow
⇒
∑
a
m
=
1
n
∑
a
m
−
1
=
1
a
m
∑
a
m
−
2
=
1
a
m
−
1
⋯
∑
a
1
=
1
a
2
⋅
1
\sum^{n}_{a_{m}=1}\sum^{a_{m}}_{a_{m-1}=1}\sum^{a_{m-1}}_{a_{m-2}=1}\cdots\sum^{a_2}_{a_1=1}\cdot1
∑am=1n∑am−1=1am∑am−2=1am−1⋯∑a1=1a2⋅1,其中,n是指每个元素的最高指向为n,即第n个元素。m指的是有m个指针。这个公式又等于多少呢?根据我们前面的推理,可写出其为
C
m
+
n
−
1
m
C^m_{m+n-1}
Cm+n−1m,那么前面的那一大堆累加(①),之和就是
C
m
+
n
−
1
m
C^m_{m+n-1}
Cm+n−1m。
自此,我们还可以继续探讨当m=1,m=2,
⋯
\cdots
⋯,的问题,鉴于我能力有限,就只写到这里了。
第一次写博客,比较紧张,所以有错误的地方,还请指出,共同学习,共同进步。
(既然是分析编程问题抽象数学原理,那完整代码就不放了,大家对这个问题有更好的解决方法,也欢迎评论)