给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。
例如:
输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2
解释:
两个元组如下:
1、 (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2、(1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/4sum-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
直接4层for循环求解,明显不能通过,放弃。
关键点:
class Solution {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
// 存储计算结果和结果出现次数的关系
Map<String, Integer> sumAbRet = new HashMap<>();
Map<String, Integer> sumCdRet = new HashMap<>();
// 分组计算
for(int i = 0; i < A.length; ++i) {
for (int j = 0; j < A.length; ++j) {
sumAbRet.put(String.valueOf(A[i] + B[j]),
sumAbRet.get(String.valueOf(A[i] + B[j])) == null ? 1 :
sumAbRet.get(String.valueOf(A[i] + B[j])) + 1);
sumCdRet.put(String.valueOf(C[i] + D[j]),
sumCdRet.get(String.valueOf(C[i] + D[j])) == null ? 1 :
sumCdRet.get(String.valueOf(C[i] + D[j])) + 1);
}
}
Integer ret = 0;
// 遍历结果集合,直接通过key值找到另一集合中满足条件的数, 注意对0做特殊处理
for (Map.Entry<String, Integer> entry : sumAbRet.entrySet()) {
if(Integer.valueOf(entry.getKey()) == 0) {
ret = ret +
(entry.getValue() * (!sumCdRet.containsKey("0") ? 0 : sumCdRet.get("0")));
}else {
ret = ret +
(entry.getValue() * (!sumCdRet.containsKey(
String.valueOf(-Integer.valueOf(entry.getKey()))
) ? 0 : sumCdRet.get(String.valueOf(-Integer.valueOf(entry.getKey())))));
}
}
return ret;
}
}
执行结果:
class Solution {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
Map<Integer, Integer> countAB = new HashMap<Integer, Integer>();
for (int u : A) {
for (int v : B) {
countAB.put(u + v, countAB.getOrDefault(u + v, 0) + 1);
}
}
int ans = 0;
for (int u : C) {
for (int v : D) {
if (countAB.containsKey(-u - v)) {
ans += countAB.get(-u - v);
}
}
}
return ans;
}
}
官方题解的耗时:
官方题解主要区别在:
getOrDefault
猜想第三点是影响耗时的关键,后续是几次调整后的代码测试结果。
class Solution {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
Map<String, Integer> sumAbRet = new HashMap<>();
Map<String, Integer> sumCdRet = new HashMap<>();
for(int i = 0; i < A.length; ++i) {
for (int j = 0; j < A.length; ++j) {
sumAbRet.put(String.valueOf(A[i] + B[j]),
sumAbRet.get(String.valueOf(A[i] + B[j])) == null ? 1 :
sumAbRet.get(String.valueOf(A[i] + B[j])) + 1);
// 改成了取负存储
sumCdRet.put(String.valueOf(-(C[i] + D[j])),
sumCdRet.get(String.valueOf(-(C[i] + D[j]))) == null ? 1 :
sumCdRet.get(String.valueOf(-(C[i] + D[j]))) + 1);
}
}
Integer ret = 0;
// 直接读
for (Map.Entry<String, Integer> entry : sumAbRet.entrySet()) {
if(sumCdRet.containsKey(entry.getKey())) {
ret = ret + entry.getValue() * sumCdRet.get(entry.getKey());
}
}
return ret;
}
}
执行结果:
耗时有所下降,但并不明显,再尝试一下将Key类型换成Integer,这样做的好处是可以减少大量的转换操作。实际上,在生产应用中,对使用Integer做Map的key需要异常慎重,因为使用Integer作为key的话,可能会因为序列化等原因导致不能正确的取到数。
class Solution {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
Map<Integer, Integer> sumAbRet = new HashMap<>();
Map<Integer, Integer> sumCdRet = new HashMap<>();
for(int i = 0; i < A.length; ++i) {
for (int j = 0; j < A.length; ++j) {
sumAbRet.put(A[i] + B[j],
sumAbRet.get(A[i] + B[j]) == null ? 1 :
sumAbRet.get(A[i] + B[j]) + 1);
sumCdRet.put(-(C[i] + D[j]),
sumCdRet.get(-(C[i] + D[j])) == null ? 1 :
sumCdRet.get(-(C[i] + D[j])) + 1);
}
}
Integer ret = 0;
for (Map.Entry<Integer, Integer> entry : sumAbRet.entrySet()) {
if(sumCdRet.containsKey(entry.getKey())) {
ret = ret + entry.getValue() * sumCdRet.get(entry.getKey());
}
}
return ret;
}
}
看来耗时的大头还是在各种转换上。但耗时还是要差一些, 考虑是否是Map的遍历比较耗时。 虽然我们最早的代码看起来是 一次二层遍历+一次Map遍历;而题解是有两次二层遍历。
但实际上,题解应该是比我的代码要少做一次Map遍历的。针对这个点我们再优化下。
class Solution {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
Map<Integer, Integer> sumAbRet = new HashMap<>();
for(int i = 0; i < A.length; ++i) {
for (int j = 0; j < A.length; ++j) {
sumAbRet.put(A[i] + B[j],
sumAbRet.get(A[i] + B[j]) == null ? 1 :
sumAbRet.get(A[i] + B[j]) + 1);
}
}
Integer ret = 0;
for(int i = 0; i < A.length; ++i) {
for (int j = 0; j < A.length; ++j) {
if(sumAbRet.containsKey(-(C[i]+D[j]))) {
ret = ret + sumAbRet.get(-(C[i]+D[j]));
}
}
}
return ret;
}
}
源自LeetCode题解: 四数相加 II:降维+初始容量+避免类型转换
思想是:通过实现专用的Map对象,完成下面2个优化。orz
1、初始化map给定合理的容量初始值;
2、避免Integer与int的转换。
代码如下:
private static class Node {
int value;
int count;
Node next;
public Node(int value) {
this.value = value;
this.count = 1;
}
public Node(int value, Node next) {
this.value = value;
this.count = 1;
this.next = next;
}
}
private static class Map {
Node[] table;
public Map(int initalCapacity) {
if (initalCapacity < 16) {
initalCapacity = 16;
} else {
initalCapacity = Integer.highestOneBit(initalCapacity - 1) << 1;
}
table = new Node[initalCapacity];
}
// 拷贝的HashMap的hash方法
private int hash(int value) {
if (value < 0) {
value = -value;
}
int h;
return (value == 0) ? 0 : (h = value) ^ (h >>> 16);
}
public void put(int value) {
int tableIndex = hash(value) & table.length - 1;
Node head = table[tableIndex];
if (head == null) {
table[tableIndex] = new Node(value);
return;
}
Node cur = head;
while (cur != null) {
if (cur.value == value) {
cur.count++;
return;
}
cur = cur.next;
}
// 头插法
table[tableIndex] = new Node(value, head);
}
public int getCount(int value) {
int tableIndex = hash(value) & table.length - 1;
Node head = table[tableIndex];
if (head == null) {
return 0;
}
Node cur = head;
while (cur != null) {
if (cur.value == value) {
return cur.count;
}
cur = cur.next;
}
return 0;
}
}
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
// 避免扩容, 初始化一个最大初始容量
Map abMap = new Map(A.length * B.length);
for (int a : A) {
for (int b : B) {
abMap.put(a + b);
}
}
int res = 0;
for (int c : C) {
for (int d : D) {
res += abMap.getCount(-c - d);
}
}
return res;
}
作者:iisimpler
链接:https://leetcode-cn.com/problems/4sum-ii/solution/si-shu-xiang-jia-iijiang-wei-chu-shi-rong-liang-bi/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。