问题描述:你的朋友提议玩一个游戏:将写有数字的n个纸片放入口袋中,你可以从口袋中抽取抽取4次纸片,每次记下纸片的数字并将其放回口袋中。如果这4个数字的总和为m,就是你赢。你挑战了好几回,结果一次也没有赢过,于是怒而撕破口袋,取出所有的纸片,检查自己是否真的有赢的可能性。请你编写一个程序,判断当纸片上的数字为k1,k2........kn时,是否存在4次和为m的方案。如果存在则输出Yes,否则输出No。
暴力解法:每一次抽取纸片共n种可能,4次抽取共n的4次方中,写一个4重循环进行遍历即可。时间复杂度:O(n^4)
#include<stdio.h>
#include<iostream>
const int MAX_N = 50;
bool existed(int n, int m, int *k){
bool re = false;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j ++)
{
for (int g = 0; g < n; g++)
{
for (int h = 0; h < n; h++)
{
if ( (k[i] + k[j] + k[g] + k[h]) == m)
{
re = true;
}
}
}
}
}
return re;
}
int main(){
int n, m, k[MAX_N];
printf("please input the value of n:\n");
scanf_s("%d", &n);
printf("please input the value of m:\n");
scanf_s("%d", &m);
printf("please input the array of k:\n");
for (int i = 0; i < n; i++)
{
scanf_s("%d", &k[i]);
}
bool re = existed(n, m, k);
if (re)
{
printf("Yes\n");
}
else
{
printf("No\n");
}
system("Pause");
return 0;
}
使用二分搜索进行优化:假设四次抽取的纸片数字分别为k[i],k[j],k[g],k[h],我们所需要做的事情就是剪裁是否存在 k[i]+k[j]+k[g]+k[h] = m。对等式进行变化可将问题转化为检查是否存在k[h],使得k[h] = m - k[i] - k[j] - k[g],则对于最内层的循环可以利用一个二分查找来进行判断从而降低时间复杂度为O(n^3logn)。
#include<stdio.h>
#include<iostream>
const int MAX_N = 50;
void swap(int *k, int d1, int d2){
int temp = k[d1];
k[d1] = k[d2];
k[d2] = temp;
}
void quicksort(int *k, int n){
int l = 0, r = n, target = k[0];
while (true)
{
do
{
l++;
} while (k[l] < target && l<r);
do
{
r--;
} while (k[r] > target);
if (l < r)
{
swap(k, l, r);
}else
{
break;
}
}
swap(k, 0, l);
}
bool binary_search(int n, int target, int *k){
int l = 0, r = n;
while(r - l >= 1){
int i = (r+l)/2;
if (k[i] == target)
{
return true;
}
else if (k[i] < target)
{
l = i+1;
}
else
{
r = i-1;
}
}
return false;
}
bool existed2(int n, int m, int *k){
quicksort(k, n);
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j ++)
{
for (int g = 0; g < n; g++)
{
int target = m - k[i] - k[j] - k[g];
if (binary_search(n, target, k))
{
return true;
}
}
}
}
return false;
}
int main(){
int n, m, k[MAX_N];
printf("please input the value of n:\n");
scanf_s("%d", &n);
printf("please input the value of m:\n");
scanf_s("%d", &m);
printf("please input the array of k:\n");
for (int i = 0; i < n; i++)
{
scanf_s("%d", &k[i]);
}
bool re = existed2(n, m, k);
if (re)
{
printf("Yes\n");
}
else
{
printf("No\n");
}
system("Pause");
}
第三种算法,基于以上的思想我们可以继续进行优化将4个数字k[i],k[j],k[g],k[h]分成两组k[i],k[j]和k[g],k[h],每一组的和的情况是相同的,共n^2种情况。则可以将问题转化为从含有n^2个数字的数组中找到是否存在两个和为m的数字。对于这个问题的解法可以这样做:
首先将n^2个数字进行快排,时间复杂度为O(n^2logn),利用两个指针分别定位在排序之后的数组的首和尾。如果两数之和大于m,则向前移动尾指针;如果小于m,向后移动头指针;如果相等返回true。时间复杂度为O(n^2),所以总的时间复杂度为O(n^2logn)。
#include<stdio.h>
#include<iostream>
const int MAX_N = 50;
void swap(int *k, int d1, int d2){
int temp = k[d1];
k[d1] = k[d2];
k[d2] = temp;
}
void quicksort(int *k, int n){
int l = 0, r = n, target = k[0];
while (true)
{
do
{
l++;
} while (k[l] < target && l<r);
do
{
r--;
} while (k[r] > target);
if (l < r)
{
swap(k, l, r);
}else
{
break;
}
}
swap(k, 0, l);
}
bool existed3(int n, int m, int *k){
int kn = n*n;
int ki = 0;
int kk[MAX_N * MAX_N];
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j ++)
{
kk[ki] = k[i] + k[j];
ki++;
}
}
quicksort(kk,kn);
int l = 0, r = kn-1;
while (l <= r)
{
if (kk[l] + kk[r] > m)
{
r--;
}
else if (kk[l] + kk[r] == m)
{
return true;
}
else
{
l++;
}
}
return false;
}
int main(){
int n, m, k[MAX_N];
printf("please input the value of n:\n");
scanf_s("%d", &n);
printf("please input the value of m:\n");
scanf_s("%d", &m);
printf("please input the array of k:\n");
for (int i = 0; i < n; i++)
{
scanf_s("%d", &k[i]);
}
bool re = existed3(n, m, k);
if (re)
{
printf("Yes\n");
}
else
{
printf("No\n");
}
system("Pause");
}