又一个让我觉得自己蠢笨的题目,这些天一直在学习从另一个视角看问题。应该会慢慢变得灵活变通吧
题目:已知一个一维数组a1…n,又已知一整数m。如能使数组a中任意几个元素之和等于m,则输出YES,否则则输出NO。
【思考分割线------------------------------------------------------------------------】
如果不是因为在递归栏目看到这题,大概是要把我难死。又是任意,又是数组,随机取数的可能性多到不胜枚举。怎么设计都不在我可以用循环和选择可以解决的。但他在递归,其实即使在递归我也难住了。因为没想到怎么递归,笑拥咧。】
【-----------------------------------------------------------】
这题的思考方式很有意思:反证法。
如果任意个数的元素之和等于m,那么这个任意就可以进行以下分类:
①末尾元素正好等于m
②末尾元素不等于m,而前面的n-1个元素中有某个元素等于m
③两个或以上元素之和为m,这种情况下,有可能取到末尾元素或者取不到,因此只要:
- 在取到末尾元素的时候证明前n-1个元素中存在任意几个之和等于 m - a[n] 即可。
- 取不到的时候就是证明前n-1个元素中存在任意几个之和等于 m ,也就是前 n-2 个元素中有元素之和为 m - a[n - 1]。
如此一来,就可以将逻辑进行一个闭环然后进行递归即——看a[n]是否取到或者等于m,再看前n-1个元素中的末尾元素是否取到或等于m,此时依旧可以看a[n-1]是否等于m;在取到末尾元素时证明前 n-1 元素中有元素之和为m - a[n],取不到时证明前 n - 2 个元素中有元素之和为m - a[n - 1]、m - a[n - 2]以此类推……
个人觉得我们经常陷入的一个思维陷阱就是希望证明出某个问题的解决细节,最好是能够搞清楚为什么这么做,比如大家都知道1+1=2,但是陷入证明陷阱之后就会想问,为什么1+1=2?凭什么??实际上我们只要使用1+1=2这个已知的东西到更复杂的问题中去就行。
所以这里的递归内部是如何运行,可以不做纠结,否则会越来越糊涂。
采用全局变量编写程序如下:
#include<iostream>
using namespace std;
const int Max1 = 51;
int a[Max1],n,m;
bool flag;
void sum(int,int);
int main(){
cin>>n;
cin>>m;
for(int i = 1;i<=n;i++)
cin>>a[i];
flag = false;
sum(n,m);
if(flag) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
return 0;
}
void sum(int n,int m){
if(a[n]==m) flag = true;
else if(n == 1) return;//递推到第一个元素后仍然没有满足条件的,结束
else{
sum(n-1,m-a[n]);//判断前n-1个元素中是否有元素之和为m- a[n]
sum(n-1,m);//判断前n-1个元素中是否有元素之和为m
}
}