Skip to content

Commit cf79871

Browse files
authored
feat: add Kadane's Algorithm and Deque data structure with comprehensive tests and documentation (#552)
1 parent 8ec0781 commit cf79871

File tree

5 files changed

+1080
-0
lines changed

5 files changed

+1080
-0
lines changed
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
using Algorithms.Other;
2+
using NUnit.Framework;
3+
using System;
4+
5+
namespace Algorithms.Tests.Other;
6+
7+
/// <summary>
8+
/// Comprehensive test suite for Kadane's Algorithm implementation.
9+
/// Tests cover various scenarios including:
10+
/// - Arrays with all positive numbers
11+
/// - Arrays with mixed positive and negative numbers
12+
/// - Arrays with all negative numbers
13+
/// - Edge cases (single element, empty array, null array)
14+
/// - Index tracking functionality
15+
/// - Long integer support for large numbers
16+
/// </summary>
17+
public static class KadanesAlgorithmTests
18+
{
19+
[Test]
20+
public static void FindMaximumSubarraySum_WithPositiveNumbers_ReturnsCorrectSum()
21+
{
22+
// Arrange: When all numbers are positive, the entire array is the maximum subarray
23+
int[] array = { 1, 2, 3, 4, 5 };
24+
25+
// Act
26+
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);
27+
28+
// Assert: Sum of all elements = 1 + 2 + 3 + 4 + 5 = 15
29+
Assert.That(result, Is.EqualTo(15));
30+
}
31+
32+
[Test]
33+
public static void FindMaximumSubarraySum_WithMixedNumbers_ReturnsCorrectSum()
34+
{
35+
// Arrange: Classic example with mixed positive and negative numbers
36+
// The maximum subarray is [4, -1, 2, 1] starting at index 3
37+
int[] array = { -2, 1, -3, 4, -1, 2, 1, -5, 4 };
38+
39+
// Act
40+
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);
41+
42+
// Assert: Maximum sum is 4 + (-1) + 2 + 1 = 6
43+
Assert.That(result, Is.EqualTo(6)); // Subarray [4, -1, 2, 1]
44+
}
45+
46+
[Test]
47+
public static void FindMaximumSubarraySum_WithAllNegativeNumbers_ReturnsLargestNegative()
48+
{
49+
// Arrange: When all numbers are negative, the algorithm returns the least negative number
50+
// This represents a subarray of length 1 containing the largest (least negative) element
51+
int[] array = { -5, -2, -8, -1, -4 };
52+
53+
// Act
54+
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);
55+
56+
// Assert: -1 is the largest (least negative) number in the array
57+
Assert.That(result, Is.EqualTo(-1));
58+
}
59+
60+
[Test]
61+
public static void FindMaximumSubarraySum_WithSingleElement_ReturnsThatElement()
62+
{
63+
// Arrange: Edge case with only one element
64+
// The only possible subarray is the element itself
65+
int[] array = { 42 };
66+
67+
// Act
68+
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);
69+
70+
// Assert: The single element is both the subarray and its sum
71+
Assert.That(result, Is.EqualTo(42));
72+
}
73+
74+
[Test]
75+
public static void FindMaximumSubarraySum_WithNullArray_ThrowsArgumentException()
76+
{
77+
// Arrange: Test defensive programming - null input validation
78+
int[]? array = null;
79+
80+
// Act & Assert: Should throw ArgumentException for null input
81+
Assert.Throws<ArgumentException>(() => KadanesAlgorithm.FindMaximumSubarraySum(array!));
82+
}
83+
84+
[Test]
85+
public static void FindMaximumSubarraySum_WithEmptyArray_ThrowsArgumentException()
86+
{
87+
// Arrange
88+
int[] array = Array.Empty<int>();
89+
90+
// Act & Assert
91+
Assert.Throws<ArgumentException>(() => KadanesAlgorithm.FindMaximumSubarraySum(array));
92+
}
93+
94+
[Test]
95+
public static void FindMaximumSubarraySum_WithAlternatingNumbers_ReturnsCorrectSum()
96+
{
97+
// Arrange: Alternating positive and negative numbers
98+
// Despite negative values, the entire array gives the maximum sum
99+
int[] array = { 5, -3, 5, -3, 5 };
100+
101+
// Act
102+
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);
103+
104+
// Assert: Sum of entire array = 5 - 3 + 5 - 3 + 5 = 9
105+
Assert.That(result, Is.EqualTo(9)); // Entire array
106+
}
107+
108+
[Test]
109+
public static void FindMaximumSubarrayWithIndices_ReturnsCorrectIndices()
110+
{
111+
// Arrange: Test the variant that returns indices of the maximum subarray
112+
// Array: [-2, 1, -3, 4, -1, 2, 1, -5, 4]
113+
// Index: 0 1 2 3 4 5 6 7 8
114+
int[] array = { -2, 1, -3, 4, -1, 2, 1, -5, 4 };
115+
116+
// Act
117+
var (maxSum, startIndex, endIndex) = KadanesAlgorithm.FindMaximumSubarrayWithIndices(array);
118+
119+
// Assert: Maximum subarray is [4, -1, 2, 1] from index 3 to 6
120+
Assert.That(maxSum, Is.EqualTo(6));
121+
Assert.That(startIndex, Is.EqualTo(3));
122+
Assert.That(endIndex, Is.EqualTo(6));
123+
}
124+
125+
[Test]
126+
public static void FindMaximumSubarrayWithIndices_WithSingleElement_ReturnsZeroIndices()
127+
{
128+
// Arrange
129+
int[] array = { 10 };
130+
131+
// Act
132+
var (maxSum, startIndex, endIndex) = KadanesAlgorithm.FindMaximumSubarrayWithIndices(array);
133+
134+
// Assert
135+
Assert.That(maxSum, Is.EqualTo(10));
136+
Assert.That(startIndex, Is.EqualTo(0));
137+
Assert.That(endIndex, Is.EqualTo(0));
138+
}
139+
140+
[Test]
141+
public static void FindMaximumSubarrayWithIndices_WithNullArray_ThrowsArgumentException()
142+
{
143+
// Arrange
144+
int[]? array = null;
145+
146+
// Act & Assert
147+
Assert.Throws<ArgumentException>(() => KadanesAlgorithm.FindMaximumSubarrayWithIndices(array!));
148+
}
149+
150+
[Test]
151+
public static void FindMaximumSubarraySum_WithLongArray_ReturnsCorrectSum()
152+
{
153+
// Arrange: Test the long integer overload with same values as int test
154+
// Verifies that the algorithm works correctly with long data type
155+
long[] array = { -2L, 1L, -3L, 4L, -1L, 2L, 1L, -5L, 4L };
156+
157+
// Act
158+
long result = KadanesAlgorithm.FindMaximumSubarraySum(array);
159+
160+
// Assert: Should produce same result as int version
161+
Assert.That(result, Is.EqualTo(6L));
162+
}
163+
164+
[Test]
165+
public static void FindMaximumSubarraySum_WithLargeLongNumbers_ReturnsCorrectSum()
166+
{
167+
// Arrange: Test with large numbers that would overflow int type
168+
// This demonstrates why the long overload is necessary
169+
// Sum would be 1,500,000,000 which fits in long but is near int.MaxValue
170+
long[] array = { 1000000000L, -500000000L, 1000000000L };
171+
172+
// Act
173+
long result = KadanesAlgorithm.FindMaximumSubarraySum(array);
174+
175+
// Assert: Entire array sum = 1,000,000,000 - 500,000,000 + 1,000,000,000 = 1,500,000,000
176+
Assert.That(result, Is.EqualTo(1500000000L));
177+
}
178+
179+
[Test]
180+
public static void FindMaximumSubarraySum_WithLongNullArray_ThrowsArgumentException()
181+
{
182+
// Arrange
183+
long[]? array = null;
184+
185+
// Act & Assert
186+
Assert.Throws<ArgumentException>(() => KadanesAlgorithm.FindMaximumSubarraySum(array!));
187+
}
188+
189+
[Test]
190+
public static void FindMaximumSubarraySum_WithZeros_ReturnsZero()
191+
{
192+
// Arrange: Edge case with all zeros
193+
// Any subarray will have sum of 0
194+
int[] array = { 0, 0, 0, 0 };
195+
196+
// Act
197+
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);
198+
199+
// Assert: Maximum sum is 0
200+
Assert.That(result, Is.EqualTo(0));
201+
}
202+
203+
[Test]
204+
public static void FindMaximumSubarraySum_WithMixedZerosAndNegatives_ReturnsZero()
205+
{
206+
// Arrange: Mix of zeros and negative numbers
207+
// The best subarray is any single zero (or multiple zeros)
208+
int[] array = { -5, 0, -3, 0, -2 };
209+
210+
// Act
211+
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);
212+
213+
// Assert: Zero is better than any negative number
214+
Assert.That(result, Is.EqualTo(0));
215+
}
216+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
namespace Algorithms.Other;
2+
3+
/// <summary>
4+
/// Kadane's Algorithm is used to find the maximum sum of a contiguous subarray
5+
/// within a one-dimensional array of numbers. It has a time complexity of O(n).
6+
/// This algorithm is a classic example of dynamic programming.
7+
/// Reference: "Introduction to Algorithms" by Cormen, Leiserson, Rivest, and Stein (CLRS).
8+
/// </summary>
9+
public static class KadanesAlgorithm
10+
{
11+
/// <summary>
12+
/// Finds the maximum sum of a contiguous subarray using Kadane's Algorithm.
13+
/// The algorithm works by maintaining two variables:
14+
/// - maxSoFar: The maximum sum found so far (global maximum)
15+
/// - maxEndingHere: The maximum sum of subarray ending at current position (local maximum)
16+
/// At each position, we decide whether to extend the existing subarray or start a new one.
17+
/// </summary>
18+
/// <param name="array">The input array of integers.</param>
19+
/// <returns>The maximum sum of a contiguous subarray.</returns>
20+
/// <exception cref="ArgumentException">Thrown when the input array is null or empty.</exception>
21+
/// <example>
22+
/// Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4].
23+
/// Output: 6 (subarray [4, -1, 2, 1]).
24+
/// </example>
25+
public static int FindMaximumSubarraySum(int[] array)
26+
{
27+
// Validate input to ensure array is not null or empty
28+
if (array == null || array.Length == 0)
29+
{
30+
throw new ArgumentException("Array cannot be null or empty.", nameof(array));
31+
}
32+
33+
// Initialize both variables with the first element
34+
// maxSoFar tracks the best sum we've seen across all subarrays
35+
int maxSoFar = array[0];
36+
37+
// maxEndingHere tracks the best sum ending at the current position
38+
int maxEndingHere = array[0];
39+
40+
// Iterate through the array starting from the second element
41+
for (int i = 1; i < array.Length; i++)
42+
{
43+
// Key decision: Either extend the current subarray or start fresh
44+
// If adding current element to existing sum is worse than the element alone,
45+
// it's better to start a new subarray from current element
46+
maxEndingHere = Math.Max(array[i], maxEndingHere + array[i]);
47+
48+
// Update the global maximum if current subarray sum is better
49+
maxSoFar = Math.Max(maxSoFar, maxEndingHere);
50+
}
51+
52+
return maxSoFar;
53+
}
54+
55+
/// <summary>
56+
/// Finds the maximum sum of a contiguous subarray and returns the start and end indices.
57+
/// This variant tracks the indices of the maximum subarray in addition to the sum.
58+
/// Useful when you need to know which elements form the maximum subarray.
59+
/// </summary>
60+
/// <param name="array">The input array of integers.</param>
61+
/// <returns>A tuple containing the maximum sum, start index, and end index.</returns>
62+
/// <exception cref="ArgumentException">Thrown when the input array is null or empty.</exception>
63+
/// <example>
64+
/// Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4].
65+
/// Output: (MaxSum: 6, StartIndex: 3, EndIndex: 6).
66+
/// The subarray is [4, -1, 2, 1].
67+
/// </example>
68+
public static (int MaxSum, int StartIndex, int EndIndex) FindMaximumSubarrayWithIndices(int[] array)
69+
{
70+
// Validate input
71+
if (array == null || array.Length == 0)
72+
{
73+
throw new ArgumentException("Array cannot be null or empty.", nameof(array));
74+
}
75+
76+
// Initialize tracking variables
77+
int maxSoFar = array[0]; // Global maximum sum
78+
int maxEndingHere = array[0]; // Local maximum sum ending at current position
79+
int start = 0; // Start index of the maximum subarray
80+
int end = 0; // End index of the maximum subarray
81+
int tempStart = 0; // Temporary start index for current subarray
82+
83+
// Process each element starting from index 1
84+
for (int i = 1; i < array.Length; i++)
85+
{
86+
// Decide whether to extend current subarray or start a new one
87+
if (array[i] > maxEndingHere + array[i])
88+
{
89+
// Starting fresh from current element is better
90+
maxEndingHere = array[i];
91+
tempStart = i; // Mark this as potential start of new subarray
92+
}
93+
else
94+
{
95+
// Extending the current subarray is better
96+
maxEndingHere = maxEndingHere + array[i];
97+
}
98+
99+
// Update global maximum and indices if we found a better subarray
100+
if (maxEndingHere > maxSoFar)
101+
{
102+
maxSoFar = maxEndingHere;
103+
start = tempStart; // Commit the start index
104+
end = i; // Current position is the end
105+
}
106+
}
107+
108+
return (maxSoFar, start, end);
109+
}
110+
111+
/// <summary>
112+
/// Finds the maximum sum of a contiguous subarray using Kadane's Algorithm for long integers.
113+
/// This overload handles larger numbers that exceed int range (up to 2^63 - 1).
114+
/// The algorithm logic is identical to the int version but uses long arithmetic.
115+
/// </summary>
116+
/// <param name="array">The input array of long integers.</param>
117+
/// <returns>The maximum sum of a contiguous subarray.</returns>
118+
/// <exception cref="ArgumentException">Thrown when the input array is null or empty.</exception>
119+
/// <example>
120+
/// Input: [1000000000L, -500000000L, 1000000000L].
121+
/// Output: 1500000000L (entire array).
122+
/// </example>
123+
public static long FindMaximumSubarraySum(long[] array)
124+
{
125+
// Validate input
126+
if (array == null || array.Length == 0)
127+
{
128+
throw new ArgumentException("Array cannot be null or empty.", nameof(array));
129+
}
130+
131+
// Initialize with first element (using long arithmetic)
132+
long maxSoFar = array[0];
133+
long maxEndingHere = array[0];
134+
135+
// Apply Kadane's algorithm with long values
136+
for (int i = 1; i < array.Length; i++)
137+
{
138+
// Decide: extend current subarray or start new one
139+
maxEndingHere = Math.Max(array[i], maxEndingHere + array[i]);
140+
141+
// Update global maximum
142+
maxSoFar = Math.Max(maxSoFar, maxEndingHere);
143+
}
144+
145+
return maxSoFar;
146+
}
147+
}

0 commit comments

Comments
 (0)