文章目錄
- 前綴和介紹
- [LeetCode53-Maximum Subarray](https://leetcode.com/problems/maximum-subarray/submissions/)
- [LintCode44-Minimun Subarray](https://www.lintcode.com/problem/minimum-subarray/description)
- [LintCode138-Subarray Sum](https://www.lintcode.com/problem/subarray-sum/description)
- [LintCode139-Subarray Sum Closest](https://www.lintcode.com/problem/subarray-sum-closest/description)
前綴和介紹
-
假設有數組
A
且A.length = n
-
則可新建一前綴和數組
preSumArray
其長度爲n+1
-
前綴和數組定義如下:
preSumArray[0] = 0
preSumArray[1] = A[0]
preSumArray[2] = A[0] +A[1]
............
preSumArray[i] = A[0] + A[1] + ........ + A[i-1]
.........................
preSumArray[n] = A[0] + A[1] + ........ + A[n-1]
-
前綴和數組有如下性質
sum(i~j) = preSumArray[j+1] - preSumArray[i]
preSumArray[j] - preSumArray[i] = Sum(i~(j - 1))
-
這個也很容易證明嘛:
preSumArray[j+1] = A[0] + A[1] + ..+ A[i-1] + ... + A[j]
preSumArray[i] = A[0] + A[1]+ .... + A[i-1]
- 二者之差等於:
A[i] + A[i+1] + .... + A[j] = sum(i~j)
-
顯然,構造前綴和數組的時間和空間複雜度均爲
O(n)
LeetCode53-Maximum Subarray
題意
- 給定數組,求解此數組中連續子數組之和最大的,返回這個最大值
思路
- 先求解該數組的前綴和數組
- 然後遍歷前綴和數組
- 分別維護一個最大值和最小值,因爲最大減最小的和最大嘛,這樣遍歷完成後得到一段連續數組和的最大值
代碼
public int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0){
return 0;
}
//0. calc the preSumArray
int n = nums.length;
int[] preSumArray = new int[n+1];
preSumArray[0] = 0;
int sum = 0;
for (int i = 0; i < n; i++) {
sum += nums[i];
preSumArray[i+1] = sum;
}
//1. find the max sum of the SubArray in the nums
//1.1 最大和的子數組一定等於 最大值減去最小值
int maxSum = Integer.MIN_VALUE;
int minSum = 0;
//1.2 遍歷preSumArray
for (int i = 1; i < preSumArray.length; i++) {
maxSum = Math.max(maxSum, preSumArray[i] - minSum);
minSum = Math.min(minSum, preSumArray[i]);
}
return maxSum;
}
LintCode44-Minimun Subarray
題意
- 和上一題完全一樣,只不過這一題求最小值
思路
- 可以先把給定數組的元素取反得到新數組
- 然後利用上一題的做法對新數組求最大值
- 然後返回最大值的相反數
代碼
public static int minSubArray(List<Integer> nums) {
//0.先對給定list的元素取反
int[] A = new int[nums.size()];
int index = 0;
for (Integer num : nums) {
A[index++] = -num;
}
//1.然後對數組A,求解連續子數組和的最大值
//1.1 依然採用前綴和技巧求解
int[] preSumArray = new int[nums.size() + 1];
preSumArray[0] = 0;
int sum = 0;
for (int i = 1; i < preSumArray.length; i++) {
sum += A[i-1];
preSumArray[i] = sum;
}
//1.2 根據前綴和求解最大值
int maxSum = Integer.MIN_VALUE;
int minSum = 0;
for (int i = 1; i < preSumArray.length; i++) {
maxSum = Math.max(maxSum, preSumArray[i] - minSum);
minSum = Math.min(minSum, preSumArray[i]);
}
//2. 最大對最大值取反,返回
return -maxSum;
}
LintCode138-Subarray Sum
題意
給定數組,求解和爲0的子數組,返回子數組的左右端點的下標
思路
- 求出該數組的前綴和數組
- 在前綴和數組中找到兩個元素相等的兩個數即可
- 然後返回其下標
- 關鍵在於前綴和數組是無序的,如何才能找到兩個相同的數,並同時記錄下標信息呢?
- 在數組相關的題目中,經常需要把index和value綁定在一起,有兩種方式可以處理
- 利用
map
- 自定義一個類
Pair
,定義兩個成員變量,分別是index
和value
- 利用
代碼
public List<Integer> subarraySum(int[] nums) {
List<Integer> res = new ArrayList<>();
if (nums == null || nums.length == 0){
return res;
}
//0. 依然計算前綴和,但是需要把前綴和index關聯起來
//0.1 這是爲了後面排序後依然有前綴和的信息
int n = nums.length;
int sum = 0;
//0.2 sum -> index
Map<Integer,Integer> map = new HashMap<>();
map.put(0, 0);
for (int i = 0; i < n ; i++) {
sum += nums[i];
//1. 在前綴和數組建立的過程中去完成查找,有效的解決了重複的sum問題
if (map.containsKey(sum)){
res.add(map.get(sum));
res.add(i);
return res;
}
map.put(sum, i+1);
}
return res;
}
LintCode139-Subarray Sum Closest
題意
- 給定數組,找出和與0最接近的子數組,返回子數組兩端的下標
思路
- 和上題基本一致
- 仍然先求出前綴和數組
- 然後對前綴和數組排序
- 然後依次找前綴和數組中相鄰兩個數的差,記錄兩數之差最小的
- 然後記錄下這兩個數的下標
- 這裏又需要把
value和index
綁定在一起,而且還需要對value
排序,所以這裏使用Pair類
代碼
class LintCode139 {
class Pair{
int index;
int preSum;
public Pair(int index, int preSum){
this.index = index;
this.preSum = preSum;
}
}
public int[] subarraySumClosest(int[] nums) {
int[] res = new int[2];
if (nums == null || nums.length == 0){
return null;
}
//0. 依然計算前綴和,但是需要把前綴和index關聯起來
//0.1 這是爲了後面排序後依然有前綴和的信息
int n = nums.length;
int sum = 0;
Pair[] pairs = new Pair[n + 1];
pairs[0] = new Pair(0, 0);
//0.2 注意這裏按照前綴和數組的下標在進行初始化
for (int i = 1; i < pairs.length; i++) {
sum += nums[i-1];
pairs[i] = new Pair(i, sum);
}
//1. 對pairs按照preSum排序
Arrays.sort(pairs,new Comparator<Pair>(){
@Override
public int compare(Pair o1, Pair o2) {
return o1.preSum - o2.preSum;
}
});
//2. 現在是升序排序,那麼差最小的二者一定是相鄰的二者
int min = Integer.MAX_VALUE;
for (int i = 0; i < n - 1; i++) {
int curMin = pairs[i+1].preSum - pairs[i].preSum;
if (curMin < min){
min = curMin;
//2.1記錄index
if (pairs[i+1].index < pairs[i].index){
res[0] = pairs[i+1].index;
//2.2 -1是因爲 preSum[j] - preSum[i] 表示的是 nums中 index = i 到 index = j - 1的和
// 這裏的index顯然應該是nums中的index,所以需要-1
res[1] = pairs[i].index - 1;
}else {
res[0] = pairs[i].index;
res[1] = pairs[i+1].index - 1;
}
}
}
return res;
}
}
細節
preSum[j] - preSum[i]
表示的是nums
中index = i 到 index = j - 1
的和- 所以最後求解的時候,
index
較大者需要-1
纔是nums中元素對應的下標