Skip to content
12 changes: 10 additions & 2 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
* [Test Max Profit Three](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/buy_sell_stock/test_max_profit_three.py)
* [Test Max Profit Two](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/buy_sell_stock/test_max_profit_two.py)
* [Test Max Profit With Fee](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/buy_sell_stock/test_max_profit_with_fee.py)
* Climb Stairs
* [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)
* 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 All @@ -69,6 +73,8 @@
* [Test Min Distance](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/min_distance/test_min_distance.py)
* Min Path Sum
* [Test Min Path Sum](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/min_path_sum/test_min_path_sum.py)
* Painthouse
* [Test Min Cost To Paint Houses](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/painthouse/test_min_cost_to_paint_houses.py)
* Palindromic Substring
* [Longest Palindromic Substring](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/palindromic_substring/longest_palindromic_substring.py)
* [Test Longest Palindromic Substring](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/dynamic_programming/palindromic_substring/test_longest_palindromic_substring.py)
Expand Down Expand Up @@ -138,12 +144,16 @@
* Spread Stones
* [Test Minimum Moves To Spread Stones](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/spread_stones/test_minimum_moves_to_spread_stones.py)
* Heap
* Kclosestelements
* [Test Find K Closest Elements](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/kclosestelements/test_find_k_closest_elements.py)
* Longest Happy String
* [Test Longest Happy String](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/longest_happy_string/test_longest_happy_string.py)
* Maximal Score After K Operations
* [Test Maximal Score](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/maximal_score_after_k_operations/test_maximal_score.py)
* Maximum Subsequence Score
* [Test Max Subsequence Score](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/maximum_subsequence_score/test_max_subsequence_score.py)
* Mergeksortedlists
* [Test Merge K Sorted Lists](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/mergeksortedlists/test_merge_k_sorted_lists.py)
* Min Cost Hire K Workers
* [Test Min Cost To Hire Workers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/min_cost_hire_k_workers/test_min_cost_to_hire_workers.py)
* Min Cost To Connect Sticks
Expand Down Expand Up @@ -676,8 +686,6 @@
* [Battleship](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/battleship/battleship.py)
* Beeramid
* [Test Bearamid](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/beeramid/test_bearamid.py)
* Climb Stairs
* [Test Climb Stairs](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/climb_stairs/test_climb_stairs.py)
* Find Missing Elem
* [Test Find Missing Elem](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/find_missing_elem/test_find_missing_elem.py)
* Hashmap
Expand Down
78 changes: 78 additions & 0 deletions algorithms/dynamic_programming/countingbits/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Counting Bits

Given an integer n, return an array ans of length n + 1 such that for each i (0 <= i <= n), ans[i] is the number of 1's
in the binary representation of i.

## Examples

```text
Example 1:

Input: n = 2
Output: [0,1,1]
Explanation:
0 --> 0
1 --> 1
2 --> 10
```

```
Example 2:

Input: n = 5
Output: [0,1,1,2,1,2]
Explanation:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101
```

## Constraints

- 0 <= `n` <= 10^5

## Solution

This solution uses a bottom-up dynamic programming approach to solve the problem.
The key to this problem lies in the fact that any binary number can be broken down into two parts: the least-significant
(rightmost bit), and the rest of the bits. The rest of the bits can be expressed as the binary number divided by 2
(rounded down), or `i >> 1`.

For example:
- 4 in binary = 100
- rightmost bit = 0
- rest of bits = 10, which is also (4 // 2) = 2 in binary.

When the number is odd,
- 5 in binary = 101
- rightmost bit = 1
- rest of bits = 10, which is also (5 // 2) = 2 in binary.

in the binary representation of i is that number plus 1 if the rightmost bit is 1. We can tell if the last significant
bit is 1 by checking if it is odd.

As an example, we know that the number of 1's in 2 is 1. This information can be used to calculate the number of 1's in 4.
The number of 1's in 4 is the number of 1's in 2 plus 0, because 4 is even.

This establishes a recurrence relationship between the number of 1's in the binary representation of i and the number of
1's in the binary representation of i // 2: if count[i] is the number of 1's in the binary representation of i, then
count[i] = count[i // 2] + (i % 2).

With the recurrence relationship established, we can now solve the problem using a bottom-up dynamic programming approach.
We start with the base case dp[0] = 0, and then build up the solution for dp[i] for i from 1 to n.

![Solution 1](./images/solutions/counting_bits_solution_1.png)
![Solution 2](./images/solutions/counting_bits_solution_2.png)
![Solution 3](./images/solutions/counting_bits_solution_3.png)
![Solution 4](./images/solutions/counting_bits_solution_4.png)
![Solution 5](./images/solutions/counting_bits_solution_5.png)
![Solution 6](./images/solutions/counting_bits_solution_6.png)
![Solution 7](./images/solutions/counting_bits_solution_7.png)
![Solution 8](./images/solutions/counting_bits_solution_8.png)
![Solution 9](./images/solutions/counting_bits_solution_9.png)
![Solution 10](./images/solutions/counting_bits_solution_10.png)
![Solution 11](./images/solutions/counting_bits_solution_11.png)
![Solution 12](./images/solutions/counting_bits_solution_12.png)
20 changes: 20 additions & 0 deletions algorithms/dynamic_programming/countingbits/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import List


def count_bits(n: int) -> List[int]:
"""
Counts the number of 1 bits in the given integer provided returning a list where count[i] stores the count of '1'
bits in the binary form of i.
Args:
n(int): integer
Returns:
list: count of 1 bits where each index contains the count of 1 bits
"""
dp = [0] * (n + 1)

for i in range(1, n + 1):
# this can also be solved as which is faster in Python
# dp[i] = dp[i >> 1] + (i & 1)
dp[i] = dp[i // 2] + (i % 2)

return dp
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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import unittest
from typing import List
from parameterized import parameterized
from algorithms.dynamic_programming.countingbits import count_bits

COUNTING_BITS_TEST_CASES = [
(6, [0, 1, 1, 2, 1, 2, 2]),
(2, [0, 1, 1]),
(5, [0, 1, 1, 2, 1, 2]),
(0, [0]),
(7, [0, 1, 1, 2, 1, 2, 2, 3]),
(10, [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2]),
]


class CountingBitsTestCase(unittest.TestCase):
@parameterized.expand(COUNTING_BITS_TEST_CASES)
def test_counting_bits(self, n: int, expected: List[int]):
actual = count_bits(n)
self.assertEqual(expected, actual)


if __name__ == "__main__":
unittest.main()
107 changes: 107 additions & 0 deletions algorithms/dynamic_programming/painthouse/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Paint House

You are a renowned painter who is given a task to paint n houses in a row. You can paint each house with one of three colors: Red, Blue, or Green. The cost of painting each house with each color is different and given in a 2D array costs:

- costs[i][0] = cost of painting house `i` Red
- costs[i][1] = cost of painting house `i` Blue
- costs[i][2] = cost of painting house `i` Green

No two neighboring houses can have the same color. Return the minimum cost to paint all houses.

## Constraints

- 1 ≤ n ≤ 100
- costs[i].length == 3
- 1 ≤ costs[i][j] ≤ 1000

## Examples

Example 1

```text
costs = [[8, 4, 15], [10, 7, 3], [6, 9, 12]]
Output: 13

Explanation:

House 0: Blue (cost = 4)
House 1: Green (cost = 3)
House 2: Red (cost = 6)
Total = 4 + 3 + 6 = 13

```

Example 2
```text
Input:

costs = [[5, 8, 6], [19, 14, 13], [7, 5, 12], [14, 5, 9]]
Output: 30

Explanation: Red(5) → Green(13) → Red(7) → Blue(5) = 30
```

## Solution

The solution implements a space-optimized dynamic programming approach using three variables to track the minimum costs.

Variable Initialization:

prev_min_cost_red = 0: Minimum cost if the current house is painted red (color 0)
prev_min_cost_blue = 0: Minimum cost if the current house is painted blue (color 1)
prev_min_cost_green = 0: Minimum cost if the current house is painted green (color 2)

These start at 0 because before painting any house, the cost is 0.

Main Loop: The algorithm iterates through each house's costs using tuple unpacking:

```python
for cost_red, cost_blue, cost_green in costs:
#
```

Where cost_red, cost_blue, cost_green represent the costs of painting the current house with red, blue, and green
respectively.

State Transition: For each house, we simultaneously update all three variables:

```python
prev_min_cost_red, prev_min_cost_blue, prev_min_cost_green = min(prev_min_cost_blue, prev_min_cost_green) + cost_red,
min(prev_min_cost_red, prev_min_cost_green) + cost_blue, min(prev_min_cost_red, prev_min_cost_blue) + cost_green
```

Breaking this down:

- New `prev_min_cost_red` (cost if current house is red): `min(prev_min_cost_blue, prev_min_cost_green) + cost_red`
We take the minimum of the previous costs where the house was NOT red (either blue or green)
Add the cost of painting the current house red

- New `prev_min_cost_blue` (cost if current house is blue): `min(prev_min_cost_red, prev_min_cost_green) + cost_blue`
We take the minimum of the previous costs where the house was NOT blue (either red or green)
Add the cost of painting the current house blue

- New `prev_min_cost_green` (cost if current house is green): `min(prev_min_cost_red, prev_min_cost_blue) + cost_green`
We take the minimum of the previous costs where the house was NOT green (either red or blue)
Add the cost of painting the current house green

The simultaneous assignment is crucial here - all three values are calculated using the old values before any updates occur.

Final Result: After processing all houses, `prev_min_cost_red`, `prev_min_cost_blue`, and `prev_min_cost_green` contain
the minimum costs to paint all houses with the last house being red, blue, or green respectively. The answer is the
minimum among these three values:

`return min(prev_min_cost_red, prev_min_cost_blue, prev_min_cost_green)`

### Complexity Analysis

#### Time Complexity: O(n)

Where n is the number of houses (length of the costs array). The algorithm iterates through the costs array exactly once,
performing constant-time operations (comparisons and additions) for each house.

#### Space Complexity: O(1)

The algorithm uses only three variables (`prev_min_cost_red`, `prev_min_cost_blue`, `prev_min_cost_green`) to track the
minimum costs regardless of the input size. The space usage remains constant as it doesn't create any additional data
structures that scale with the input. The variables are reused and updated in each iteration rather than storing all
intermediate results.
43 changes: 43 additions & 0 deletions algorithms/dynamic_programming/painthouse/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import List


def min_cost_to_paint_houses_alternate_colors(costs: List[List[int]]) -> int:
"""
Finds the minimum cost to paint houses in a street without repeating colors consecutively
Args:
costs(list): Costs of painting houses as a 2D list
Returns:
int: minimum cost of painting houses
"""
# If there are no costs of painting the houses, then the minimum cost is 0
if not costs:
return 0

# initially, the cost of painting any house any color is 0
# Initialize minimum costs for painting up to previous house with each color
# prev_min_cost_red: min cost if previous house painted red
# prev_min_cost_blue: min cost if previous house painted blue
# prev_min_cost_green: min cost if previous house painted green
prev_min_cost_red = 0
prev_min_cost_blue = 0
prev_min_cost_green = 0

# Iterate through each house cost
for current_cost in costs:
cost_red, cost_blue, cost_green = current_cost

# Calculate minimum cost for current house with each color
# Current house painted red: add red cost to min of (prev blue, prev green)
# Current house painted blue: add blue cost to min of (prev red, prev green)
# Current house painted green: add green cost to min of (prev red, prev blue)
curr_min_cost_red = min(prev_min_cost_blue, prev_min_cost_green) + cost_red
curr_min_cost_blue = min(prev_min_cost_red, prev_min_cost_green) + cost_blue
curr_min_cost_green = min(prev_min_cost_red, prev_min_cost_blue) + cost_green

# Update previous costs for next iteration
prev_min_cost_red = curr_min_cost_red
prev_min_cost_blue = curr_min_cost_blue
prev_min_cost_green = curr_min_cost_green

# Return minimum cost among all three color options for the last house
return min(prev_min_cost_red, prev_min_cost_blue, prev_min_cost_green)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import unittest
from typing import List
from parameterized import parameterized
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),
([[5, 8, 6], [19, 14, 13], [7, 5, 12], [14, 5, 9]], 30),
]


class MinCostToPaintHouseTestCase(unittest.TestCase):
@parameterized.expand(MIN_COST_PAINT_HOUSE)
def test_min_cost_to_paint_houses(self, cost: List[List[int]], expected: int):
actual = min_cost_to_paint_houses_alternate_colors(cost)
self.assertEqual(expected, actual)


if __name__ == '__main__':
unittest.main()
29 changes: 29 additions & 0 deletions algorithms/heap/kclosestelements/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Find K Closest Elements

Given a sorted array nums, a target value target, and an integer k, find the k closest elements to target in the array,
where "closest" is the absolute difference between each element and target. Return these elements in an array, sorted in
ascending order.

## Examples

```text
nums = [-1, 0, 1, 4, 6]
target = 1
k = 3

Output
[-1, 0, 1]

Explanation
-1 is 2 away from 1, 0 is 1 away from 1, and 1 is 0 away from 1. All other elements are more than 2 away.
Since we need to return the elements in ascending order, the answer is [-1, 0, 1]
```

```text
nums = [5, 6, 7, 8, 9]
target = 10
k = 2

Output:
[8, 9]
```
18 changes: 18 additions & 0 deletions algorithms/heap/kclosestelements/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from typing import List
import heapq


def k_closest(nums: List[int], k: int, target: int):
heap = []

for num in nums:
diff = abs(num - target)

if len(heap) < k:
heapq.heappush(heap, (-diff, num))
elif diff < -heap[0][0]:
heapq.heappushpop(heap, (-diff, num))

distances = [pair[1] for pair in heap]
distances.sort()
return distances
Loading
Loading