使用一个线性级别的算法(而非基于二分查找的线性对数级别的算法)实现TwoSumFaster来计算一排序的数组中和为0的整数对的数量。用相同的思想为3-sum问题给出一个平方级别的算法。
线性级别的算法增长的数量级为N,常见的是使用一层for循环。对于顺序排列的数组,只要从数组两端向中间逼近匹配即可。若a[head] + a[tail] > 0,则tail – ;若a[head] + a[tail] < 0,则head ++。就好比两个人从两地出发的相遇问题,两人的总行程为数组长度。关键代码如下:
public long twoCount(int[] a) {
Arrays.sort(a);
int N = a.length;
long cnt = 0;
for(int i = 0, head = 0, tail = N - 1; i < N; i ++) {
if(a[head] + a[tail] >= 0) {
if(a[head] + a[tail] == 0)
cnt ++;
tail --;
} else
head ++;
}
return cnt;
}
对于3-sum,同理,先确定第一个数字a[i](第一层for循环),再从头a[i + 1]和尾a[N-1]中确定另外两个数字(第二层for循环)。该算法是平方级别的算法,增长的数量级为N2。关键代码如下:
public long threeCount(int[] a) {
Arrays.sort(a);
int N = a.length;
long cnt = 0;
for(int i = 0; i < N; i ++) {
for(int j = i + 1, head = i + 1, tail = N - 1; j < N; j++) {
if(a[head] + a[tail] + a[i] >= 0) {
if(a[head] + a[tail] + a[i] == 0)
cnt ++;
tail --;
} else
head ++;
}
}
return cnt;
}
以上程序在求和时未考虑int溢出的情况,在计数的时候存在溢出的可能,故cnt的类型为long。threeCount毕竟是平方级别的算法,解决100万个整数(1Mintts.txt)花费了半个多小时的时间。
特别感谢网友emergency_rose的指正!
存在缺陷1:当数列中出现重复元素的时候,上述的方法将可能出现遗漏某些整数对的情况。因为tail --总是出现在head ++之前,这时数列“尾部”出现重复元素时,可以正常计算整数对;而当数列“头部”出现重复元素时,将出现遗漏整数对的情况。所以,当head ++操作时需要判断a[head ++] == a[head],若重复,则整数对直接加上与a[head]匹配的对数。
存在缺陷2:当head与tail相遇后,for循环应该停止,否则整数对将出现重复。
则有twoCount代码如下,3-sum同理。
public long twoCount(int[] a) {
Arrays.sort(a);
int N = a.length;
long cnt = 0;
long emp = 0;
for(int i = 0, head = 0, tail = N - 1; i < N && head > tail; i ++) {
if(a[head] + a[tail] >= 0) {
if(a[head] + a[tail] == 0) {
cnt ++; emp ++;
}
tail --;
} else if(a[head] == a[head ++]){
cnt += emp; emp = 0;
}
}
return cnt;
}
特别感谢网友m0_56157806的指正!
当重复元素大于2使,emp会被过早清零,则有
public static long twoCount(int[] a) {
Arrays.sort(a);
int N = a.length;
long cnt = 0;
long emp = 0;
for(int i = 0, head = 0, tail = N - 1; i < N && head < tail; i ++) {
if(a[head] + a[tail] >= 0) {
if(a[head] + a[tail] == 0) {
cnt ++; emp ++;
}
tail --;
} else if(a[head] == a[head +1]){
cnt += emp;
if(head+2 > tail && a[head + 1] != a[head + 2])
emp = 0;
head ++;
}
}
return cnt;
}