先来看看题目。
题目是这样的:你和朋友提议玩一个游戏:将写有数字的n个纸片放入口袋中,你可以从口袋中抽取4次纸片,每次记下纸片上的数字后放会口袋中。如果这四个数字的和是m,就是你赢负责就是你的朋友赢。你挑战了好几回,结果一次也没赢过,于是怒而撕破口袋,取出所有纸片,检查自己是否真的有赢的可能性。请你编写一个程序,判断当前纸片上所有写的数字是k1,k2,k3….km时,是否存在抽取4次和为m的方案。如果存在,输出Yes,f否则,输出No.
限制条件:
1<=n<=50
1<=m<=10^8
1<=ki<=10^8
输入
n=3
m=10
k={1,3,5}
输出
Yes(例如4次抽取的结果是1,1,3,5和就是10)
看到这道题第一反应四层for循环枚举出所有方案就可以完全解决了
解决代码如下(java):
public static void main(String[] args){
Scanner a=new Scanner(System.in);
int n=a.nextInt();//接受输入的n(n个纸片)
int m=a.nextInt();//接受输入的m(和为m)
a.nextLine();
int[] k=new int[n];
for(int i=0;i<k.length;i++){
k[i]=a.nextInt();//每个数字
}
if(f(n,m,k)){
System.out.println("Yes");
}else{
System.out.println("No");
}
}
public static boolean f(int n,int m,int[] k){
for(int a=0;a<k.length;a++){
for(int b=0;b<k.length;b++){
for(int c=0;c<k.length;c++){
for(int d=0;d<k.length;d++){
if(k[a]+k[b]+k[c]+k[d]==m){
return true;
}
}
}
}
}
return false;
}
此算法的时间复杂度O(n^4).
当n的数字比较大时,运行起来就比较费劲了
那么可不可以将这四层循环进行进一步的优化呢
首先来想办法解决去掉最内层循环
解决思路:
上面这层算法最内层所做的事情是ka+kB+kc+kd=m;
那么是不是可以转变为m-ka-kb-kc的值是否在k数组中存在。
但是还有一点问题,去找在k数组中存在需要遍历整个K数组,那么这样的话就相当于没有任何改变。
现在需要解决的问题变成怎样在数组中更快速的找到一个值
我想到的解决思路一共有两种:
1.改进算法,用二分法来搜索数组中的这个值(二分法的前提是这个数组必须是排好序的)
解决代码(java)
public static void main(String[] args){
Scanner a=new Scanner(System.in);
int n=a.nextInt();//接受输入的n(n个纸片)
int m=a.nextInt();//接受输入的m(和为m)
a.nextLine();
int[] k=new int[n];
for(int i=0;i<k.length;i++){
k[i]=a.nextInt();//每个数字
}
if(f(n,m,k)){
System.out.println("Yes");
}else{
System.out.println("No");
}
}
public static boolean f(int n,int m,int[] k){
for(int a=0;a<n;a++){
for(int b=0;b<n;b++){
for(int c=0;c<n;c++){
if(f1(m-k[a]-k[b]-k[c],k,0,n)){
return true;
}
}
}
}
return false;
}
public static boolean f1(int p,int[] k,int l,int r){//二分搜索,p为要搜索的数,搜索的范围为[l,r]
if(l+1==r||l==r){
return false;
}
if(p==k[(l+r)/2]){
return true;
}
if(p>k[(l+r)/2])
return f1(p,k,(l+r)/2+1,r);
else
return f1(p,k,l,(l+r)/2-1);
}
2.改进数据结构。设计数组i[10000];让对应数组i下标为k值的为1。这样在找数字p时只需要判断k[p]是否等于1就可以了
代码示例(java):
public static void main(String[] args){
Scanner a=new Scanner(System.in);
int n=a.nextInt();//接受输入的n(n个纸片)
int m=a.nextInt();//接受输入的m(和为m)
a.nextLine();
int[] k=new int[n];
for(int i=0;i<n;i++){
k[i]=a.nextInt();
}
if(f(n,m,k)){
System.out.println("Yes");
}else{
System.out.println("No");
}
}
public static boolean f(int n,int m,int[] k){
int[] i=new int[100*1000];//!注意这里
for(int j=0;j<n;j++){
i[k[j]]=1;//!注意这里
}
for(int a=0;a<n;a++){
for(int b=0;b<n;b++){
for(int c=0;c<n;c++){
if(m-k[a]-k[b]-k[c]>0&&i[m-k[a]-k[b]-k[c]]==1){//!注意这里
return true;
}
}
}
}
return false;
}
还可以再将第三层循环去掉
转化为找到数组中的两个数和为m-ka-kb
那么首先要去枚举出所有两个数的和,然后再按照上面两种思路来进行优化。
!注意:不能在将第二曾循环去掉。因为枚举两个的和需要两层循环,这时找赢的方法的循环是两层,时间复杂度是(O(n^2))。而当枚举的是三个之和时,就需要三个循环了,这时找赢的方法的循环是1层,这时的时间复杂度是(O(n^3))