Skip to content

Commit 13a3e7e

Browse files
authored
Merge branch 'master' into master
2 parents ed36d8a + 6868bf8 commit 13a3e7e

File tree

6 files changed

+449
-0
lines changed

6 files changed

+449
-0
lines changed

src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,47 @@ public static boolean isPalindrome(final SinglyLinkedList linkedList) {
3030

3131
return true;
3232
}
33+
34+
// Optimised approach with O(n) time complexity and O(1) space complexity
35+
36+
public static boolean isPalindromeOptimised(Node head) {
37+
if (head == null || head.next == null) {
38+
return true;
39+
}
40+
Node slow = head;
41+
Node fast = head;
42+
while (fast != null && fast.next != null) {
43+
slow = slow.next;
44+
fast = fast.next.next;
45+
}
46+
Node midNode = slow;
47+
48+
Node prevNode = null;
49+
Node currNode = midNode;
50+
Node nextNode;
51+
while (currNode != null) {
52+
nextNode = currNode.next;
53+
currNode.next = prevNode;
54+
prevNode = currNode;
55+
currNode = nextNode;
56+
}
57+
Node left = head;
58+
Node right = prevNode;
59+
while (left != null && right != null) {
60+
if (left.val != right.val) {
61+
return false;
62+
}
63+
right = right.next;
64+
left = left.next;
65+
}
66+
return true;
67+
}
68+
static class Node {
69+
int val;
70+
Node next;
71+
Node(int val) {
72+
this.val = val;
73+
this.next = null;
74+
}
75+
}
3376
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package com.thealgorithms.searches;
2+
3+
import java.util.ArrayList;
4+
import java.util.HashMap;
5+
import java.util.List;
6+
import java.util.Map;
7+
import java.util.Objects;
8+
9+
/**
10+
* Inverted Index implementation with BM25 Scoring for movie search.
11+
* This class supports adding movie documents and searching for terms
12+
* within those documents using the BM25 algorithm.
13+
* @author Prayas Kumar (https://github.com/prayas7102)
14+
*/
15+
16+
class Movie {
17+
int docId; // Unique identifier for the movie
18+
String name; // Movie name
19+
double imdbRating; // IMDb rating of the movie
20+
int releaseYear; // Year the movie was released
21+
String content; // Full text content (could be the description or script)
22+
23+
/**
24+
* Constructor for the Movie class.
25+
* @param docId Unique identifier for the movie.
26+
* @param name Name of the movie.
27+
* @param imdbRating IMDb rating of the movie.
28+
* @param releaseYear Release year of the movie.
29+
* @param content Content or description of the movie.
30+
*/
31+
Movie(int docId, String name, double imdbRating, int releaseYear, String content) {
32+
this.docId = docId;
33+
this.name = name;
34+
this.imdbRating = imdbRating;
35+
this.releaseYear = releaseYear;
36+
this.content = content;
37+
}
38+
39+
/**
40+
* Get all the words from the movie's name and content.
41+
* Converts the name and content to lowercase and splits on non-word characters.
42+
* @return Array of words from the movie name and content.
43+
*/
44+
public String[] getWords() {
45+
return (name + " " + content).toLowerCase().split("\\W+");
46+
}
47+
48+
@Override
49+
public String toString() {
50+
return "Movie{"
51+
+ "docId=" + docId + ", name='" + name + '\'' + ", imdbRating=" + imdbRating + ", releaseYear=" + releaseYear + '}';
52+
}
53+
}
54+
55+
class SearchResult {
56+
int docId; // Unique identifier of the movie document
57+
double relevanceScore; // Relevance score based on the BM25 algorithm
58+
59+
/**
60+
* Constructor for SearchResult class.
61+
* @param docId Document ID (movie) for this search result.
62+
* @param relevanceScore The relevance score based on BM25 scoring.
63+
*/
64+
SearchResult(int docId, double relevanceScore) {
65+
this.docId = docId;
66+
this.relevanceScore = relevanceScore;
67+
}
68+
69+
public int getDocId() {
70+
return docId;
71+
}
72+
73+
@Override
74+
public String toString() {
75+
return "SearchResult{"
76+
+ "docId=" + docId + ", relevanceScore=" + relevanceScore + '}';
77+
}
78+
79+
@Override
80+
public boolean equals(Object o) {
81+
if (this == o) {
82+
return true;
83+
}
84+
if (o == null || getClass() != o.getClass()) {
85+
return false;
86+
}
87+
SearchResult that = (SearchResult) o;
88+
return docId == that.docId && Double.compare(that.relevanceScore, relevanceScore) == 0;
89+
}
90+
91+
@Override
92+
public int hashCode() {
93+
return Objects.hash(docId, relevanceScore);
94+
}
95+
96+
public double getRelevanceScore() {
97+
return this.relevanceScore;
98+
}
99+
}
100+
101+
public final class BM25InvertedIndex {
102+
private Map<String, Map<Integer, Integer>> index; // Inverted index mapping terms to document id and frequency
103+
private Map<Integer, Movie> movies; // Mapping of movie document IDs to Movie objects
104+
private int totalDocuments; // Total number of movies/documents
105+
private double avgDocumentLength; // Average length of documents (number of words)
106+
private static final double K = 1.5; // BM25 tuning parameter, controls term frequency saturation
107+
private static final double B = 0.75; // BM25 tuning parameter, controls length normalization
108+
109+
/**
110+
* Constructor for BM25InvertedIndex.
111+
* Initializes the inverted index and movie storage.
112+
*/
113+
BM25InvertedIndex() {
114+
index = new HashMap<>();
115+
movies = new HashMap<>();
116+
totalDocuments = 0;
117+
avgDocumentLength = 0.0;
118+
}
119+
120+
/**
121+
* Add a movie to the index.
122+
* @param docId Unique identifier for the movie.
123+
* @param name Name of the movie.
124+
* @param imdbRating IMDb rating of the movie.
125+
* @param releaseYear Release year of the movie.
126+
* @param content Content or description of the movie.
127+
*/
128+
public void addMovie(int docId, String name, double imdbRating, int releaseYear, String content) {
129+
Movie movie = new Movie(docId, name, imdbRating, releaseYear, content);
130+
movies.put(docId, movie);
131+
totalDocuments++;
132+
133+
// Get words (terms) from the movie's name and content
134+
String[] terms = movie.getWords();
135+
int docLength = terms.length;
136+
137+
// Update the average document length
138+
avgDocumentLength = (avgDocumentLength * (totalDocuments - 1) + docLength) / totalDocuments;
139+
140+
// Update the inverted index
141+
for (String term : terms) {
142+
// Create a new entry if the term is not yet in the index
143+
index.putIfAbsent(term, new HashMap<>());
144+
145+
// Get the list of documents containing the term
146+
Map<Integer, Integer> docList = index.get(term);
147+
if (docList == null) {
148+
docList = new HashMap<>();
149+
index.put(term, docList); // Ensure docList is added to the index
150+
}
151+
// Increment the term frequency in this document
152+
docList.put(docId, docList.getOrDefault(docId, 0) + 1);
153+
}
154+
}
155+
156+
public int getMoviesLength() {
157+
return movies.size();
158+
}
159+
160+
/**
161+
* Search for documents containing a term using BM25 scoring.
162+
* @param term The search term.
163+
* @return A list of search results sorted by relevance score.
164+
*/
165+
public List<SearchResult> search(String term) {
166+
term = term.toLowerCase(); // Normalize search term
167+
if (!index.containsKey(term)) {
168+
return new ArrayList<>(); // Return empty list if term not found
169+
}
170+
171+
Map<Integer, Integer> termDocs = index.get(term); // Documents containing the term
172+
List<SearchResult> results = new ArrayList<>();
173+
174+
// Compute IDF for the search term
175+
double idf = computeIDF(termDocs.size());
176+
177+
// Calculate relevance scores for all documents containing the term
178+
for (Map.Entry<Integer, Integer> entry : termDocs.entrySet()) {
179+
int docId = entry.getKey();
180+
int termFrequency = entry.getValue();
181+
Movie movie = movies.get(docId);
182+
if (movie == null) {
183+
continue; // Skip this document if movie doesn't exist
184+
}
185+
double docLength = movie.getWords().length;
186+
187+
// Compute BM25 relevance score
188+
double score = computeBM25Score(termFrequency, docLength, idf);
189+
results.add(new SearchResult(docId, score));
190+
}
191+
192+
// Sort the results by relevance score in descending order
193+
results.sort((r1, r2) -> Double.compare(r2.relevanceScore, r1.relevanceScore));
194+
return results;
195+
}
196+
197+
/**
198+
* Compute the BM25 score for a given term and document.
199+
* @param termFrequency The frequency of the term in the document.
200+
* @param docLength The length of the document.
201+
* @param idf The inverse document frequency of the term.
202+
* @return The BM25 relevance score for the term in the document.
203+
*/
204+
private double computeBM25Score(int termFrequency, double docLength, double idf) {
205+
double numerator = termFrequency * (K + 1);
206+
double denominator = termFrequency + K * (1 - B + B * (docLength / avgDocumentLength));
207+
return idf * (numerator / denominator);
208+
}
209+
210+
/**
211+
* Compute the inverse document frequency (IDF) of a term.
212+
* The IDF measures the importance of a term across the entire document set.
213+
* @param docFrequency The number of documents that contain the term.
214+
* @return The inverse document frequency (IDF) value.
215+
*/
216+
private double computeIDF(int docFrequency) {
217+
// Total number of documents in the index
218+
return Math.log((totalDocuments - docFrequency + 0.5) / (docFrequency + 0.5));
219+
}
220+
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,22 @@ public static boolean isValid(String s) {
3838
}
3939
return head == 0;
4040
}
41+
public static boolean isValidParentheses(String s) {
42+
int i = -1;
43+
char[] stack = new char[s.length()];
44+
String openBrackets = "({[";
45+
String closeBrackets = ")}]";
46+
for (char ch : s.toCharArray()) {
47+
if (openBrackets.indexOf(ch) != -1) {
48+
stack[++i] = ch;
49+
} else {
50+
if (i >= 0 && openBrackets.indexOf(stack[i]) == closeBrackets.indexOf(ch)) {
51+
i--;
52+
} else {
53+
return false;
54+
}
55+
}
56+
}
57+
return i == -1;
58+
}
4159
}

src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import org.junit.jupiter.api.Test;
88

99
public class PalindromeSinglyLinkedListTest {
10+
11+
// Stack-based tests
1012
@Test
1113
public void testWithEmptyList() {
1214
assertTrue(PalindromeSinglyLinkedList.isPalindrome(new SinglyLinkedList()));
@@ -67,4 +69,74 @@ public void testWithListWithEvenLengthNegative() {
6769
exampleList.insert(20);
6870
assertFalse(PalindromeSinglyLinkedList.isPalindrome(exampleList));
6971
}
72+
73+
// Optimized approach tests
74+
@Test
75+
public void testOptimisedWithEmptyList() {
76+
assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(null));
77+
}
78+
79+
@Test
80+
public void testOptimisedWithSingleElement() {
81+
PalindromeSinglyLinkedList.Node node = new PalindromeSinglyLinkedList.Node(100);
82+
assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(node));
83+
}
84+
85+
@Test
86+
public void testOptimisedWithOddLengthPositive() {
87+
PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(1);
88+
PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(2);
89+
PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(1);
90+
node1.next = node2;
91+
node2.next = node3;
92+
assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(node1));
93+
}
94+
95+
@Test
96+
public void testOptimisedWithOddLengthPositive2() {
97+
PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(3);
98+
PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(2);
99+
PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(1);
100+
PalindromeSinglyLinkedList.Node node4 = new PalindromeSinglyLinkedList.Node(2);
101+
PalindromeSinglyLinkedList.Node node5 = new PalindromeSinglyLinkedList.Node(3);
102+
node1.next = node2;
103+
node2.next = node3;
104+
node3.next = node4;
105+
node4.next = node5;
106+
assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(node1));
107+
}
108+
109+
@Test
110+
public void testOptimisedWithEvenLengthPositive() {
111+
PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(10);
112+
PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(20);
113+
PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(20);
114+
PalindromeSinglyLinkedList.Node node4 = new PalindromeSinglyLinkedList.Node(10);
115+
node1.next = node2;
116+
node2.next = node3;
117+
node3.next = node4;
118+
assertTrue(PalindromeSinglyLinkedList.isPalindromeOptimised(node1));
119+
}
120+
121+
@Test
122+
public void testOptimisedWithOddLengthNegative() {
123+
PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(1);
124+
PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(2);
125+
PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(2);
126+
node1.next = node2;
127+
node2.next = node3;
128+
assertFalse(PalindromeSinglyLinkedList.isPalindromeOptimised(node1));
129+
}
130+
131+
@Test
132+
public void testOptimisedWithEvenLengthNegative() {
133+
PalindromeSinglyLinkedList.Node node1 = new PalindromeSinglyLinkedList.Node(10);
134+
PalindromeSinglyLinkedList.Node node2 = new PalindromeSinglyLinkedList.Node(20);
135+
PalindromeSinglyLinkedList.Node node3 = new PalindromeSinglyLinkedList.Node(20);
136+
PalindromeSinglyLinkedList.Node node4 = new PalindromeSinglyLinkedList.Node(20);
137+
node1.next = node2;
138+
node2.next = node3;
139+
node3.next = node4;
140+
assertFalse(PalindromeSinglyLinkedList.isPalindromeOptimised(node1));
141+
}
70142
}

0 commit comments

Comments
 (0)