题目描述:
给定由N个整数组成的数组nums,找出三个元素之和为0的三元组(非重复序列),并记录这样的三元组的数量。
threeSum算法
//ThreeSum 用于统计一个数组中和为 0 的三元组数量。
public interface threeSum {
int count(int[] nums);
}
方法一:threeSumLow
分析:ThreeSumLow 算法内循环(三层循环 for) 判断if(num[i] + num[j] + num[k] = 0)语句是否成立,总共执行次数为N(N-1)(N-2) [其中N为数组的长度,即.N = nums.length],因此该算法近似执行为N3/6,时间复杂度为O(N3)。
解题思路
- 三个元素分别用一层循环;
- 第一层循环从元素nums[0]开始;
- 因为非重复,则第二个元素必定是在第一个元素之后的元素,所以从nums[0+1]开始循环;同理,第三个元素从nums[0+1+1]=nums[2]开始;
- 第三组循环一直循环结束后,第二个元素下标+1前进一位进行循环,第二组循环结束后,第一个元素下标+1进行循环,直到第一组循环结束。
public class threeSumSlow implements threeSum {
//count(int[] nums)函数是记录三个元素之和为0的三元组数量
public int count(int[] nums) {
int N = nums.length;
int cnt = 0;//初始化三元组数量为0
for (int i = 0; i < N; i++) {
//从数组起始位0开始,即数组的第一个元素开始
for (int j = i + 1; j < N; j++) {
//第二层循环,从第二个元素往后
for (int k = j + 1; k < N; k++) {
//第三层循环,从第三个元素之后开始
if (nums[i] + nums[j] + nums[k] == 0) {
//判断三个元素之和是否为零
cnt++;//如果三个元素之和为0,数量+1;如果不为0,继续第三层循环直到k=N
}
}
}
}
return cnt;
}
}
方法二:threeSumBinarySearch
分析:先将数组进行排序,然后对数组中两个元素求和,并用二分查找方法查找是否存在该和的相反数,如果存在,就说明存在三元素之和为 0 的三元素。二分查找只有数组不含有相同元素才能使用这种解法,否则二分查找的结果会出错。该方法可以将 ThreeSum 算法时间复杂度降为 O(N2logN)。
解题思路
- 两层循环+一次二分查找算法;
- 将数组元素进行sort()函数排序;
- 用算法一中同样的方法进行二次循环,对数组中两个元素求和赋给target;
- 执行二分查找算法判断数组中是否存在target的相反数。
public class threeSumBinarySearch implements ThreeSum {
public int cout(int[] nums) {
Arrays.sort(nums);\\通过Arrays.sort()函数将数组nums进行排序
int N = nums.length;
int cnt = 0;
//进行二次循环(内循环)
for (int i = 0; i < N; i++) {
for (int j = i + 1; j < N; j++) {
int target = -(num[i] + nums[j]);//求数组内两个元素之和,赋给target
int index = binarySearch.search(nums, target);//调用函数BinarySearch.search()求与target相加为0的元素的小标
//保证求出的下标大于 j,否则会出现重复统计。
if (index > j) {
cnt++;//如果三个元素之和为0,数量+1;如果不为0,继续第二层循环直到j=N
}
}
}
return cnt;
}
}
//BinarySearch 二分查找算法
public class binarySearch {
public static int search(int[] nums, int target) {
int low = 0, high = nums.length - 1;//low、high分别表示为数组左侧低阶和数组右侧高阶的下标
while (low <= high) {
int mid = low + (high - 1)/2;//将数组从中间二分
if (target == nums[mid]) {
//如果中间元素满足相反数条件,直接返回
return mid;
} else if (target > nums[mid]) {
//如果中间元素<相反数,则继续[mid+1, high]二分循环查找
low = mid + 1;
} else {
//如果中间元素>相反数,则继续[low, mid-1]二分循环查找
high = mid - 1;
}
}
return -1;//在这个函数中,如果有异常就返回-1,即 return -1;
//这是一种编程技巧,如果出现异常就通过返回-1就知道该函数出现异常
}
方法三:threeSumTwoPointer
分析:将数组排序,然后通过双指针进行查找,从数组两侧往中间进行查找,时间复杂度为O(N2)。
解题思路
- 将数组元素进行sort()函数排序;
- 数组两侧相加,从中间元素这种查找与两侧相加的值相等的第三个元素;
public class threeSumTwoPointer implements ThreeSum {
public int count(int[], nums) {
int N = nums.length;
int cnt = 0;
Arrays.sort(nums);\\通过Arrays.sort()函数将数组nums进行排序
for (int i = 0; i < N - 2; i++) {
//将nums[0]的相反数赋给target,num[low]、nums[high]分别初始化为数组第二个和最后一个元素
int low = i + 1, high = N - 1, target = -num[i];
while (low < high) {
int sum = nums[low] + nums[high];//两元素相加赋给sum
//if else判断sum 和 target 的大小关系,即三元素之和是否为0
if (sum == target) {
cnt++;
low++;
high--;
} else if (sum < target) {
low++;
} else {
high--;
}
}
}
return cnt;
}
}