Skip to content

Commit dc3d64f

Browse files
authored
feat: add Difference Array algorithm implementation and tests (#7244)
1 parent 2ea3873 commit dc3d64f

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-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: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package com.thealgorithms.prefixsum;
2+
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
class DifferenceArrayTest {
10+
11+
@Test
12+
void testStandardRangeUpdate() {
13+
int[] input = {10, 20, 30, 40, 50};
14+
DifferenceArray da = new DifferenceArray(input);
15+
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+
da.update(0, 2, 10);
28+
da.update(2, 4, 20);
29+
30+
long[] expected = {20, 20, 40, 30, 30};
31+
assertArrayEquals(expected, da.getResultArray());
32+
}
33+
34+
@Test
35+
void testIntegerOverflowSafety() {
36+
int[] input = {Integer.MAX_VALUE, 100};
37+
DifferenceArray da = new DifferenceArray(input);
38+
39+
da.update(0, 0, 100);
40+
41+
long[] result = da.getResultArray();
42+
long expectedVal = (long) Integer.MAX_VALUE + 100;
43+
44+
assertEquals(expectedVal, result[0]);
45+
}
46+
47+
@Test
48+
void testFullRangeUpdate() {
49+
int[] input = {1, 2, 3};
50+
DifferenceArray da = new DifferenceArray(input);
51+
52+
da.update(0, 2, 100);
53+
54+
long[] expected = {101, 102, 103};
55+
assertArrayEquals(expected, da.getResultArray());
56+
}
57+
58+
@Test
59+
void testBoundaryWriteOptimization() {
60+
int[] input = {5, 5};
61+
DifferenceArray da = new DifferenceArray(input);
62+
63+
da.update(1, 1, 5);
64+
65+
long[] expected = {5, 10};
66+
67+
assertArrayEquals(expected, da.getResultArray());
68+
}
69+
70+
@Test
71+
void testLargeMassiveUpdate() {
72+
int[] input = {0};
73+
DifferenceArray da = new DifferenceArray(input);
74+
75+
int iterations = 100000;
76+
for (int i = 0; i < iterations; i++) {
77+
da.update(0, 0, 1);
78+
}
79+
80+
assertEquals(100000L, da.getResultArray()[0]);
81+
}
82+
83+
@Test
84+
void testNullInputThrowsException() {
85+
assertThrows(IllegalArgumentException.class, () -> new DifferenceArray(null));
86+
}
87+
88+
@Test
89+
void testEmptyInputThrowsException() {
90+
assertThrows(IllegalArgumentException.class, () -> new DifferenceArray(new int[] {}));
91+
}
92+
93+
@Test
94+
void testInvalidRangeNegativeIndex() {
95+
DifferenceArray da = new DifferenceArray(new int[] {1, 2, 3});
96+
assertThrows(IllegalArgumentException.class, () -> da.update(-1, 1, 5));
97+
}
98+
99+
@Test
100+
void testInvalidRangeOutOfBounds() {
101+
DifferenceArray da = new DifferenceArray(new int[] {1, 2, 3});
102+
assertThrows(IllegalArgumentException.class, () -> da.update(0, 3, 5));
103+
}
104+
105+
@Test
106+
void testInvalidRangeStartGreaterThanEnd() {
107+
DifferenceArray da = new DifferenceArray(new int[] {1, 2, 3});
108+
assertThrows(IllegalArgumentException.class, () -> da.update(2, 1, 5));
109+
}
110+
}

0 commit comments

Comments
 (0)