Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 216 additions & 0 deletions Algorithms.Tests/Other/KadanesAlgorithmTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
using Algorithms.Other;
using NUnit.Framework;
using System;

namespace Algorithms.Tests.Other;

/// <summary>
/// Comprehensive test suite for Kadane's Algorithm implementation.
/// Tests cover various scenarios including:
/// - Arrays with all positive numbers
/// - Arrays with mixed positive and negative numbers
/// - Arrays with all negative numbers
/// - Edge cases (single element, empty array, null array)
/// - Index tracking functionality
/// - Long integer support for large numbers
/// </summary>
public static class KadanesAlgorithmTests
{
[Test]
public static void FindMaximumSubarraySum_WithPositiveNumbers_ReturnsCorrectSum()
{
// Arrange: When all numbers are positive, the entire array is the maximum subarray
int[] array = { 1, 2, 3, 4, 5 };

// Act
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);

// Assert: Sum of all elements = 1 + 2 + 3 + 4 + 5 = 15
Assert.That(result, Is.EqualTo(15));
}

[Test]
public static void FindMaximumSubarraySum_WithMixedNumbers_ReturnsCorrectSum()
{
// Arrange: Classic example with mixed positive and negative numbers
// The maximum subarray is [4, -1, 2, 1] starting at index 3
int[] array = { -2, 1, -3, 4, -1, 2, 1, -5, 4 };

// Act
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);

// Assert: Maximum sum is 4 + (-1) + 2 + 1 = 6
Assert.That(result, Is.EqualTo(6)); // Subarray [4, -1, 2, 1]
}

[Test]
public static void FindMaximumSubarraySum_WithAllNegativeNumbers_ReturnsLargestNegative()
{
// Arrange: When all numbers are negative, the algorithm returns the least negative number
// This represents a subarray of length 1 containing the largest (least negative) element
int[] array = { -5, -2, -8, -1, -4 };

// Act
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);

// Assert: -1 is the largest (least negative) number in the array
Assert.That(result, Is.EqualTo(-1));
}

[Test]
public static void FindMaximumSubarraySum_WithSingleElement_ReturnsThatElement()
{
// Arrange: Edge case with only one element
// The only possible subarray is the element itself
int[] array = { 42 };

// Act
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);

// Assert: The single element is both the subarray and its sum
Assert.That(result, Is.EqualTo(42));
}

[Test]
public static void FindMaximumSubarraySum_WithNullArray_ThrowsArgumentException()
{
// Arrange: Test defensive programming - null input validation
int[]? array = null;

// Act & Assert: Should throw ArgumentException for null input
Assert.Throws<ArgumentException>(() => KadanesAlgorithm.FindMaximumSubarraySum(array!));
}

[Test]
public static void FindMaximumSubarraySum_WithEmptyArray_ThrowsArgumentException()
{
// Arrange
int[] array = Array.Empty<int>();

// Act & Assert
Assert.Throws<ArgumentException>(() => KadanesAlgorithm.FindMaximumSubarraySum(array));
}

[Test]
public static void FindMaximumSubarraySum_WithAlternatingNumbers_ReturnsCorrectSum()
{
// Arrange: Alternating positive and negative numbers
// Despite negative values, the entire array gives the maximum sum
int[] array = { 5, -3, 5, -3, 5 };

// Act
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);

// Assert: Sum of entire array = 5 - 3 + 5 - 3 + 5 = 9
Assert.That(result, Is.EqualTo(9)); // Entire array
}

[Test]
public static void FindMaximumSubarrayWithIndices_ReturnsCorrectIndices()
{
// Arrange: Test the variant that returns indices of the maximum subarray
// Array: [-2, 1, -3, 4, -1, 2, 1, -5, 4]
// Index: 0 1 2 3 4 5 6 7 8
int[] array = { -2, 1, -3, 4, -1, 2, 1, -5, 4 };

// Act
var (maxSum, startIndex, endIndex) = KadanesAlgorithm.FindMaximumSubarrayWithIndices(array);

// Assert: Maximum subarray is [4, -1, 2, 1] from index 3 to 6
Assert.That(maxSum, Is.EqualTo(6));
Assert.That(startIndex, Is.EqualTo(3));
Assert.That(endIndex, Is.EqualTo(6));
}

[Test]
public static void FindMaximumSubarrayWithIndices_WithSingleElement_ReturnsZeroIndices()
{
// Arrange
int[] array = { 10 };

// Act
var (maxSum, startIndex, endIndex) = KadanesAlgorithm.FindMaximumSubarrayWithIndices(array);

// Assert
Assert.That(maxSum, Is.EqualTo(10));
Assert.That(startIndex, Is.EqualTo(0));
Assert.That(endIndex, Is.EqualTo(0));
}

[Test]
public static void FindMaximumSubarrayWithIndices_WithNullArray_ThrowsArgumentException()
{
// Arrange
int[]? array = null;

// Act & Assert
Assert.Throws<ArgumentException>(() => KadanesAlgorithm.FindMaximumSubarrayWithIndices(array!));
}

[Test]
public static void FindMaximumSubarraySum_WithLongArray_ReturnsCorrectSum()
{
// Arrange: Test the long integer overload with same values as int test
// Verifies that the algorithm works correctly with long data type
long[] array = { -2L, 1L, -3L, 4L, -1L, 2L, 1L, -5L, 4L };

// Act
long result = KadanesAlgorithm.FindMaximumSubarraySum(array);

// Assert: Should produce same result as int version
Assert.That(result, Is.EqualTo(6L));
}

[Test]
public static void FindMaximumSubarraySum_WithLargeLongNumbers_ReturnsCorrectSum()
{
// Arrange: Test with large numbers that would overflow int type
// This demonstrates why the long overload is necessary
// Sum would be 1,500,000,000 which fits in long but is near int.MaxValue
long[] array = { 1000000000L, -500000000L, 1000000000L };

// Act
long result = KadanesAlgorithm.FindMaximumSubarraySum(array);

// Assert: Entire array sum = 1,000,000,000 - 500,000,000 + 1,000,000,000 = 1,500,000,000
Assert.That(result, Is.EqualTo(1500000000L));
}

[Test]
public static void FindMaximumSubarraySum_WithLongNullArray_ThrowsArgumentException()
{
// Arrange
long[]? array = null;

// Act & Assert
Assert.Throws<ArgumentException>(() => KadanesAlgorithm.FindMaximumSubarraySum(array!));
}

[Test]
public static void FindMaximumSubarraySum_WithZeros_ReturnsZero()
{
// Arrange: Edge case with all zeros
// Any subarray will have sum of 0
int[] array = { 0, 0, 0, 0 };

// Act
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);

// Assert: Maximum sum is 0
Assert.That(result, Is.EqualTo(0));
}

[Test]
public static void FindMaximumSubarraySum_WithMixedZerosAndNegatives_ReturnsZero()
{
// Arrange: Mix of zeros and negative numbers
// The best subarray is any single zero (or multiple zeros)
int[] array = { -5, 0, -3, 0, -2 };

// Act
int result = KadanesAlgorithm.FindMaximumSubarraySum(array);

// Assert: Zero is better than any negative number
Assert.That(result, Is.EqualTo(0));
}
}
147 changes: 147 additions & 0 deletions Algorithms/Other/KadanesAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
namespace Algorithms.Other;

/// <summary>
/// Kadane's Algorithm is used to find the maximum sum of a contiguous subarray
/// within a one-dimensional array of numbers. It has a time complexity of O(n).
/// This algorithm is a classic example of dynamic programming.
/// Reference: "Introduction to Algorithms" by Cormen, Leiserson, Rivest, and Stein (CLRS).
/// </summary>
public static class KadanesAlgorithm
{
/// <summary>
/// Finds the maximum sum of a contiguous subarray using Kadane's Algorithm.
/// The algorithm works by maintaining two variables:
/// - maxSoFar: The maximum sum found so far (global maximum)
/// - maxEndingHere: The maximum sum of subarray ending at current position (local maximum)
/// At each position, we decide whether to extend the existing subarray or start a new one.
/// </summary>
/// <param name="array">The input array of integers.</param>
/// <returns>The maximum sum of a contiguous subarray.</returns>
/// <exception cref="ArgumentException">Thrown when the input array is null or empty.</exception>
/// <example>
/// Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4].
/// Output: 6 (subarray [4, -1, 2, 1]).
/// </example>
public static int FindMaximumSubarraySum(int[] array)
{
// Validate input to ensure array is not null or empty
if (array == null || array.Length == 0)
{
throw new ArgumentException("Array cannot be null or empty.", nameof(array));
}

// Initialize both variables with the first element
// maxSoFar tracks the best sum we've seen across all subarrays
int maxSoFar = array[0];

// maxEndingHere tracks the best sum ending at the current position
int maxEndingHere = array[0];

// Iterate through the array starting from the second element
for (int i = 1; i < array.Length; i++)
{
// Key decision: Either extend the current subarray or start fresh
// If adding current element to existing sum is worse than the element alone,
// it's better to start a new subarray from current element
maxEndingHere = Math.Max(array[i], maxEndingHere + array[i]);

// Update the global maximum if current subarray sum is better
maxSoFar = Math.Max(maxSoFar, maxEndingHere);
}

return maxSoFar;
}

/// <summary>
/// Finds the maximum sum of a contiguous subarray and returns the start and end indices.
/// This variant tracks the indices of the maximum subarray in addition to the sum.
/// Useful when you need to know which elements form the maximum subarray.
/// </summary>
/// <param name="array">The input array of integers.</param>
/// <returns>A tuple containing the maximum sum, start index, and end index.</returns>
/// <exception cref="ArgumentException">Thrown when the input array is null or empty.</exception>
/// <example>
/// Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4].
/// Output: (MaxSum: 6, StartIndex: 3, EndIndex: 6).
/// The subarray is [4, -1, 2, 1].
/// </example>
public static (int MaxSum, int StartIndex, int EndIndex) FindMaximumSubarrayWithIndices(int[] array)
{
// Validate input
if (array == null || array.Length == 0)
{
throw new ArgumentException("Array cannot be null or empty.", nameof(array));
}

// Initialize tracking variables
int maxSoFar = array[0]; // Global maximum sum
int maxEndingHere = array[0]; // Local maximum sum ending at current position
int start = 0; // Start index of the maximum subarray
int end = 0; // End index of the maximum subarray
int tempStart = 0; // Temporary start index for current subarray

// Process each element starting from index 1
for (int i = 1; i < array.Length; i++)
{
// Decide whether to extend current subarray or start a new one
if (array[i] > maxEndingHere + array[i])
{
// Starting fresh from current element is better
maxEndingHere = array[i];
tempStart = i; // Mark this as potential start of new subarray
}
else
{
// Extending the current subarray is better
maxEndingHere = maxEndingHere + array[i];
}

// Update global maximum and indices if we found a better subarray
if (maxEndingHere > maxSoFar)
{
maxSoFar = maxEndingHere;
start = tempStart; // Commit the start index
end = i; // Current position is the end
}
}

return (maxSoFar, start, end);
}

/// <summary>
/// Finds the maximum sum of a contiguous subarray using Kadane's Algorithm for long integers.
/// This overload handles larger numbers that exceed int range (up to 2^63 - 1).
/// The algorithm logic is identical to the int version but uses long arithmetic.
/// </summary>
/// <param name="array">The input array of long integers.</param>
/// <returns>The maximum sum of a contiguous subarray.</returns>
/// <exception cref="ArgumentException">Thrown when the input array is null or empty.</exception>
/// <example>
/// Input: [1000000000L, -500000000L, 1000000000L].
/// Output: 1500000000L (entire array).
/// </example>
public static long FindMaximumSubarraySum(long[] array)
{
// Validate input
if (array == null || array.Length == 0)
{
throw new ArgumentException("Array cannot be null or empty.", nameof(array));
}

// Initialize with first element (using long arithmetic)
long maxSoFar = array[0];
long maxEndingHere = array[0];

// Apply Kadane's algorithm with long values
for (int i = 1; i < array.Length; i++)
{
// Decide: extend current subarray or start new one
maxEndingHere = Math.Max(array[i], maxEndingHere + array[i]);

// Update global maximum
maxSoFar = Math.Max(maxSoFar, maxEndingHere);
}

return maxSoFar;
}
}
Loading