Skip to content

Commit 4e409aa

Browse files
authored
Merge pull request #195 from BrianLusina/feat/algorithms-trie-index-pairs-of-string
feat(algorithms, trie, index pairs): index pairs of strings
2 parents 83eee67 + ee4b9c4 commit 4e409aa

7 files changed

Lines changed: 126 additions & 0 deletions

File tree

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,8 @@
426426
* Smallest Range Covering K Lists
427427
* [Test Smallest Range Covering Elements From K Lists](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/top_k_elements/smallest_range_covering_k_lists/test_smallest_range_covering_elements_from_k_lists.py)
428428
* Trie
429+
* Index Pairs Of A String
430+
* [Test Index Pairs Of A String](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/trie/index_pairs_of_a_string/test_index_pairs_of_a_string.py)
429431
* Longest Word With Prefixes
430432
* [Test Longest Word With Prefixes](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/trie/longest_word_with_prefixes/test_longest_word_with_prefixes.py)
431433
* Topkfreqwords
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Index Pairs of a String
2+
3+
Given a string text and an array of strings words, return a list of all index pairs [i, j] such that the substring
4+
`text[i...j]` is present in words.
5+
6+
Return the pairs [i, j] in a sorted order, first by the value of i, and if two pairs have the same i, by the value of j.
7+
8+
## Constraints
9+
10+
- 1 ≤ text.length ≤ 100
11+
- 1 ≤ words.length ≤ 20
12+
- 1 ≤ words[i].length ≤ 50
13+
- text and words[i] consist of lowercase English letters.
14+
- All the strings of words are unique.
15+
16+
## Examples
17+
18+
![Example 1](./images/examples/index_pairs_of_a_string_example_1.png)
19+
![Example 2](./images/examples/index_pairs_of_a_string_example_2.png)
20+
![Example 3](./images/examples/index_pairs_of_a_string_example_3.png)
21+
22+
## Solution
23+
24+
The algorithm uses a trie to find all pairs of start and end indexes for substrings in a given text that match words
25+
from a list. First, it builds the trie by inserting each word from the list. Then, it iterates over each starting index
26+
in the text to match substrings using the trie. For each character sequence, it checks if the current character exists
27+
in the trie and traverses accordingly. If a word’s end is found (marked by a flag), the start and end indexes of the
28+
matched substring are recorded in the result. This method optimizes substring searching using the trie structure to
29+
avoid redundant checks and efficiently match multiple words in the text.
30+
31+
The algorithm to solve this problem is as follows:
32+
33+
1. Insert each word from the list into the trie. Each character is added as a node, and isEndOfWord is set to mark the
34+
end of a word.
35+
2. Loop through each character in text (starting at index i). For each starting index, try to find substrings that match
36+
words in the trie by traversing them.
37+
3. For each character at position i, the algorithm begins traversing the trie from the root node. It then checks whether
38+
each subsequent character (from index i to j) is a child node in the trie. If the character is found, the traversal
39+
continues to the next character. A valid match has been found if the current node in the Trie marks the end of a word
40+
(i.e., isEndOfWord is True). In that case, the index pair [i, j] is recorded, where i is the start index, and j is the
41+
end index of the matched word.
42+
4. After checking all starting indexes, return the list of index pairs representing matched words’ start and end positions.
43+
44+
### Time Complexity
45+
46+
Inserting n words of average length m into the trie takes O(n∗m). For each index i in text, we perform a search that
47+
takes linear time in the length of the substring. This gives an overall time complexity of O(l∗k), where l is the length
48+
of the text, and k is the average length of a word.
49+
50+
### Space Complexity
51+
52+
The space the trie uses depends on the number of characters in the list words. If there are n words with an average length
53+
of m, the trie can take up to `O(n∗m)` space, assuming no overlapping prefixes among the words. In the worst case, if all
54+
words are unique and have no shared prefixes, each character is stored separately.
55+
56+
The result list stores the index pairs [i, j]. In the worst case, every possible substring of text could be a word in
57+
the list words. This would result in `O(l^2)` pairs, where l is the length of the string text. So, the space complexity
58+
for the result list is `O(l^2)` in the worst case.
59+
60+
Thus, the overall space complexity is `O(n∗m+l^2)`.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import List
2+
from datastructures.trees.trie import Trie
3+
4+
def index_pairs(text: str, words: List[str]) -> List[List[int]]:
5+
trie = Trie()
6+
7+
for word in words:
8+
trie.insert(word)
9+
10+
results = []
11+
12+
# loop through each character in the text
13+
for idx, char in enumerate(text):
14+
# start from the root of the Trie for each character in the text
15+
node = trie.root
16+
17+
# Check each possible substring starting from index idx
18+
for j in range(idx, len(text)):
19+
ch = text[j]
20+
# If the character is not in the current Trie Node's children, stop searching
21+
if ch not in node.children:
22+
break
23+
24+
# Move to the next node in the Trie
25+
node = node.children[ch]
26+
27+
# If we reach the end of a word, record the indices
28+
if node.is_end:
29+
results.append([idx, j])
30+
31+
return results
47.5 KB
Loading
44.9 KB
Loading
28.5 KB
Loading
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import unittest
2+
from typing import List
3+
from parameterized import parameterized
4+
from algorithms.trie.index_pairs_of_a_string import index_pairs
5+
6+
INDEX_PAIRS_OF_A_STRING = [
7+
(
8+
"thestoryofeducativeandme",
9+
["story", "feduc", "educative"],
10+
[[3, 7], [9, 13], [10, 18]],
11+
),
12+
("xyxyx", ["xyx", "xy"], [[0, 1], [0, 2], [2, 3], [2, 4]]),
13+
("howareyou", ["how", "are", "you"], [[0, 2], [3, 5], [6, 8]]),
14+
("weather", ["weather"], [[0, 6]]),
15+
(
16+
"aquickbrownfoxjumpsoverthelazydog",
17+
["quick", "fox", "dog"],
18+
[[1, 5], [11, 13], [30, 32]],
19+
),
20+
]
21+
22+
23+
class IndexPairsOfAStringTestCase(unittest.TestCase):
24+
@parameterized.expand(INDEX_PAIRS_OF_A_STRING)
25+
def test_index_pairs_of_a_string(
26+
self, text: str, words: List[str], expected: List[List[int]]
27+
):
28+
actual = index_pairs(text, words)
29+
self.assertEqual(expected, actual)
30+
31+
32+
if __name__ == "__main__":
33+
unittest.main()

0 commit comments

Comments
 (0)