Skip to content

Commit 508f840

Browse files
Merge branch 'master' into fix/javadoc-warnings-7393
2 parents 8555c0e + b3e31b5 commit 508f840

21 files changed

Lines changed: 1270 additions & 34 deletions

File tree

.github/workflows/close-failed-prs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
steps:
1717
- name: Close stale PRs
18-
uses: actions/github-script@v8
18+
uses: actions/github-script@v9
1919
with:
2020
github-token: ${{ secrets.GITHUB_TOKEN }}
2121
script: |

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,7 @@
816816
- 📄 [Lower](src/main/java/com/thealgorithms/strings/Lower.java)
817817
- 📄 [Manacher](src/main/java/com/thealgorithms/strings/Manacher.java)
818818
- 📄 [MyAtoi](src/main/java/com/thealgorithms/strings/MyAtoi.java)
819+
- 📄 [MoveHashToEnd](src/main/java/com/thealgorithms/strings/MoveHashToEnd.java)
819820
- 📄 [Palindrome](src/main/java/com/thealgorithms/strings/Palindrome.java)
820821
- 📄 [Pangram](src/main/java/com/thealgorithms/strings/Pangram.java)
821822
- 📄 [PermuteString](src/main/java/com/thealgorithms/strings/PermuteString.java)
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package com.thealgorithms.datastructures.trees;
2+
3+
/**
4+
* 2D Segment Tree (Tree of Trees) implementation.
5+
* This data structure supports point updates and submatrix sum queries
6+
* in a 2D grid. It achieves this by nesting 1D Segment Trees within a 1D Segment Tree.
7+
*
8+
* Time Complexity:
9+
* - Build/Initialization: O(N * M)
10+
* - Point Update: O(log N * log M)
11+
* - Submatrix Query: O(log N * log M)
12+
*
13+
* @see <a href="https://cp-algorithms.com/data_structures/segment_tree.html#2d-segment-tree">2D Segment Tree</a>
14+
*/
15+
public class SegmentTree2D {
16+
17+
/**
18+
* Represents a 1D Segment Tree.
19+
* This is equivalent to your 'Sagara' struct. It manages the columns (X-axis).
20+
*/
21+
public static class SegmentTree1D {
22+
private int n;
23+
private final int[] tree;
24+
25+
/**
26+
* Initializes the 1D Segment Tree with the nearest power of 2.
27+
*
28+
* @param size The expected number of elements (columns).
29+
*/
30+
public SegmentTree1D(int size) {
31+
n = 1;
32+
while (n < size) {
33+
n *= 2;
34+
}
35+
tree = new int[n * 2];
36+
}
37+
38+
/**
39+
* Recursively updates a point in the 1D tree.
40+
*/
41+
private void update(int index, int val, int node, int lx, int rx) {
42+
if (rx - lx == 1) {
43+
tree[node] = val;
44+
return;
45+
}
46+
47+
int mid = lx + (rx - lx) / 2;
48+
int leftChild = node * 2 + 1;
49+
int rightChild = node * 2 + 2;
50+
51+
if (index < mid) {
52+
update(index, val, leftChild, lx, mid);
53+
} else {
54+
update(index, val, rightChild, mid, rx);
55+
}
56+
57+
tree[node] = tree[leftChild] + tree[rightChild];
58+
}
59+
60+
/**
61+
* Public wrapper to update a specific index.
62+
*
63+
* @param index The column index to update.
64+
* @param val The new value.
65+
*/
66+
public void update(int index, int val) {
67+
update(index, val, 0, 0, n);
68+
}
69+
70+
/**
71+
* Retrieves the exact value at a specific leaf node.
72+
*
73+
* @param index The column index.
74+
* @return The value at the given index.
75+
*/
76+
public int get(int index) {
77+
return query(index, index + 1, 0, 0, n);
78+
}
79+
80+
/**
81+
* Recursively queries the sum in a 1D range.
82+
*/
83+
private int query(int l, int r, int node, int lx, int rx) {
84+
if (lx >= r || rx <= l) {
85+
return 0; // Out of bounds
86+
}
87+
if (lx >= l && rx <= r) {
88+
return tree[node]; // Fully inside
89+
}
90+
91+
int mid = lx + (rx - lx) / 2;
92+
int leftSum = query(l, r, node * 2 + 1, lx, mid);
93+
int rightSum = query(l, r, node * 2 + 2, mid, rx);
94+
95+
return leftSum + rightSum;
96+
}
97+
98+
/**
99+
* Public wrapper to query the sum in the range [l, r).
100+
*
101+
* @param l Left boundary (inclusive).
102+
* @param r Right boundary (exclusive).
103+
* @return The sum of the range.
104+
*/
105+
public int query(int l, int r) {
106+
return query(l, r, 0, 0, n);
107+
}
108+
}
109+
110+
// --- Start of 2D Segment Tree (equivalent to 'Sagara2D') ---
111+
112+
private int n;
113+
private final SegmentTree1D[] tree;
114+
115+
/**
116+
* Initializes the 2D Segment Tree.
117+
*
118+
* @param rows The number of rows in the matrix.
119+
* @param cols The number of columns in the matrix.
120+
*/
121+
public SegmentTree2D(int rows, int cols) {
122+
n = 1;
123+
while (n < rows) {
124+
n *= 2;
125+
}
126+
tree = new SegmentTree1D[n * 2];
127+
for (int i = 0; i < n * 2; i++) {
128+
// Every node in the outer tree is a full 1D tree!
129+
tree[i] = new SegmentTree1D(cols);
130+
}
131+
}
132+
133+
/**
134+
* Recursively updates a point in the 2D grid.
135+
*/
136+
private void update(int row, int col, int val, int node, int lx, int rx) {
137+
if (rx - lx == 1) {
138+
tree[node].update(col, val);
139+
return;
140+
}
141+
142+
int mid = lx + (rx - lx) / 2;
143+
int leftChild = node * 2 + 1;
144+
int rightChild = node * 2 + 2;
145+
146+
if (row < mid) {
147+
update(row, col, val, leftChild, lx, mid);
148+
} else {
149+
update(row, col, val, rightChild, mid, rx);
150+
}
151+
152+
// The value of the current node's column is the sum of its children's column values
153+
int leftVal = tree[leftChild].get(col);
154+
int rightVal = tree[rightChild].get(col);
155+
tree[node].update(col, leftVal + rightVal);
156+
}
157+
158+
/**
159+
* Public wrapper to update a specific point (row, col).
160+
*
161+
* @param row The row index.
162+
* @param col The column index.
163+
* @param val The new value.
164+
*/
165+
public void update(int row, int col, int val) {
166+
update(row, col, val, 0, 0, n);
167+
}
168+
169+
/**
170+
* Recursively queries the sum in a submatrix.
171+
*/
172+
private int query(int top, int bottom, int left, int right, int node, int lx, int rx) {
173+
if (lx >= bottom || rx <= top) {
174+
return 0; // Out of bounds
175+
}
176+
if (lx >= top && rx <= bottom) {
177+
// Fully inside the row range, so delegate the column query to the 1D tree
178+
return tree[node].query(left, right);
179+
}
180+
181+
int mid = lx + (rx - lx) / 2;
182+
int leftSum = query(top, bottom, left, right, node * 2 + 1, lx, mid);
183+
int rightSum = query(top, bottom, left, right, node * 2 + 2, mid, rx);
184+
185+
return leftSum + rightSum;
186+
}
187+
188+
/**
189+
* Public wrapper to query the sum of a submatrix.
190+
* Note: boundaries are [top, bottom) and [left, right).
191+
*
192+
* @param top Top row index (inclusive).
193+
* @param bottom Bottom row index (exclusive).
194+
* @param left Left column index (inclusive).
195+
* @param right Right column index (exclusive).
196+
* @return The sum of the submatrix.
197+
*/
198+
public int query(int top, int bottom, int left, int right) {
199+
return query(top, bottom, left, right, 0, 0, n);
200+
}
201+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.thealgorithms.dynamicprogramming;
2+
3+
import java.util.Arrays;
4+
import java.util.Comparator;
5+
6+
/**
7+
* Computes the minimum search cost of an optimal binary search tree.
8+
*
9+
* <p>The algorithm sorts the keys, preserves the corresponding search frequencies, and uses
10+
* dynamic programming with Knuth's optimization to compute the minimum weighted search cost.
11+
*
12+
* <p>Example: if keys = [10, 12] and frequencies = [34, 50], the best tree puts 12 at the root
13+
* and 10 as its left child. The total cost is 50 * 1 + 34 * 2 = 118.
14+
*
15+
* <p>Reference:
16+
* https://en.wikipedia.org/wiki/Optimal_binary_search_tree
17+
*/
18+
public final class OptimalBinarySearchTree {
19+
private OptimalBinarySearchTree() {
20+
}
21+
22+
/**
23+
* Computes the minimum weighted search cost for the given keys and search frequencies.
24+
*
25+
* @param keys the BST keys
26+
* @param frequencies the search frequencies associated with the keys
27+
* @return the minimum search cost
28+
* @throws IllegalArgumentException if the input is invalid
29+
*/
30+
public static long findOptimalCost(int[] keys, int[] frequencies) {
31+
validateInput(keys, frequencies);
32+
if (keys.length == 0) {
33+
return 0L;
34+
}
35+
36+
int[][] sortedNodes = sortNodes(keys, frequencies);
37+
int nodeCount = sortedNodes.length;
38+
long[] prefixSums = buildPrefixSums(sortedNodes);
39+
long[][] optimalCost = new long[nodeCount][nodeCount];
40+
int[][] root = new int[nodeCount][nodeCount];
41+
42+
// Small example:
43+
// keys = [10, 12]
44+
// frequencies = [34, 50]
45+
// Choosing 12 as the root gives cost 50 * 1 + 34 * 2 = 118,
46+
// which is better than choosing 10 as the root.
47+
48+
// Base case: a subtree containing one key has cost equal to its frequency,
49+
// because that key becomes the root of the subtree and is searched at depth 1.
50+
for (int index = 0; index < nodeCount; index++) {
51+
optimalCost[index][index] = sortedNodes[index][1];
52+
root[index][index] = index;
53+
}
54+
55+
// Build solutions for longer and longer key ranges.
56+
// optimalCost[start][end] stores the minimum search cost for keys in that range.
57+
for (int length = 2; length <= nodeCount; length++) {
58+
for (int start = 0; start <= nodeCount - length; start++) {
59+
int end = start + length - 1;
60+
61+
// Every key in this range moves one level deeper when we choose a root,
62+
// so the sum of frequencies is added once to the subtree cost.
63+
long frequencySum = prefixSums[end + 1] - prefixSums[start];
64+
optimalCost[start][end] = Long.MAX_VALUE;
65+
66+
// Knuth's optimization:
67+
// the best root for [start, end] lies between the best roots of
68+
// [start, end - 1] and [start + 1, end], so we search only this interval.
69+
int leftBoundary = root[start][end - 1];
70+
int rightBoundary = root[start + 1][end];
71+
for (int currentRoot = leftBoundary; currentRoot <= rightBoundary; currentRoot++) {
72+
long leftCost = currentRoot > start ? optimalCost[start][currentRoot - 1] : 0L;
73+
long rightCost = currentRoot < end ? optimalCost[currentRoot + 1][end] : 0L;
74+
long currentCost = frequencySum + leftCost + rightCost;
75+
76+
if (currentCost < optimalCost[start][end]) {
77+
optimalCost[start][end] = currentCost;
78+
root[start][end] = currentRoot;
79+
}
80+
}
81+
}
82+
}
83+
84+
return optimalCost[0][nodeCount - 1];
85+
}
86+
87+
private static void validateInput(int[] keys, int[] frequencies) {
88+
if (keys == null || frequencies == null) {
89+
throw new IllegalArgumentException("Keys and frequencies cannot be null");
90+
}
91+
if (keys.length != frequencies.length) {
92+
throw new IllegalArgumentException("Keys and frequencies must have the same length");
93+
}
94+
95+
for (int frequency : frequencies) {
96+
if (frequency < 0) {
97+
throw new IllegalArgumentException("Frequencies cannot be negative");
98+
}
99+
}
100+
}
101+
102+
private static int[][] sortNodes(int[] keys, int[] frequencies) {
103+
int[][] sortedNodes = new int[keys.length][2];
104+
for (int index = 0; index < keys.length; index++) {
105+
sortedNodes[index][0] = keys[index];
106+
sortedNodes[index][1] = frequencies[index];
107+
}
108+
109+
// Sort by key so the nodes can be treated as an in-order BST sequence.
110+
Arrays.sort(sortedNodes, Comparator.comparingInt(node -> node[0]));
111+
112+
for (int index = 1; index < sortedNodes.length; index++) {
113+
if (sortedNodes[index - 1][0] == sortedNodes[index][0]) {
114+
throw new IllegalArgumentException("Keys must be distinct");
115+
}
116+
}
117+
118+
return sortedNodes;
119+
}
120+
121+
private static long[] buildPrefixSums(int[][] sortedNodes) {
122+
long[] prefixSums = new long[sortedNodes.length + 1];
123+
for (int index = 0; index < sortedNodes.length; index++) {
124+
// prefixSums[i] holds the total frequency of the first i sorted keys.
125+
// This lets us get the frequency sum of any range in O(1) time.
126+
prefixSums[index + 1] = prefixSums[index] + sortedNodes[index][1];
127+
}
128+
return prefixSums;
129+
}
130+
}

0 commit comments

Comments
 (0)