Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@
* [Test First Unique Character](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/first_unique_character/test_first_unique_character.py)
* Jewels And Stones
* [Test Jewels And Stones](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/jewels_and_stones/test_jewels_and_stones.py)
* Powerful Integers
* [Test Powerful Integers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/powerful_integers/test_powerful_integers.py)
* Ransom Note
* [Test Ransom Note](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/hash_table/ransom_note/test_ransom_note.py)
* Heap
Expand Down
33 changes: 33 additions & 0 deletions algorithms/dynamic_programming/max_subarray/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,36 @@ find_subarr_maxsum([4, -1, 2, 1, -40, 1, 2, -1, 4]) == [[[4, -1, 2, 1], [1, 2, -
```

If the array does not have any sub-array with a positive sum of its terms, the function will return [[], 0].

## Solution

We’ll solve this problem using Kadane’s Algorithm. It’s a dynamic programming approach. The key idea is that we can
efficiently find the maximum subarray ending at any position based on the maximum subarray ending at the previous
position.

The subproblem here is finding the maximum subarray sum that ends at a specific index i. We need to calculate this for
every index i in the array. The base case is the first element of the array, where both current subarray sum and maximum
subarray sum are initialized with the first element’s value. This is the starting point for solving the subproblems. At
each step, we reuse the previously computed maximum subarray sum to find the solution for the current subproblem.

The steps of the algorithm are given below:

1. Initialize two variables, current_sum and max_sum, with the value of the first element in the input list. These
variables are used to keep track of the current sum of the subarray being considered, and the maximum sum found so
far.
2. Iterate through the input list, starting from the second element to the end of the list. Within the loop, perform the
following steps:
- If current_sum + nums[i] is smaller than nums[i], this indicates that starting a new subarray with nums[i] would
yield a larger sum than extending the previous subarray. In such cases, reset current_sum to nums[i].
- Compare the current max_sum with the updated current_sum. This step ensures that the max_sum always stores the
maximum sum encountered so far.
3. After the loop completes, the function returns the max_sum, which represents the maximum sum of any contiguous
subarray within the input list.

### Time Complexity
The time complexity of this solution is O(n) because we are iterating the array once, where n is the total number of
elements in the array.

### Space Complexity

The space complexity of this solution is O(1).
127 changes: 127 additions & 0 deletions algorithms/hash_table/powerful_integers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Powerful Integers

Given three integers x, y, and bound, return a list of all the powerful integers that have a value less than or equal to
bound.

An integer is powerful if it can be represented as xi + yj for some integers i >= 0 and j >= 0.

You may return the answer in any order. In your answer, each value should occur at most once.

## Examples

Example 1:

```text
Input: x = 2, y = 3, bound = 10
Output: [2,3,4,5,7,9,10]
Explanation:
2 = 20 + 30
3 = 21 + 30
4 = 20 + 31
5 = 21 + 31
7 = 22 + 31
9 = 23 + 30
10 = 20 + 32
```

Example 2:
```text
Input: x = 3, y = 5, bound = 15
Output: [2,4,6,8,10,14]
```

## Constraints

- 1 <= x, y <= 100
- 0 <= bound <= 10^6

## Topics

- Hash Table
- Math
- Enumeration

## Solution(s)

1. [Logarithmic Bounds](#logarithmic-bounds)
2. [Logarithmic Bound 2](#logarithmic-bound-2)

### Logarithmic Bounds

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
to have nested loops that iterate from [0⋯bound]. However, this is very inefficient because the bound can be an extremely
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
values and combinations. There is a way to find a much smaller bound for the powers.

m^n <= bound

This formula implies that

n<=log(m) bound

> We can use the log function to determine the bounds for the powers of "x" and "y".

#### Algorithm

1. Let's define `a` as the power bound for the number `x`. Thus `a=log(x)bound`.
2. Similarly, let's define `b` as the power bound for the number `y`. Thus `b=log(y)bound`.
3. Now we will have our nested for-loop structure where the outer loop will iterate from [0⋯a] and the inner loop will
iterate from [0⋯b].
4. We will use a set to store our results. This is because we might generate the same value multiple times. E.g.
`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
`resultSet` to store our answers.
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
a powerful integer and we add it to our set of answers.
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,
then their power-bound will be equal to bound itself. Also, it doesn't matter what their power-bound is because 1^N
is always 1. Thus, when the number is 1, we don't need to loop from [0⋯N] and we can break early.
7. Finally, convert the set to a list and return.

#### Time Complexity

Time Complexity: Let `N` be `log(x)bound` and `M` be `log(y)bound`. Then the overall time complexity is `O(N×M)` because
we used a nested loop structure to calculate all of the powerful integers.

#### Space Complexity

`O(N×M)` because we use a set to omit duplicates. We could just use our result list to check membership before adding
values. However, that would be costly in terms of time complexity because it would require a full scan of the result list
to see if the value already exists.

### Logarithmic Bound 2

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
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
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
`resultSet`. The set automatically handles deduplication, ensuring each powerful integer appears at most once. A special
case arises when x or y equals 1, because 1^i is always 1 regardless of the exponent, which would cause an infinite loop.
We handle this by breaking out of the respective loop after the first iteration when x or y is 1.

Solution steps:

1. Initialize an empty set resultSet to store unique powerful integers.
2. Initialize `powX` to 1, representing x^0
3. Start an outer while loop that continues as long as `powX ≤ bound`.
- Inside the outer loop, initialize `powY` to 1, representing y^0
- Start an inner while loop that continues as long as `powX + powY ≤ bound`.
- Add the value `powX` + `powY` to `resultSet`.
- If y equals 1, break out of the inner loop immediately, since y^j will always be 1 and further iterations would
not produce new values.
- Otherwise, multiply `powY` by y to advance to the next power of y.
- After the inner loop, if x equals 1, break out of the outer loop immediately, since x^i will always be 1 and further
iterations would not produce new values.
- Otherwise, multiply `powX` by x to advance to the next power of x.
4. Convert resultSet to a list and return it.

#### Time Complexity

The time complexity of the solution is O(logx(bound)) * logy(bound) because the outer loop runs at most O(logx(bound))
times and the inner loop runs at most O(logy(bound)) times for each iteration of the outer loop. Since bound ≤10^6 and
the minimum base greater than 1 is 2, each loop runs at most about log2(10^6)≈20 iterations, making this very efficient.
When x or y is 1, the corresponding loop runs only once.

#### Space Complexity

The space complexity is `O(logx(bound)) * logy(bound)` because in the worst case, every combination of powers produces
a unique sum, and all of these are stored in the resultSet. This is bounded by the total number of pairs enumerated,
which is at most `O(logx(bound)) * logy(bound)`.
55 changes: 55 additions & 0 deletions algorithms/hash_table/powerful_integers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from typing import List, Set
from math import log


def powerful_integers(x: int, y: int, bound: int) -> List[int]:
# Use a set to store unique powerful integers
result_set: Set[int] = set()

# Compute powers of x up to bound
# If x == 1, x^i is always 1, so only need i=0
pow_x = 1 # x^0 = 1
while pow_x <= bound:
# For each power of x, iterate over powers of y
pow_y = 1 # y^0 = 1
while pow_x + pow_y <= bound:
# Add the powerful integer to the set
result_set.add(pow_x + pow_y)
# If y is 1, y^j is always 1, so break after first iteration
if y == 1:
break
# Move to next power of y
pow_y *= y
# If x is 1, x^i is always 1, so break after first iteration
if x == 1:
break
# Move to next power of x
pow_x *= x

# Convert set to list and return
return list(result_set)


def powerful_integers_logarithmic_bounds(x: int, y: int, bound: int) -> List[int]:
if bound == 0:
return []

a = bound if x == 1 else int(log(bound, x))
b = bound if y == 1 else int(log(bound, y))

result_set = set([])

for i in range(a + 1):
for j in range(b + 1):
value = x**i + y**j

if value <= bound:
result_set.add(value)

if y == 1:
break

if x == 1:
break

return list(result_set)
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import unittest
from typing import List
from parameterized import parameterized
from algorithms.hash_table.powerful_integers import (
powerful_integers,
powerful_integers_logarithmic_bounds,
)

POWERFUL_INTEGERS_TEST_CASE = [
(2, 2, 20, [2, 3, 4, 5, 6, 8, 9, 10, 12, 16, 17, 18, 20]),
(1, 1, 5, [2]),
(5, 3, 50, [2, 4, 6, 8, 10, 14, 26, 28, 32, 34]),
(100, 100, 1000000, [2, 101, 200, 10001, 10100, 20000]),
(2, 5, 0, []),
(2, 3, 10, [2, 3, 4, 5, 7, 9, 10]),
(3, 5, 15, [2, 4, 6, 8, 10, 14]),
]


class PowerfulIntegersTestCase(unittest.TestCase):
@parameterized.expand(POWERFUL_INTEGERS_TEST_CASE)
def test_powerful_integers(self, x: int, y: int, bound: int, expected: List[int]):
actual = powerful_integers(x, y, bound)
actual.sort()
self.assertEqual(expected, actual)

@parameterized.expand(POWERFUL_INTEGERS_TEST_CASE)
def test_powerful_integers_logarithmic_bounds(
self, x: int, y: int, bound: int, expected: List[int]
):
actual = powerful_integers_logarithmic_bounds(x, y, bound)
actual.sort()
self.assertEqual(expected, actual)


if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions algorithms/trie/index_pairs_of_a_string/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import List
from datastructures.trees.trie import Trie


def index_pairs(text: str, words: List[str]) -> List[List[int]]:
trie = Trie()

Expand Down
Loading