mirror of
https://github.com/TheAlgorithms/Java.git
synced 2026-03-13 08:40:43 +08:00
feat: add Difference Array algorithm implementation and tests (#7244)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user