最大子段和
令f[i]为从莫一点开始到a[i]为止最大的子段和,则有以下转移方程:
\[f_i = \max(f_{i-1} + a[i], a[i])\]
因为只需遍历一次数组就可求出,所以复杂度为\(O(n)\)
package org.sequix.homework3;
/**
* 提供求最大子段和的工具类。
*
* @author sequix
* @version 0.0.1
* @since 2018/03/26
*/
public class MaxSubArray {
/**
* 返回数组的最大子段和。
* f[i] 到a[i]为止最大的子段和。
* f[i] = max(f[i-1]+a[i], a[i])
*
* @param arr 被求数组
* @return 最大子段和
*/
public static int msa(int[] arr) {
if (arr.length == 0) {
throw new IllegalArgumentException("expected a non-empty array");
}
int cur = arr[0];
int ans = cur;
for (int i = 1, len = arr.length; i < len; ++i) {
cur += arr[i];
if (arr[i] > cur) cur = arr[i];
if (cur > ans) ans = cur;
}
return ans;
}
}
测试
为了方便的测试,这里另写了一个工具类TestUtils。
在TestUtils.msa中,以另一种方式求最大子段和。其枚举所有的子段,选出最大的。此解法时间复杂度为\(O(n^2)\),但其正确性显而易见,所以用于对拍测试。
package org.sequix.homework3;
import java.util.Random;
/**
* 提供测试用工具函数。
* @author squix
* @since 2018/03/19
*/
class TestUtils {
private static Random random = new Random();
/**
* 生成[min, max]范围内的随机数。
* @param min 最小值,最小可为Integer.MIN_VALUE
* @param max 最大值,最大可为Integer.MAX_VALUE
* @return 生成的随机数
*/
static int randomInteger(int min, int max) {
long num = (long) max - min + 1;
long kth = (long) (random.nextDouble() * num);
long ret = (long) min + kth;
return (int) ret;
}
/**
* 生成随机数数组。
* @param size 数组大小
* @param minElement 元素最小值
* @param maxElement 元素最大值
* @return 生成的数组
*/
static int[] generateRandomArray(int size, int minElement, int maxElement) {
int[] arr = new int[size];
for (int i = 0; i < size; ++i)
arr[i] = TestUtils.randomInteger(minElement, maxElement);
return arr;
}
/**
* 返回数组的最大子段和。用于和MaxSubArry.msa()对拍。
* @param arr 被求数组
* @return 最大子段和
*/
static int msa(int[] arr) {
int length = arr.length;
int[] sum = new int[arr.length];
sum[0] = arr[0];
for (int i = 1; i < length; ++i) {
sum[i] = sum[i-1] + arr[i];
}
int ans = arr[0];
for (int left = 0; left < length; ++left) {
for (int right = left; right < length; ++right) {
int tmp = sum[right];
if (left > 0) tmp -= sum[left-1];
if (ans < tmp) ans = tmp;
}
}
return ans;
}
}
具体的测试类如下:
package org.sequix.homework3;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
/**
* MaxSubArray 的测试类。
*
* @author sequix
* @version 0.0.1
* @since 2018/03/26
*/
@RunWith(JUnitPlatform.class)
public class MaxSubArrayTest {
@Test(expected=IllegalArgumentException.class)
public void testEmptyArray() {
int[] original = new int[0];
MaxSubArray.msa(original);
}
@Test
public void testGeneral() {
int[] original = new int[] {-2, 11, -4, 13, -5, -2};
int expected = TestUtils.msa(original);
assertEquals(expected, MaxSubArray.msa(original));
}
@Test
public void testOnlyNegatives() {
int[] original = TestUtils.generateRandomArray(10000, -10000, -1);
int expected = TestUtils.msa(original);
assertEquals(expected, MaxSubArray.msa(original));
}
@Test
public void testOnlyPositives() {
int[] original = TestUtils.generateRandomArray(10000, 1, 10000);
int expected = TestUtils.msa(original);
assertEquals(expected, MaxSubArray.msa(original));
}
@RepeatedTest(10)
public void testRandom() {
int[] original = TestUtils.generateRandomArray(10000, -10000, 10000);
int expected = TestUtils.msa(original);
assertEquals(expected, MaxSubArray.msa(original));
}
}
测试效果
外链
代码:Github