寻找和固定的两个或三个数

xiaoxiao2021-02-27  459

题目

快速找出一个包含 n 个整数的数组中所有两个数的组合,让这两个数之和等于一个给定的值sum。输入 sum n ,接着是所有整数,要求输出所有两个数的组合。

分析1

如果直接遍历穷举将会得到O(n2)的时间复杂度。对于每一个元素 array[i] 来说,可以将问题转化为 sumarray[i] 是否在数组中,而如果事先将数组排序则可以利用二分查找来完成,此时算法的时间复杂度为 O(nlog(n)+log(n))=O(nlog(n))

另外一个思路同样是将数组排序,接着设置两个指针 i j分别从两端向中间遍历数组,如果 array[i]+array[j]=sum ,则 i 后移且j前移;如果 array[i]+array[j]<sum ,则 i 后移j不动; array[i]+array[j]>sum ,则 i 不动j前移;最终找到所有这样的两个数的组合。这样遍历的依据是,对于 k=0i1 ,一定有 array[k]+array[j]array[i]+array[j] i 后移表示和递增,而对于l=j 1n1,一定有 array[i]+array[l]array[i]+array[j] j 前移表示和递减,从而利用双指针遍历就可以依次找出所有两个数的组合。这样算法的时间复杂度依然为O(nlog(n) n)=O(nlog(n))

能否让时间复杂度进一步减小呢?利用哈希往往能用空间换取时间。不用排序,将所有整数放入哈希表中,对于每一个元素 array[i] 去查表 sumarray[i] 是否存在,这只需要 O(1) 的时间,从而算法的时间复杂度为 O(n) ,但是对于重复的组合需要额外去记录。

代码1

import java.util.*; public class Sum2 { static void sum2bs(int[] array, int sum) { Arrays.sort(array); int n = array.length; int remain, pos; for (int i = 0; i < n; i++) { remain = sum - array[i]; pos = binarySearch(array, remain, i);// 如果一个数只使用一次则左边界为i+1 if (pos != -1) System.out.println(array[i] + " " + remain); } } // 数组从左向右遍历,设置查找的左边界就可以避免重复组合 static int binarySearch(int[] array, int target, int left) { int i = left, j = array.length - 1; int mid; while (i <= j) { mid = i + (j - i) / 2; if (array[mid] == target) { return mid; } else if (array[mid] < target) { i = mid + 1; } else { j = mid - 1; } } return -1; } static void sum2bt(int[] array, int sum) { Arrays.sort(array); int n = array.length; int i = 0, j = n - 1; while (i <= j) {// 如果一个数只使用一次则i<j if (array[i] + array[j] == sum) { System.out.println(array[i] + " " + array[j]); i++; j--; } else if (array[i] + array[j] < sum) { i++; } else { j--; } } } static void sum2hash(int[] array, int sum) { int n = array.length; Set<Integer> set = new HashSet<>(); for (int i = 0; i < n; i++) { set.add(array[i]); } int remain; for (int i = 0; i < n; i++) { remain = sum - array[i]; if (set.contains(remain)) System.out.println(array[i] + " " + remain); } } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int sum = sc.nextInt(); int n = sc.nextInt(); int[] array = new int[n]; for (int i = 0; i < n; i++) { array[i] = sc.nextInt(); } sum2bs(array, sum); } }

扩展

将找出所有两个数的组合改为找出所有三个数的组合,其余不变。

分析2

找三个数可以使用同样的思路,但是使用二分查找和双指针遍历就会有不同的复杂度。考虑到 remain2=sumarray[i] 只能有 n 个值,对于每个remain2可以转化为找和为 remain2 的两个数的组合,如果使用二分查找时间复杂度为 O(nlog(n)+nnlog(n))=O(n2log(n)) 。但是如果使用双指针遍历,由于找两个数的组合只需要 O(n) 的时间,故时间复杂度为 O(nlog(n)+nn)=O(n2)

代码2

import java.util.*; public class Sum3 { static void sum3bs(int[] array, int sum) { Arrays.sort(array); int n = array.length; int remain1, remain2, pos; for (int i = 0; i < n; i++) { remain2 = sum - array[i]; for (int j = i; j < n; j++) { remain1 = remain2 - array[j]; pos = binarySearch(array, remain1, j);// 如果一个数只使用一次则左边界为j+1 if (pos != -1) System.out.println(array[i] + " " + array[j] + " " + remain1); } } } // 数组从左向右遍历,设置查找的左边界就可以避免重复组合 static int binarySearch(int[] array, int target, int left) { int i = left, j = array.length - 1; int mid; while (i <= j) { mid = i + (j - i) / 2; if (array[mid] == target) { return mid; } else if (array[mid] < target) { i = mid + 1; } else { j = mid - 1; } } return -1; } static void sum3bt(int[] array, int sum) { Arrays.sort(array); int n = array.length; int remain2, j, k; for (int i = 0; i < n; i++) { remain2 = sum - array[i]; j = i;// 如果一个数只使用一次则j=i+1 k = n - 1; while (j <= k) {// 如果一个数只使用一次则条件为j<k if (array[j] + array[k] == remain2) { System.out.println(array[i] + " " + array[j] + " " + array[k]); j++; k--; } else if (array[j] + array[k] < remain2) { j++; } else { k--; } } } } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int sum = sc.nextInt(); int n = sc.nextInt(); int[] array = new int[n]; for (int i = 0; i < n; i++) { array[i] = sc.nextInt(); } sum3bs(array, sum); } }
转载请注明原文地址: https://www.6miu.com/read-975.html

最新回复(0)