Skip to content

Commit ad930f7

Browse files
authored
Merge pull request #196 from BrianLusina/feat/algorithms-powerful-integers
feat(algorithms, hash-maps): powerful integers
2 parents 4e409aa + 50a762d commit ad930f7

6 files changed

Lines changed: 255 additions & 0 deletions

File tree

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@
217217
* [Test First Unique Character](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/first_unique_character/test_first_unique_character.py)
218218
* Jewels And Stones
219219
* [Test Jewels And Stones](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/jewels_and_stones/test_jewels_and_stones.py)
220+
* Powerful Integers
221+
* [Test Powerful Integers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/powerful_integers/test_powerful_integers.py)
220222
* Ransom Note
221223
* [Test Ransom Note](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/ransom_note/test_ransom_note.py)
222224
* Heap

algorithms/dynamic_programming/max_subarray/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,36 @@ find_subarr_maxsum([4, -1, 2, 1, -40, 1, 2, -1, 4]) == [[[4, -1, 2, 1], [1, 2, -
2020
```
2121

2222
If the array does not have any sub-array with a positive sum of its terms, the function will return [[], 0].
23+
24+
## Solution
25+
26+
We’ll solve this problem using Kadane’s Algorithm. It’s a dynamic programming approach. The key idea is that we can
27+
efficiently find the maximum subarray ending at any position based on the maximum subarray ending at the previous
28+
position.
29+
30+
The subproblem here is finding the maximum subarray sum that ends at a specific index i. We need to calculate this for
31+
every index i in the array. The base case is the first element of the array, where both current subarray sum and maximum
32+
subarray sum are initialized with the first element’s value. This is the starting point for solving the subproblems. At
33+
each step, we reuse the previously computed maximum subarray sum to find the solution for the current subproblem.
34+
35+
The steps of the algorithm are given below:
36+
37+
1. Initialize two variables, current_sum and max_sum, with the value of the first element in the input list. These
38+
variables are used to keep track of the current sum of the subarray being considered, and the maximum sum found so
39+
far.
40+
2. Iterate through the input list, starting from the second element to the end of the list. Within the loop, perform the
41+
following steps:
42+
- If current_sum + nums[i] is smaller than nums[i], this indicates that starting a new subarray with nums[i] would
43+
yield a larger sum than extending the previous subarray. In such cases, reset current_sum to nums[i].
44+
- Compare the current max_sum with the updated current_sum. This step ensures that the max_sum always stores the
45+
maximum sum encountered so far.
46+
3. After the loop completes, the function returns the max_sum, which represents the maximum sum of any contiguous
47+
subarray within the input list.
48+
49+
### Time Complexity
50+
The time complexity of this solution is O(n) because we are iterating the array once, where n is the total number of
51+
elements in the array.
52+
53+
### Space Complexity
54+
55+
The space complexity of this solution is O(1).
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Powerful Integers
2+
3+
Given three integers x, y, and bound, return a list of all the powerful integers that have a value less than or equal to
4+
bound.
5+
6+
An integer is powerful if it can be represented as xi + yj for some integers i >= 0 and j >= 0.
7+
8+
You may return the answer in any order. In your answer, each value should occur at most once.
9+
10+
## Examples
11+
12+
Example 1:
13+
14+
```text
15+
Input: x = 2, y = 3, bound = 10
16+
Output: [2,3,4,5,7,9,10]
17+
Explanation:
18+
2 = 20 + 30
19+
3 = 21 + 30
20+
4 = 20 + 31
21+
5 = 21 + 31
22+
7 = 22 + 31
23+
9 = 23 + 30
24+
10 = 20 + 32
25+
```
26+
27+
Example 2:
28+
```text
29+
Input: x = 3, y = 5, bound = 15
30+
Output: [2,4,6,8,10,14]
31+
```
32+
33+
## Constraints
34+
35+
- 1 <= x, y <= 100
36+
- 0 <= bound <= 10^6
37+
38+
## Topics
39+
40+
- Hash Table
41+
- Math
42+
- Enumeration
43+
44+
## Solution(s)
45+
46+
1. [Logarithmic Bounds](#logarithmic-bounds)
47+
2. [Logarithmic Bound 2](#logarithmic-bound-2)
48+
49+
### Logarithmic Bounds
50+
51+
Our approach here will only focus on finding the bounds for numbers x and y. One way to get the bounds on the powers is
52+
to have nested loops that iterate from [0⋯bound]. However, this is very inefficient because the bound can be an extremely
53+
large value and a nested-loop over this bound will take forever to finish. Also, we don't need to iterate over all of the
54+
values and combinations. There is a way to find a much smaller bound for the powers.
55+
56+
m^n <= bound
57+
58+
This formula implies that
59+
60+
n<=log(m) bound
61+
62+
> We can use the log function to determine the bounds for the powers of "x" and "y".
63+
64+
#### Algorithm
65+
66+
1. Let's define `a` as the power bound for the number `x`. Thus `a=log(x)bound`.
67+
2. Similarly, let's define `b` as the power bound for the number `y`. Thus `b=log(y)bound`.
68+
3. Now we will have our nested for-loop structure where the outer loop will iterate from [0⋯a] and the inner loop will
69+
iterate from [0⋯b].
70+
4. We will use a set to store our results. This is because we might generate the same value multiple times. E.g.
71+
`2^1 + 3^2 = 11` and `2^3 + 3^1 = 11`. We only need to include the value 11 once and hence, we will use a set called
72+
`resultSet` to store our answers.
73+
5. At each step, we calculate `x^a + y^b` and check if this value is less than or equal to bound. If it is, then this is
74+
a powerful integer and we add it to our set of answers.
75+
6. We need special break conditions to handle the scenario when x or y is 1. This is because if the number x or y is 1,
76+
then their power-bound will be equal to bound itself. Also, it doesn't matter what their power-bound is because 1^N
77+
is always 1. Thus, when the number is 1, we don't need to loop from [0⋯N] and we can break early.
78+
7. Finally, convert the set to a list and return.
79+
80+
#### Time Complexity
81+
82+
Time Complexity: Let `N` be `log(x)bound` and `M` be `log(y)bound`. Then the overall time complexity is `O(N×M)` because
83+
we used a nested loop structure to calculate all of the powerful integers.
84+
85+
#### Space Complexity
86+
87+
`O(N×M)` because we use a set to omit duplicates. We could just use our result list to check membership before adding
88+
values. However, that would be costly in terms of time complexity because it would require a full scan of the result list
89+
to see if the value already exists.
90+
91+
### Logarithmic Bound 2
92+
93+
The key insight is that since x^i and y^j grow exponentially, the number of distinct powers of x and y that remain within
94+
bound is at most O(log(bound) each. We can enumerate all pairs (i,j) by iterating through powers of x in an outer loop
95+
and powers of y in an inner loop, adding each sum x^i + y^j that is less than or equal to bound into a hash set called
96+
`resultSet`. The set automatically handles deduplication, ensuring each powerful integer appears at most once. A special
97+
case arises when x or y equals 1, because 1^i is always 1 regardless of the exponent, which would cause an infinite loop.
98+
We handle this by breaking out of the respective loop after the first iteration when x or y is 1.
99+
100+
Solution steps:
101+
102+
1. Initialize an empty set resultSet to store unique powerful integers.
103+
2. Initialize `powX` to 1, representing x^0
104+
3. Start an outer while loop that continues as long as `powX ≤ bound`.
105+
- Inside the outer loop, initialize `powY` to 1, representing y^0
106+
- Start an inner while loop that continues as long as `powX + powY ≤ bound`.
107+
- Add the value `powX` + `powY` to `resultSet`.
108+
- If y equals 1, break out of the inner loop immediately, since y^j will always be 1 and further iterations would
109+
not produce new values.
110+
- Otherwise, multiply `powY` by y to advance to the next power of y.
111+
- After the inner loop, if x equals 1, break out of the outer loop immediately, since x^i will always be 1 and further
112+
iterations would not produce new values.
113+
- Otherwise, multiply `powX` by x to advance to the next power of x.
114+
4. Convert resultSet to a list and return it.
115+
116+
#### Time Complexity
117+
118+
The time complexity of the solution is O(logx(bound)) * logy(bound) because the outer loop runs at most O(logx(bound))
119+
times and the inner loop runs at most O(logy(bound)) times for each iteration of the outer loop. Since bound ≤10^6 and
120+
the minimum base greater than 1 is 2, each loop runs at most about log2(10^6)≈20 iterations, making this very efficient.
121+
When x or y is 1, the corresponding loop runs only once.
122+
123+
#### Space Complexity
124+
125+
The space complexity is `O(logx(bound)) * logy(bound)` because in the worst case, every combination of powers produces
126+
a unique sum, and all of these are stored in the resultSet. This is bounded by the total number of pairs enumerated,
127+
which is at most `O(logx(bound)) * logy(bound)`.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from typing import List, Set
2+
from math import log
3+
4+
5+
def powerful_integers(x: int, y: int, bound: int) -> List[int]:
6+
# Use a set to store unique powerful integers
7+
result_set: Set[int] = set()
8+
9+
# Compute powers of x up to bound
10+
# If x == 1, x^i is always 1, so only need i=0
11+
pow_x = 1 # x^0 = 1
12+
while pow_x <= bound:
13+
# For each power of x, iterate over powers of y
14+
pow_y = 1 # y^0 = 1
15+
while pow_x + pow_y <= bound:
16+
# Add the powerful integer to the set
17+
result_set.add(pow_x + pow_y)
18+
# If y is 1, y^j is always 1, so break after first iteration
19+
if y == 1:
20+
break
21+
# Move to next power of y
22+
pow_y *= y
23+
# If x is 1, x^i is always 1, so break after first iteration
24+
if x == 1:
25+
break
26+
# Move to next power of x
27+
pow_x *= x
28+
29+
# Convert set to list and return
30+
return list(result_set)
31+
32+
33+
def powerful_integers_logarithmic_bounds(x: int, y: int, bound: int) -> List[int]:
34+
if bound == 0:
35+
return []
36+
37+
a = bound if x == 1 else int(log(bound, x))
38+
b = bound if y == 1 else int(log(bound, y))
39+
40+
result_set = set([])
41+
42+
for i in range(a + 1):
43+
for j in range(b + 1):
44+
value = x**i + y**j
45+
46+
if value <= bound:
47+
result_set.add(value)
48+
49+
if y == 1:
50+
break
51+
52+
if x == 1:
53+
break
54+
55+
return list(result_set)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import unittest
2+
from typing import List
3+
from parameterized import parameterized
4+
from algorithms.hash_table.powerful_integers import (
5+
powerful_integers,
6+
powerful_integers_logarithmic_bounds,
7+
)
8+
9+
POWERFUL_INTEGERS_TEST_CASE = [
10+
(2, 2, 20, [2, 3, 4, 5, 6, 8, 9, 10, 12, 16, 17, 18, 20]),
11+
(1, 1, 5, [2]),
12+
(5, 3, 50, [2, 4, 6, 8, 10, 14, 26, 28, 32, 34]),
13+
(100, 100, 1000000, [2, 101, 200, 10001, 10100, 20000]),
14+
(2, 5, 0, []),
15+
(2, 3, 10, [2, 3, 4, 5, 7, 9, 10]),
16+
(3, 5, 15, [2, 4, 6, 8, 10, 14]),
17+
]
18+
19+
20+
class PowerfulIntegersTestCase(unittest.TestCase):
21+
@parameterized.expand(POWERFUL_INTEGERS_TEST_CASE)
22+
def test_powerful_integers(self, x: int, y: int, bound: int, expected: List[int]):
23+
actual = powerful_integers(x, y, bound)
24+
actual.sort()
25+
self.assertEqual(expected, actual)
26+
27+
@parameterized.expand(POWERFUL_INTEGERS_TEST_CASE)
28+
def test_powerful_integers_logarithmic_bounds(
29+
self, x: int, y: int, bound: int, expected: List[int]
30+
):
31+
actual = powerful_integers_logarithmic_bounds(x, y, bound)
32+
actual.sort()
33+
self.assertEqual(expected, actual)
34+
35+
36+
if __name__ == "__main__":
37+
unittest.main()

algorithms/trie/index_pairs_of_a_string/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import List
22
from datastructures.trees.trie import Trie
33

4+
45
def index_pairs(text: str, words: List[str]) -> List[List[int]]:
56
trie = Trie()
67

0 commit comments

Comments
 (0)