最近做Leetcode上的4Sum,自己想到了一个不太好的方法,刚刚好可以过,代码如下:
#include <iostream>
#include <string>
#include <vector>
#include <stdlib.h>
#include <map>
#include <algorithm>
#include <set>
#include <time.h>
using namespace std;
struct From
{
bool used;
int m_a;
int m_b;
From(int a, int b) :m_a(a), m_b(b), used(false)
{
}
};
bool check(const From& l, const From& r)
{
if (l.m_a == r.m_a || l.m_a == r.m_b ||
l.m_b == r.m_a || l.m_b == r.m_b)
{
// 说明有重复
return false;
}
return true;
}
class Solution {
private:
public:
vector<vector<int> > fourSum(vector<int> &num, int target) {
clock_t start, finish;
clock_t start0, finish0;
double duration;
start0 = clock();
start = clock();
//预处理
multimap<int, From> newNums;
for (int i = 0; i < num.size(); ++i)
{
for (int j = i + 1; j < num.size(); ++j)
{
if (num[i] <= num[j])
{
newNums.insert(make_pair(num[i] + num[j], From(i, j)));
}
else
{
newNums.insert(make_pair(num[i] + num[j], From(j, i)));
}
}
}
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf("%f seconds\n", duration);
start = clock();
vector<vector<int>> ans;
set <vector<int>> duplicate;
auto l = newNums.begin();
for (auto i : newNums)
{
auto ret = newNums.equal_range(target - i.first);
for (auto j = ret.first; j != ret.second; ++j)
{
if (j->second.used)
{
continue;
}
if (check(i.second, j->second))
{
vector<int> solution;
solution.push_back(num[i.second.m_a]);
solution.push_back(num[i.second.m_b]);
solution.push_back(num[j->second.m_a]);
solution.push_back(num[j->second.m_b]);
sort(solution.begin(), solution.end());
if (duplicate.find(solution) == duplicate.end())
{
duplicate.insert(solution);
ans.push_back(solution);
}
}
}
i.second.used = true;
}
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf("%f seconds\n", duration);
finish0 = clock();
duration = (double)(finish0 - start0) / CLOCKS_PER_SEC;
printf("Function Run : %f seconds\n", duration);
return ans;
}
};
我自己分析了一下复杂度,预处理是O(n^2*log(n^2)),newNums中的个数应该为O(n^2),之后在newNums中找找满足条件的结果,这段代码的复杂度应为O(N^2*log(N^2))*log(k)
前面的O(N^2*log(N^2))是最外面的for循环加上在multimap中查找元素的消耗,后面的log(k),k是去重set中的个数,k最坏情况可能有O(n^2)*O(n^2)个。
所以最后我这方法的复杂度应该是O(N*N*logN*logN) PS:希望没有算错
按理说在大数据量的情况下比O(N^3)的算法要快,我觉得应该是Leetcode的数据量比较小的缘故,所以构造multimap和set浪费了不少时间而体现不出来优势。我就自己写了个测试用例来看看到底这个算法是慢在哪里。测试用例如下:
int main()
{
clock_t start, finish;
double duration;
string str;
vector<int> num;// = { 91277418, 66271374, 38763793, 4092006, 11415077, 60468277, 1122637, 72398035, -62267800, 22082642, 60359529, -16540633, 92671879, -64462734, -55855043, -40899846, 88007957, -57387813, -49552230, -96789394, 18318594, -3246760, -44346548, -21370279, 42493875, 25185969, 83216261, -70078020, -53687927, -76072023, -65863359, -61708176, -29175835, 85675811, -80575807, -92211746, 44755622, -23368379, 23619674, -749263, -40707953, -68966953, 72694581, -52328726, -78618474, 40958224, -2921736, -55902268, -74278762, 63342010, 29076029, 58781716, 56045007, -67966567, -79405127, -45778231, -47167435, 1586413, -58822903, -51277270, 87348634, -86955956, -47418266, 74884315, -36952674, -29067969, -98812826, -44893101, -22516153, -34522513, 34091871, -79583480, 47562301, 6154068, 87601405, -48859327, -2183204, 17736781, 31189878, -23814871, -35880166, 39204002, 93248899, -42067196, -49473145, -75235452, -61923200, 64824322, -88505198, 20903451, -80926102, 56089387, -58094433, 37743524, -71480010, -14975982, 19473982, 47085913, -90793462, -33520678, 70775566, -76347995, -16091435, 94700640, 17183454, 85735982, 90399615, -86251609, -68167910, -95327478, 90586275, -99524469, 16999817, 27815883, -88279865, 53092631, 75125438, 44270568, -23129316, -846252, -59608044, 90938699, 80923976, 3534451, 6218186, 41256179, -9165388, -11897463, 92423776, -38991231, -6082654, 92275443, 74040861, 77457712, -80549965, -42515693, 69918944, -95198414, 15677446, -52451179, -50111167, -23732840, 39520751, -90474508, -27860023, 65164540, 26582346, -20183515, 99018741, -2826130, -28461563, -24759460, -83828963, -1739800, 71207113, 26434787, 52931083, -33111208, 38314304, -29429107, -5567826, -5149750, 9582750, 85289753, 75490866, -93202942, -85974081, 7365682, -42953023, 21825824, 68329208, -87994788, 3460985, 18744871, -49724457, -12982362, -47800372, 39958829, -95981751, -71017359, -18397211, 27941418, -34699076, 74174334, 96928957, 44328607, 49293516, -39034828, 5945763, -47046163, 10986423, 63478877, 30677010, -21202664, -86235407, 3164123, 8956697, -9003909, -18929014, -73824245 };
for (int i = 0; i < 1500; ++i)
{
num.push_back(rand());
}
Solution s;
start = clock();
auto ans = s.fourSum(num, rand());
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf("Total %d : %f seconds\n", ans.size(), duration);
}
上面被注释掉的是从Leetcode里抓出来的一组解,他的N只有200,确实数据量很小,我就想测测当N等于1500时候的时间分布,用VS2013 Release下运行结果如下:
当N等于2000时,时间分布为:
2000/1500 = 1.33,因此对应于预处理耗时所增加的时间比理论应(2000*2000)*log((2000*2000))/(1500*1500)*log((1500*1500)) = 1.85,实际为3.134/1.602 = 1.95,基本一致。
第二部分由于最终结果差了很多,1500时找到符合条件的有将近8w个,而2000时只有400多个,因为结果集的差异,所以查重的消耗会有差异。但2000数据量下竟然比预处理所花的时间,这是让我觉得比较疑惑的。。。
但从测试结果看出,函数调用所花的时间和在main函数里测量的时间差了非常多,竟然在2000数据量情况下查了有9倍之多,3s增加到28s。可函数返回,除了将结果拷贝出来,就只能把栈上的变量析构?因为2000数量量时,结果拷贝的数据量才400左右,但耗时还是明显增加,难道是因为变量析构占了大量的时间?
为了验证我的猜测,将上述代码newNums从临时变量转成成员变量,这样函数返回时不会进行析构,再重新运行结果:
这便证明了确实是因为容器析构占用了大量的时间,在数量量小(输入N为200)的时候,这一部分的耗时还不是很明显,当数量量稍微增加到2000,便非常显著了,这是我以前没有想到的,以为析构会很快,没想到竟然这么耗时。
PS:这里有一个挺好的判重方法,在set里不用vector而是用转成string,这样效率能高一些