Skip to content

Commit 959ced9

Browse files
authored
Added mos algorithm and dice thrower (#6591)
* Add Sum of Squares algorithm implementation * Format code and add Wikipedia URL for Lagrange's theorem * Fixed clang-format issues * Added Mo's Algorithm and DiceThrower recursive algorithms - Mo's Algorithm: Square root decomposition for offline range queries(Imp in CP) - DiceThrower: Recursive backtracking for dice combinations(very imp) - Both algorithms include comprehensive test suites - Formatted with clang-format and i follow contribution guidelines * Fixed checkstyle violation * Fixed SpotBugs issue * Added in PMD exclusions * Improved test coverage for better Codecov scores. * Fixed clang-format issues in test files * Add Mo's Algorithm and DiceThrower algorithms with comprehensive tests * Fixed PartitionProblem.java documentation comment placement
1 parent 596302b commit 959ced9

File tree

5 files changed

+800
-0
lines changed

5 files changed

+800
-0
lines changed

pmd-exclude.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,14 @@ com.thealgorithms.others.LinearCongruentialGenerator=UselessMainMethod
8888
com.thealgorithms.others.Luhn=UnnecessaryFullyQualifiedName,UselessMainMethod
8989
com.thealgorithms.others.Mandelbrot=UselessMainMethod,UselessParentheses
9090
com.thealgorithms.others.MiniMaxAlgorithm=UselessMainMethod,UselessParentheses
91+
com.thealgorithms.others.MosAlgorithm=UselessMainMethod
9192
com.thealgorithms.others.PageRank=UselessMainMethod,UselessParentheses
9293
com.thealgorithms.others.PerlinNoise=UselessMainMethod,UselessParentheses
9394
com.thealgorithms.others.QueueUsingTwoStacks=UselessParentheses
9495
com.thealgorithms.others.Trieac=UselessMainMethod,UselessParentheses
9596
com.thealgorithms.others.Verhoeff=UnnecessaryFullyQualifiedName,UselessMainMethod
9697
com.thealgorithms.puzzlesandgames.Sudoku=UselessMainMethod
98+
com.thealgorithms.recursion.DiceThrower=UselessMainMethod
9799
com.thealgorithms.searches.HowManyTimesRotated=UselessMainMethod
98100
com.thealgorithms.searches.InterpolationSearch=UselessParentheses
99101
com.thealgorithms.searches.KMPSearch=UselessParentheses
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
package com.thealgorithms.others;
2+
3+
import java.util.Arrays;
4+
import java.util.Comparator;
5+
6+
/**
7+
* Mo's Algorithm (Square Root Decomposition) for offline range queries
8+
*
9+
* Mo's Algorithm is used to answer range queries efficiently when:
10+
* 1. Queries can be processed offline (all queries known beforehand)
11+
* 2. We can efficiently add/remove elements from current range
12+
* 3. The problem has optimal substructure for range operations
13+
*
14+
* Time Complexity: O((N + Q) * sqrt(N)) where N = array size, Q = number of queries
15+
* Space Complexity: O(N + Q)
16+
*
17+
* @see <a href="https://www.geeksforgeeks.org/dsa/mos-algorithm-query-square-root-decomposition-set-1-introduction/">Mo's Algorithm</a>
18+
* @author BEASTSHRIRAM
19+
*/
20+
public final class MosAlgorithm {
21+
22+
/**
23+
* Query structure to store range queries
24+
*/
25+
public static class Query {
26+
public final int left;
27+
public final int right;
28+
public final int index; // Original index of query
29+
public int result; // Result of the query
30+
31+
public Query(int left, int right, int index) {
32+
this.left = left;
33+
this.right = right;
34+
this.index = index;
35+
this.result = 0;
36+
}
37+
}
38+
39+
private MosAlgorithm() {
40+
// Utility class
41+
}
42+
43+
/**
44+
* Solves range sum queries using Mo's Algorithm
45+
*
46+
* @param arr the input array
47+
* @param queries array of queries to process
48+
* @return array of results corresponding to each query
49+
*/
50+
public static int[] solveRangeSumQueries(int[] arr, Query[] queries) {
51+
if (arr == null || queries == null || arr.length == 0) {
52+
return new int[0];
53+
}
54+
55+
int n = arr.length;
56+
int blockSize = (int) Math.sqrt(n);
57+
58+
// Sort queries using Mo's ordering
59+
Arrays.sort(queries, new MoComparator(blockSize));
60+
61+
// Initialize variables for current range
62+
int currentLeft = 0;
63+
int currentRight = -1;
64+
int currentSum = 0;
65+
66+
// Process each query
67+
for (Query query : queries) {
68+
// Expand or shrink the current range to match query range
69+
70+
// Expand right boundary
71+
while (currentRight < query.right) {
72+
currentRight++;
73+
currentSum += arr[currentRight];
74+
}
75+
76+
// Shrink right boundary
77+
while (currentRight > query.right) {
78+
currentSum -= arr[currentRight];
79+
currentRight--;
80+
}
81+
82+
// Expand left boundary
83+
while (currentLeft < query.left) {
84+
currentSum -= arr[currentLeft];
85+
currentLeft++;
86+
}
87+
88+
// Shrink left boundary
89+
while (currentLeft > query.left) {
90+
currentLeft--;
91+
currentSum += arr[currentLeft];
92+
}
93+
94+
// Store the result
95+
query.result = currentSum;
96+
}
97+
98+
// Extract results in original query order
99+
int[] results = new int[queries.length];
100+
for (Query query : queries) {
101+
results[query.index] = query.result;
102+
}
103+
104+
return results;
105+
}
106+
107+
/**
108+
* Solves range frequency queries using Mo's Algorithm
109+
* Example: Count occurrences of a specific value in range [L, R]
110+
*
111+
* @param arr the input array
112+
* @param queries array of queries to process
113+
* @param targetValue the value to count in each range
114+
* @return array of results corresponding to each query
115+
*/
116+
public static int[] solveRangeFrequencyQueries(int[] arr, Query[] queries, int targetValue) {
117+
if (arr == null || queries == null || arr.length == 0) {
118+
return new int[0];
119+
}
120+
121+
int n = arr.length;
122+
int blockSize = (int) Math.sqrt(n);
123+
124+
// Sort queries using Mo's ordering
125+
Arrays.sort(queries, new MoComparator(blockSize));
126+
127+
// Initialize variables for current range
128+
int currentLeft = 0;
129+
int currentRight = -1;
130+
int currentCount = 0;
131+
132+
// Process each query
133+
for (Query query : queries) {
134+
// Expand right boundary
135+
while (currentRight < query.right) {
136+
currentRight++;
137+
if (arr[currentRight] == targetValue) {
138+
currentCount++;
139+
}
140+
}
141+
142+
// Shrink right boundary
143+
while (currentRight > query.right) {
144+
if (arr[currentRight] == targetValue) {
145+
currentCount--;
146+
}
147+
currentRight--;
148+
}
149+
150+
// Expand left boundary
151+
while (currentLeft < query.left) {
152+
if (arr[currentLeft] == targetValue) {
153+
currentCount--;
154+
}
155+
currentLeft++;
156+
}
157+
158+
// Shrink left boundary
159+
while (currentLeft > query.left) {
160+
currentLeft--;
161+
if (arr[currentLeft] == targetValue) {
162+
currentCount++;
163+
}
164+
}
165+
166+
// Store the result
167+
query.result = currentCount;
168+
}
169+
170+
// Extract results in original query order
171+
int[] results = new int[queries.length];
172+
for (Query query : queries) {
173+
results[query.index] = query.result;
174+
}
175+
176+
return results;
177+
}
178+
179+
/**
180+
* Comparator for Mo's Algorithm query ordering
181+
* Queries are sorted by block of left endpoint, then by right endpoint
182+
*/
183+
private static class MoComparator implements Comparator<Query> {
184+
private final int blockSize;
185+
186+
MoComparator(int blockSize) {
187+
this.blockSize = blockSize;
188+
}
189+
190+
@Override
191+
public int compare(Query a, Query b) {
192+
int blockA = a.left / blockSize;
193+
int blockB = b.left / blockSize;
194+
195+
if (blockA != blockB) {
196+
return Integer.compare(blockA, blockB);
197+
}
198+
199+
// For odd blocks, sort right in ascending order
200+
// For even blocks, sort right in descending order (optimization)
201+
if ((blockA & 1) == 1) {
202+
return Integer.compare(a.right, b.right);
203+
} else {
204+
return Integer.compare(b.right, a.right);
205+
}
206+
}
207+
}
208+
209+
/**
210+
* Demo method showing usage of Mo's Algorithm
211+
*
212+
* @param args command line arguments
213+
*/
214+
public static void main(String[] args) {
215+
// Example: Range sum queries
216+
int[] arr = {1, 3, 5, 2, 7, 6, 3, 1, 4, 8};
217+
218+
Query[] queries = {
219+
new Query(0, 2, 0), // Sum of elements from index 0 to 2: 1+3+5 = 9
220+
new Query(1, 4, 1), // Sum of elements from index 1 to 4: 3+5+2+7 = 17
221+
new Query(2, 6, 2), // Sum of elements from index 2 to 6: 5+2+7+6+3 = 23
222+
new Query(3, 8, 3) // Sum of elements from index 3 to 8: 2+7+6+3+1+4 = 23
223+
};
224+
225+
System.out.println("Array: " + Arrays.toString(arr));
226+
System.out.println("Range Sum Queries:");
227+
228+
// Store original queries for display
229+
Query[] originalQueries = new Query[queries.length];
230+
for (int i = 0; i < queries.length; i++) {
231+
originalQueries[i] = new Query(queries[i].left, queries[i].right, queries[i].index);
232+
}
233+
234+
int[] results = solveRangeSumQueries(arr, queries);
235+
236+
for (int i = 0; i < originalQueries.length; i++) {
237+
System.out.printf("Query %d: Sum of range [%d, %d] = %d%n", i, originalQueries[i].left, originalQueries[i].right, results[i]);
238+
}
239+
240+
// Example: Range frequency queries
241+
System.out.println("\nRange Frequency Queries (count of value 3):");
242+
Query[] freqQueries = {
243+
new Query(0, 5, 0), // Count of 3 in range [0, 5]: 1 occurrence
244+
new Query(2, 8, 1), // Count of 3 in range [2, 8]: 2 occurrences
245+
new Query(6, 9, 2) // Count of 3 in range [6, 9]: 1 occurrence
246+
};
247+
248+
// Store original frequency queries for display
249+
Query[] originalFreqQueries = new Query[freqQueries.length];
250+
for (int i = 0; i < freqQueries.length; i++) {
251+
originalFreqQueries[i] = new Query(freqQueries[i].left, freqQueries[i].right, freqQueries[i].index);
252+
}
253+
254+
int[] freqResults = solveRangeFrequencyQueries(arr, freqQueries, 3);
255+
256+
for (int i = 0; i < originalFreqQueries.length; i++) {
257+
System.out.printf("Query %d: Count of 3 in range [%d, %d] = %d%n", i, originalFreqQueries[i].left, originalFreqQueries[i].right, freqResults[i]);
258+
}
259+
}
260+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.thealgorithms.recursion;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
/**
7+
* DiceThrower - Generates all possible dice roll combinations that sum to a target
8+
*
9+
* This algorithm uses recursive backtracking to find all combinations of dice rolls
10+
* (faces 1-6) that sum to a given target value.
11+
*
12+
* Example: If target = 4, possible combinations include:
13+
* - "1111" (1+1+1+1 = 4)
14+
* - "13" (1+3 = 4)
15+
* - "22" (2+2 = 4)
16+
* - "4" (4 = 4)
17+
*
18+
* @author BEASTSHRIRAM
19+
* @see <a href="https://en.wikipedia.org/wiki/Backtracking">Backtracking Algorithm</a>
20+
*/
21+
public final class DiceThrower {
22+
23+
private DiceThrower() {
24+
// Utility class
25+
}
26+
27+
/**
28+
* Returns all possible dice roll combinations that sum to the target
29+
*
30+
* @param target the target sum to achieve with dice rolls
31+
* @return list of all possible combinations as strings
32+
*/
33+
public static List<String> getDiceCombinations(int target) {
34+
if (target < 0) {
35+
throw new IllegalArgumentException("Target must be non-negative");
36+
}
37+
return generateCombinations("", target);
38+
}
39+
40+
/**
41+
* Prints all possible dice roll combinations that sum to the target
42+
*
43+
* @param target the target sum to achieve with dice rolls
44+
*/
45+
public static void printDiceCombinations(int target) {
46+
if (target < 0) {
47+
throw new IllegalArgumentException("Target must be non-negative");
48+
}
49+
printCombinations("", target);
50+
}
51+
52+
/**
53+
* Recursive helper method to generate all combinations
54+
*
55+
* @param current the current combination being built
56+
* @param remaining the remaining sum needed
57+
* @return list of all combinations from this state
58+
*/
59+
private static List<String> generateCombinations(String current, int remaining) {
60+
List<String> combinations = new ArrayList<>();
61+
62+
// Base case: if remaining sum is 0, we found a valid combination
63+
if (remaining == 0) {
64+
combinations.add(current);
65+
return combinations;
66+
}
67+
68+
// Try all possible dice faces (1-6), but not more than remaining sum
69+
for (int face = 1; face <= 6 && face <= remaining; face++) {
70+
List<String> subCombinations = generateCombinations(current + face, remaining - face);
71+
combinations.addAll(subCombinations);
72+
}
73+
74+
return combinations;
75+
}
76+
77+
/**
78+
* Recursive helper method to print all combinations
79+
*
80+
* @param current the current combination being built
81+
* @param remaining the remaining sum needed
82+
*/
83+
private static void printCombinations(String current, int remaining) {
84+
// Base case: if remaining sum is 0, we found a valid combination
85+
if (remaining == 0) {
86+
System.out.println(current);
87+
return;
88+
}
89+
90+
// Try all possible dice faces (1-6), but not more than remaining sum
91+
for (int face = 1; face <= 6 && face <= remaining; face++) {
92+
printCombinations(current + face, remaining - face);
93+
}
94+
}
95+
96+
/**
97+
* Demo method to show usage
98+
*
99+
* @param args command line arguments
100+
*/
101+
public static void main(String[] args) {
102+
int target = 4;
103+
104+
System.out.println("All dice combinations that sum to " + target + ":");
105+
List<String> combinations = getDiceCombinations(target);
106+
107+
for (String combination : combinations) {
108+
System.out.println(combination);
109+
}
110+
111+
System.out.println("\nTotal combinations: " + combinations.size());
112+
}
113+
}

0 commit comments

Comments
 (0)