Skip to content

Commit 97674e9

Browse files
committed
feat: add Difference Array algorithm implementation and tests
1 parent 2ea3873 commit 97674e9

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.thealgorithms.prefixsum;
2+
3+
/**
4+
* Implements the Difference Array algorithm.
5+
*
6+
* <p>
7+
* The Difference Array is an auxiliary data structure that enables efficient range update operations.
8+
* It is based on the mathematical concept of Finite Differences.
9+
* </p>
10+
*
11+
* <p>
12+
* <strong>Key Operations:</strong>
13+
* <ul>
14+
* <li>Range Update (Add value to [L, R]): O(1)</li>
15+
* <li>Reconstruction (Prefix Sum): O(N)</li>
16+
* </ul>
17+
* </p>
18+
*
19+
* @see <a href="https://en.wikipedia.org/wiki/Finite_difference">Finite Difference (Wikipedia)</a>
20+
* @see <a href="https://en.wikipedia.org/wiki/Prefix_sum">Prefix Sum (Wikipedia)</a>
21+
* @author Chahat Sandhu, <a href="https://github.com/singhc7">singhc7</a>
22+
*/
23+
public class DifferenceArray {
24+
25+
private final long[] differenceArray;
26+
private final int n;
27+
28+
/**
29+
* Initializes the Difference Array from a given integer array.
30+
*
31+
* @param inputArray The initial array. Cannot be null or empty.
32+
* @throws IllegalArgumentException if the input array is null or empty.
33+
*/
34+
public DifferenceArray(int[] inputArray) {
35+
if (inputArray == null || inputArray.length == 0) {
36+
throw new IllegalArgumentException("Input array cannot be null or empty.");
37+
}
38+
this.n = inputArray.length;
39+
// Size n + 1 allows for branchless updates at the right boundary (r + 1).
40+
this.differenceArray = new long[n + 1];
41+
initializeDifferenceArray(inputArray);
42+
}
43+
44+
private void initializeDifferenceArray(int[] inputArray) {
45+
differenceArray[0] = inputArray[0];
46+
for (int i = 1; i < n; i++) {
47+
differenceArray[i] = inputArray[i] - inputArray[i - 1];
48+
}
49+
}
50+
51+
/**
52+
* Adds a value to all elements in the range [l, r].
53+
*
54+
* <p>
55+
* This method uses a branchless approach by allocating an extra element at the end
56+
* of the array, avoiding the conditional check for the right boundary.
57+
* </p>
58+
*
59+
* @param l The starting index (inclusive).
60+
* @param r The ending index (inclusive).
61+
* @param val The value to add.
62+
* @throws IllegalArgumentException if the range is invalid.
63+
*/
64+
public void update(int l, int r, int val) {
65+
if (l < 0 || r >= n || l > r) {
66+
throw new IllegalArgumentException(String.format("Invalid range: [%d, %d] for array of size %d", l, r, n));
67+
}
68+
69+
differenceArray[l] += val;
70+
differenceArray[r + 1] -= val;
71+
}
72+
73+
/**
74+
* Reconstructs the final array using prefix sums.
75+
*
76+
* @return The resulting array after all updates. Returns long[] to handle potential overflows.
77+
*/
78+
public long[] getResultArray() {
79+
long[] result = new long[n];
80+
result[0] = differenceArray[0];
81+
82+
for (int i = 1; i < n; i++) {
83+
result[i] = differenceArray[i] + result[i - 1];
84+
}
85+
return result;
86+
}
87+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package com.thealgorithms.prefixsum;
2+
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
class DifferenceArrayTest {
9+
10+
@Test
11+
void testStandardRangeUpdate() {
12+
int[] input = {10, 20, 30, 40, 50};
13+
DifferenceArray da = new DifferenceArray(input);
14+
15+
// Update range [1, 3] by adding 5 -> {10, 25, 35, 45, 50}
16+
da.update(1, 3, 5);
17+
18+
long[] expected = {10, 25, 35, 45, 50};
19+
assertArrayEquals(expected, da.getResultArray());
20+
}
21+
22+
@Test
23+
void testMultipleOverlappingUpdates() {
24+
int[] input = {10, 10, 10, 10, 10};
25+
DifferenceArray da = new DifferenceArray(input);
26+
27+
// Add 10 to [0, 2] -> {20, 20, 20, 10, 10}
28+
da.update(0, 2, 10);
29+
// Add 20 to [2, 4] -> {20, 20, 40, 30, 30}
30+
// Index 2 overlap: 10 + 10 + 20 = 40
31+
da.update(2, 4, 20);
32+
33+
long[] expected = {20, 20, 40, 30, 30};
34+
assertArrayEquals(expected, da.getResultArray());
35+
}
36+
37+
@Test
38+
void testIntegerOverflowSafety() {
39+
// Using Integer.MAX_VALUE to ensure long[] handles the overflow correctly
40+
int[] input = {Integer.MAX_VALUE, 100};
41+
DifferenceArray da = new DifferenceArray(input);
42+
43+
da.update(0, 0, 100);
44+
45+
long[] result = da.getResultArray();
46+
long expectedVal = (long) Integer.MAX_VALUE + 100;
47+
48+
assert result[0] == expectedVal;
49+
}
50+
51+
@Test
52+
void testFullRangeUpdate() {
53+
int[] input = {1, 2, 3};
54+
DifferenceArray da = new DifferenceArray(input);
55+
56+
da.update(0, 2, 100);
57+
58+
long[] expected = {101, 102, 103};
59+
assertArrayEquals(expected, da.getResultArray());
60+
}
61+
62+
@Test
63+
void testBoundaryWriteOptimization() {
64+
// Verifies that writing to the internal 'n' index (r+1) does not throw exceptions
65+
int[] input = {5, 5};
66+
DifferenceArray da = new DifferenceArray(input);
67+
68+
da.update(1, 1, 5); // r + 1 = 2, which is out of bounds for the input array size, but valid for differenceArray
69+
70+
long[] expected = {5, 10};
71+
assertArrayEquals(expected, da.getResultArray());
72+
}
73+
74+
@Test
75+
void testNullInputThrowsException() {
76+
assertThrows(IllegalArgumentException.class, () -> new DifferenceArray(null));
77+
}
78+
79+
@Test
80+
void testEmptyInputThrowsException() {
81+
assertThrows(IllegalArgumentException.class, () -> new DifferenceArray(new int[] {}));
82+
}
83+
84+
@Test
85+
void testInvalidRangeNegativeIndex() {
86+
DifferenceArray da = new DifferenceArray(new int[] {1, 2, 3});
87+
assertThrows(IllegalArgumentException.class, () -> da.update(-1, 1, 5));
88+
}
89+
90+
@Test
91+
void testInvalidRangeOutOfBounds() {
92+
DifferenceArray da = new DifferenceArray(new int[] {1, 2, 3});
93+
assertThrows(IllegalArgumentException.class, () -> da.update(0, 3, 5));
94+
}
95+
96+
@Test
97+
void testInvalidRangeStartGreaterThanEnd() {
98+
DifferenceArray da = new DifferenceArray(new int[] {1, 2, 3});
99+
assertThrows(IllegalArgumentException.class, () -> da.update(2, 1, 5));
100+
}
101+
}

0 commit comments

Comments
 (0)