对于一个长度为N的整型数组A, 数组里所有的数都是正整数,对于两个满足0 <= X <= Y < N的整数,A[X], A[X+1] … A[Y]构成A的一个切片,记作(X, Y).
用三个下标 m1, m2, m3(下标满足条件0 < m1, m1 + 1 < m2, m2 +1 < m3 < N – 1)可以把这个整型数组分成(0, m1-1), (m1+1, m2-1), (m2+1, m3-1), (m3+1, N-1) 四个切片。
如果这四个切片的整数求和相等,称作“四等分”。编写一个函数,求一个给定的整型数组是否可以四等分,如果可以,返回一个布尔类型的true,如果不可以返回一个布尔类型的false。
限制条件:数组A最多有1,000,000项,数组中的整数取值范围介于-1,000,000到1,000,000之间。
要求:函数的计算复杂度为O(N),使用的额外存储空间(除了输入的数组之外)最多为O(N)。例子:
对于数组A=[2, 5, 1, 1, 1, 1, 4, 1, 7, 3, 7] 存在下标 2, 7, 9使得数组分成四个分片[2, 5], [1, 1, 1, 4], [7], [7],这三个分片内整数之和相等,所以对于这个数组,函数应该返回true。
对于数组 A=[10, 2, 11, 13, 1, 1, 1, 1, 1],找不到能把数组四等分的下标,所以函数应该返回false。
今年参加了阿里的实习生春招,在进行编程测验之前已经了解到部分同学遇到的是这道题,所以事先做了准备,然而轮到我时已经换题了,可能是不同岗位题目不一样吧。
这道题描述的啰哩啰嗦,实际上就是给一个全是正数的数组,问是否能将该数组分割成四部分,每一部分累加和相等,分割点不算
要解决这题首先要明确数组四等分的结构,即3个分割点,4个切片,有了这个概念,在草纸上画画,问题很容易就解决了。
首先我们遍历一遍数组构造一个HashMap存储 ( arr[i]左边所有元素的和 , i ) ,然后大多数人选择让分割点m1、m3从两端像中间移动的同时累加两个切片内元素(谁小谁移动),当两个切片相等时根据四等分结构看能否找到满足条件的分割点m2。
这里我选择仅将分割点m1由左向右移动,如果数组可以四等分,依据3个分割点,4个切片这样一个基本结构,一旦分割点m1确定了,m2、m3也就相应确定了,如果最后找不到符合基本结构的m2、m3,则可以断定数组不可以四等分,时间复杂度O(N),代码如下:
public static boolean canSplits(int[] arr) {
if (arr == null || arr.length < 7) { // 元素个数小于7,无法构成3个分割点、4个切片的基本结构
return false;
}
HashMap<Long, Integer> leftSumToIndex = new HashMap<>();
Long sum = (long) arr[0];
for (int i = 1; i < arr.length; i++) {
leftSumToIndex.put(sum, i); // 把(arr[i]左边所有元素的和, i)存入HashMap
sum += arr[i];
}
Long part = (long) arr[0]; // part为切片内元素的和
for (int m1 = 1; m1 < arr.length - 5; m1++) { // 遍历分割点m1
Long m2LeftSum = part + arr[m1] + part; // 分割点m2左边所有元素的和
if (leftSumToIndex.containsKey(m2LeftSum)) {
int m2 = leftSumToIndex.get(m2LeftSum);
Long m3LeftSum = m2LeftSum + arr[m2] + part; // 分割点m3左边所有元素的和
if (leftSumToIndex.containsKey(m3LeftSum)) {
int m3 = leftSumToIndex.get(m3LeftSum);
if (m3LeftSum + arr[m3] + part == sum) {
return true;
}
}
}
part += arr[m1];
}
return false;
}
对于像Two Sum、Longest Substring Without Repeating Characters以及本题这种涉及数组下标的一系列问题,优先考虑事先构造一个HashMap看是否对后续的求解有帮助。在HashMap中,每次查询操作的时间复杂度均为O(1),所以它一直是编写高效算法的利器。