Given an array with n integers, you need to find if there are triplets (i, j, k) which satisfies following conditions:

  1. 0 < i, i + 1 < j, j + 1 < k < n - 1
  2. Sum of subarrays (0, i - 1), (i + 1, j - 1), (j + 1, k - 1) and (k + 1, n - 1) should be equal.

where we define that subarray (L, R) represents a slice of the original array starting from the element indexed L to the element indexed R.

Example:

Input: [1,2,1,2,1,2,1]
Output: True
Explanation:
i = 1, j = 3, k = 5.
sum(0, i - 1) = sum(0, 0) = 1
sum(i + 1, j - 1) = sum(2, 2) = 1
sum(j + 1, k - 1) = sum(4, 4) = 1
sum(k + 1, n - 1) = sum(6, 6) = 1

Note:

  1. 1 <= n <= 2000.
  2. Elements in the given array will be in range [-1,000,000, 1,000,000].
 
这道题给了我们一个数组,让我们找出三个位置,使得数组被分为四段,使得每段之和相等,问存不存在这样的三个位置,注意三个位置上的数字不属于任何一段。刚开始博主觉得这题貌似跟之前那道 Partition Equal Subset Sum 很像,所以在想能不能用DP来做,可是想了半天不知道DP该如何定义,更别说推导状态转移方程了。于是就尝试了建立累加和数组,并搜索所有的可能组合,进行暴力破解,结果却TLE了。说明OJ不接受时间复杂度为三次方的解法,那么就要想办法来优化了,博主只好上网学习大神们的解法,发现大神们的解法果然巧妙,只是改变了一个查找顺序,就轻易的将时间复杂度降到了平方级,碉堡了有木有。思路是这样的,因为我们需要找三个位置i,j,k,如果我们按正常的顺序来暴力搜索,那么就会遍历所有的情况,其实大部分的情况都是不符合题意的,会有大量的无用的运算。而如果我们换一个角度,先搜索j的位置,那么i和k的位置就可以固定在一个小的范围内了,而且可以在j的循环里面同时进行,这样就少嵌套了一个循环,所以时间复杂度会降一维度。确定j的范围应该左右各留3个数字,因为四段均不能为空,而且分割位上的数字不能算入四段。再确定了j的位置后,i和k的位置就能分别确定了,我们要做的是先遍历i的所有可能位置,然后遍历所有的拆分情况,如果拆出的两段和相等,则把这个相等的值加入一个集合中,然后再遍历k的所有情况,同样遍历所有的拆分情况,如果拆出两段和相等,再看这个相等的和是否在集合中,如果存在,说明拆出的四段和都可以相同,那么返回true即可,否则当遍历结束了,返回false。唉,为啥自己就想不到呢,估计这就是和大神的差距吧,泪目中。。
 
解法一:
class Solution {
public:
bool splitArray(vector<int>& nums) {
if (nums.size() < ) return false;
int n = nums.size();
vector<int> sums = nums;
for (int i = ; i < n; ++i) {
sums[i] = sums[i - ] + nums[i];
}
for (int j = ; j < n - ; ++j) {
unordered_set<int> s;
for (int i = ; i < j - ; ++i) {
if (sums[i - ] == (sums[j - ] - sums[i])) {
s.insert(sums[i - ]);
}
}
for (int k = j + ; k < n - ; ++k) {
int s3 = sums[k - ] - sums[j], s4 = sums[n - ] - sums[k];
if (s3 == s4 && s.count(s3)) return true;
}
}
return false;
}
};

下面这种解法是递归的暴力破解写法,刚开始博主还纳闷了,为啥博主之前写的迭代形式的暴力破解过不了OJ,而这个递归版本的确能通过呢,仔细研究了一下,发现这种解法有两个地方做了优化。第一个优化是在for循环里面,如果i不等于1,且当前数字和之前数字均为0,那么跳过这个位置,因为加上0也不会对target有任何影响,那为什么要加上i不等于1的判断呢,因为输入数组如果是七个0,那么实际上应该返回true的,而如果没有i != 1这个条件限制,后面的代码均不会得到执行,那么就直接返回false了,是不对的。第二个优化的地方是在递归函数里面,只有当curSum等于target了,才进一步调用递归函数,这样就相当于做了剪枝处理,减少了大量的不必要的运算,这可能就是其可以通过OJ的原因吧。

再来说下子函数for循环的终止条件 i < n - 5 + 2*cnt 是怎么得来的,是的,这块的确是个优化,因为i的位置是题目中三个分割点i,j,k的位置,所以其分别有自己可以取值的范围,由于只有三个分割点,所以cnt的取值可以是0,1,2。
-> 当cnt=0时,说明是第一个分割点,那么i < n - 5,表示后面必须最少要留5个数字,因为分割点本身的数字不记入子数组之和,那么所留的五个数字为:数字,第二个分割点,数字,第三个分割点,数字。
-> 当cnt=1时,说明是第二个分割点,那么i < n - 3,表示后面必须最少要留3个数字,因为分割点本身的数字不记入子数组之和,那么所留的三个数字为:数字,第三个分割点,数字。
-> 当cnt=2时,说明是第三个分割点,那么i < n - 1,表示后面必须最少要留1个数字,因为分割点本身的数字不记入子数组之和,那么所留的一个数字为:数字。

解法二:

class Solution {
public:
bool splitArray(vector<int>& nums) {
if (nums.size() < ) return false;
int n = nums.size(), target = ;
int sum = accumulate(nums.begin(), nums.end(), );
for (int i = ; i < n - ; ++i) {
if (i != && nums[i] == && nums[i - ] == ) continue;
target += nums[i - ];
if (helper(nums, target, sum - target - nums[i], i + , )) {
return true;
}
}
return false;
}
bool helper(vector<int>& nums, int target, int sum, int start, int cnt) {
if (cnt == ) return sum == target;
int curSum = , n = nums.size();
for (int i = start + ; i < n - + * cnt; ++i) {
curSum += nums[i - ];
if (curSum == target && helper(nums, target, sum - curSum - nums[i], i + , cnt + )) {
return true;
}
}
return false;
}
};

基于上面递归的优化方法的启发,博主将两个优化方法加到了之前写的迭代的暴力破解解法上,就能通过OJ了,perfect!

解法三:

class Solution {
public:
bool splitArray(vector<int>& nums) {
int n = nums.size();
vector<int> sums = nums;
for (int i = ; i < n; ++i) {
sums[i] = sums[i - ] + nums[i];
}
for (int i = ; i <= n - ; ++i) {
if (i != && nums[i] == && nums[i - ] == ) continue;
for (int j = i + ; j <= n - ; ++j) {
if (sums[i - ] != (sums[j - ] - sums[i])) continue;
for (int k = j + ; k <= n - ; ++k) {
int sum3 = sums[k - ] - sums[j];
int sum4 = sums[n - ] - sums[k];
if (sum3 == sum4 && sum3 == sums[i - ]) {
return true;
}
}
}
}
return false;
}
};

参考资料:

https://leetcode.com/problems/split-array-with-equal-sum/

https://leetcode.com/problems/split-array-with-equal-sum/discuss/101484/java-solution-dfs

https://leetcode.com/problems/split-array-with-equal-sum/discuss/101481/simple-java-solution-on2

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Split Array with Equal Sum 分割数组成和相同的子数组的更多相关文章

  1. [LeetCode] 548. Split Array with Equal Sum 分割数组成和相同的子数组

    Given an array with n integers, you need to find if there are triplets (i, j, k) which satisfies fol ...

  2. [LeetCode] Split Array With Same Average 分割数组成相同平均值的小数组

    In a given integer array A, we must move every element of A to either list B or list C. (B and C ini ...

  3. LeetCode 548. Split Array with Equal Sum (分割数组使得子数组的和都相同)$

    Given an array with n integers, you need to find if there are triplets (i, j, k) which satisfies fol ...

  4. [LeetCode] Split Array into Fibonacci Sequence 分割数组成斐波那契序列

    Given a string S of digits, such as S = "123456579", we can split it into a Fibonacci-like ...

  5. 【LeetCode】548. Split Array with Equal Sum 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 暴力 日期 题目地址:https://leetcode ...

  6. 【LeetCode Weekly Contest 26 Q4】Split Array with Equal Sum

    [题目链接]:https://leetcode.com/contest/leetcode-weekly-contest-26/problems/split-array-with-equal-sum/ ...

  7. leetcode548 Split Array with Equal Sum

    思路: 使用哈希表降低复杂度.具体来说: 枚举j: 枚举i,如果sum[i - 1] == sum[j - 1] - sum[i],就用哈希表把sum[i - 1]记录下来: 枚举k,如果sum[k ...

  8. 『随笔』.Net 底层 数组[] 的 基本设计探秘 512 子数组

    static void Main(string[] args) { Console.ReadKey(); //初始化数组 不会立即开辟内存字节, 只有实际给数组赋值时 才会开辟内存 // //猜测数组 ...

  9. [LeetCode] Split Array Largest Sum 分割数组的最大值

    Given an array which consists of non-negative integers and an integer m, you can split the array int ...

随机推荐

  1. linux挂载windows共享文件夹

    1.建立共享文件夹 2.在linux中挂载共享目录 #mount -t cifs -o username=administrator,password=你的系统账号密码 //192.168.0.22/ ...

  2. 爬虫(BeautifulSoup--select--class的选择)

    <div class="item name" title="中央公园"> <a href="/Attraction_Review-g ...

  3. 单例模式、简单工厂模式、XML解析

    单例模式: 什么是单例模式? 针对特定问题提出的特定解决方案 为什么使用设计模式? 让程序有更好的可扩展性 在哪里使用? 一般情况下,开发中真正使用设计模式的地方,JVM(虚拟机)底层机制模式 usi ...

  4. selenium2自动化测试学习笔记(一)

    从这周开始学习自动化测试,采用selenium2,目标是在本月学习到appium,并测试公司的真实APP项目. 系统环境:win10 语言:python3.6.4 工具:selenium2 IDE:p ...

  5. alpha-咸鱼冲刺day1

    一,合照 emmmmm.自然是没有的. 二,项目燃尽图 三,项目进展 登陆界面随意写了一下.(明天用来做测试的) 把学姐给我的模板改成了自家的个人主页界面,侧边栏啥的都弄出来了(快撒花花!) 四,问题 ...

  6. JavaScript简写技巧总结

    在日常工作中,JavaScript一些常用的简写技巧,将直接影响到我们的开发效率,现将常用技巧整理如下: 1. 空(null, undefined)验证     当我们创建了一个新的变量,我们通常会去 ...

  7. vue jquery js 获取当前时间本周的第一天 和 本月的第一天

    交互的时候传输数据 后台要求这样的数据 直接上代码 这是我找度姨要的  附上链接  https://www.cnblogs.com/wasabii/p/7756560.html 它里面有本季度第一天  ...

  8. C语言学习(二)

    今天在程序员面试题中,碰到一个有意思的题目:数组a[N],存放了1至N-1个数,其中某个数重复一次,现在要求找出重复的数字且程序时间复杂度必须为O(N).乍一看,如果不计时间复杂度和空间复杂度程序比较 ...

  9. HTTP协议的消息头:Content-Type和Accept的作用

    一.背景知识 1.概述 Http报头分为通用报头,请求报头,响应报头和实体报头. 请求方的http报头结构:通用报头|请求报头|实体报头 响应方的http报头结构:通用报头|响应报头|实体报头 Acc ...

  10. ELK学习总结(1-1)ELK是什么

    1.elk 是什么 ? Elastic Stack(旧称ELK Stack),是一种能够从任意数据源抽取数据,并实时对数据进行搜索.分析和可视化展现的数据分析框架.(hadoop同一个开发人员) ja ...