数组拆分(微软2017笔试题)

给定一个长度为N的数组A=[A1,A2, … AN],请你将A拆分成3段连续的子数组:

A1, A2, … Ap | Ap+1, Ap+2, … Aq | Aq+1,Aq+2, … AN

S1 = A1 + A2 + … Ap

S2 = Ap+1 + Ap+2 + … Aq

S3 = Aq+1 + Aq+2 + … AN

问有多少种划分方案可以使得S1, S2, S3两两相差不超过1.

N <= 100000,  -1000000 <= Ai <= 1000000

例子:

A = [3, 2, 1, 0, 2]

3 | 2 | 1 0 2  

3 | 2 1 | 0 2  

3 | 2 1 0 | 2

 

基本思路:枚举分界点p和q的位置,计算S1、S2和S3;如果符合要求ans++

for p = 1 … N-2:

  forq = p + 1 … N-1:

   S1=S2=S3=0

   for i = 1 … p {S1+=A[i]}

   for i = p+1 … q {S2+=A[i]}

   for i = q+1 … N {S3+=A[i]}

   if |S1-S2|<=1 && |S2-S3|<=1 && |S3-S1| <=1:

     ans++

 

优化思路:枚举分界点p和q的位置,利用前缀和计算S1、S2和S3;如果符合要求ans++

for p = 1 … N-2:

  forq = p + 1 … N-1:

   S1=s[p] – s[0]

   S2=s[q] – s[p]

   S3=s[N] – s[q]

   if |S1-S2|<=1 && |S2-S3|<=1 && |S3-S1| <=1:

     ans++

优化思路的代码:

#include
using namespace std;
const int maxn = 100005;
typedef long long ll;
int n;
ll sum[maxn];
int main()
{
	map cnt;
	cin>>n;
	sum[0] = 0;
	int tmp;
	//下面求前缀和 
	for(int i =1;i <= n;i ++)
	{
		cin>>tmp;
		sum[i] = sum[i-1] + tmp; 
	}
	//下面枚举q  
	ll ans = 0;
	ll s1,s2,s3;
	for(int q = n;q > 2 ;q --)  // 第3个数字 到最后一个数字
	{
		s3 = sum[n] - sum[q-1];
		
		for(int p = 1;p <= q-2;p ++)
		{
			s1 = sum[p];
			if(abs(s1- s3) <= 1)
			{
				s2 = sum[q-1] - sum[p];
				if(abs(s1 - s2) <= 1 && abs(s2 - s3) <= 1)
				ans ++;
			}
		}
	} 
	cout<

但是这样的枚举的时间复杂度仍然在0(n^2),所以我们还要想更好的优化思路

 

试想  枚举p q 不行,那么我枚举一个q行不行呢    枚举q  得到s3 然后利用前缀和

判断s1和和s3的关系  如果满足  则计算剩下的s2  如果s2仍然满足  则这是一个符合的分割方法。   这样就把问题缩小了一个量级 

还要注意数字的范围 最大的数肯定超过了int 所以这里我们要用longlong

#include
using namespace std;
const int maxn = 100005;
typedef long long ll;
int n;
ll sum[maxn];
int a[maxn];
int main()
{
	map cnt;
	cin>>n;
	sum[0] = 0;
	//录入数据 
	for(int i =1;i <= n;i ++)
	cin>>a[i];
	//下面计算前缀和
	for(int i = 1 ;i <= n;i ++)
	{
		sum[i] = sum[i-1] + a[i];
		
		if(!cnt.count(sum[i]))
		cnt[sum[i]] = 0;
		
		if(i < n)
		cnt[sum[i]] ++;  //和值尾sum[i] 的情况 +1 
	} 
	
	ll ans = 0;
	ll s3 = 0;
	for(int i = n;i > 1;i --)
	{
		if(i > 1)
		cnt[sum[i-1]] --;  //此时 刨去 s3 前面不要的那个前缀和  因为 s2的长度至少为1
		
		s3 += a[i]; 	//s3  每向前推一次 加一次a[i]  即从最后第一项开始加
		 
		for(int t = s3-1; t <= s3+1;t ++)
		{
			if(abs(sum[n] - s3 - t - s3) <= 1 && abs(sum[n] - s3 - t - t) <= 1)
			ans += cnt[t];
			/*这里比较难理解,解释一下,  
			sum[n]就是所有总数的和,减去s3 就是s1和s2的和 再减去t(此时t充当s1)剩下的就是s2  再减去s3
			即判断s2与s3的差值绝对值是否小于等于1  同理后面判断的是s1和s2   如果都满足就加上cnt[t]
			为什么加cnt[t], 设想 s3为最后一个数字的和,那么剩下的n-1 为s1和s2的和 ,在剩下的数字里面
			有cnt[t]种分割s1和s2的方法 都使得s1 s2 和s3 的差值在1以内  所以直接加上cnt[t]即可
			这样优化之后的复杂度即为n*3 即O(n); 
			*/
		} 
	}
	cout<




你可能感兴趣的:(数组拆分(微软2017笔试题))