Skip to content

Commit 964175f

Browse files
Merge branch 'master' into main
2 parents 03f4d41 + dfaa495 commit 964175f

File tree

6 files changed

+210
-70
lines changed

6 files changed

+210
-70
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
<dependency>
113113
<groupId>com.puppycrawl.tools</groupId>
114114
<artifactId>checkstyle</artifactId>
115-
<version>13.0.0</version>
115+
<version>13.1.0</version>
116116
</dependency>
117117
</dependencies>
118118
</plugin>

src/main/java/com/thealgorithms/searches/BinarySearch.java

Lines changed: 90 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,32 @@
33
import com.thealgorithms.devutils.searches.SearchAlgorithm;
44

55
/**
6-
* Binary search is one of the most popular algorithms The algorithm finds the
7-
* position of a target value within a sorted array
8-
* IMPORTANT
9-
* This algorithm works correctly only if the input array is sorted
10-
* in ascending order.
11-
* <p>
12-
* Worst-case performance O(log n) Best-case performance O(1) Average
13-
* performance O(log n) Worst-case space complexity O(1)
6+
* Binary Search Algorithm Implementation
7+
*
8+
* <p>Binary search is one of the most efficient searching algorithms for finding a target element
9+
* in a SORTED array. It works by repeatedly dividing the search space in half, eliminating half of
10+
* the remaining elements in each step.
11+
*
12+
* <p>IMPORTANT: This algorithm ONLY works correctly if the input array is sorted in ascending
13+
* order.
14+
*
15+
* <p>Algorithm Overview: 1. Start with the entire array (left = 0, right = array.length - 1) 2.
16+
* Calculate the middle index 3. Compare the middle element with the target: - If middle element
17+
* equals target: Found! Return the index - If middle element is less than target: Search the right
18+
* half - If middle element is greater than target: Search the left half 4. Repeat until element is
19+
* found or search space is exhausted
20+
*
21+
* <p>Performance Analysis: - Best-case time complexity: O(1) - Element found at middle on first
22+
* try - Average-case time complexity: O(log n) - Most common scenario - Worst-case time
23+
* complexity: O(log n) - Element not found or at extreme end - Space complexity: O(1) - Only uses
24+
* a constant amount of extra space
25+
*
26+
* <p>Example Walkthrough: Array: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19] Target: 7
27+
*
28+
* <p>Step 1: left=0, right=9, mid=4, array[4]=9 (9 &gt; 7, search left half) Step 2: left=0,
29+
* right=3, mid=1, array[1]=3 (3 &lt; 7, search right half) Step 3: left=2, right=3, mid=2,
30+
* array[2]=5 (5 &lt; 7, search right half) Step 4: left=3, right=3, mid=3, array[3]=7 (Found!
31+
* Return index 3)
1432
*
1533
* @author Varun Upadhyay (https://github.com/varunu28)
1634
* @author Podshivalov Nikita (https://github.com/nikitap492)
@@ -20,41 +38,89 @@
2038
class BinarySearch implements SearchAlgorithm {
2139

2240
/**
23-
* @param array is an array where the element should be found
24-
* @param key is an element which should be found
25-
* @param <T> is any comparable type
26-
* @return index of the element
41+
* Generic method to perform binary search on any comparable type. This is the main entry point
42+
* for binary search operations.
43+
*
44+
* <p>Example Usage:
45+
* <pre>
46+
* Integer[] numbers = {1, 3, 5, 7, 9, 11};
47+
* int result = new BinarySearch().find(numbers, 7);
48+
* // result will be 3 (index of element 7)
49+
*
50+
* int notFound = new BinarySearch().find(numbers, 4);
51+
* // notFound will be -1 (element 4 does not exist)
52+
* </pre>
53+
*
54+
* @param <T> The type of elements in the array (must be Comparable)
55+
* @param array The sorted array to search in (MUST be sorted in ascending order)
56+
* @param key The element to search for
57+
* @return The index of the key if found, -1 if not found or if array is null/empty
2758
*/
2859
@Override
2960
public <T extends Comparable<T>> int find(T[] array, T key) {
61+
// Handle edge case: empty array
3062
if (array == null || array.length == 0) {
3163
return -1;
3264
}
65+
66+
// Delegate to the core search implementation
3367
return search(array, key, 0, array.length - 1);
3468
}
3569

3670
/**
37-
* This method implements the Generic Binary Search
71+
* Core recursive implementation of binary search algorithm. This method divides the problem
72+
* into smaller subproblems recursively.
73+
*
74+
* <p>How it works:
75+
* <ol>
76+
* <li>Calculate the middle index to avoid integer overflow</li>
77+
* <li>Check if middle element matches the target</li>
78+
* <li>If not, recursively search either left or right half</li>
79+
* <li>Base case: left &gt; right means element not found</li>
80+
* </ol>
81+
*
82+
* <p>Time Complexity: O(log n) because we halve the search space each time.
83+
* Space Complexity: O(log n) due to recursive call stack.
3884
*
39-
* @param array The array to make the binary search
40-
* @param key The number you are looking for
41-
* @param left The lower bound
42-
* @param right The upper bound
43-
* @return the location of the key
85+
* @param <T> The type of elements (must be Comparable)
86+
* @param array The sorted array to search in
87+
* @param key The element we're looking for
88+
* @param left The leftmost index of current search range (inclusive)
89+
* @param right The rightmost index of current search range (inclusive)
90+
* @return The index where key is located, or -1 if not found
4491
*/
4592
private <T extends Comparable<T>> int search(T[] array, T key, int left, int right) {
93+
// Base case: Search space is exhausted
94+
// This happens when left pointer crosses right pointer
4695
if (right < left) {
47-
return -1; // this means that the key not found
96+
return -1; // Key not found in the array
4897
}
49-
// find median
50-
int median = (left + right) >>> 1;
98+
99+
// Calculate middle index
100+
// Using (left + right) / 2 could cause integer overflow for large arrays
101+
// So we use: left + (right - left) / 2 which is mathematically equivalent
102+
// but prevents overflow
103+
int median = (left + right) >>> 1; // Unsigned right shift is faster division by 2
104+
105+
// Get the value at middle position for comparison
51106
int comp = key.compareTo(array[median]);
52107

108+
// Case 1: Found the target element at middle position
53109
if (comp == 0) {
54-
return median;
55-
} else if (comp < 0) {
110+
return median; // Return the index where element was found
111+
}
112+
// Case 2: Target is smaller than middle element
113+
// This means if target exists, it must be in the LEFT half
114+
else if (comp < 0) {
115+
// Recursively search the left half
116+
// New search range: [left, median - 1]
56117
return search(array, key, left, median - 1);
57-
} else {
118+
}
119+
// Case 3: Target is greater than middle element
120+
// This means if target exists, it must be in the RIGHT half
121+
else {
122+
// Recursively search the right half
123+
// New search range: [median + 1, right]
58124
return search(array, key, median + 1, right);
59125
}
60126
}

src/main/java/com/thealgorithms/strings/KMP.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.thealgorithms.strings;
22

3+
import java.util.ArrayList;
4+
import java.util.List;
5+
36
/**
47
* Implementation of Knuth–Morris–Pratt algorithm Usage: see the main function
58
* for an example
@@ -8,16 +11,19 @@ public final class KMP {
811
private KMP() {
912
}
1013

11-
// a working example
12-
13-
public static void main(String[] args) {
14-
final String haystack = "AAAAABAAABA"; // This is the full string
15-
final String needle = "AAAA"; // This is the substring that we want to find
16-
kmpMatcher(haystack, needle);
17-
}
14+
/**
15+
* find the starting index in string haystack[] that matches the search word P[]
16+
*
17+
* @param haystack The text to be searched
18+
* @param needle The pattern to be searched for
19+
* @return A list of starting indices where the pattern is found
20+
*/
21+
public static List<Integer> kmpMatcher(final String haystack, final String needle) {
22+
List<Integer> occurrences = new ArrayList<>();
23+
if (haystack == null || needle == null || needle.isEmpty()) {
24+
return occurrences;
25+
}
1826

19-
// find the starting index in string haystack[] that matches the search word P[]
20-
public static void kmpMatcher(final String haystack, final String needle) {
2127
final int m = haystack.length();
2228
final int n = needle.length();
2329
final int[] pi = computePrefixFunction(needle);
@@ -32,10 +38,11 @@ public static void kmpMatcher(final String haystack, final String needle) {
3238
}
3339

3440
if (q == n) {
35-
System.out.println("Pattern starts: " + (i + 1 - n));
41+
occurrences.add(i + 1 - n);
3642
q = pi[q - 1];
3743
}
3844
}
45+
return occurrences;
3946
}
4047

4148
// return the prefix function
Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,30 @@
11
package com.thealgorithms.strings;
22

3-
import java.util.Scanner;
3+
import java.util.ArrayList;
4+
import java.util.List;
45

56
/**
67
* @author Prateek Kumar Oraon (https://github.com/prateekKrOraon)
78
*
8-
An implementation of Rabin-Karp string matching algorithm
9-
Program will simply end if there is no match
9+
* An implementation of Rabin-Karp string matching algorithm
10+
* Program will simply end if there is no match
1011
*/
1112
public final class RabinKarp {
1213
private RabinKarp() {
1314
}
1415

15-
public static Scanner scanner = null;
16-
public static final int ALPHABET_SIZE = 256;
16+
private static final int ALPHABET_SIZE = 256;
1717

18-
public static void main(String[] args) {
19-
scanner = new Scanner(System.in);
20-
System.out.println("Enter String");
21-
String text = scanner.nextLine();
22-
System.out.println("Enter pattern");
23-
String pattern = scanner.nextLine();
24-
25-
int q = 101;
26-
searchPat(text, pattern, q);
18+
public static List<Integer> search(String text, String pattern) {
19+
return search(text, pattern, 101);
2720
}
2821

29-
private static void searchPat(String text, String pattern, int q) {
22+
public static List<Integer> search(String text, String pattern, int q) {
23+
List<Integer> occurrences = new ArrayList<>();
24+
if (text == null || pattern == null || pattern.isEmpty()) {
25+
return occurrences;
26+
}
27+
3028
int m = pattern.length();
3129
int n = text.length();
3230
int t = 0;
@@ -35,48 +33,42 @@ private static void searchPat(String text, String pattern, int q) {
3533
int j = 0;
3634
int i = 0;
3735

38-
h = (int) Math.pow(ALPHABET_SIZE, m - 1) % q;
36+
if (m > n) {
37+
return new ArrayList<>();
38+
}
39+
40+
// h = pow(ALPHABET_SIZE, m-1) % q
41+
for (i = 0; i < m - 1; i++) {
42+
h = h * ALPHABET_SIZE % q;
43+
}
3944

4045
for (i = 0; i < m; i++) {
41-
// hash value is calculated for each character and then added with the hash value of the
42-
// next character for pattern as well as the text for length equal to the length of
43-
// pattern
4446
p = (ALPHABET_SIZE * p + pattern.charAt(i)) % q;
4547
t = (ALPHABET_SIZE * t + text.charAt(i)) % q;
4648
}
4749

4850
for (i = 0; i <= n - m; i++) {
49-
// if the calculated hash value of the pattern and text matches then
50-
// all the characters of the pattern is matched with the text of length equal to length
51-
// of the pattern if all matches then pattern exist in string if not then the hash value
52-
// of the first character of the text is subtracted and hash value of the next character
53-
// after the end of the evaluated characters is added
5451
if (p == t) {
55-
// if hash value matches then the individual characters are matched
5652
for (j = 0; j < m; j++) {
57-
// if not matched then break out of the loop
5853
if (text.charAt(i + j) != pattern.charAt(j)) {
5954
break;
6055
}
6156
}
6257

63-
// if all characters are matched then pattern exist in the string
6458
if (j == m) {
65-
System.out.println("Pattern found at index " + i);
59+
occurrences.add(i);
6660
}
6761
}
6862

69-
// if i<n-m then hash value of the first character of the text is subtracted and hash
70-
// value of the next character after the end of the evaluated characters is added to get
71-
// the hash value of the next window of characters in the text
7263
if (i < n - m) {
73-
t = (ALPHABET_SIZE * (t - text.charAt(i) * h) + text.charAt(i + m)) % q;
74-
75-
// if hash value becomes less than zero than q is added to make it positive
64+
t = (t - text.charAt(i) * h % q);
7665
if (t < 0) {
77-
t = (t + q);
66+
t += q;
7867
}
68+
t = t * ALPHABET_SIZE % q;
69+
t = (t + text.charAt(i + m)) % q;
7970
}
8071
}
72+
return occurrences;
8173
}
8274
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.thealgorithms.strings;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.List;
6+
import org.junit.jupiter.api.Test;
7+
8+
public class KMPTest {
9+
10+
@Test
11+
public void testNullInputs() {
12+
assertEquals(List.of(), KMP.kmpMatcher(null, "A"));
13+
assertEquals(List.of(), KMP.kmpMatcher("A", null));
14+
assertEquals(List.of(), KMP.kmpMatcher(null, null));
15+
}
16+
17+
@Test
18+
public void testKMPMatcher() {
19+
assertEquals(List.of(0, 1), KMP.kmpMatcher("AAAAABAAABA", "AAAA"));
20+
assertEquals(List.of(0, 3), KMP.kmpMatcher("ABCABC", "ABC"));
21+
assertEquals(List.of(10), KMP.kmpMatcher("ABABDABACDABABCABAB", "ABABCABAB"));
22+
assertEquals(List.of(), KMP.kmpMatcher("ABCDE", "FGH"));
23+
assertEquals(List.of(), KMP.kmpMatcher("A", "AA"));
24+
assertEquals(List.of(0, 1, 2), KMP.kmpMatcher("AAA", "A"));
25+
assertEquals(List.of(0), KMP.kmpMatcher("A", "A"));
26+
assertEquals(List.of(), KMP.kmpMatcher("", "A"));
27+
assertEquals(List.of(), KMP.kmpMatcher("A", ""));
28+
}
29+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.thealgorithms.strings;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.List;
6+
import org.junit.jupiter.api.Test;
7+
8+
public class RabinKarpTest {
9+
10+
@Test
11+
public void testNullInputs() {
12+
assertEquals(List.of(), RabinKarp.search(null, "A"));
13+
assertEquals(List.of(), RabinKarp.search("A", null));
14+
assertEquals(List.of(), RabinKarp.search(null, null));
15+
}
16+
17+
@Test
18+
public void testHashCollision() {
19+
// 'a' = 97. (char)198 % 101 = 97.
20+
// For length 1, h = 1. p = 97. t = 198 % 101 = 97.
21+
// Collision occurs, loop checks characters: 198 != 97, breaks.
22+
char collisionChar = (char) 198;
23+
String text = String.valueOf(collisionChar);
24+
String pattern = "a";
25+
assertEquals(List.of(), RabinKarp.search(text, pattern));
26+
}
27+
28+
@Test
29+
public void testSearchWithCustomQ() {
30+
// Using a different prime
31+
assertEquals(List.of(0, 1), RabinKarp.search("AAAA", "AAA", 13));
32+
}
33+
34+
@Test
35+
public void testRabinKarpSearch() {
36+
assertEquals(List.of(0, 1), RabinKarp.search("AAAAABAAABA", "AAAA"));
37+
assertEquals(List.of(0, 3), RabinKarp.search("ABCABC", "ABC"));
38+
assertEquals(List.of(10), RabinKarp.search("ABABDABACDABABCABAB", "ABABCABAB"));
39+
assertEquals(List.of(), RabinKarp.search("ABCDE", "FGH"));
40+
assertEquals(List.of(), RabinKarp.search("A", "AA"));
41+
assertEquals(List.of(0, 1, 2), RabinKarp.search("AAA", "A"));
42+
assertEquals(List.of(0), RabinKarp.search("A", "A"));
43+
assertEquals(List.of(), RabinKarp.search("", "A"));
44+
assertEquals(List.of(), RabinKarp.search("A", ""));
45+
}
46+
}

0 commit comments

Comments
 (0)