数组精选题目三连(5)

子数组的最大累加和问题

输入一个整形数组,求数组中连续的子数组使其和最大。比如,数组x

应该返回 x[2..6]的和187.

 

这四个代码完成的功能都是求最大子数组(注意用词准确,子数组连续,子序列可以不连续)。

1)

for(i = 1; i <= n; i++)
	scanf("%d", &num[i]);
ans = num[1]; 
for(i = 1; i <= n; i++)
{
	for(j = i; j <= n; j++) 
	{
		s = 0;
		for(k = i; k <= j; k++)
			s += num[k];
		if(s > ans)
			ans = s;
	}
}

分别枚举每一个子数组的起点和终点,也就是i和j,对于每一个起点和终点,对中间部分求和,也就是k循环。显然有n个起点n个终点(去重减半,不影响复杂度),所以子数组数量为O(N^2),对于每个子数组,我们要遍历一下求和,子数组长度1-n不等,遍历一遍平均O(N),乘起来O(N^3).(注意可能产生时间更大的错觉)。找出所有子数组中最大的即可。

2)

for(i = 1; i <= n; i++)
	scanf("%d", &num[i]);
sum[0] = 0;
for(i = 1; i <= n; i++) {
	sum[i] = num[i] + sum[i - 1];
}
ans = num[1]; 
for(i = 1; i <= n; i++) {
	for(j = i; j <= n; j++) {
		s = sum[j] - sum[i - 1];
		if(s > ans) ans = s;
	}
}

预处理出每一个以第一个元素开始,第i个元素结尾的子数组和,还是枚举每个起点终点,但是我们求和时直接减就可以了,不用遍历。对于每个子数组,操作为O(1),子数组数量O(N^2),所以总时间O(N^2).

3)

int solve(int left, int right)
{
	if(left == right)
		return num[left];
 
	mid = (left + right) / 2;
	lans = solve(left, mid);
	rans = solve(mid + 1, right);
 
	sum = 0, lmax = num[mid], rmax = num[mid + 1];
	for(i = mid; i >= left; i--) {
		sum += num[i];
		if(sum > lmax) lmax = sum;
	}
	sum = 0;
	for(i = mid + 1; i <= right; i++) {
		sum += num[i];
		if(sum > rmax) rmax = sum;
	}
 
	ans = lmax + rmax;
	if(lans > ans) ans = lans;
	if(rans > ans) ans = rans;
	return ans;
}
 
int main(void)
{
	scanf("%d", &n);
	for(i = 1; i <= n; i++)
		scanf("%d", &num[i]);
 
	printf("%d\n", solve(1, n));
	return 0;
}

二分,求左右两边最大子数组,取最大。但是还有一种情况:包含断点的那些子数组也要考虑,请思考那两个那两个循环为什么那么写?最后逻辑为何正确?

4)动态规划入门思想

没有枚举,num[i]的含义是以下标i结尾的所有子数组中最大的。

遍历数组,对于第i个元素,它的所有子数组下标范围有[1,i],[2,i].....[i-1,i],还有它自己,我们看i-1个元素,他的子数组为[1,i-1],[2,i-1].....[i-1]。请想num[i]的含义,我们求i结尾的,只要把i-1结尾的最大加上i就好了,当然如果i-1结尾最大子数组是负的,i结尾最大子数组就是它本身。

为什么O(N)?时间省在哪里了?我们省掉了许多没必要的计算,计算i时,之前的数组和已经都计算过,朴素算法并没有记录下来,而是重复计算,造成时间浪费。算法优化的过程就是去掉重复计算的过程。
 

for(i = 1; i <= n; i++)
	scanf("%d", &num[i]);
 
num[0] = 0;
ans = num[1];
for(i = 1; i <= n; i++) 
{
	if(num[i - 1] > 0) 
		num[i] += num[i - 1];
	else
		num[i] += 0;
	if(num[i] > ans) 
		ans = num[i];
}

子矩阵的最大累加和问题

给一个矩阵,请找出一个矩阵中,和最大的子矩阵。

 

 

 

如果大家看懂了之前的讲解,我给个提示:利用第二个代码和第四个代码思想的结合

 

 

解释:

1   2  3   4

-1 -2  1   2

1   3   -2  1

-1  -2  -1  -3

如图是前三行整体最大

怎么做呢?

先用第二个代码的思想,我们进行预处理

每个数代表这一列到这个数位置截止,累加和。

1  2  3  4

0  0  4  6

1  3  2  7

0  1  1  4

然后,我们枚举每一列的起点和终点分别为第0,1,2,3行

然后压缩成一维来做

比如求1-3行的这个矩形,我们拿0和3行减一下就行了

0-1,1-2,1-3,4-4=-1,-1,-2,0就是1-3行压缩后的结果

然后按一维dp来做就好
 

public class SubMatrixMaxSum {
	public static int maxSum(int[][] m) {
		if (m == null || m.length == 0 || m[0].length == 0) {
			return 0;
		}
		int max = Integer.MIN_VALUE;
		int cur = 0;
		int[] s = null; // 累加数组
		for (int i = 0; i != m.length; i++) {
			s = new int[m[0].length];
			for (int j = i; j != m.length; j++) {
				cur = 0;
				for (int k = 0; k != s.length; k++) {
					s[k] += m[j][k];
					cur += s[k];
					max = Math.max(max, cur);
					cur = cur < 0 ? 0 : cur;
				}
			}
		}
		return max;
	}

	public static void main(String[] args) {
		int[][] matrix = { { -90, 48, 78 }, { 64, -40, 64 }, { -81, -7, 66 } };
		System.out.println(maxSum(matrix));

	}

}

子数组的最大累乘积

题目:

给定一个double类型的数组arr,其中的元素可正、可负、可为0。返回子数组累乘的最大乘积。

思路:

假设以arr[i-1]结尾的数组最小累乘积为min,最大累乘积为max,那么以arr[i]结尾的数组的最大累乘积可能有三种情况。

  • max*arr[i]//本身乘之前的最大累乘
  • min*arr[i]//可能是负负得正变成最大的
  • arr[i]//可能就是它本身,比如之前的max小于1

public class SubArrayMaxProduct {

	public static double maxProduct(double[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		double max = arr[0];
		double min = arr[0];
		double res = arr[0];
		double maxEnd = 0;
		double minEnd = 0;
		for (int i = 1; i < arr.length; ++i) {
			maxEnd = max * arr[i];
			minEnd = min * arr[i];
			max = Math.max(Math.max(maxEnd, minEnd), arr[i]);
			min = Math.min(Math.min(maxEnd, minEnd), arr[i]);
			res = Math.max(res, max);
		}
		return res;
	}

	public static void main(String[] args) {
		double[] arr = { -2.5, 4, 0, 3, 0.5, 8, -1 };
		System.out.println(maxProduct(arr));

	}

}

 

  • 110
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 33
    评论
评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兔老大RabbitMQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值