面试题之子序列分析

子序列分析

原题

给定长度为n的整数数列:a0,a1,..,an-1,以及整数S。这个数列会有连续的子序列的整数总和大于S的,求这些数列中,最小的长度。

分析

如果只是像题目这样的描述,没有强调正数,可以采用O(n^2)的方法。----------解法一

但是,很多同学在讨论的时候,指出了如果是正数,解法将会有什么样的变化。这个很好。不考虑正负的O(n^2)的方法,这里不详细说了,我们来讨论,当数列中都是正数的情况。

介绍一个利用排序+二分的方法。对于子序列ai...at,子序列和s=ai+...+at=sum[t]-sum[i-1]。sum[t]表示数列a0...at的和。那么,数组sum天然就是递增的,可以进行二分查找。 那么如何进行二分查找呢?对于数组sum,遍历找到第一个k,sum[k]>S,二分查找k前面的某一个j,j是sum[k]-sum[j]>S里最大的一个,则k-j是最小的。依次遍历完数组。 可以得到最小的长度,整体的时间复杂度O(nlogn),空间复杂度为O(n)。------解法二

是否有更快的方法呢?从以上两个方法,我们可以有如下的观察:---------解法三

  • a0...at>S,则a0...at+1无需再考虑

  • 对于a0...at>S,只需尝试a1...at是否>S,如果大于S,则更新最短长度。

  • 如果不大于S,大于S的只可能是a1...atat+1等。

鉴于以上的观察,我们有如下的算法:设置索引i,j指向第一个整数:

  1. ++j,直到sum[j]-sum[i]>S,这里不需要额外保存sum,为了方便说明。记录子序列长度

  2. ++i,如果sum[j]-sum[i]>S,更新最小子序列长度。直到sum[j]-sum[i]<=S。

  3. ++j,直到sum[j]-sum[i]>S。重复上面的两步,直到数组遍历完毕。

整个算法的时间复杂度为O(n)。下面我们做一个示例数组为{5,1,3,5,10,7,4,9,2,8},S=10,i=j=0开始

  1. 当j=3时,和为14>10,则更新最小长度为4

  2. 对i进行递增操作,和为9<10,不满足条件。对j进行递增

  3. 当i=1,j=4时,和为19>10,长度为4,不更新最小长度。递增i,直到i=3,此时和15>10,更新最小长度为2,

  4. 依次类推

最终得到最小长度为2.

【分析完毕】


附:

解法一代码:

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int n, s, i, j, tmp, count = 0, tmp_count, flag = 0;
	scanf("%d %d", &n, &s);
	int *a = (int *)malloc(sizeof(int) * n);
	for (i = 0; i < n; i++)
		scanf("%d", &a[i]);
	
	for (i = 0; i < n; i++)
	{
		tmp = 0, tmp_count = 0;
		for (j = i; j < n; j++)
		{
			if (tmp < s)
			{
				tmp += a[j];
				tmp_count++;
			}
			else
			{
				tmp_count++;
				if (flag == 0)
				{
					count = tmp_count;
					flag = 1;
				}
				else if (tmp_count < count)
				{
					count = tmp_count;
				}
				break;
			}
		}
	}
	printf("number is:%d\n", count);
	return 0;
}

解法二:

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int n, s, i, j, tmp_sum = 0, count = 0, tmp_count;
	scanf("%d %d", &n, &s);
	int *a = (int *)malloc(sizeof(int) * n);
	int *sum = (int *)malloc(sizeof(int) * n);
	for (i = 0; i < n; i++)
	{
		scanf("%d", &a[i]);
		tmp_sum += a[i];
		sum[i] = tmp_sum;
	}

	for (i = 0; i < n; i++)
	{
		if (sum[i] > s)
		{
			if (count == 0)
				count = i;
			tmp_count = i;
			for (j = 0; j < i; j++)
			{
				if (sum[i] - sum[j] > s)
					tmp_count = i - j;
				else 
				{
					if (tmp_count < count)
						count = tmp_count;
					break;
				}
			}
		}
	}
	printf("%d\n", count);
	return 0;
}
解法三:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值