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
87 changes: 87 additions & 0 deletions src/main/java/com/thealgorithms/prefixsum/DifferenceArray.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
110 changes: 110 additions & 0 deletions src/test/java/com/thealgorithms/prefixsum/DifferenceArrayTest.java
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading