feat: add Difference Array algorithm implementation and tests (#7244)

This commit is contained in:
Chahat Sandhu
2026-01-27 15:29:05 -06:00
committed by GitHub
parent 2ea3873b9f
commit dc3d64f51d
2 changed files with 197 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
package com.thealgorithms.prefixsum;
/**
* Implements the Difference Array algorithm.
*
* <p>
* The Difference Array is an auxiliary data structure that enables efficient range update operations.
* It is based on the mathematical concept of Finite Differences.
* </p>
*
* <p>
* <strong>Key Operations:</strong>
* <ul>
* <li>Range Update (Add value to [L, R]): O(1)</li>
* <li>Reconstruction (Prefix Sum): O(N)</li>
* </ul>
* </p>
*
* @see <a href="https://en.wikipedia.org/wiki/Finite_difference">Finite Difference (Wikipedia)</a>
* @see <a href="https://en.wikipedia.org/wiki/Prefix_sum">Prefix Sum (Wikipedia)</a>
* @author Chahat Sandhu, <a href="https://github.com/singhc7">singhc7</a>
*/
public class DifferenceArray {
private final long[] differenceArray;
private final int n;
/**
* Initializes the Difference Array from a given integer array.
*
* @param inputArray The initial array. Cannot be null or empty.
* @throws IllegalArgumentException if the input array is null or empty.
*/
public DifferenceArray(int[] inputArray) {
if (inputArray == null || inputArray.length == 0) {
throw new IllegalArgumentException("Input array cannot be null or empty.");
}
this.n = inputArray.length;
// Size n + 1 allows for branchless updates at the right boundary (r + 1).
this.differenceArray = new long[n + 1];
initializeDifferenceArray(inputArray);
}
private void initializeDifferenceArray(int[] inputArray) {
differenceArray[0] = inputArray[0];
for (int i = 1; i < n; i++) {
differenceArray[i] = inputArray[i] - inputArray[i - 1];
}
}
/**
* Adds a value to all elements in the range [l, r].
*
* <p>
* This method uses a branchless approach by allocating an extra element at the end
* of the array, avoiding the conditional check for the right boundary.
* </p>
*
* @param l The starting index (inclusive).
* @param r The ending index (inclusive).
* @param val The value to add.
* @throws IllegalArgumentException if the range is invalid.
*/
public void update(int l, int r, int val) {
if (l < 0 || r >= n || l > r) {
throw new IllegalArgumentException(String.format("Invalid range: [%d, %d] for array of size %d", l, r, n));
}
differenceArray[l] += val;
differenceArray[r + 1] -= val;
}
/**
* Reconstructs the final array using prefix sums.
*
* @return The resulting array after all updates. Returns long[] to handle potential overflows.
*/
public long[] getResultArray() {
long[] result = new long[n];
result[0] = differenceArray[0];
for (int i = 1; i < n; i++) {
result[i] = differenceArray[i] + result[i - 1];
}
return result;
}
}

View File

@@ -0,0 +1,110 @@
package com.thealgorithms.prefixsum;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
class DifferenceArrayTest {
@Test
void testStandardRangeUpdate() {
int[] input = {10, 20, 30, 40, 50};
DifferenceArray da = new DifferenceArray(input);
da.update(1, 3, 5);
long[] expected = {10, 25, 35, 45, 50};
assertArrayEquals(expected, da.getResultArray());
}
@Test
void testMultipleOverlappingUpdates() {
int[] input = {10, 10, 10, 10, 10};
DifferenceArray da = new DifferenceArray(input);
da.update(0, 2, 10);
da.update(2, 4, 20);
long[] expected = {20, 20, 40, 30, 30};
assertArrayEquals(expected, da.getResultArray());
}
@Test
void testIntegerOverflowSafety() {
int[] input = {Integer.MAX_VALUE, 100};
DifferenceArray da = new DifferenceArray(input);
da.update(0, 0, 100);
long[] result = da.getResultArray();
long expectedVal = (long) Integer.MAX_VALUE + 100;
assertEquals(expectedVal, result[0]);
}
@Test
void testFullRangeUpdate() {
int[] input = {1, 2, 3};
DifferenceArray da = new DifferenceArray(input);
da.update(0, 2, 100);
long[] expected = {101, 102, 103};
assertArrayEquals(expected, da.getResultArray());
}
@Test
void testBoundaryWriteOptimization() {
int[] input = {5, 5};
DifferenceArray da = new DifferenceArray(input);
da.update(1, 1, 5);
long[] expected = {5, 10};
assertArrayEquals(expected, da.getResultArray());
}
@Test
void testLargeMassiveUpdate() {
int[] input = {0};
DifferenceArray da = new DifferenceArray(input);
int iterations = 100000;
for (int i = 0; i < iterations; i++) {
da.update(0, 0, 1);
}
assertEquals(100000L, da.getResultArray()[0]);
}
@Test
void testNullInputThrowsException() {
assertThrows(IllegalArgumentException.class, () -> new DifferenceArray(null));
}
@Test
void testEmptyInputThrowsException() {
assertThrows(IllegalArgumentException.class, () -> new DifferenceArray(new int[] {}));
}
@Test
void testInvalidRangeNegativeIndex() {
DifferenceArray da = new DifferenceArray(new int[] {1, 2, 3});
assertThrows(IllegalArgumentException.class, () -> da.update(-1, 1, 5));
}
@Test
void testInvalidRangeOutOfBounds() {
DifferenceArray da = new DifferenceArray(new int[] {1, 2, 3});
assertThrows(IllegalArgumentException.class, () -> da.update(0, 3, 5));
}
@Test
void testInvalidRangeStartGreaterThanEnd() {
DifferenceArray da = new DifferenceArray(new int[] {1, 2, 3});
assertThrows(IllegalArgumentException.class, () -> da.update(2, 1, 5));
}
}