题目描述
有 n 个物品,每个物品有 k 个属性,第 i 件物品的第 j 个属性用一个正整数表示为 ai,j,两个不同的物品 i,j 被称为完美对当且仅当 ai,2 + aj,1= ai,2+ aj,2=···=ai,k + aj,k,求完美数的个数。
输入描述
第一行两个数字n,k.
接下来n行,第i行k个数字表示
ai,1,ai,2,···ai,k
1<=n<=10^5, 2<=k<=10, 1<ai<=100
输出描述
一行一个数字表示答案
示例1
输入:
5 3
2 11 21
19 10 1
20 11 1
6 15 24
18 27 36
输出:3
解题思路
第一想法是直接暴力法,任选两组数组,接着依次从1,2,3···k判断是否满足条件即可。同时做好提交超时的心理准备,因为时间复杂度已经达到了O(n^3)
级别了。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// n,k
int n = in.nextInt(), k = in.nextInt();
//二维数组a
int[][] a = new int[n][k];
for (int i = 0; i < n; i++) {
for (int j = 0; j < k; j++) {
a[i][j] = in.nextInt();
}
}
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (check(a, i, j, k)) {
res++;
}
}
}
System.out.println(res);
}
//判断两数组是否满足完美对的条件
private static boolean check(int[][] a, int i, int j, int k) {
int sum = a[i][0] + a[j][0];
for (int idx = 1; idx < k; idx++) {
if (a[i][idx] + a[j][idx] != sum) {
return false;
}
}
return true;
}
}
超时了,那么就考虑如何优化?首先需要注意的是,无论如何优化算法,算法的时间复杂度一定是介于O(n^2)
和O(n^3)
之间。接着,如何优化其实是个数学问题:
原式 | 等值变换 |
---|---|
ai,1+aj,1=ai,2+aj,2 | ai,1-ai,2=aj,2-aj,1 |
ai,2+aj,2=ai,3+aj,3 | ai,2-ai,3=aj,3-aj,2 |
ai,3+aj,3=ai,4+aj,4 | ai,3-ai,4=aj,4-aj,3 |
··· | ··· |
ai,k-1+aj,k-1=ai,k+aj,k | ai,k-1-ai,k=aj,k-aj,k-1 |
原式并没有什么特点,主要看右侧 等值变换的式子。然后把等值变换的式子从上至下相加:
(ai,1-ai,2)+(ai,2-ai,3)+(ai,3-aj,4)+···+(ai,k-1-ai,k)=(aj,2-aj,1)+(aj,3-aj,2)+(aj,4-aj,3)+···+(aj,k-aj,k-1)
不妨设Li =(ai,1-ai,2)+(ai,2-ai,3)+(ai,3-aj,4)+···+(ai,k-1-ai,k) ,则有 -Lj=(aj,2-aj,1)+(aj,3-aj,2)+(aj,4-aj,3)+···+(aj,k-aj,k-1)
根据上述推到,对于完美对i,j来说,一定有 Li = - Lj成立。换言之,若Li != Lj,那么i,j对必然不是完美对
下述代码正是借助L排除掉不符合条件的数组对实现的:
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
//n,k
int n = in.nextInt(), k = in.nextInt();
//二维数组a
int[][] a = new int[n][k];
//L条件
int[] L = new int[n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < k; j++) {
a[i][j] = in.nextInt();
if (j > 0) {
//构建L
L[i] += a[i][j - 1] - a[i][j];
}
}
}
int res = 0;
//L值与下标列表 映射
Map<Integer, List<Integer>> map = new HashMap<>();
for (int i = 0; i < n; i++) {
//过滤不符合L条件
if (map.containsKey(-L[i])) {
for (Integer e : map.get(-L[i])) {
if (check(a, i, e, k)) {
res++;
}
}
}
if (!map.containsKey(L[i])) {
map.put(L[i], new ArrayList<>());
}
map.get(L[i]).add(i);
}
System.out.println(res);
}
private static boolean check(int[][] a, int i, int j, int k) {
int sum = a[i][0] + a[j][0];
for (int idx = 1; idx < k; idx++) {
if (a[i][idx] + a[j][idx] != sum) {
return false;
}
}
return true;
}
}
各位看官,如果觉得我解释的还算清楚,就给我点个赞吧!(骗赞侠,哈哈)