求数组中和最大的子数组(数组中和最大的子串)
2012-10-30 11:22
232 查看
某公司面试题,回来找的答案这个是最好的。
又一个经典问题,对于一个包含负值的数字串array[1...n],要找到他的一个子串array[i...j](0<=i<=j<=n),使得在array的所有子串中,array[i...j]的和最大。
这里我们需要注意子串和子序列之间的区别。子串是指数组中连续的若干个元素,而子序列只要求各元素的顺序与其在数组中一致,而没有连续的要求。对于一个元素数为n的数组,其含有2^n个子序列和n(n+1)/2个子串。如果使用穷举法,则至少需要O(n^2)的时间才能得到答案。卡耐基梅隆大学的Jay Kadane的给出了一个线性时间算法,我们就来看看,如何在线性时间内解决最大子串和问题。
要说明Kadane算法的正确性,需要两个结论。首先,对于array[1...n],如果array[i...j]就是满足和最大的子串,那么对于任何k(i<=k<=j),我们有array[i...k]的和大于0。因为如果存在k使得array[i...k]的和小于0,那么我们就有array[k+1...j]的和大于array[i...j],这与我们假设的array[i...j]就是array中和最大子串矛盾。
其次,我们可以将数组从左到右分割为若干子串,使得除了最后一个子串之外,其余子串的各元素之和小于0,且对于所有子串array[i...j]和任意k(i<=k<j),有array[i...k]的和大于0。此时我们要说明的是,满足条件的和最大子串,只能是上述某个子串的前缀,而不可能跨越多个子串。我们假设array[p...q],是array的和最大子串,且array[p...q],跨越了array[i...j],array[j+1...k]。根据我们的分组方式,存在i<=m<j使得array[i...m]的和是array[i...j]中的最大值,存在j+1<=n<k使得array[j+1...n]的和是array[j+1...k]的最大值。由于array[m+1...j]使得array[i...j]的和小于0。此时我们可以比较array[i...m]和array[j+1...n],如果array[i...m]的和大于array[j+1...n]则array[i...m]>array[p...q],否array[j+1...n]>array[p...q],无论谁大,我们都可以找到比array[p...q]和更大的子串,这与我们的假设矛盾,所以满足条件的array[p...q]不可能跨越两个子串。对于跨越更多子串的情况,由于各子串的和均为负值,所以同样可以证明存在和更大的非跨越子串的存在。对于单元素和最大的特例,该结论也适用。
根据上述结论,我们就得到了Kadane算法的执行流程,从头到尾遍历目标数组,将数组分割为满足上述条件的子串,同时得到各子串的最大前缀和,然后比较各子串的最大前缀和,得到最终答案。我们以array={−2, 1, −3, 4, −1, 2, 1, −5, 4}为例,来简单说明一下算法步骤。通过遍历,可以将数组分割为如下3个子串(-2),(1,-3),(4,-1,2,1,-5,4),这里对于(-2)这样的情况,单独分为一组。各子串的最大前缀和为-2,1,6,所以目标串的最大子串和为6。
下面是实现代码:
这里我们需要注意,对于数组元素全为负的情况,由于不满足上述的两条结论,所以Kadane算法无法给出正确答案。
该问题是1977年Ulf Grenander提出的一个数字图像方面的问题,1984年Jay Kadane才给出了这个优美的解决方案。有些问题,看似解法简单,但是实际上其原理,要比代码复杂得多。
支持全部都是负数的修改C语言实现如下:
[cpp]
view plaincopyprint?
#include <stdio.h>
#include <stdlib.h>
void printArray(int * array, int len);
int subArrayMaxSum(int *a, int len, int* start, int * end);
int main(int argc, char* argv[], char* env[])
{
int arrays[][9] = {
{-2, 1, -3, 4, -1, 2, 1, -5, 4},
{-2, -1, -3, -4, -1, -2, -1, -5, -4},
{-2, 1, -3, -4, 10, -2, 1, -5, 4, }
};
int len;
int left;
int right;
int maxSum;
int i;
int n = sizeof(arrays)/sizeof(arrays[0]);
for(i=0; i<n; i++) {
len = sizeof(arrays[i])/sizeof(arrays[i][0]);
maxSum = subArrayMaxSum(arrays[i], len, &left, &right);
printf("array:");
printArray(arrays[i], len);
printf(".\nThe max sum sub array is \n");
printArray(arrays[i]+left, right-left+1);
printf("\n the max sum is %d \n\n\n", maxSum);
}
return 0;
}
void printArray(int * array, int len) {
int i;
printf("[");
for(i=0; i<len; i++){
printf("%d, ", array[i]);
}
printf("]");
}
int subArrayMaxSum(int *array, int len, int* start, int* end){
int left;
int right;
int cur_left;
int max_sum;
int cur_sum;
int i;
left = 0;
right = 0;
cur_left = 0;
max_sum = 0;
cur_sum = 0;
for(i=0; i<len; i++) {
if(cur_sum < 0) { //开始新的子数组
cur_sum = array[i];
cur_left = i;
}else {
if(cur_sum > max_sum) { //更新最大子数组区间和最大子和。
max_sum = cur_sum;
if(left != cur_left) left = cur_left;
right = i-1;
}
cur_sum += array[i];
}
}
if(max_sum == 0) { // 元素全部为负
max_sum = array[0];
for(i=1; i<len; i++) {
if(array[i] > max_sum) {
max_sum = array[i];
left = i;
}
}
right = left;
}
*start = left;
*end = right;
return max_sum;
}
测试结果:
又一个经典问题,对于一个包含负值的数字串array[1...n],要找到他的一个子串array[i...j](0<=i<=j<=n),使得在array的所有子串中,array[i...j]的和最大。
这里我们需要注意子串和子序列之间的区别。子串是指数组中连续的若干个元素,而子序列只要求各元素的顺序与其在数组中一致,而没有连续的要求。对于一个元素数为n的数组,其含有2^n个子序列和n(n+1)/2个子串。如果使用穷举法,则至少需要O(n^2)的时间才能得到答案。卡耐基梅隆大学的Jay Kadane的给出了一个线性时间算法,我们就来看看,如何在线性时间内解决最大子串和问题。
要说明Kadane算法的正确性,需要两个结论。首先,对于array[1...n],如果array[i...j]就是满足和最大的子串,那么对于任何k(i<=k<=j),我们有array[i...k]的和大于0。因为如果存在k使得array[i...k]的和小于0,那么我们就有array[k+1...j]的和大于array[i...j],这与我们假设的array[i...j]就是array中和最大子串矛盾。
其次,我们可以将数组从左到右分割为若干子串,使得除了最后一个子串之外,其余子串的各元素之和小于0,且对于所有子串array[i...j]和任意k(i<=k<j),有array[i...k]的和大于0。此时我们要说明的是,满足条件的和最大子串,只能是上述某个子串的前缀,而不可能跨越多个子串。我们假设array[p...q],是array的和最大子串,且array[p...q],跨越了array[i...j],array[j+1...k]。根据我们的分组方式,存在i<=m<j使得array[i...m]的和是array[i...j]中的最大值,存在j+1<=n<k使得array[j+1...n]的和是array[j+1...k]的最大值。由于array[m+1...j]使得array[i...j]的和小于0。此时我们可以比较array[i...m]和array[j+1...n],如果array[i...m]的和大于array[j+1...n]则array[i...m]>array[p...q],否array[j+1...n]>array[p...q],无论谁大,我们都可以找到比array[p...q]和更大的子串,这与我们的假设矛盾,所以满足条件的array[p...q]不可能跨越两个子串。对于跨越更多子串的情况,由于各子串的和均为负值,所以同样可以证明存在和更大的非跨越子串的存在。对于单元素和最大的特例,该结论也适用。
根据上述结论,我们就得到了Kadane算法的执行流程,从头到尾遍历目标数组,将数组分割为满足上述条件的子串,同时得到各子串的最大前缀和,然后比较各子串的最大前缀和,得到最终答案。我们以array={−2, 1, −3, 4, −1, 2, 1, −5, 4}为例,来简单说明一下算法步骤。通过遍历,可以将数组分割为如下3个子串(-2),(1,-3),(4,-1,2,1,-5,4),这里对于(-2)这样的情况,单独分为一组。各子串的最大前缀和为-2,1,6,所以目标串的最大子串和为6。
下面是实现代码:
int Kadane(const int array[], size_t length, unsigned int& left, unsigned int& right) { unsigned int i, cur_left, cur_right; int cur_max, max; cur_max = max = left = right = cur_left = cur_right = 0; for(i = 0; i < length; ++i) { cur_max += array[i]; if(cur_max > 0) { cur_right = i; if(max < cur_max) { max = cur_max; left = cur_left; right = cur_right; } } else { cur_max = 0; cur_left = cur_right = i + 1; } } return max; }
这里我们需要注意,对于数组元素全为负的情况,由于不满足上述的两条结论,所以Kadane算法无法给出正确答案。
该问题是1977年Ulf Grenander提出的一个数字图像方面的问题,1984年Jay Kadane才给出了这个优美的解决方案。有些问题,看似解法简单,但是实际上其原理,要比代码复杂得多。
支持全部都是负数的修改C语言实现如下:
[cpp]
view plaincopyprint?
#include <stdio.h>
#include <stdlib.h>
void printArray(int * array, int len);
int subArrayMaxSum(int *a, int len, int* start, int * end);
int main(int argc, char* argv[], char* env[])
{
int arrays[][9] = {
{-2, 1, -3, 4, -1, 2, 1, -5, 4},
{-2, -1, -3, -4, -1, -2, -1, -5, -4},
{-2, 1, -3, -4, 10, -2, 1, -5, 4, }
};
int len;
int left;
int right;
int maxSum;
int i;
int n = sizeof(arrays)/sizeof(arrays[0]);
for(i=0; i<n; i++) {
len = sizeof(arrays[i])/sizeof(arrays[i][0]);
maxSum = subArrayMaxSum(arrays[i], len, &left, &right);
printf("array:");
printArray(arrays[i], len);
printf(".\nThe max sum sub array is \n");
printArray(arrays[i]+left, right-left+1);
printf("\n the max sum is %d \n\n\n", maxSum);
}
return 0;
}
void printArray(int * array, int len) {
int i;
printf("[");
for(i=0; i<len; i++){
printf("%d, ", array[i]);
}
printf("]");
}
int subArrayMaxSum(int *array, int len, int* start, int* end){
int left;
int right;
int cur_left;
int max_sum;
int cur_sum;
int i;
left = 0;
right = 0;
cur_left = 0;
max_sum = 0;
cur_sum = 0;
for(i=0; i<len; i++) {
if(cur_sum < 0) { //开始新的子数组
cur_sum = array[i];
cur_left = i;
}else {
if(cur_sum > max_sum) { //更新最大子数组区间和最大子和。
max_sum = cur_sum;
if(left != cur_left) left = cur_left;
right = i-1;
}
cur_sum += array[i];
}
}
if(max_sum == 0) { // 元素全部为负
max_sum = array[0];
for(i=1; i<len; i++) {
if(array[i] > max_sum) {
max_sum = array[i];
left = i;
}
}
right = left;
}
*start = left;
*end = right;
return max_sum;
}
#include <stdio.h> #include <stdlib.h> void printArray(int * array, int len); int subArrayMaxSum(int *a, int len, int* start, int * end); int main(int argc, char* argv[], char* env[]) { int arrays[][9] = { {-2, 1, -3, 4, -1, 2, 1, -5, 4}, {-2, -1, -3, -4, -1, -2, -1, -5, -4}, {-2, 1, -3, -4, 10, -2, 1, -5, 4, } }; int len; int left; int right; int maxSum; int i; int n = sizeof(arrays)/sizeof(arrays[0]); for(i=0; i<n; i++) { len = sizeof(arrays[i])/sizeof(arrays[i][0]); maxSum = subArrayMaxSum(arrays[i], len, &left, &right); printf("array:"); printArray(arrays[i], len); printf(".\nThe max sum sub array is \n"); printArray(arrays[i]+left, right-left+1); printf("\n the max sum is %d \n\n\n", maxSum); } return 0; } void printArray(int * array, int len) { int i; printf("["); for(i=0; i<len; i++){ printf("%d, ", array[i]); } printf("]"); } int subArrayMaxSum(int *array, int len, int* start, int* end){ int left; int right; int cur_left; int max_sum; int cur_sum; int i; left = 0; right = 0; cur_left = 0; max_sum = 0; cur_sum = 0; for(i=0; i<len; i++) { if(cur_sum < 0) { //开始新的子数组 cur_sum = array[i]; cur_left = i; }else { if(cur_sum > max_sum) { //更新最大子数组区间和最大子和。 max_sum = cur_sum; if(left != cur_left) left = cur_left; right = i-1; } cur_sum += array[i]; } } if(max_sum == 0) { // 元素全部为负 max_sum = array[0]; for(i=1; i<len; i++) { if(array[i] > max_sum) { max_sum = array[i]; left = i; } } right = left; } *start = left; *end = right; return max_sum; }
测试结果:
相关文章推荐
- C - 求整数数组中和最大子串
- 求数组中和最大的子数组(数组中和最大的子串)
- 最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和
- 最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和
- 关于如何查找数组中和最大的子数组
- bc+变化后与变化前相减成一个数组,然后就是求最大子串和的问题了
- 给一个数组,求他的子串,使该子串的和最大
- ] 找工作知识储备(2)---数组字符串那些经典算法:最大子序列和,最长递增子序列,最长公共子串,最长公共子序列,字符串编辑距离,最长不重复子串,最长回文子串
- 转帖-POJ 2774 后缀数组 题目要求:求s1,s2的最大子串
- 编程之美--求数组连续子串最大和
- 动态规划求数组中和最大字串的值 以及 字符串的循环移位(要求空间复杂度O(1),时间复杂度O(n))
- 求数组中最大和为零的子串 滴滴打车2016笔试题目
- 【各种最...】最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和
- 求数组中和最大的子数组与始末下标
- 一个整形数组中有正数也有负数,数组中连续一个或多个组成子数组,每个子数组都有一个和,求所有子数组中和的最大值
- 寻找数组的子数组中和的最大值
- 寻找数组中和最大的子序列
- java中数组求最大值,最小值,中和,平均
- 编程珠玑 用后缀数组求字符串的最大重复子串
- 一个正负数组,求其最大的连续子串和