Skip to content

Commit de628c6

Browse files
Fix off-by-one bounds guards in SegmentTree update and getSum
1 parent 346f591 commit de628c6

2 files changed

Lines changed: 106 additions & 3 deletions

File tree

src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ public class SegmentTree {
88

99
/* Constructor which takes the size of the array and the array as a parameter*/
1010
public SegmentTree(int n, int[] arr) {
11+
if (arr == null) {
12+
throw new IllegalArgumentException("Input array must not be null");
13+
}
14+
if (n <= 0 || n > arr.length) {
15+
throw new IllegalArgumentException("Size must be in the range [1, " + arr.length + "], but was " + n);
16+
}
1117
this.n = n;
1218
int x = (int) (Math.ceil(Math.log(n) / Math.log(2)));
1319
int segSize = 2 * (int) Math.pow(2, x) - 1;
1420

1521
this.segTree = new int[segSize];
1622
this.arr = arr;
17-
this.n = n;
1823
constructTree(arr, 0, n - 1, 0);
1924
}
2025

@@ -47,7 +52,8 @@ private void updateTree(int start, int end, int index, int diff, int segIndex) {
4752

4853
/* A function to update the value at a particular index*/
4954
public void update(int index, int value) {
50-
if (index < 0 || index > n) {
55+
// Valid positions are 0..n-1; index == n is out of bounds and must not reach arr[index].
56+
if (index < 0 || index >= n) {
5157
return;
5258
}
5359

@@ -73,7 +79,8 @@ private int getSumTree(int start, int end, int qStart, int qEnd, int segIndex) {
7379

7480
/* A function to query the sum of the subarray [start...end]*/
7581
public int getSum(int start, int end) {
76-
if (start < 0 || end > n || start > end) {
82+
// The last queryable position is n-1, so end == n is an out of range query.
83+
if (start < 0 || end >= n || start > end) {
7784
return 0;
7885
}
7986
return getSumTree(0, n - 1, start, end, 0);
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.thealgorithms.datastructures.trees;
2+
3+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
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+
import org.junit.jupiter.params.ParameterizedTest;
9+
import org.junit.jupiter.params.provider.CsvSource;
10+
import org.junit.jupiter.params.provider.ValueSource;
11+
12+
class SegmentTreeTest {
13+
14+
private static SegmentTree treeOf(int... values) {
15+
return new SegmentTree(values.length, values);
16+
}
17+
18+
@ParameterizedTest
19+
@CsvSource({"0, 4, 15", "0, 0, 1", "4, 4, 5", "1, 3, 9", "2, 4, 12"})
20+
void testRangeSums(int start, int end, int expected) {
21+
assertEquals(expected, treeOf(1, 2, 3, 4, 5).getSum(start, end));
22+
}
23+
24+
@Test
25+
void testSingleElementTree() {
26+
SegmentTree tree = treeOf(42);
27+
assertEquals(42, tree.getSum(0, 0));
28+
tree.update(0, 7);
29+
assertEquals(7, tree.getSum(0, 0));
30+
}
31+
32+
@Test
33+
void testUpdateIsReflectedInSubsequentQueries() {
34+
SegmentTree tree = treeOf(1, 2, 3, 4, 5);
35+
tree.update(2, 10);
36+
assertEquals(22, tree.getSum(0, 4));
37+
assertEquals(16, tree.getSum(1, 3));
38+
tree.update(0, -1);
39+
assertEquals(20, tree.getSum(0, 4));
40+
}
41+
42+
@Test
43+
void testNegativeValues() {
44+
SegmentTree tree = treeOf(-5, 3, -2, 8);
45+
assertEquals(4, tree.getSum(0, 3));
46+
assertEquals(-4, tree.getSum(0, 2));
47+
}
48+
49+
/**
50+
* index == n is past the last element, so it must be rejected by the guard instead of reaching
51+
* the backing array and throwing {@link ArrayIndexOutOfBoundsException}.
52+
*/
53+
@ParameterizedTest
54+
@ValueSource(ints = {5, 6, 100, -1})
55+
void testUpdateOutOfRangeIndexIsIgnored(int index) {
56+
SegmentTree tree = treeOf(1, 2, 3, 4, 5);
57+
assertDoesNotThrow(() -> tree.update(index, 99));
58+
assertEquals(15, tree.getSum(0, 4), "out of range update must not modify the tree");
59+
}
60+
61+
@ParameterizedTest
62+
@CsvSource({"0, 5", "0, 6", "3, 2", "-1, 3", "5, 5"})
63+
void testOutOfRangeQueriesReturnZero(int start, int end) {
64+
assertEquals(0, treeOf(1, 2, 3, 4, 5).getSum(start, end));
65+
}
66+
67+
@Test
68+
void testConstructorRejectsInvalidSize() {
69+
assertThrows(IllegalArgumentException.class, () -> new SegmentTree(0, new int[] {1, 2, 3}));
70+
assertThrows(IllegalArgumentException.class, () -> new SegmentTree(-1, new int[] {1, 2, 3}));
71+
assertThrows(IllegalArgumentException.class, () -> new SegmentTree(4, new int[] {1, 2, 3}));
72+
}
73+
74+
@Test
75+
void testConstructorRejectsNullArray() {
76+
assertThrows(IllegalArgumentException.class, () -> new SegmentTree(3, null));
77+
}
78+
79+
@ParameterizedTest
80+
@ValueSource(ints = {1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17})
81+
void testMatchesBruteForceForVariousSizes(int size) {
82+
int[] values = new int[size];
83+
for (int i = 0; i < size; i++) {
84+
values[i] = i * 3 - 4;
85+
}
86+
SegmentTree tree = new SegmentTree(size, values.clone());
87+
88+
for (int start = 0; start < size; start++) {
89+
int expected = 0;
90+
for (int end = start; end < size; end++) {
91+
expected += values[end];
92+
assertEquals(expected, tree.getSum(start, end), "sum of [" + start + ", " + end + "] with size " + size);
93+
}
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)