Kadane's algorithm is a greedy/dynamic programming algorithm that can be used on an array. It is used to calculate the maximum sum subarray ending at a particular position and typically runs in O(n) time.
Motivation
Q: Find a non-empty subarray with the largest sum.
We need to find a group of contiguous elements in an array that result in the maximal sum.
The brute force way to approach this would be to go through every single subarray and calculate the sum, while keeping track of the maximum sum.
For every iteration of our outer for loop, our inner loop does linear work. This makes the complexity O(n2).
defbruteForce(nums):
maxSum = nums[0]for i inrange(len(nums)):
curSum =0for j inrange(i,len(nums)):
curSum += nums[j]
maxSum =max(maxSum, curSum)return maxSum
publicstaticintbruteForce(int[] nums){int maxSum = nums[0];for(int i =0; i < nums.length; i++){int curSum =0;for(int j = i; j < nums.length; j++){
curSum += nums[j];
maxSum =Math.max(maxSum, curSum);}}return maxSum;}
intbruteForce(vector<int>& nums){int maxSum = nums[0];for(int i =0; i < nums.size(); i++){int curSum =0;for(int j = i; j < nums.size(); j++){
curSum += nums[j];
maxSum =max(maxSum, curSum);}}return maxSum;}
functionbruteForce(nums){let maxSum = nums[0];for(letL=0;L< nums.length;L++){let curSum =0;for(letR=L;R< nums.length;R++){
curSum += nums[R];
maxSum = Math.max(maxSum, curSum);}}return maxSum;}
publicstaticintBruteForce(int[] nums){int maxSum = nums[0];for(int i =0; i < nums.Length; i++){int curSum =0;for(int j = i; j < nums.Length; j++){
curSum += nums[j];
maxSum = Math.Max(maxSum, curSum);}}return maxSum;}
funcbruteForce(nums []int)int{
maxSum := nums[0]for i :=0; i <len(nums); i++{
curSum :=0for j := i; j <len(nums); j++{
curSum += nums[j]if curSum > maxSum {
maxSum = curSum
}}}return maxSum
}
funbruteForce(nums: IntArray): Int {var maxSum = nums[0]for(i in nums.indices){var curSum =0for(j in i until nums.size){
curSum += nums[j]
maxSum =max(maxSum, curSum)}}return maxSum
}
funcbruteForce(_ nums:[Int])->Int{var maxSum = nums[0]for i in0..<nums.count {var curSum =0for j in i..<nums.count {
curSum += nums[j]
maxSum =max(maxSum, curSum)}}return maxSum
}
While this approach works, it is not the most efficient. The intuition behind Kadane's algorithm is that:
If all elements in the array are positive, the maximum sum subarray is the entire array.
If we ever have a negative sum subarray, that's the case we want to avoid.
Optimizing with Kadane's Algorithm
Kadane's algorithm tells us that there is a way to calculate the largest sum by only making one pass on the array, bringing the complexity down to linear time. Let's look at how that can be done.
Since we are looking for the largest sum, it is a good idea to avoid negative numbers because we know that contradicts what the question is asking for. Negative numbers will only make our sum smaller.
But sometimes we may need to include a negative number to get the surrounding positive numbers.
For example, the array [6, -2, 7] has a maximum sum of 11. If we exclude the -2, we can't include both 6 and 7.
But that's not always the case. If we have [1, -3, 7], the maximum sum is 7. Including the -3 isn't worth it just to get the 1.
The pattern is that if we ever have a negative subarray sum, we should discard it and start a new subarray. This is because we know that the sum will only get smaller if we include it.
Kadane's algorithm runs one loop.
We keep track of the curSum by adding the current element to it.
Before we add the current element, we check if the curSum is negative. If it is, we reset it to zero.
We initialize the maxSum to the first element in the array. This is technically a subarray of size 1. (We could have initialized it to any other element in the array.)
We update the maxSum by taking the maximum of the current sum and the maximum sum so far.
It's possible that every element in the array is negative. In that case, the maximum sum would be the largest negative number.
defkadanes(nums):
maxSum = nums[0]
curSum =0for n in nums:
curSum =max(curSum,0)
curSum += n
maxSum =max(maxSum, curSum)return maxSum
publicstaticintkadanes(int[] nums){int maxSum = nums[0];int curSum =0;for(int n : nums){
curSum =Math.max(curSum,0);
curSum += n;
maxSum =Math.max(maxSum, curSum);}return maxSum;}
intkadanes(vector<int>& nums){int maxSum = nums[0];int curSum =0;for(int n : nums){
curSum =max(curSum,0);
curSum += n;
maxSum =max(maxSum, curSum);}return maxSum;}
functionkadanes(nums){let maxSum = nums[0];let curSum =0;for(let n of nums){
curSum = Math.max(curSum,0);
curSum += n;
maxSum = Math.max(maxSum, curSum);}return maxSum;}
publicstaticintKadanes(int[] nums){int maxSum = nums[0];int curSum =0;foreach(int n in nums){
curSum = Math.Max(curSum,0);
curSum += n;
maxSum = Math.Max(maxSum, curSum);}return maxSum;}
funckadanes(nums []int)int{
maxSum := nums[0]
curSum :=0for_, n :=range nums {if curSum <0{
curSum =0}
curSum += n
if curSum > maxSum {
maxSum = curSum
}}return maxSum
}
funkadanes(nums: IntArray): Int {var maxSum = nums[0]var curSum =0for(n in nums){
curSum =max(curSum,0)
curSum += n
maxSum =max(maxSum, curSum)}return maxSum
}
funckadanes(_ nums:[Int])->Int{var maxSum = nums[0]var curSum =0for n in nums {
curSum =max(curSum,0)
curSum += n
maxSum =max(maxSum, curSum)}return maxSum
}
Sliding Window
Sometimes, a problem may ask to return the actual subarray containing the largest sum, instead of just the sum itself. Previously, we didn't have two explicit pointers that kept track of the subarray in the previous implementation but we can actually do this by keeping track of a "window". A window in this case denotes a contiguous subarray that does not break our constraint of the sum staying positive.
To do this, we can have a left pointer, L = 0, and a right pointer, R. The left and right pointers define the boundaries of our window (inclusive).
Since we want the subarray with the maximum sum, we can also have two other pointers, maxL and maxR, which keep track of the subarray that contains the maximum sum so far. This way, we don't lose them when we move L and R.
Similar to before, if our current sum becomes negative, we can move our left pointer all the way to our right pointer. This means that our constraint was broken and we remove all elements from the left and start a new window.
defslidingWindow(nums):
maxSum = nums[0]
curSum =0
maxL, maxR =0,0
L =0for R inrange(len(nums)):if curSum <0:
curSum =0
L = R
curSum += nums[R]if curSum > maxSum:
maxSum = curSum
maxL, maxR = L, R
return[maxL, maxR]
publicstaticint[]slidingWindow(int[] nums){int maxSum = nums[0];int curSum =0;int maxL =0, maxR =0;intL=0;for(intR=0;R< nums.length;R++){if(curSum <0){
curSum =0;L=R;}
curSum += nums[R];if(curSum > maxSum){
maxSum = curSum;
maxL =L;
maxR =R;}}returnnewint[]{maxL, maxR};}
vector<int>slidingWindow(vector<int> nums){int maxSum = nums[0];int curSum =0;int maxL =0, maxR =0;int L =0;for(int R =0; R < nums.size(); R++){if(curSum <0){
curSum =0;
L = R;}
curSum += nums[R];if(curSum > maxSum){
maxSum = curSum;
maxL = L;
maxR = R;}}return vector<int>{maxL, maxR};}
functionslidingWindow(nums){let maxSum = nums[0];let curSum =0;let maxL =0, maxR =0;letL=0;for(letR=0;R< nums.length;R++){if(curSum <0){
curSum =0;L=R;}
curSum += nums[R];if(curSum > maxSum){
maxSum = curSum;
maxL =L;
maxR =R;}}return[maxL, maxR];}
publicstaticint[]SlidingWindow(int[] nums){int maxSum = nums[0];int curSum =0;int maxL =0, maxR =0;int L =0;for(int R =0; R < nums.Length; R++){if(curSum <0){
curSum =0;
L = R;}
curSum += nums[R];if(curSum > maxSum){
maxSum = curSum;
maxL = L;
maxR = R;}}returnnewint[]{ maxL, maxR };}
funcslidingWindow(nums []int)[]int{
maxSum := nums[0]
curSum :=0
maxL :=0
maxR :=0
L :=0for R :=0; R <len(nums); R++{if curSum <0{
curSum =0
L = R
}
curSum += nums[R]if curSum > maxSum {
maxSum = curSum
maxL = L
maxR = R
}}return[]int{maxL, maxR}}
funslidingWindow(nums: IntArray): IntArray {var maxSum = nums[0]var curSum =0var maxL =0var maxR =0var L =0for(R in nums.indices){if(curSum <0){
curSum =0
L = R
}
curSum += nums[R]if(curSum > maxSum){
maxSum = curSum
maxL = L
maxR = R
}}returnintArrayOf(maxL, maxR)}
funcslidingWindow(_ nums:[Int])->[Int]{var maxSum = nums[0]var curSum =0var maxL =0, maxR =0varL=0forRin0..<nums.count {if curSum <0{
curSum =0L=R}
curSum += nums[R]if curSum > maxSum {
maxSum = curSum
maxL =L
maxR =R}}return[maxL, maxR]}
Time & Space Complexity
Time
Since we only make one pass through the array, our time complexity is O(n).
Space
With Kadane's algorithm, we only need a few variables to keep track of the maximum sum and the current sum. This makes our space complexity O(1).
Closing Notes
Next, let's formally look at the sliding window technique. There are two variations, fixed sliding window and variable sized sliding window, both of which are useful for different kinds of problems.