leetcode题目:(链接https://leetcode-cn.com/problems/partition-array-into-three-parts-with-equal-sum)。
给你一个整数数组 A,只有可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false。
形式上,如果可以找出索引 i+1 < j 且满足 (A[0] + A[1] + … + A[i] == A[i+1] + A[i+2] + … + A[j-1] == A[j] + A[j-1] + … + A[A.length - 1]) 就可以将数组三等分。
示例 1:
输入:[0,2,1,-6,6,-7,9,1,2,0,1]
输出:true
解释:0 + 2 + 1 = -6 + 6 - 7 + 9 + 1 = 2 + 0 + 1
示例 2:
输入:[0,2,1,-6,6,7,9,-1,2,0,1]
输出:false
示例 3:
输入:[3,3,6,5,-2,2,5,1,-9,4]
输出:true
解释:3 + 3 = 6 = 5 - 2 + 2 + 5 + 1 - 9 + 4
最直接的方法为暴力法,从前往后拼凑第一段的和,然后看有没有相等的第二段和第三段;
例如输入为[0,2,1,-6,6,-7,9,1,2,0,1],我们先假设第一段为0,找有没有和0的第二段和第三段。没有的话再假设第一段为 0+2=2,找有没有和为2的第二段和第三段,依此下去直到遍历完或者找到符合条件的为止。这种方法费时费力,有大量的冗余计算,并且编码过程也很绕。
后面看到评论里有说用双指针,豁然开朗,我们完全可以设两个指针分别指向数组的头和尾。因为能被分为三段的前提条件是数组和能被3整除,所以我们只需要找到三段,每段和为sum / 3即可。从前往后找一段,从后往前找一段,再确认中间一段存在。
但是我一开始使用这种方法还是想得太多了,前后只需找到一个符合条件的就可以了,按照贪心算法,头指针取最小,尾指针取最大,中间的一定满足。
暴力法:
//Java
class Solution {
public boolean canThreePartsEqualSum(int[] A) {
int length = A.length;
int sum = 0;
for(int i = 0; i < length - 2; i++) {
sum = sum + A[i]; //求第一段和
int temp1 = 0;
for(int j = i + 1; j < length - 1; j++) {
temp1 = temp1 + A[j]; //求第二段和
if(temp1 == sum) {
int temp2 = 0;
for(int k = j + 1; k < length; k++) {
temp2 = temp2 + A[k]; //求第三段和
if(temp2 == sum && k == length - 1) {
return true;
}
}
}
}
}
return false;
}
}
双指针最初代码:
//Java
class Solution {
public boolean canThreePartsEqualSum(int[] A) {
int sum = 0;
for(int i = 0; i < A.length; i++) { //只有数组和能被三整除才有可能三等分
sum = sum + A[i];
}
if(sum % 3 != 0) {
return false;
}
int avar = sum / 3;
int low = 0;
int high = A.length - 1;
int first = 0; //第一段
int second = 0; //第二段
int third = 0; //第三段
boolean undergo = false; //确认第二段的存在
while(high > low) {
first = first + A[low]; //从前往后第一段求和
if(first == avar) { //第一段符合标准时,从后往前计算第三段
while(high > low) {
third = third + A[high];
if(third == avar) { //第三段符合标准时,计算第二段
for(int i = low + 1; i < high; i++) {
undergo = true; //确认第二段存在
second = second + A[i];
}
if(second == avar && undergo) {
return true;
} else { //得不到正确的第二段时,归零
second = 0;
}
}
high--;
}
}
high = A.length - 1; //将尾指针和第三段置回原位
third = 0;
low++; //增大第一段,重新计算
}
return false;
}
}
双指针优化后代码:
//Java
class Solution {
public boolean canThreePartsEqualSum(int[] A) {
int sum = 0;
for(int i = 0; i < A.length; i++) { //只有数组和能被三整除才有可能三等分
sum = sum + A[i];
}
if(sum % 3 != 0) {
return false;
}
int avar = sum / 3;
int low = 0;
int high = A.length - 1;
int first = 0; //第一段
int third = 0; //第三段
first = A[low]; //初始化,为了避免默认值0正好等于avar的情况
third = A[high];
while(low + 1 < high) {
//这个if一定要放在while的最前面,这是为了保证满足low+1 < high。
//如果不放在第一个的话,low和high可能会变化,无法保证第二段存在。
if(first == avar && third == avar) {
return true;
}
if(first != avar) {
low++;
first = first + A[low];
}
if(third != avar) {
high--;
third = third + A[high];
}
}
return false;
}
}
时间复杂度:仅遍历了一遍数组,时间复杂度为 O ( N ) O(N) O(N)。
空间复杂度:仅使用常数个变量,空间复杂度为 O ( 1 ) O(1) O(1)。