Skip to content
Merged
6 changes: 6 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
* [Test Climb Stairs](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/climb_stairs/test_climb_stairs.py)
* Countingbits
* [Test Counting Bits](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/countingbits/test_counting_bits.py)
* Decodeways
* [Test Num Decodings](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/decodeways/test_num_decodings.py)
* Domino Tromino Tiling
* [Test Domino Tromino Tiling](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/domino_tromino_tiling/test_domino_tromino_tiling.py)
* Duffle Bug Value
Expand Down Expand Up @@ -214,6 +216,8 @@
* [Test Divide Chocolate](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/search/binary_search/divide_chocolate/test_divide_chocolate.py)
* Maxruntime N Computers
* [Test Max Runtime](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/search/binary_search/maxruntime_n_computers/test_max_runtime.py)
* Split Array Largest Sum
* [Test Split Array Largest Sum](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/search/binary_search/split_array_largest_sum/test_split_array_largest_sum.py)
* [Test Binary Search](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/search/binary_search/test_binary_search.py)
* Interpolation
* [Test Interpolation Search](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/search/interpolation/test_interpolation_search.py)
Expand Down Expand Up @@ -255,6 +259,8 @@
* Two Pointers
* Array 3 Pointers
* [Test Array 3 Pointers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/array_3_pointers/test_array_3_pointers.py)
* Count Pairs
* [Test Count Pairs](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/count_pairs/test_count_pairs.py)
* Find Sum Of Three
* [Test Find Sum Of Three](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/find_sum_of_three/test_find_sum_of_three.py)
* Merge Sorted Arrays
Expand Down
84 changes: 84 additions & 0 deletions algorithms/dynamic_programming/decodeways/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Decode Ways

You have intercepted a secret message encoded as a string of numbers. The message is decoded via the following mapping:

```text
'1' -> "A"
'2' -> "B"
'3' -> "C"
...
'26' -> "Z"
```
However, while decoding the message, you realize that there are many different ways you can decode the message because
some codes are contained in other codes ("2" and "5" vs "25").

For example, "11106" can be decoded into:

- "AAJF" with the grouping (1, 1, 10, 6)
- "KJF" with the grouping (11, 10, 6)
- The grouping (1, 11, 06) is invalid because "06" is not a valid code (only "6" is valid).

Note: there may be strings that are impossible to decode.

Given a string s containing only digits, return the number of ways to decode it. If the entire string cannot be decoded
in any valid way, return 0.

The test cases are generated so that the answer fits in a 32-bit integer.

## Examples

Example 1

```text
Input: s = "12"

Output: 2

Explanation:

"12" could be decoded as "AB" (1 2) or "L" (12).
```

Example 2

```text
Input: s = "226"

Output: 3

Explanation:

"226" could be decoded as "BZ" (2 26), "VF" (22 6), or "BBF" (2 2 6).
```

Example 3

```text
Input: s = "06"

Output: 0

Explanation:

"06" cannot be mapped to "F" because of the leading zero ("6" is different from "06"). In this case, the string is not
a valid encoding, so return 0.
```

Example 4

```text
s = 101
output = 1
Explanation: The only way to decode it is "JA". "01" cannot be decoded into "A" as "1" and "01" are different.
```

## Constraints

- 1 <= s.length <= 100
- s contains only digits and may contain leading zero(s).

## Topics

- String
- Dynamic Programming

28 changes: 28 additions & 0 deletions algorithms/dynamic_programming/decodeways/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
def num_decodings(s: str) -> int:
if not s or s[0] == "0":
return 0

n = len(s)
# Initialize the dp array. dp[i] will store the number of ways to decode the string s[:i]
dp = [0] * (n + 1)
dp[0] = 1
dp[1] = 1

# Iterate through the string
for i in range(2, n + 1):
# Get the current character
current_char = s[i - 1]
# Get the previous character
prev_char = s[i - 2]

# If the current character is not a zero, then we can decode it as a single character
if current_char != "0":
dp[i] += dp[i - 1]

# If the previous character is 1 or 2 and the current character is less than or equal to 6, then we can decode
# it as a two character
if prev_char == "1" or (prev_char == "2" and current_char <= "6"):
dp[i] += dp[i - 2]

# Return the number of ways to decode the string
return dp[n]
28 changes: 28 additions & 0 deletions algorithms/dynamic_programming/decodeways/test_num_decodings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import unittest
from parameterized import parameterized
from algorithms.dynamic_programming.decodeways import num_decodings

NUM_DECODINGS_TEST_CASES = [
("11106", 2),
("12", 2),
("226", 3),
("06", 0),
("101", 1),
("12", 2),
("012", 0),
("0", 0),
("30", 0),
("10", 1),
("27", 1),
]


class NumDecodingsTestCase(unittest.TestCase):
@parameterized.expand(NUM_DECODINGS_TEST_CASES)
def test_num_decodings(self, s: str, expected: int):
actual = num_decodings(s)
self.assertEqual(expected, actual)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import unittest
from typing import List
from parameterized import parameterized
from algorithms.dynamic_programming.painthouse import min_cost_to_paint_houses_alternate_colors
from algorithms.dynamic_programming.painthouse import (
min_cost_to_paint_houses_alternate_colors,
)

MIN_COST_PAINT_HOUSE = [
([[8, 4, 15], [10, 7, 3], [6, 9, 12]], 13),
Expand All @@ -16,5 +18,5 @@ def test_min_cost_to_paint_houses(self, cost: List[List[int]], expected: int):
self.assertEqual(expected, actual)


if __name__ == '__main__':
if __name__ == "__main__":
unittest.main()
106 changes: 106 additions & 0 deletions algorithms/search/binary_search/split_array_largest_sum/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Split Array Largest Sum

Given an integer list nums and an integer k, split nums into k non-empty subarrays such that the largest sum among these
subarrays is minimized. The task is to find the minimized largest sum by choosing the split such that the largest sum of
every split of subarrays is the minimum among the sum of other splits.

## Constraints

- 1 <= nums.length <= 1000
- 0 <= nums[i] <= 10^4
- 1 <= k <= nums.length

## Examples

![Example 1](images/examples/split_array_largest_sum_example_1.png)
![Example 2](images/examples/split_array_largest_sum_example_2.png)
![Example 3](images/examples/split_array_largest_sum_example_3.png)
![Example 4](images/examples/split_array_largest_sum_example_4.png)

## Topics

- Array
- Binary Search
- Dynamic Programming
- Greedy
- Prefix Sum

## Solution

In a brute force method, you would try all possible ways to split the array into k subarrays, calculate the largest sum
for each split, and then find the smallest among those largest sums. This approach is extremely inefficient because the
number of ways to split the array grows exponentially as the size increases.

We reverse the process by guessing a value for the minimum largest sum and checking if it’s feasible:

- Instead of iterating through all splits, we only focus on testing whether a specific value allows us to split the
array into k subarrays.

- But wait—just because one value works doesn’t mean it’s the smallest feasible value. We keep exploring smaller values
to achieve the most optimized result.

The solution uses the binary search approach to find the optimal largest subarray sum without testing all possible splits.
The binary search finds the smallest possible value of the largest subarray sum and applies searching over the range of
possible values for this largest sum. But how do we guess this value? We guess the value using a certain range.
Here’s how:

- **Left boundary**: The maximum element in the array is the minimum possible value for the largest subarray sum. This
is because any valid subarray must have a sum at least as large as the largest element.

- **Right boundary**: The maximum possible value for the largest subarray sum is the sum of all elements in the array.
You would get this sum if the entire array were one single subarray.

The binary search iteratively tests midpoints in the above ranges. It determines whether dividing the array results in
at most k subarrays will result in the smallest largest sum. If it does, the search shifts to lower values to minimize
the largest sum. Otherwise, it shifts to higher values. Still, there might be subarrays whose sum could be smaller, so
the search keeps going until the search range crosses each other, i.e., **left boundary** > **right boundary**.

Here’s the step-by-step implementation of the solution:

- Start by initializing the ranges for search. The left will be the largest number in the array, and the right will be
the sum of all numbers.
- Use a guessing approach. Start by considering a mid value between the left and right as a test value.
- Check if it is possible to divide the array into k subarrays so that the sum of no subarray is greater than mid.
- Start with an empty sum and add numbers from the array. If adding the next number exceeds mid:
- Start a new subarray with that number and increment the count of the subarrays.
- Return FALSE if the count exceeds k. Otherwise, return TRUE.
- Adjust the guessing range by checking if the number of subarrays needed is within the k and reduce the mid to see if
a smaller largest sum is possible.
- Otherwise, if the count of subarrays is more than k:
- Increase the mid to make larger groups possible.
- Continue adjusting the mid until left < right. Return left as it contains the minimized largest possible sum.

- Let’s look at the following illustrations to get a better understanding of the solution:

![Solution 1](images/solutions/split_array_largest_sum_solution_1.png)
![Solution 2](images/solutions/split_array_largest_sum_solution_2.png)
![Solution 3](images/solutions/split_array_largest_sum_solution_3.png)
![Solution 4](images/solutions/split_array_largest_sum_solution_4.png)
![Solution 5](images/solutions/split_array_largest_sum_solution_5.png)
![Solution 6](images/solutions/split_array_largest_sum_solution_6.png)
![Solution 7](images/solutions/split_array_largest_sum_solution_7.png)
![Solution 8](images/solutions/split_array_largest_sum_solution_8.png)
![Solution 9](images/solutions/split_array_largest_sum_solution_9.png)
![Solution 10](images/solutions/split_array_largest_sum_solution_10.png)
![Solution 11](images/solutions/split_array_largest_sum_solution_11.png)
![Solution 12](images/solutions/split_array_largest_sum_solution_12.png)
![Solution 13](images/solutions/split_array_largest_sum_solution_13.png)
![Solution 14](images/solutions/split_array_largest_sum_solution_14.png)
![Solution 15](images/solutions/split_array_largest_sum_solution_15.png)
![Solution 16](images/solutions/split_array_largest_sum_solution_16.png)
![Solution 17](images/solutions/split_array_largest_sum_solution_17.png)
![Solution 18](images/solutions/split_array_largest_sum_solution_18.png)
![Solution 19](images/solutions/split_array_largest_sum_solution_19.png)
![Solution 20](images/solutions/split_array_largest_sum_solution_20.png)

### Time Complexity

The time complexity of this solution is O(n log(m)), where n is the length of the input array, and m is the difference
between `max(nums)` and `sum(nums)` because the range of possible sums considered during the binary search is from
`max(nums)` to `sum(nums)`. This range size determines the number of iterations in the binary search. The tighter this
range, the fewer iterations are needed. However, in the worst case, it spans the full difference: `sum(nums) - max(nums)`.
The time complexity becomes `n log(m)` because the `can_split` function is called `n` times for each iteration.

### Space Complexity

The space complexity of this solution is O(1) because only constant space is used.
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from typing import List


def split_array(nums: List[int], k: int) -> int:
if not nums:
return -1

left = max(nums)
right = sum(nums)
first_true_index = -1

def feasible(max_sum: int) -> bool:
"""
Check if we can split the array into at most k sub arrays with each sub array sum less than or equal to max_sum
"""
current_sum = 0
# Start with one
sub_array_count = 1

for num in nums:
if current_sum + num > max_sum:
current_sum = num
sub_array_count += 1
else:
current_sum += num

return sub_array_count <= k

while left <= right:
mid = (right + left) // 2
if feasible(mid):
first_true_index = mid
# Find a smaller valid value
right = mid - 1
else:
# Find a larger valid value
left = mid + 1

return first_true_index


def split_array_2(nums, k):
if not nums:
return -1

# Set the initial search range for the largest sum:
# Minimum is the largest number in the array, and maximum is the sum of all numbers
left, right = max(nums), sum(nums)

def can_split(middle: int):
# Initialize the count of subarrays and the current sum of the current subarray
subarrays = 1
current_sum = 0

for num in nums:
# Check if adding the current number exceeds the allowed sum (mid)
if current_sum + num > middle:
# Increment the count of subarrays
subarrays += 1
# Start a new subarray with the current number
current_sum = num

# If the number of subarrays exceeds the allowed k, return False
if subarrays > k:
return False
else:
# Otherwise, add the number to the current subarray
current_sum += num

# Return True if the array can be split within the allowed subarray count
return True

# Perform binary search to find the minimum largest sum
while left < right:
# Find the middle value of the current range
mid = (left + right) // 2

# Check if the array can be split into k or fewer subarrays with this maximum sum
if can_split(mid):
# If possible, try a smaller maximum sum
right = mid
else:
# Otherwise, increase the range to allow larger sums
left = mid + 1

# Return the smallest maximum sum that satisfies the condition
return left
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading