diff --git a/DIRECTORY.md b/DIRECTORY.md index b304ffee..5a1e67f8 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -133,6 +133,8 @@ * Valid Path * [Test Valid Path](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/graphs/valid_path/test_valid_path.py) * Greedy + * Assign Cookies + * [Test Assign Cookies](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/assign_cookies/test_assign_cookies.py) * Boats * [Test Boats To Save People](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/boats/test_boats_to_save_people.py) * Gas Stations @@ -143,8 +145,12 @@ * [Test Majority Element](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/majority_element/test_majority_element.py) * Min Arrows * [Test Find Min Arrows](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/min_arrows/test_find_min_arrows.py) + * Minimum Refuel Stops + * [Test Min Refueling Stops](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/minimum_refuel_stops/test_min_refueling_stops.py) * 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) + * Two City Scheduling + * [Test Two City Scheduling](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/greedy/two_city_scheduling/test_two_city_scheduling.py) * Heap * Kclosestelements * [Test Find K Closest Elements](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/heap/kclosestelements/test_find_k_closest_elements.py) @@ -231,6 +237,10 @@ * [Test Longest Substring K Repeating Chars](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sliding_window/longest_substring_with_k_repeating_chars/test_longest_substring_k_repeating_chars.py) * Longest Substring Without Repeating Characters * [Test Longest Substring Without Repeating Characters](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sliding_window/longest_substring_without_repeating_characters/test_longest_substring_without_repeating_characters.py) + * Max Points From Cards + * [Test Max Points From Cards](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sliding_window/max_points_from_cards/test_max_points_from_cards.py) + * Max Sum Of Subarray + * [Test Max Sum Sub Array](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sliding_window/max_sum_of_subarray/test_max_sum_sub_array.py) * Repeated Dna Sequences * [Test Repeated Dna Sequences](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/sliding_window/repeated_dna_sequences/test_repeated_dna_sequences.py) * Sorting @@ -482,9 +492,11 @@ * [Binary Tree](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/tree/binary_tree.py) * [Test Binary Tree](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/tree/test_binary_tree.py) * [Test Binary Tree Deserialize](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/tree/test_binary_tree_deserialize.py) + * [Test Binary Tree Invert Tree](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/tree/test_binary_tree_invert_tree.py) * [Test Binary Tree Min Camera Cover](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/tree/test_binary_tree_min_camera_cover.py) * [Test Binary Tree Serialize](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/tree/test_binary_tree_serialize.py) * [Test Binary Tree Visible Nodes](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/tree/test_binary_tree_visible_nodes.py) + * [Tree Utils](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/tree/tree_utils.py) * [Utils](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/binary/utils.py) * Btree * [Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/btree/node.py) diff --git a/algorithms/dynamic_programming/buy_sell_stock/README.md b/algorithms/dynamic_programming/buy_sell_stock/README.md index 1eea3443..959be07a 100644 --- a/algorithms/dynamic_programming/buy_sell_stock/README.md +++ b/algorithms/dynamic_programming/buy_sell_stock/README.md @@ -126,5 +126,3 @@ Output: 6 - Arrays - Dynamic Programming - Greedy - - diff --git a/algorithms/dynamic_programming/buy_sell_stock/__init__.py b/algorithms/dynamic_programming/buy_sell_stock/__init__.py index 4940803e..2ea7a4fa 100644 --- a/algorithms/dynamic_programming/buy_sell_stock/__init__.py +++ b/algorithms/dynamic_programming/buy_sell_stock/__init__.py @@ -5,6 +5,13 @@ def max_profit(prices: List[int]) -> int: """ Find the maximum profit that can be made from buying and selling a stock once + + Time complexity is O(n) as we iterate through each price to get the maximum profit + Space complexity is O(1) as no extra space is used + Args: + prices(list): list of prices + Returns: + int: maximum profit that can be made """ if prices is None or len(prices) < 2: return 0 @@ -12,15 +19,20 @@ def max_profit(prices: List[int]) -> int: start_price = prices[0] current_max_profit = 0 - for price in prices: - if price < start_price: - start_price = price - elif price - start_price > current_max_profit: - current_max_profit = price - start_price + for price in prices[1:]: + start_price = min(start_price, price) + current_max_profit = max(current_max_profit, price - start_price) return current_max_profit +def max_profit_two_pointers(prices: List[int]) -> int: + """ + Variation of max_profit using 2 pointers + Complexity Analysis: + Space: O(1), no extra memory is used + Time: O(n), where n is the size of the input list & we iterate through the list only once + """ def max_profit_two_pointers(prices: List[int]) -> int: """ Variation of max_profit using 2 pointers @@ -31,10 +43,15 @@ def max_profit_two_pointers(prices: List[int]) -> int: if prices is None or len(prices) < 2: return 0 + number_of_prices = len(prices) + + left, right = 0, 1 + return 0 + left, right = 0, 1 current_max_profit = 0 - while right < len(prices): + while right < number_of_prices: low = prices[left] high = prices[right] diff --git a/algorithms/dynamic_programming/buy_sell_stock/test_max_profit.py b/algorithms/dynamic_programming/buy_sell_stock/test_max_profit.py index 84f95e0a..ccf58f8d 100644 --- a/algorithms/dynamic_programming/buy_sell_stock/test_max_profit.py +++ b/algorithms/dynamic_programming/buy_sell_stock/test_max_profit.py @@ -1,6 +1,9 @@ import unittest -from . import max_profit, max_profit_two_pointers +from algorithms.dynamic_programming.buy_sell_stock import ( + max_profit, + max_profit_two_pointers, +) class MaxProfitTestCases(unittest.TestCase): diff --git a/algorithms/fast_and_slow/find_duplicate/README.md b/algorithms/fast_and_slow/find_duplicate/README.md index d3bc53e1..7b283662 100644 --- a/algorithms/fast_and_slow/find_duplicate/README.md +++ b/algorithms/fast_and_slow/find_duplicate/README.md @@ -37,3 +37,178 @@ Explanation 3: ``` > Note: You cannot modify the given array nums. You have to solve the problem using only constant extra space. + +## Solution + +This solution involves two key steps: identifying the cycle and locating the entry point of this identified cycle, which +represents the duplicate number in the array. + +The fast and slow pointers technique detects such cycles efficiently, where one pointer takes one step at a time and the +other advances by two steps. Initially pointing at the start of the array, the position of each pointer for the next step +is determined by the current value they are pointing to. If a pointer points to the value 5 at index 0, its next position +will be index 5. As the pointers traverse the array, both will eventually catch up because there’s a cycle. This is +because, in the noncyclic part, the distance between the pointers increases by one index in each iteration. However, once +both pointers enter the cyclic part, the fast pointer starts closing the gap on the slow pointer, decreasing the distance +by one index each iteration until they meet. + +Once the duplicate number is confirmed, we reset one of the pointers (usually the slow pointer) to index 0 while the +other stays at the position where the pointers met. Then, both pointers move one step at a time until they meet again. +With this positioning and pace of pointers, pointers are guaranteed to meet at the cycle’s starting point, corresponding +to the duplicate number. + +Now, let’s look at the detailed workflow of the solution: + +For this problem, the duplicate number will create a cycle in the nums array. The cycle in the nums array helps identify +the duplicate number. + +To find the cycle, we’ll move in the nums array using the `f(x)=nums[x]`, where x is the index of the array. This +function constructs the following sequence to move: + + x, nums[x], nums[nums[x]], nums[nums[nums[x]]], ... + +In the sequence above, every new element is an element in nums present at the index of the previous element. + +Let’s say we have an array,[2, 3, 1, 3]. We’ll start with `x=nums[0]`, which is 2, present at the 0th index of the array +and then move to nums[x], which is 1, present at the 2nd index. Since we found 1 at the 2nd index, we’ll move to the 1st, +and so on. This example shows that if we’re given an array of length n+1, with values in the range [1, n], we can use this +traversal technique to visit all the locations in the array. + +The following example illustrates this traversal: + +![Solution 1](./images/solutions/find_duplicate_solution_1.png) +![Solution 2](./images/solutions/find_duplicate_solution_2.png) +![Solution 3](./images/solutions/find_duplicate_solution_3.png) +![Solution 4](./images/solutions/find_duplicate_solution_4.png) +![Solution 5](./images/solutions/find_duplicate_solution_5.png) +![Solution 6](./images/solutions/find_duplicate_solution_6.png) +![Solution 7](./images/solutions/find_duplicate_solution_7.png) +![Solution 8](./images/solutions/find_duplicate_solution_8.png) +![Solution 9](./images/solutions/find_duplicate_solution_9.png) +![Solution 10](./images/solutions/find_duplicate_solution_10.png) +![Solution 11](./images/solutions/find_duplicate_solution_11.png) + +Now, let's dive deep into how our two parts of the solution work. + +In the first part, the slow pointer moves once, while the fast pointer moves twice as fast as the slow pointer until +both of the pointers meet each other. Since the fast pointer is moving twice as fast as the slow pointer, it will be the +first one to enter and move around the cycle. At some point after the slow pointer also enters and moves in the cycle, +the fast pointer will meet the slow pointer. This will be the intersection point. + +> Note: The intersection point of the two pointers is, in the general case, not the entry point of the cycle. + +![Solution 12](./images/solutions/find_duplicate_solution_12.png) +![Solution 13](./images/solutions/find_duplicate_solution_13.png) +![Solution 14](./images/solutions/find_duplicate_solution_14.png) +![Solution 15](./images/solutions/find_duplicate_solution_15.png) +![Solution 16](./images/solutions/find_duplicate_solution_16.png) +![Solution 17](./images/solutions/find_duplicate_solution_17.png) +![Solution 18](./images/solutions/find_duplicate_solution_18.png) +![Solution 19](./images/solutions/find_duplicate_solution_19.png) +![Solution 20](./images/solutions/find_duplicate_solution_20.png) + +In part two, we’ll start moving again in the cycle, but this time, we’ll slow down the fast pointer so that it moves +with the same speed as the slow pointer. + +Let’s look at the journeys of the two pointers in part two: + +- The slow pointer will start from the 0th position. +- The fast pointer will start from the intersection point. +- After a certain number of steps, let’s call it F, the slow pointer meets the fast pointer. This is the ending point + for both pointers. +- This common ending position will be the entry point of the cycle. + +Let’s look at the visual presentation of the second part of our solution: + +![Solution 21](./images/solutions/find_duplicate_solution_21.png) +![Solution 22](./images/solutions/find_duplicate_solution_22.png) +![Solution 23](./images/solutions/find_duplicate_solution_23.png) + +Now, let’s try to understand how it is that our solution is able to always locate the entry point of the cycle. + +Let’s return to the example we just discussed, using this graphical representation: + +![Solution 24](./images/solutions/find_duplicate_solution_24.png) + +- 7 is the intersection point where the slow and fast pointers will meet. +- 8 is the entry point of the cycle, which is our duplicate number. + +The fast pointer is traversing two times faster than the slow pointer. This can be represented by the following equation: + +> dfast = 2dslow ———(1) + +Here, d represents the number of elements traversed. + +Let’s look at the following diagram to see the steps taken by the slow and fast pointers from the starting point to the +intersection point: + +![Solution 25](./images/solutions/find_duplicate_solution_25.png) + +> A list with a cycle + +In the diagram above: + +- Green represents the entry point of the cycle. +- Blue represents the intersection point. +- Yellow represents the starting point. +- F represents the steps taken from the starting point to the entry point. +- a represents the steps taken to reach the intersection point from the entry point. +- C represents the cycle length, in terms of the number of steps taken to go once around the cycle. + +With this setup in mind, let’s see the distance traveled by the slow and fast pointers. The slow pointer travels F steps +from the starting point to the entry point of the cycle and then takes a steps from the entry point to the intersection +point of the cycle, that is, the point where both pointers intersect. So, we can express the distance traveled by the +slow pointer in the form of this equation: + +dslow = F+a ——— (2) + +In the time it takes the slow pointer to travel F+a steps, the fast pointer, since it’s traveling twice as fast as the +slow pointer, will have also traveled around the cycle at least once. So, we can say the fast pointer, first, travels F +steps from the starting point to the entry point of the cycle, then travels at least a cycle, and at the end travels a +steps from the entry point to the intersection point of the cycle. Now, we can express the distance traveled by the fast +pointer as the following equation: + +dfast = F+C+a ——— (3) + +Recall eq. (1): + +dfast = 2dslow ——— (1) + +If we substitute the equivalent expression of dslow given in eq. (2) and the equivalent expression of dfast given in eq. +(3) into eq. (1), we get: + +F + C + a = 2(F + a) + +Let’s simplify this equation: + +F + C + a = 2F + 2a +C = F + a + +Therefore, the distance from the starting point to the intersection point, F+a, equals C. + +We can also re-arrange this equality as follows: + +F=C−a + +Let’s consult our diagram again: + +![Solution 26](./images/solutions/find_duplicate_solution_26.png) + +As we can see, C−a is, in fact, the distance from the intersection point back to the entry point. This illustrates why, +when we move one pointer forward, starting at the intersection point, and another pointer from the starting point, the +point where they meet is the entry point of the cycle. + +> Note: The proof above does not consider the case where F is longer than the length of the cycle. In this situation, +> it’s possible that the fast pointer will go around the cycle more than once. To express this more general case, we can +> say that the distance covered by the fast pointer from the entry point to the intersection point is: F+nC+a, where n +> is a positive integer. As a result, our substitution will take this form: F + nC + a = 2(F+a), which simplifies to +> nC=F+a, that is: F = nC − a. This simply means that after going around the cycle n times, the fast pointer will still +> be a steps behind the entry point of the cycle. + +### Time Complexity + +The time complexity of the algorithm is O(n), where n is the length of nums. This is because, in each part of the +solution, the slow pointer traverses nums just once. + +### Space Complexity + +The algorithm takes O(1) space complexity, since we only used constant space to store the fast and slow pointers. diff --git a/algorithms/fast_and_slow/find_duplicate/__init__.py b/algorithms/fast_and_slow/find_duplicate/__init__.py index 47092e19..dddfdeb8 100644 --- a/algorithms/fast_and_slow/find_duplicate/__init__.py +++ b/algorithms/fast_and_slow/find_duplicate/__init__.py @@ -26,20 +26,38 @@ def find_duplicate_floyd_algo(numbers: List[int]) -> int: if len(numbers) <= 1: return -1 - - slow = numbers[0] - fast = numbers[numbers[0]] - - while slow != fast: + # Initialize the fast and slow pointers and make them point the first + # element of the array + slow = fast = numbers[0] + + # PART #1 + # Traverse in array until the intersection point is found + while True: + # Move the slow pointer using the nums[slow] flow slow = numbers[slow] + # Move the fast pointer two times fast as the slow pointer using the + # nums[nums[fast]] flow fast = numbers[numbers[fast]] - - fast = 0 + # Break the loop when slow pointer becomes equal to the fast pointer, i.e., + # if the intersection is found + if slow == fast: + break + + # PART #2 + # Make the slow pointer point the starting position of an array again, i.e., + # start the slow pointer from starting position + slow = numbers[0] + # Traverse in the array until the slow pointer becomes equal to the + # fast pointer while fast != slow: + # Move the slow pointer using the nums[slow] flow slow = numbers[slow] + # Move the fast pointer slower than before, i.e., move the fast pointer + # using the nums[fast] flow fast = numbers[fast] - return slow + # Return the fast pointer as it points the duplicate number of the array + return fast def find_duplicate(numbers: List[int]) -> int: diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_1.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_1.png new file mode 100644 index 00000000..b0f6c8b1 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_1.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_10.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_10.png new file mode 100644 index 00000000..09315531 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_10.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_11.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_11.png new file mode 100644 index 00000000..76e18260 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_11.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_12.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_12.png new file mode 100644 index 00000000..d671e37b Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_12.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_13.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_13.png new file mode 100644 index 00000000..63dd239a Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_13.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_14.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_14.png new file mode 100644 index 00000000..ce99a201 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_14.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_15.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_15.png new file mode 100644 index 00000000..c5b3a361 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_15.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_16.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_16.png new file mode 100644 index 00000000..d95fef9c Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_16.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_17.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_17.png new file mode 100644 index 00000000..b223a1e2 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_17.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_18.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_18.png new file mode 100644 index 00000000..6b1b0c6d Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_18.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_19.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_19.png new file mode 100644 index 00000000..b66e863e Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_19.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_2.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_2.png new file mode 100644 index 00000000..42c467c9 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_2.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_20.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_20.png new file mode 100644 index 00000000..51f6703b Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_20.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_21.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_21.png new file mode 100644 index 00000000..74ad1009 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_21.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_22.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_22.png new file mode 100644 index 00000000..1973a8bf Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_22.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_23.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_23.png new file mode 100644 index 00000000..fe87feb7 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_23.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_24.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_24.png new file mode 100644 index 00000000..5a1bcab0 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_24.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_25.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_25.png new file mode 100644 index 00000000..a2693ec8 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_25.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_26.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_26.png new file mode 100644 index 00000000..96d85ad6 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_26.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_3.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_3.png new file mode 100644 index 00000000..4f2dc011 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_3.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_4.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_4.png new file mode 100644 index 00000000..d666eed8 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_4.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_5.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_5.png new file mode 100644 index 00000000..7a57a04d Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_5.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_6.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_6.png new file mode 100644 index 00000000..8116ed05 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_6.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_7.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_7.png new file mode 100644 index 00000000..00e73d7d Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_7.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_8.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_8.png new file mode 100644 index 00000000..87eb9128 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_8.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_9.png b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_9.png new file mode 100644 index 00000000..79d72454 Binary files /dev/null and b/algorithms/fast_and_slow/find_duplicate/images/solutions/find_duplicate_solution_9.png differ diff --git a/algorithms/fast_and_slow/find_duplicate/test_find_duplicate.py b/algorithms/fast_and_slow/find_duplicate/test_find_duplicate.py index 0e8b7e9f..b3cb104e 100644 --- a/algorithms/fast_and_slow/find_duplicate/test_find_duplicate.py +++ b/algorithms/fast_and_slow/find_duplicate/test_find_duplicate.py @@ -1,153 +1,48 @@ import unittest - -from . import find_duplicate, find_duplicate_floyd_algo +from typing import List, Union +from copy import deepcopy +from parameterized import parameterized +from algorithms.fast_and_slow.find_duplicate import ( + find_duplicate, + find_duplicate_floyd_algo, +) + +FIND_DUPLICATE_TEST_CASES = [ + ([3, 4, 1, 4, 2], 4), + ([1, 2, 3], -1), + ([3, 4, 1, 4, 1], [1, 4]), + ([1, 3, 3, 4, 2, 5], 3), + ([1, 5, 3, 4, 2, 5], 5), + ([1, 2, 3, 4, 5, 6, 6, 7], 6), + ([4, 6, 7, 7, 3, 5, 2, 8, 1], 7), + ([9, 8, 7, 6, 2, 3, 5, 4, 1, 9], 9), + ([3, 4, 4, 4, 2], 4), + ([1, 1], 1), + ([1, 3, 4, 2, 2], 2), + ([1, 3, 6, 2, 7, 3, 5, 4], 3), + ([1, 2, 2], 2), +] class FindDuplicateTestCases(unittest.TestCase): - def test_1(self): - """nums = [3, 4, 1, 4, 2] should return 4""" - nums = [3, 4, 1, 4, 2] - expected = 4 - actual = find_duplicate(nums) - - self.assertEqual(expected, actual) - - def test_2(self): - """nums = [1, 2, 3] should return -1""" - nums = [1, 2, 3] - expected = -1 - actual = find_duplicate(nums) - - self.assertEqual(expected, actual) - - def test_3(self): - """nums = [3, 4, 1, 4, 1] should return 1 or 4""" - nums = [3, 4, 1, 4, 1] - expected_1 = 1 - expected_4 = 4 - - actual = find_duplicate(nums) - - self.assertIn(actual, [expected_1, expected_4]) - - -class FindDuplicateWithFastAndSlowPointersTestCases(unittest.TestCase): - def test_1(self): - """nums = [3, 4, 1, 4, 2] should return 4""" - nums = [3, 4, 1, 4, 2] - expected = 4 - actual = find_duplicate_floyd_algo(nums) - - self.assertEqual(expected, actual) - - # @unittest.skip("Failing due to IndexError. Needs further investigation") - def test_2(self): - """nums = [1, 2, 3] should return -1""" - nums = [1, 2, 3] - expected = -1 - actual = find_duplicate_floyd_algo(nums) - - self.assertEqual(expected, actual) - - def test_3(self): - """nums = [3, 4, 1, 4, 1] should return 1 or 4""" - nums = [3, 4, 1, 4, 1] - expected_1 = 1 - expected_4 = 4 - - actual = find_duplicate_floyd_algo(nums) - - self.assertIn(actual, [expected_1, expected_4]) - - def test_4(self): - """nums = [1, 3, 3, 4, 2, 5] should return 3""" - nums = [1, 3, 3, 4, 2, 5] - expected = 3 - - actual = find_duplicate_floyd_algo(nums) - - self.assertEqual(actual, expected) - - def test_5(self): - """nums = [1, 5, 3, 4, 2, 5] should return 5""" - nums = [1, 5, 3, 4, 2, 5] - expected = 5 - - actual = find_duplicate_floyd_algo(nums) - - self.assertEqual(actual, expected) - - def test_6(self): - """nums = [1, 2, 3, 4, 5, 6, 6, 7] should return 6""" - nums = [1, 2, 3, 4, 5, 6, 6, 7] - expected = 6 - - actual = find_duplicate_floyd_algo(nums) - - self.assertEqual(actual, expected) - - def test_7(self): - """nums = [4, 6, 7, 7, 3, 5, 2, 8, 1] should return 7""" - nums = [4, 6, 7, 7, 3, 5, 2, 8, 1] - expected = 7 - - actual = find_duplicate_floyd_algo(nums) - - self.assertEqual(actual, expected) - - def test_8(self): - """nums = [9, 8, 7, 6, 2, 3, 5, 4, 1, 9] should return 9""" - nums = [9, 8, 7, 6, 2, 3, 5, 4, 1, 9] - expected = 9 - - actual = find_duplicate_floyd_algo(nums) - - self.assertEqual(actual, expected) - - def test_9(self): - """nums = [3, 4, 4, 4, 2] should return 4""" - nums = [3, 4, 4, 4, 2] - expected = 4 - - actual = find_duplicate_floyd_algo(nums) - - self.assertEqual(actual, expected) - - def test_10(self): - """nums = [1, 1] should return 1""" - nums = [1, 1] - expected = 1 - - actual = find_duplicate_floyd_algo(nums) - - self.assertEqual(actual, expected) - - def test_11(self): - """nums = [1,3,4,2,2] should return 2""" - nums = [1, 3, 4, 2, 2] - expected = 2 - - actual = find_duplicate_floyd_algo(nums) - - self.assertEqual(actual, expected) - - def test_12(self): - """nums = [1,3,6,2,7,3,5,4] should return 3""" - nums = [1, 3, 6, 2, 7, 3, 5, 4] - expected = 3 - - actual = find_duplicate_floyd_algo(nums) - - self.assertEqual(actual, expected) - - def test_13(self): - """nums = [1,2,2] should return 2""" - nums = [1, 2, 2] - expected = 2 - - actual = find_duplicate_floyd_algo(nums) - - self.assertEqual(actual, expected) + @parameterized.expand(FIND_DUPLICATE_TEST_CASES) + def test_find_duplicate_floyd_algo( + self, nums: List[int], expected: Union[List[int], int] + ): + actual = find_duplicate_floyd_algo(nums) + if type(expected) == list: + self.assertIn(actual, expected) + else: + self.assertEqual(expected, actual) + + @parameterized.expand(FIND_DUPLICATE_TEST_CASES) + def test_find_duplicate(self, nums: List[int], expected: Union[List[int], int]): + numbers = deepcopy(nums) + actual = find_duplicate(numbers) + if type(expected) == list: + self.assertIn(actual, expected) + else: + self.assertEqual(expected, actual) if __name__ == "__main__": diff --git a/algorithms/greedy/assign_cookies/README.md b/algorithms/greedy/assign_cookies/README.md new file mode 100644 index 00000000..a658e604 --- /dev/null +++ b/algorithms/greedy/assign_cookies/README.md @@ -0,0 +1,84 @@ +# Assign Cookies + +Assume you are an awesome parent and want to give your children some cookies. But, you should give each child at most +one cookie. + +Each child i has a greed factor g[i], which is the minimum size of a cookie that the child will be content with; and +each cookie j has a size s[j]. If s[j] >= g[i], we can assign the cookie j to the child i, and the child i will be content. +Your goal is to maximize the number of your content children and output the maximum number. + +## Constraints + +- 1 <= greed.length <= 3 * 104 +- 0 <= cookies.length <= 3 * 104 +- 1 <= greed[i], cookies[j] <= 2^31 - 1 + +## Examples + +Example 1: +```text +Input: g = [1,2,3], s = [1,1] +Output: 1 +Explanation: You have 3 children and 2 cookies. The greed factors of 3 children are 1, 2, 3. +And even though you have 2 cookies, since their size is both 1, you could only make the child whose greed factor is 1 +content. +You need to output 1. +``` + +Example 2: +```text +Input: g = [1,2], s = [1,2,3] +Output: 2 +Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2. +You have 3 cookies and their sizes are big enough to gratify all of the children, +You need to output 2. +``` + +## Topics + +- Array +- Two Pointers +- Greedy +- Sorting + +## Solution + +Intiutively, we want to give each child the smallest cookie that satisfies them. This allows us to save the larger +cookies for the greedier children and allows us to maximize the number of satisfied children. + +The greedy algorithm sorts both the greeds and cookies arrays in ascending order. This places the child with the smallest +greed and the smallest cookie at the front of each array. + +For example: + +```text +greeds = [1, 3, 3, 4] +cookies = [2, 2, 3, 4] +``` + +We then initialize two pointers `i` and `j` to the start of the `greeds` and `cookies` arrays, respectively. `i` +represents the current child and `j` represents the current cookie. + +If `cookies[j] >= greeds[i]`, that means the current cookie can satisfy the current child. We increment the number of +satisfied children and move to the next child and cookie. + +![Solution 1](./images/solutions/assign_cookies_solution_1.png) + +If `cookies[j] < greeds[i]`, that means the current cookie cannot satisfy the current child, so we move to the next cookie +to see if it can. + +![Solution 2](./images/solutions/assign_cookies_solution_2.png) + +We can continue this process until we reach the end of either the greeds or cookies arrays, and return the number of +satisfied children as the result. + +### Complexity Analysis + +#### Time Complexity + +O(n log n + m log m) where n is the number of children and m is the number of cookies. We sort the +greeds and cookies arrays in O(n log n + m log m) time, and then iterate through the arrays in O(n + m) time. + +#### Space Complexity + +O(1) We only use a constant amount of space for variables. diff --git a/algorithms/greedy/assign_cookies/__init__.py b/algorithms/greedy/assign_cookies/__init__.py new file mode 100644 index 00000000..d9a1bf86 --- /dev/null +++ b/algorithms/greedy/assign_cookies/__init__.py @@ -0,0 +1,21 @@ +from typing import List + + +def find_content_children(greeds: List[int], cookies: List[int]) -> int: + # This in-place sorting of both g and s results in a time complexity of O(n log(n) + m log(m)) + greeds.sort() + cookies.sort() + + cookie, greed = 0, 0 + count = 0 + + # We iterate through each greed factor and cookie + while greed < len(greeds) and cookie < len(cookies): + # When we get a cookie that satisfies kid i, we assign that cookie to the child + # and move along, increasing the count as well + if cookies[cookie] >= greeds[greed]: + count += 1 + greed += 1 + cookie += 1 + + return count diff --git a/algorithms/greedy/assign_cookies/images/solutions/assign_cookies_solution_1.png b/algorithms/greedy/assign_cookies/images/solutions/assign_cookies_solution_1.png new file mode 100644 index 00000000..4687fbb0 Binary files /dev/null and b/algorithms/greedy/assign_cookies/images/solutions/assign_cookies_solution_1.png differ diff --git a/algorithms/greedy/assign_cookies/images/solutions/assign_cookies_solution_2.png b/algorithms/greedy/assign_cookies/images/solutions/assign_cookies_solution_2.png new file mode 100644 index 00000000..577815d5 Binary files /dev/null and b/algorithms/greedy/assign_cookies/images/solutions/assign_cookies_solution_2.png differ diff --git a/algorithms/greedy/assign_cookies/test_assign_cookies.py b/algorithms/greedy/assign_cookies/test_assign_cookies.py new file mode 100644 index 00000000..e6d476ee --- /dev/null +++ b/algorithms/greedy/assign_cookies/test_assign_cookies.py @@ -0,0 +1,24 @@ +import unittest +from typing import List +from parameterized import parameterized +from algorithms.greedy.assign_cookies import find_content_children + + +ASSIGN_COOKIES_TEST_CASES = [ + ([1, 2, 3], [1, 1], 1), + ([1, 2], [1, 2, 3], 2), + ([10, 9, 8, 7], [5, 6, 7, 8], 2), +] + + +class AssignCookiesTestCase(unittest.TestCase): + @parameterized.expand(ASSIGN_COOKIES_TEST_CASES) + def test_find_content_children( + self, greed: List[int], cookies: List[int], expected: int + ): + actual = find_content_children(greed, cookies) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/algorithms/greedy/gas_stations/README.md b/algorithms/greedy/gas_stations/README.md index fefddf57..33dc5940 100644 --- a/algorithms/greedy/gas_stations/README.md +++ b/algorithms/greedy/gas_stations/README.md @@ -42,3 +42,54 @@ Therefore, you can't travel around the circuit once no matter where you start. - Array - Greedy + +## Solution + +If there is more gas along the route than the cost of the route, then there is guaranteed to be a solution to the problem. +So the first step is to check if the sum of the gas is greater than or equal to the sum of the cost. If it is not, then +we return -1. + +Next, we iterate through the gas station to find the starting index of our circuit using a greedy approach: whenever we +don't have enough gas to reach the next station, we move our starting gas station to the next station and reset our gas +tank. + +We start at the first station, and fill our tank with gas[0] = 5 units of gas. From there, it takes cost[0] = 1 units of +gas to travel to the next station, so we arrive at station 2 (index 1) with 4 units of gas. + +![Solution 1](./images/solutions/gas_stations_solution_1.png) +![Solution 2](./images/solutions/gas_stations_solution_2.png) +![Solution 3](./images/solutions/gas_stations_solution_3.png) + +At station 2, we fill our tank with gas[1] = 2 units of gas, for a total of 6 units of gas. It takes cost[1] = 5 units +of gas to travel to the next station, so we arrive at station 3 with 1 unit of gas + +![Solution 4](./images/solutions/gas_stations_solution_4.png) +![Solution 5](./images/solutions/gas_stations_solution_5.png) + +Now at station 3, we fill our tank with gas[2] = 0 units of gas, for a total of 1 unit of gas. It takes cost[2] = 5 units +of gas to travel to the next station, which we don't have. + +This is where our greedy approach comes in. We reset our starting station to the next station i + 1 and reset our gas +tank to 0. We can do this because all other start indexes between 0 and 2 will also run into the same problem of not +having enough gas to reach the next station, so we can rule them out. + +![Solution 6](./images/solutions/gas_stations_solution_6.png) +![Solution 7](./images/solutions/gas_stations_solution_7.png) + +If we follow this approach of resetting the start index and gas tank whenever we don't have enough gas to reach the next +station, then when we finish iterating, the last start index will be the solution to the problem. + +![Solution 8](./images/solutions/gas_stations_solution_8.png) +![Solution 9](./images/solutions/gas_stations_solution_9.png) +![Solution 10](./images/solutions/gas_stations_solution_10.png) +![Solution 11](./images/solutions/gas_stations_solution_11.png) + +### Complexity Analysis + +#### Time Complexity + +O(n) where n is the number of gas stations. We only iterate through the gas stations once. + +#### Space Complexity + +O(1) We only use constant extra space for variables. diff --git a/algorithms/greedy/gas_stations/__init__.py b/algorithms/greedy/gas_stations/__init__.py index 9edf2ef8..fc7e8d35 100644 --- a/algorithms/greedy/gas_stations/__init__.py +++ b/algorithms/greedy/gas_stations/__init__.py @@ -22,10 +22,14 @@ def can_complete_circuit(gas: List[int], cost: List[int]) -> int: gas_tank, start_index = 0, 0 - for i in range(len(gas)): - gas_tank += gas[i] - cost[i] + for gas_station in range(len(gas)): + # can reach next station: + # update remaining fuel + gas_tank += gas[gas_station] - cost[gas_station] if gas_tank < 0: - start_index = i + 1 + # can't reach next station: + # try starting from next station + start_index = gas_station + 1 gas_tank = 0 return start_index diff --git a/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_1.png b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_1.png new file mode 100644 index 00000000..b0169f81 Binary files /dev/null and b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_1.png differ diff --git a/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_10.png b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_10.png new file mode 100644 index 00000000..1a0138e8 Binary files /dev/null and b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_10.png differ diff --git a/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_11.png b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_11.png new file mode 100644 index 00000000..1f6cf96e Binary files /dev/null and b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_11.png differ diff --git a/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_2.png b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_2.png new file mode 100644 index 00000000..91a3250e Binary files /dev/null and b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_2.png differ diff --git a/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_3.png b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_3.png new file mode 100644 index 00000000..d7752e11 Binary files /dev/null and b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_3.png differ diff --git a/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_4.png b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_4.png new file mode 100644 index 00000000..bcd98658 Binary files /dev/null and b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_4.png differ diff --git a/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_5.png b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_5.png new file mode 100644 index 00000000..75a458d5 Binary files /dev/null and b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_5.png differ diff --git a/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_6.png b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_6.png new file mode 100644 index 00000000..5556ca47 Binary files /dev/null and b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_6.png differ diff --git a/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_7.png b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_7.png new file mode 100644 index 00000000..8a0c5152 Binary files /dev/null and b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_7.png differ diff --git a/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_8.png b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_8.png new file mode 100644 index 00000000..0e6a22df Binary files /dev/null and b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_8.png differ diff --git a/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_9.png b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_9.png new file mode 100644 index 00000000..b92f1fc4 Binary files /dev/null and b/algorithms/greedy/gas_stations/images/solutions/gas_stations_solution_9.png differ diff --git a/algorithms/greedy/gas_stations/test_gas_stations.py b/algorithms/greedy/gas_stations/test_gas_stations.py index 67b1b7e2..dd804cfc 100644 --- a/algorithms/greedy/gas_stations/test_gas_stations.py +++ b/algorithms/greedy/gas_stations/test_gas_stations.py @@ -3,15 +3,16 @@ from parameterized import parameterized from algorithms.greedy.gas_stations import can_complete_circuit +CAN_COMPLETE_CIRCUIT_TEST_CASES = [ + ([1, 2, 3, 4, 5], [3, 4, 5, 1, 2], 3), + ([2, 3, 4], [3, 4, 3], -1), + ([1, 2], [2, 1], 1), + ([5, 2, 0, 3, 3], [1, 5, 5, 1, 1], 3), +] + class CanCompleteCircuitTestCase(unittest.TestCase): - @parameterized.expand( - [ - ([1, 2, 3, 4, 5], [3, 4, 5, 1, 2], 3), - ([2, 3, 4], [3, 4, 3], -1), - ([1, 2], [2, 1], 1), - ] - ) + @parameterized.expand(CAN_COMPLETE_CIRCUIT_TEST_CASES) def test_can_complete_circuit(self, gas: List[int], cost: List[int], expected: int): actual = can_complete_circuit(gas, cost) self.assertEqual(expected, actual) diff --git a/algorithms/greedy/jump_game/README.md b/algorithms/greedy/jump_game/README.md index f5ef9ea1..9db14736 100644 --- a/algorithms/greedy/jump_game/README.md +++ b/algorithms/greedy/jump_game/README.md @@ -25,7 +25,9 @@ to reach the last index. ## Solutions 1. [Naive Approach](#naive-approach) -1. [Optimized Approach using Greedy Pattern](#optimized-approach-using-greedy-pattern) +2. [Optimized Approach using Greedy Pattern](#optimized-approach-using-greedy-pattern) + - [Variation 1](#variation-1) + - [Variation 2](#variation-2) ### Naive Approach @@ -40,6 +42,8 @@ for large inputs. ### Optimized Approach using Greedy Pattern +#### Variation 1 + An optimized way to solve this problem is using a greedy approach that works in reverse. Instead of trying every possible forward jump, we flip the logic: start from the end and ask, “Can we reach this position from any earlier index?” @@ -65,7 +69,7 @@ target remains beyond index 0, then no path exists from the start to the end, an ![Solution 10](./images/solutions/jump_game_1_solution_10.png) ![Solution 11](./images/solutions/jump_game_1_solution_11.png) -#### Algorithm +###### Algorithm 1. We begin by setting the last index of the array as our initial target using the variable `target = len(nums) - 1`. This target represents the position we are trying to reach, starting from the end and working backward. By initializing the @@ -86,7 +90,7 @@ target remains beyond index 0, then no path exists from the start to the end, an - Or reach the start without ever being able to update the target to 0, which means there is no valid path. In this case, we return FALSE. -#### Solution Summary +###### Solution Summary 1. Set the last index of the array as the target index. 2. Traverse the array backward and verify if we can reach the target index from any of the previous indexes. @@ -94,6 +98,45 @@ target remains beyond index 0, then no path exists from the start to the end, an - We repeat this process until we’ve traversed the entire array. 3. Return TRUE if, through this process, we can reach the first index of the array. Otherwise, return FALSE. +#### Variation 2 + +Think of each index as a platform. The value at each platform tells you the maximum distance you can jump from there. +You need to figure out: can you reach the goal? + +![Solution 2.1](./images/solutions/jump_game_1_solution_2.1.png) + +The question isn't about how to get there - just whether it's possible. + +We don't need to track every possible path. We just need to track the farthest position we can reach so far. + +![Solution 2.2](./images/solutions/jump_game_1_solution_2.2.png) + +As we iterate through the array: +- At each position i, update max_reach = max(max_reach, i + nums[i]) +- If i > max_reach, we're stuck - return false +- If we finish the loop, return true + +- Let's trace through nums = [2, 3, 1, 1, 4]: + +![Solution 2.3](./images/solutions/jump_game_1_solution_2.3.png) + +i=0: Can we reach index 0? Yes (we start here). From here, we can reach up to index 2. max_reach = 2. + +![Solution 2.4](./images/solutions/jump_game_1_solution_2.4.png) + +i=1: Can we reach index 1? Yes (1 ≤ 2). From here, we can reach up to index 4! max_reach = 4. + +![Solution 2.5](./images/solutions/jump_game_1_solution_2.5.png) + +i=2: Can we reach index 2? Yes (2 ≤ 4). max_reach stays 4. +We continue... max_reach = 4 >= n-1 = 4. Answer: true! + +What if we can't reach the end? Consider nums = [3, 2, 1, 0, 4]: + +![Solution 2.6](./images/solutions/jump_game_1_solution_2.6.png) + +At index 3, nums[3] = 0 means we can't jump anywhere. Since max_reach = 3 and we need to reach index 4, we're stuck. + #### Time Complexity The time complexity of the above solution is O(n), since we traverse the array only once, where n is the number of @@ -103,7 +146,6 @@ elements in the array. The space complexity of the above solution is O(1), because we do not use any extra space. - --- # Jump Game II diff --git a/algorithms/greedy/jump_game/__init__.py b/algorithms/greedy/jump_game/__init__.py index 309e4e2d..4c0b73ed 100644 --- a/algorithms/greedy/jump_game/__init__.py +++ b/algorithms/greedy/jump_game/__init__.py @@ -10,15 +10,13 @@ def can_jump(nums: List[int]) -> bool: bool: True if can jump to the last index, False otherwise """ - current_position = nums[0] + max_reach = 0 - for idx in range(1, len(nums)): - if current_position == 0: + for i in range(len(nums)): + if i > max_reach: return False - current_position -= 1 - - current_position = max(current_position, nums[idx]) + max_reach = max(max_reach, i + nums[i]) return True diff --git a/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.1.png b/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.1.png new file mode 100644 index 00000000..027c2e70 Binary files /dev/null and b/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.1.png differ diff --git a/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.2.png b/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.2.png new file mode 100644 index 00000000..93c9e359 Binary files /dev/null and b/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.2.png differ diff --git a/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.3.png b/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.3.png new file mode 100644 index 00000000..89d818ae Binary files /dev/null and b/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.3.png differ diff --git a/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.4.png b/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.4.png new file mode 100644 index 00000000..5e1749bd Binary files /dev/null and b/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.4.png differ diff --git a/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.5.png b/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.5.png new file mode 100644 index 00000000..40c98260 Binary files /dev/null and b/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.5.png differ diff --git a/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.6.png b/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.6.png new file mode 100644 index 00000000..62703fbc Binary files /dev/null and b/algorithms/greedy/jump_game/images/solutions/jump_game_1_solution_2.6.png differ diff --git a/algorithms/greedy/jump_game/test_jump_game.py b/algorithms/greedy/jump_game/test_jump_game.py index 890ba93f..54b0574f 100644 --- a/algorithms/greedy/jump_game/test_jump_game.py +++ b/algorithms/greedy/jump_game/test_jump_game.py @@ -13,6 +13,8 @@ ([2, 3, 1, 1, 9], True), ([4, 0, 0, 0, 4], True), ([1], True), + ([2, 0, 0], True), + ([5, 9, 3, 2, 1, 0, 2, 3, 3, 1, 0, 0], True), ] diff --git a/algorithms/greedy/minimum_refuel_stops/README.md b/algorithms/greedy/minimum_refuel_stops/README.md new file mode 100644 index 00000000..49532d59 --- /dev/null +++ b/algorithms/greedy/minimum_refuel_stops/README.md @@ -0,0 +1,79 @@ +# Minimum Number of Refueling Stops + +You need to find the minimum number of refueling stops that a car needs to make to cover a distance, target. For +simplicity, assume that the car has to travel from west to east in a straight line. There are various fuel stations on +the way that are represented as a 2-D array of stations, i.e., stations[i]=[di,fi], where di is the distance (in miles) +of the ith gas station from the starting position, and fi is the amount of fuel (in liters) that it stores. Initially, +the car starts with k liters of fuel. The car consumes one liter of fuel for every mile traveled. Upon reaching a gas +station, the car can stop and refuel using all the petrol stored at the station. If it cannot reach the target, the +program returns −1. + +> Note: If the car reaches a station with 0 fuel left, it can refuel from that station, and all the fuel from that +> station can be transferred to the car. If the car reaches the target with 0 fuel left, it is still considered to have +> arrived. + +## Constraints + +- 1 <= `target`, `k` <= 10^9 +- 0 <= `stations.length`, <= 900 +- 1 <= di < di+1 < `target` +- 1 <= fi < 10^9 + +## Examples + +![Example 1](./images/examples/minimum_number_of_refueling_stops_example_1.png) +![Example 2](./images/examples/minimum_number_of_refueling_stops_example_2.png) +![Example 3](./images/examples/minimum_number_of_refueling_stops_example_3.png) +![Example 4](./images/examples/minimum_number_of_refueling_stops_example_4.png) +![Example 5](./images/examples/minimum_number_of_refueling_stops_example_5.png) +![Example 6](./images/examples/minimum_number_of_refueling_stops_example_6.png) +![Example 7](./images/examples/minimum_number_of_refueling_stops_example_7.png) +![Example 8](./images/examples/minimum_number_of_refueling_stops_example_8.png) + +## Solutions + +### Using Max Heap + +This problem can be solved using the greedy algorithm, since the car has to reach the destination using a minimum number +of refueling stops. This means that the car needs a maximum fuel amount at any point. The idea is to make optimal choices +by selecting the fuel station with the maximum fuel capacity at each step to reach the target distance with the minimum +number of refueling stops. + +To cater to the problem of selecting the maximum fuel value, we can use the max-heap to keep track of fuel capacity for +refueling because the top of the max-heap will always have the highest fuel value. Therefore we can take the highest fuel +value from the top of the max-heap to reach the target by using the minimum number of refueling stops. To implement this +methodology, we will create a function, min_refuel_stops. The steps of the function are given below: + +1. If the starting fuel is greater than or equal to the target distance, return 0. It means no extra fuel is required, + and the car can reach the target using the starting fuel. +2. Otherwise, iterate until the maximum distance is equal to or greater than the target, and the car is not out of fuel: + - If we have a fuel station that can be used to refuel, and the vehicle has enough fuel to reach it, add the refueling + station to the max-heap. + - If the max-heap contains no fuel stations, the vehicle can’t reach the target, and the function returns −1. In + simpler words, the car doesn’t have enough fuel to reach the target even after stopping at all the fuel stations. + - Otherwise, if the max-heap has fuel stations and the vehicle doesn’t have enough fuel to go to the next station, + the vehicle refuels from the fuel station with the maximum fuel value. After refueling, we remove the fuel value of + this refueling station from the max-heap and also increment the number of stops. +3. After executing the loop, the function returns the total number of refueling stops required to reach the target + distance. + +![Solution 1](./images/solutions/minimum_number_of_refueling_stops_solution_max_heap_1.png) +![Solution 2](./images/solutions/minimum_number_of_refueling_stops_solution_max_heap_2.png) +![Solution 3](./images/solutions/minimum_number_of_refueling_stops_solution_max_heap_3.png) +![Solution 4](./images/solutions/minimum_number_of_refueling_stops_solution_max_heap_4.png) +![Solution 5](./images/solutions/minimum_number_of_refueling_stops_solution_max_heap_5.png) +![Solution 6](./images/solutions/minimum_number_of_refueling_stops_solution_max_heap_6.png) +![Solution 7](./images/solutions/minimum_number_of_refueling_stops_solution_max_heap_7.png) +![Solution 8](./images/solutions/minimum_number_of_refueling_stops_solution_max_heap_8.png) +![Solution 9](./images/solutions/minimum_number_of_refueling_stops_solution_max_heap_9.png) +![Solution 10](./images/solutions/minimum_number_of_refueling_stops_solution_max_heap_10.png) + +#### Time Complexity + +The time complexity of the solution above is O(n×log(n)), where n is the total number of stations. + +#### Space Complexity + +The space complexity of the solution above is O(n), since there will be maximum n fuel capacities in a heap. + + diff --git a/algorithms/greedy/minimum_refuel_stops/__init__.py b/algorithms/greedy/minimum_refuel_stops/__init__.py new file mode 100644 index 00000000..0f2065e2 --- /dev/null +++ b/algorithms/greedy/minimum_refuel_stops/__init__.py @@ -0,0 +1,78 @@ +from typing import List +import heapq + + +def min_refuel_stops(target: int, start_fuel: int, stations: List[List[int]]): + # if the start fuel is 0, we can't start the journey + if start_fuel == 0: + return -1 + + # No need to stop to refuel, the car can reach the target distance with the starting fuel + if start_fuel >= target: + return 0 + + # Create a max heap to store the fuel capacities of stations in + # such a way that maximum fuel capacity is at the top of the heap + max_heap = [] + i, n = 0, len(stations) + # This will keep track of the minimum refueling stops + min_stops = 0 + max_distance = start_fuel + + # loop until the car reaches the target, or it is out of fuel + while max_distance < target: + # If there are still stations and the next one is within range, add its fuel capacity to the max heap + if i < n and stations[i][0] <= max_distance: + heapq.heappush(max_heap, -stations[i][1]) + i += 1 + # If there are no more stations, and we can't reach the target, return -1 + elif not max_heap: + return -1 + else: + # Otherwise, fill up at the station with the highest fuel capacity and increment stops + max_distance += -heapq.heappop(max_heap) + min_stops += 1 + + return min_stops + + +def min_refuel_stops_2(target: int, start_fuel: int, stations: List[List[int]]): + # if the start fuel is 0, we can't start the journey + if start_fuel == 0: + return -1 + + # No need to stop to refuel, the car can reach the target distance with the starting fuel + if start_fuel >= target: + return 0 + + max_heap = [] + + # Append target to stations to treat final destination as station with no fuel + stations.append([target, 0]) + + # This will keep track of the minimum refueling stops + min_stops = 0 + # Keeps track of the distance covered from the start. This will be used to calculate the distance to the next station + distance_covered = 0 + # Current fuel level is the current fuel level we have + current_fuel_level = start_fuel + + for idx in range(len(stations)): + station_distance, station_fuel = stations[idx] + # The Fuel cost is the station distance from the beginning minus the distance covered so far + fuel_cost = station_distance - distance_covered + + current_fuel_level -= fuel_cost + + while current_fuel_level < 0 and max_heap: + current_fuel_level -= heapq.heappop(max_heap) + min_stops += 1 + + # check if we can reach the next station + if current_fuel_level < 0: + return -1 + + heapq.heappush(max_heap, -station_fuel) + distance_covered = station_distance + + return min_stops diff --git a/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_1.png b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_1.png new file mode 100644 index 00000000..91c7b840 Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_1.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_2.png b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_2.png new file mode 100644 index 00000000..34a95b81 Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_2.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_3.png b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_3.png new file mode 100644 index 00000000..5fd5ef12 Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_3.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_4.png b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_4.png new file mode 100644 index 00000000..376a9470 Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_4.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_5.png b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_5.png new file mode 100644 index 00000000..01ddcc9d Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_5.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_6.png b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_6.png new file mode 100644 index 00000000..9acc70b7 Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_6.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_7.png b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_7.png new file mode 100644 index 00000000..3439b9ef Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_7.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_8.png b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_8.png new file mode 100644 index 00000000..7354afb9 Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/examples/minimum_number_of_refueling_stops_example_8.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_1.png b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_1.png new file mode 100644 index 00000000..6ee5f2c4 Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_1.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_10.png b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_10.png new file mode 100644 index 00000000..dc10ec8e Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_10.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_2.png b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_2.png new file mode 100644 index 00000000..29acc086 Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_2.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_3.png b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_3.png new file mode 100644 index 00000000..3747720e Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_3.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_4.png b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_4.png new file mode 100644 index 00000000..0c5df2bf Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_4.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_5.png b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_5.png new file mode 100644 index 00000000..b1d3446f Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_5.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_6.png b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_6.png new file mode 100644 index 00000000..d394ca5d Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_6.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_7.png b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_7.png new file mode 100644 index 00000000..96763040 Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_7.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_8.png b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_8.png new file mode 100644 index 00000000..93dc0502 Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_8.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_9.png b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_9.png new file mode 100644 index 00000000..f47781dd Binary files /dev/null and b/algorithms/greedy/minimum_refuel_stops/images/solutions/minimum_number_of_refueling_stops_solution_max_heap_9.png differ diff --git a/algorithms/greedy/minimum_refuel_stops/test_min_refueling_stops.py b/algorithms/greedy/minimum_refuel_stops/test_min_refueling_stops.py new file mode 100644 index 00000000..5289fe56 --- /dev/null +++ b/algorithms/greedy/minimum_refuel_stops/test_min_refueling_stops.py @@ -0,0 +1,36 @@ +import unittest +from typing import List +from parameterized import parameterized +from algorithms.greedy.minimum_refuel_stops import min_refuel_stops, min_refuel_stops_2 + +MIN_REFUELING_STOPS_TEST_CASES = [ + (3, 3, [], 0), + (1, 1, [], 0), + (100, 1, [[10, 100]], -1), + (120, 10, [[10, 60], [20, 25], [30, 30], [60, 40]], 3), + (15, 3, [[2, 5], [3, 1], [6, 3], [12, 6]], 4), + (570, 140, [[140, 200], [160, 130], [310, 200], [330, 250]], 2), + (1360, 380, [[310, 160], [380, 620], [700, 89], [850, 190], [990, 360]], 2), + (59, 14, [[9, 12], [11, 7], [13, 16], [21, 18], [47, 6]], 3), + (100, 10, [[10, 60], [20, 30], [30, 30], [60, 40]], 2), +] + + +class MinRefuelStopsTestCase(unittest.TestCase): + @parameterized.expand(MIN_REFUELING_STOPS_TEST_CASES) + def test_min_refueling_stops( + self, target: int, start_fuel: int, stations: List[List[int]], expected: int + ): + actual = min_refuel_stops(target, start_fuel, stations) + self.assertEqual(expected, actual) + + @parameterized.expand(MIN_REFUELING_STOPS_TEST_CASES) + def test_min_refueling_stops_2( + self, target: int, start_fuel: int, stations: List[List[int]], expected: int + ): + actual = min_refuel_stops_2(target, start_fuel, stations) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/algorithms/greedy/two_city_scheduling/README.md b/algorithms/greedy/two_city_scheduling/README.md new file mode 100644 index 00000000..fc3e7f81 --- /dev/null +++ b/algorithms/greedy/two_city_scheduling/README.md @@ -0,0 +1,75 @@ +# Two City Scheduling + +A recruiter plans to hire n people and conducts their interviews at two different locations of the company. He +evaluates the cost of inviting candidates to both these locations. The plan is to invite 50% at one location, and the +rest at the other location, keeping costs to a minimum. + +We are given an array, costs, where costs[i]=[aCosti ,bCosti]], the cost of inviting the ith person to City A is aCosti, +and the cost of inviting the same person to City B bCosti. + +You need to determine the minimum cost to invite all the candidates for the interview such that exactly n/2 people are +invited in each city. + +## Constraints + +- 2 <= `costs.length` <= 100 +- `costs.length` is even +- 1 <= `aCosti`, `bCosti` <= 1000 + +## Examples + +![Example 1](./images/examples/two_city_scheduling_example_1.png) +![Example 2](./images/examples/two_city_scheduling_example_2.png) +![Example 3](./images/examples/two_city_scheduling_example_3.png) + +## Solution + +Let’s take an example to understand the solution to this problem. Given the costs array [[100,10],[1000,10],[500,50],[100,1]], +we want to invite half the people to City A and the other half to City B. We can see that inviting the first person and +the fourth person to City A and the second and the third person to City B minimizes the cost to 100 + 100 + 50 + 10 = 260. + +Let’s take the difference of all city pairs (aCosti − bCosti) [90,990,450,99]. If we sort our cost array based on these +differences ([90, 99, 450, 990]), then the cost array will be [[100,10],[100,1],[500,50],[1000,10]]. Now, if we invite +the first half of the array to City A and the next half to City B, then the cost is minimized to 100+100+50+10=260. + +This algorithm uses a greedy approach that chooses the minimum cost for inviting a person to a city based on the difference +between the cost of inviting them to City A and City B. The idea behind this is that if the cost difference is large, +inviting a person to a city with a lower cost is optimal, because the difference is large. After sorting the array based +on difference, the costs with large differences will be in the second half of the array. When the difference is large, +it means the second value will be much less than the first value in the costs array. Therefore, we are inviting the +second half to City B for optimization. + +The steps of the solution according to the above-mentioned methodology are given below: + +1. Declare a variable, total_cost, initialized to 0. +2. Sort the costs array in ascending order based on the difference between the cost of sending a person to City A versus + sending the same person to City B. +3. Iterate over the sorted costs array to calculate the minimum costs required to send n/2 people to each city. We will + perform the following steps: + - For the first n/2 elements in the costs array, add aCosti to the total cost for inviting candidates to City A. + - For the remaining n/2 elements in the costs array, add bCosti to the total cost for inviting candidates to City B. +4. Return the total minimum cost after traversing the costs array. + + +![Solution 1](./images/solutions/two_city_scheduling_solution_1.png) +![Solution 2](./images/solutions/two_city_scheduling_solution_2.png) +![Solution 3](./images/solutions/two_city_scheduling_solution_3.png) +![Solution 4](./images/solutions/two_city_scheduling_solution_4.png) +![Solution 5](./images/solutions/two_city_scheduling_solution_5.png) +![Solution 6](./images/solutions/two_city_scheduling_solution_6.png) +![Solution 7](./images/solutions/two_city_scheduling_solution_7.png) + +### Time Complexity + +We only traverse the array once but since we use sorting, the time complexity of the solution becomes O(nlog(n)). + +### Space Complexity + +In Python, the sorting algorithm takes O(n) space to sort the costs array. Therefore, the space complexity of the above +solution is O(n). + +## Topics + +- Array +- Greedy +- Sorting diff --git a/algorithms/greedy/two_city_scheduling/__init__.py b/algorithms/greedy/two_city_scheduling/__init__.py new file mode 100644 index 00000000..eb4fd3b9 --- /dev/null +++ b/algorithms/greedy/two_city_scheduling/__init__.py @@ -0,0 +1,20 @@ +from typing import List + + +def two_city_scheduling(costs: List[List[int]]) -> int: + if not costs: + return 0 + + # Sort by the relative cost difference of sending someone to city A over city B + # Negative or small differences appear first + costs.sort(key=lambda x: x[0] - x[1]) + + # This gives us the number of people to send to each city. This is equivalent to len(costs) // 2 + n = len(costs) >> 1 + + total_cost = 0 + + for i in range(n): + total_cost += costs[i][0] + costs[i + n][1] + + return total_cost diff --git a/algorithms/greedy/two_city_scheduling/images/examples/two_city_scheduling_example_1.png b/algorithms/greedy/two_city_scheduling/images/examples/two_city_scheduling_example_1.png new file mode 100644 index 00000000..d6172eaf Binary files /dev/null and b/algorithms/greedy/two_city_scheduling/images/examples/two_city_scheduling_example_1.png differ diff --git a/algorithms/greedy/two_city_scheduling/images/examples/two_city_scheduling_example_2.png b/algorithms/greedy/two_city_scheduling/images/examples/two_city_scheduling_example_2.png new file mode 100644 index 00000000..212c6083 Binary files /dev/null and b/algorithms/greedy/two_city_scheduling/images/examples/two_city_scheduling_example_2.png differ diff --git a/algorithms/greedy/two_city_scheduling/images/examples/two_city_scheduling_example_3.png b/algorithms/greedy/two_city_scheduling/images/examples/two_city_scheduling_example_3.png new file mode 100644 index 00000000..7d060486 Binary files /dev/null and b/algorithms/greedy/two_city_scheduling/images/examples/two_city_scheduling_example_3.png differ diff --git a/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_1.png b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_1.png new file mode 100644 index 00000000..317cb0f1 Binary files /dev/null and b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_1.png differ diff --git a/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_2.png b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_2.png new file mode 100644 index 00000000..03d46628 Binary files /dev/null and b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_2.png differ diff --git a/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_3.png b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_3.png new file mode 100644 index 00000000..6ba2ff69 Binary files /dev/null and b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_3.png differ diff --git a/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_4.png b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_4.png new file mode 100644 index 00000000..4fe1c741 Binary files /dev/null and b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_4.png differ diff --git a/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_5.png b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_5.png new file mode 100644 index 00000000..2467d577 Binary files /dev/null and b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_5.png differ diff --git a/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_6.png b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_6.png new file mode 100644 index 00000000..a3176751 Binary files /dev/null and b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_6.png differ diff --git a/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_7.png b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_7.png new file mode 100644 index 00000000..456b9760 Binary files /dev/null and b/algorithms/greedy/two_city_scheduling/images/solutions/two_city_scheduling_solution_7.png differ diff --git a/algorithms/greedy/two_city_scheduling/test_two_city_scheduling.py b/algorithms/greedy/two_city_scheduling/test_two_city_scheduling.py new file mode 100644 index 00000000..2d035e7e --- /dev/null +++ b/algorithms/greedy/two_city_scheduling/test_two_city_scheduling.py @@ -0,0 +1,37 @@ +import unittest +from typing import List +from parameterized import parameterized +from algorithms.greedy.two_city_scheduling import two_city_scheduling + +TWO_CITY_SCHEDULING_TEST_CASES = [ + ([[10, 20], [30, 200], [400, 50], [30, 20]], 110), + ([[259, 770], [448, 54], [926, 667], [184, 139], [840, 118], [577, 469]], 1859), + ( + [ + [515, 563], + [451, 713], + [537, 709], + [343, 819], + [855, 779], + [457, 60], + [650, 359], + [631, 42], + ], + 3086, + ), + ([[1, 2], [3, 4], [5, 6], [7, 8]], 18), + ([[1, 2], [1, 2], [1, 2], [1, 2]], 6), + ([[100, 10], [1000, 10], [500, 50], [100, 1]], 260), + ([[100, 10], [1000, 10], [500, 50], [100, 1], [50, 1], [2000, 40]], 350), +] + + +class TwoCitySchedulingTestCase(unittest.TestCase): + @parameterized.expand(TWO_CITY_SCHEDULING_TEST_CASES) + def test_two_city_scheduling(self, costs: List[List[int]], expected: int): + actual = two_city_scheduling(costs) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/algorithms/sliding_window/max_points_from_cards/README.md b/algorithms/sliding_window/max_points_from_cards/README.md new file mode 100644 index 00000000..0a9ce48f --- /dev/null +++ b/algorithms/sliding_window/max_points_from_cards/README.md @@ -0,0 +1,101 @@ +# Max Points You Can Obtain From Cards + +Given an array of integers representing card values, write a function to calculate the maximum score you can achieve by +picking exactly k cards. + +You must pick cards in order from either end. You can take some cards from the beginning, then switch to taking cards +from the end, but you cannot skip cards or pick from the middle. + +For example, with k = 3: + +- Take the first 3 cards: valid +- Take the last 3 cards: valid +- Take the first card, then the last 2 cards: valid +- Take the first 2 cards, then the last card: valid +- Take card at index 0, skip some, then take card at index 5: not valid (skipping cards) + +## Constraints + +- 1 <= k <= cards.length +- 1 <= `cards.length` <= 10^5 +- 1 <= `cards[i]` <= 10^4 + +## Examples + +Example 1: +```text +Input: cardPoints = [1,2,3,4,5,6,1], k = 3 +Output: 12 +Explanation: After the first step, your score will always be 1. However, choosing the rightmost card first will maximize +your total score. The optimal strategy is to take the three cards on the right, giving a final score of 1 + 6 + 5 = 12. +``` + +Example 2: +```text +Input: cardPoints = [2,2,2], k = 2 +Output: 4 +Explanation: Regardless of which two cards you take, your score will always be 4. +``` + +Example 3: +```text +Input: cardPoints = [9,7,7,9,7,7,9], k = 7 +Output: 55 +Explanation: You have to take all the cards. Your score is the sum of points of all cards. +``` + +## Topics + +- Array +- Sliding Window +- Prefix Sum + +## Solutions + +When you pick k cards from either end of the array, you're actually leaving behind n - k consecutive cards in the middle +that you didn't pick. + +For example, with cards = [2,11,4,5,3,9,2] and k = 3: + +![Solution 1](./images/solutions/max_points_from_cards_solution_1.png) + +Every possible way to pick 3 cards from the ends corresponds to a different window of 4 cards (n - k = 7 - 3 = 4) in the +middle that we're NOT picking. + +### Why This Matters + +Since we know the total sum of all cards, we can calculate: + +Sum of picked cards = Total sum - Sum of unpicked cards + +So to maximize the sum of picked cards, we need to minimize the sum of unpicked cards. + +This transforms the problem: instead of trying all combinations of picking from ends, we find the minimum sum of any +window of size n - k. + +![Solution 2](./images/solutions/max_points_from_cards_solution_2.png) + +### Algorithm + +Use a fixed-length sliding window of size n - k to find the minimum sum of any consecutive n - k cards. For each window +position, calculate total - window_sum to get the corresponding score, and track the maximum. + +![Solution 3](./images/solutions/max_points_from_cards_solution_3.png) +![Solution 4](./images/solutions/max_points_from_cards_solution_4.png) +![Solution 5](./images/solutions/max_points_from_cards_solution_5.png) +![Solution 6](./images/solutions/max_points_from_cards_solution_6.png) +![Solution 7](./images/solutions/max_points_from_cards_solution_7.png) +![Solution 8](./images/solutions/max_points_from_cards_solution_8.png) +![Solution 9](./images/solutions/max_points_from_cards_solution_9.png) +![Solution 10](./images/solutions/max_points_from_cards_solution_10.png) +![Solution 11](./images/solutions/max_points_from_cards_solution_11.png) +![Solution 12](./images/solutions/max_points_from_cards_solution_12.png) +![Solution 13](./images/solutions/max_points_from_cards_solution_13.png) +![Solution 14](./images/solutions/max_points_from_cards_solution_14.png) +![Solution 15](./images/solutions/max_points_from_cards_solution_15.png) +![Solution 16](./images/solutions/max_points_from_cards_solution_16.png) +![Solution 17](./images/solutions/max_points_from_cards_solution_17.png) +![Solution 18](./images/solutions/max_points_from_cards_solution_18.png) +![Solution 19](./images/solutions/max_points_from_cards_solution_19.png) +![Solution 20](./images/solutions/max_points_from_cards_solution_20.png) +![Solution 21](./images/solutions/max_points_from_cards_solution_21.png) diff --git a/algorithms/sliding_window/max_points_from_cards/__init__.py b/algorithms/sliding_window/max_points_from_cards/__init__.py new file mode 100644 index 00000000..f84a92f5 --- /dev/null +++ b/algorithms/sliding_window/max_points_from_cards/__init__.py @@ -0,0 +1,32 @@ +from typing import List + + +def max_score(card_points: List[int], k: int) -> int: + number_of_cards = len(card_points) + total_points = sum(card_points) + + # If we have to pick all the cards or more cards that we have been provided with, then the max score is the total + # of all the cards + if k >= number_of_cards: + return total_points + + # Maintain a state or the window sum to calculate the sum of the cards in the window + window_sum = 0 + # Max points is the score we get from the cards we pick, this is what we eventually return + max_points = 0 + # This will keep track of the index of the card to remove from the current window as we traverse the cards + start = 0 + + for end in range(number_of_cards): + # Add the card at the end of the window to the window sum + window_sum += card_points[end] + + # Calculates if we have a valid window to calculate the max points + if end - start + 1 == number_of_cards - k: + max_points = max(max_points, total_points - window_sum) + # Contract the window, by removing the card at the start of the window + window_sum -= card_points[start] + # Move to the next index + start += 1 + + return max_points diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_1.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_1.png new file mode 100644 index 00000000..e885db2a Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_1.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_10.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_10.png new file mode 100644 index 00000000..fed3491f Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_10.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_11.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_11.png new file mode 100644 index 00000000..9310f5a8 Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_11.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_12.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_12.png new file mode 100644 index 00000000..24044dd4 Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_12.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_13.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_13.png new file mode 100644 index 00000000..edad6c86 Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_13.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_14.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_14.png new file mode 100644 index 00000000..72909a4a Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_14.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_15.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_15.png new file mode 100644 index 00000000..95c10cae Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_15.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_16.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_16.png new file mode 100644 index 00000000..4026f9ec Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_16.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_17.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_17.png new file mode 100644 index 00000000..ca71e838 Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_17.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_18.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_18.png new file mode 100644 index 00000000..f0249c25 Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_18.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_19.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_19.png new file mode 100644 index 00000000..a5c9d2ff Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_19.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_2.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_2.png new file mode 100644 index 00000000..cb1cdd5f Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_2.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_20.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_20.png new file mode 100644 index 00000000..b9d4e8a5 Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_20.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_21.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_21.png new file mode 100644 index 00000000..31390261 Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_21.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_3.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_3.png new file mode 100644 index 00000000..b996a19f Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_3.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_4.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_4.png new file mode 100644 index 00000000..8aa8f45b Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_4.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_5.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_5.png new file mode 100644 index 00000000..97fa21c7 Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_5.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_6.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_6.png new file mode 100644 index 00000000..31c8e199 Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_6.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_7.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_7.png new file mode 100644 index 00000000..556243b6 Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_7.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_8.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_8.png new file mode 100644 index 00000000..4eac33cf Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_8.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_9.png b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_9.png new file mode 100644 index 00000000..05c7db90 Binary files /dev/null and b/algorithms/sliding_window/max_points_from_cards/images/solutions/max_points_from_cards_solution_9.png differ diff --git a/algorithms/sliding_window/max_points_from_cards/test_max_points_from_cards.py b/algorithms/sliding_window/max_points_from_cards/test_max_points_from_cards.py new file mode 100644 index 00000000..978aef98 --- /dev/null +++ b/algorithms/sliding_window/max_points_from_cards/test_max_points_from_cards.py @@ -0,0 +1,28 @@ +import unittest +from typing import List +from parameterized import parameterized +from algorithms.sliding_window.max_points_from_cards import max_score + + +MAX_POINTS_FROM_PICKING_K_CARDS = [ + ([1, 2, 3, 4, 5, 6, 1], 3, 12), + ([2, 2, 2], 2, 4), + ([9, 7, 7, 9, 7, 7, 9], 7, 55), + ([2, 11, 4, 5, 3, 9, 2], 3, 17), + ([1, 100, 10, 0, 4, 5, 6], 3, 111), + ([1, 1000, 1], 1, 1), + ([1, 79, 80, 1, 1, 1, 200, 1], 3, 202), + ([100, 40, 17, 9, 73, 75], 3, 248), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5, 40), +] + + +class MaxPointsFromPickingKCardsTestCase(unittest.TestCase): + @parameterized.expand(MAX_POINTS_FROM_PICKING_K_CARDS) + def test_max_points_from_k_cards(self, cards: List[int], k: int, expected: int): + actual = max_score(cards, k) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/algorithms/sliding_window/max_sum_of_subarray/README.md b/algorithms/sliding_window/max_sum_of_subarray/README.md new file mode 100644 index 00000000..825153f1 --- /dev/null +++ b/algorithms/sliding_window/max_sum_of_subarray/README.md @@ -0,0 +1,38 @@ +# Maximum Sum of Subarray with Size K + +Given an array of integers nums and an integer k, find the maximum sum of any contiguous subarray of size k. + +## Examples + +Example 1 + +```text +Input: nums = [2, 1, 5, 1, 3, 2], k = 3 +Output: 9 +Explanation: The subarray with the maximum sum is [5, 1, 3] with a sum of 9. +``` + +## Solution + +We start by extending the window to size k. Whenever our window is of size k, we first compute the sum of the window and +update max_sum if it is larger than max_sum. Then, we contract the window by removing the leftmost element to prepare for +the next iteration. Note how we calculate the sum of the window incrementally by adding the new element and removing from +the previous sum. + +![Solution 1](./images/solutions/max_sum_of_subarray_size_k_solution_1.png) +![Solution 2](./images/solutions/max_sum_of_subarray_size_k_solution_2.png) +![Solution 3](./images/solutions/max_sum_of_subarray_size_k_solution_3.png) +![Solution 4](./images/solutions/max_sum_of_subarray_size_k_solution_4.png) +![Solution 5](./images/solutions/max_sum_of_subarray_size_k_solution_5.png) +![Solution 6](./images/solutions/max_sum_of_subarray_size_k_solution_6.png) +![Solution 7](./images/solutions/max_sum_of_subarray_size_k_solution_7.png) +![Solution 8](./images/solutions/max_sum_of_subarray_size_k_solution_8.png) +![Solution 9](./images/solutions/max_sum_of_subarray_size_k_solution_9.png) +![Solution 10](./images/solutions/max_sum_of_subarray_size_k_solution_10.png) +![Solution 11](./images/solutions/max_sum_of_subarray_size_k_solution_11.png) +![Solution 12](./images/solutions/max_sum_of_subarray_size_k_solution_12.png) +![Solution 13](./images/solutions/max_sum_of_subarray_size_k_solution_13.png) +![Solution 14](./images/solutions/max_sum_of_subarray_size_k_solution_14.png) +![Solution 15](./images/solutions/max_sum_of_subarray_size_k_solution_15.png) +![Solution 16](./images/solutions/max_sum_of_subarray_size_k_solution_16.png) +![Solution 17](./images/solutions/max_sum_of_subarray_size_k_solution_17.png) diff --git a/algorithms/sliding_window/max_sum_of_subarray/__init__.py b/algorithms/sliding_window/max_sum_of_subarray/__init__.py new file mode 100644 index 00000000..750f20ce --- /dev/null +++ b/algorithms/sliding_window/max_sum_of_subarray/__init__.py @@ -0,0 +1,64 @@ +from typing import List + + +def max_sum_subarray(nums: List[int], k: int) -> int: + n = len(nums) + + # If the length of the numbers is less than k, we return 0, as we can't get a contiguous subarray of size k from nums + if n < k: + return 0 + + start = 0 + state = 0 + max_sum = float("-inf") + + for end in range(n): + state += nums[end] + + if end - start + 1 == k: + max_sum = max(max_sum, state) + state -= nums[start] + start += 1 + + return max_sum + + +def max_sum_subarray_2(nums: List[int], k: int) -> int: + n = len(nums) + + # If the length of the numbers is less than k, we return 0, as we can't get a contiguous subarray of size k from nums + if n < k: + return 0 + + window_sum = sum(nums[:k]) + max_sum = window_sum + start = 0 + + for end in range(k, n): + window_sum += nums[end] - nums[start] + start += 1 + max_sum = max(max_sum, window_sum) + + return max_sum + + +def max_sum_subarray_3(nums: List[int], k: int) -> int: + n = len(nums) + + # If the length of the numbers is less than k, we return 0, as we can't get a contiguous subarray of size k from nums + if n < k: + return 0 + + # Use two points which will act as the boundary of the window. In this window, the sum of the elements will be + # checked to get the max sum of the sub array + lower_bound = 0 + upper_bound = k + max_sum = 0 + + while upper_bound <= n: + current_sum = sum(nums[lower_bound:upper_bound]) + max_sum = max(current_sum, max_sum) + lower_bound += 1 + upper_bound += 1 + + return max_sum diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_1.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_1.png new file mode 100644 index 00000000..c22942a2 Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_1.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_10.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_10.png new file mode 100644 index 00000000..e2475d47 Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_10.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_11.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_11.png new file mode 100644 index 00000000..793dc07b Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_11.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_12.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_12.png new file mode 100644 index 00000000..468b3b85 Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_12.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_13.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_13.png new file mode 100644 index 00000000..dd06ef5f Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_13.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_14.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_14.png new file mode 100644 index 00000000..20aaadb3 Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_14.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_15.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_15.png new file mode 100644 index 00000000..0cc9ef72 Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_15.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_16.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_16.png new file mode 100644 index 00000000..cdf315dc Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_16.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_17.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_17.png new file mode 100644 index 00000000..b3391954 Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_17.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_2.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_2.png new file mode 100644 index 00000000..6d5d7e85 Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_2.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_3.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_3.png new file mode 100644 index 00000000..914b3b42 Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_3.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_4.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_4.png new file mode 100644 index 00000000..6db8e393 Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_4.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_5.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_5.png new file mode 100644 index 00000000..72194e76 Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_5.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_6.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_6.png new file mode 100644 index 00000000..667c03d7 Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_6.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_7.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_7.png new file mode 100644 index 00000000..fc74ec91 Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_7.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_8.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_8.png new file mode 100644 index 00000000..7de2dd6d Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_8.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_9.png b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_9.png new file mode 100644 index 00000000..dcb677fa Binary files /dev/null and b/algorithms/sliding_window/max_sum_of_subarray/images/solutions/max_sum_of_subarray_size_k_solution_9.png differ diff --git a/algorithms/sliding_window/max_sum_of_subarray/test_max_sum_sub_array.py b/algorithms/sliding_window/max_sum_of_subarray/test_max_sum_sub_array.py new file mode 100644 index 00000000..ec2263c6 --- /dev/null +++ b/algorithms/sliding_window/max_sum_of_subarray/test_max_sum_sub_array.py @@ -0,0 +1,41 @@ +import unittest +from typing import List +from parameterized import parameterized +from algorithms.sliding_window.max_sum_of_subarray import ( + max_sum_subarray, + max_sum_subarray_2, + max_sum_subarray_3, +) + +MAX_SUM_SUBARRAY_OF_SIZE_K = [ + ([2, 1, 5, 1, 3, 2], 3, 9), + ([4, 2, -1, 9, 7, -3, 5], 4, 18), + ([4, 2, 4, 5, 6], 4, 17), + ([1, 2, 3, 4, 5], 2, 9), + ([1, 1, 1, 1, 1], 1, 1), + ([5, 5, 5, 5, 5], 3, 15), + ([1, 2, 3, 1, 2, 3], 3, 6), + ([1, 2, 1, 3, 1, 1, 1], 3, 6), + ([1, 2, 3, 4, 5, 6], 5, 20), +] + + +class MaxSumSubArrayOfSizeKTestCase(unittest.TestCase): + @parameterized.expand(MAX_SUM_SUBARRAY_OF_SIZE_K) + def test_max_sum_subarray_size_k(self, nums: List[int], k: int, expected: int): + actual = max_sum_subarray(nums, k) + self.assertEqual(expected, actual) + + @parameterized.expand(MAX_SUM_SUBARRAY_OF_SIZE_K) + def test_max_sum_subarray_size_k_2(self, nums: List[int], k: int, expected: int): + actual = max_sum_subarray_2(nums, k) + self.assertEqual(expected, actual) + + @parameterized.expand(MAX_SUM_SUBARRAY_OF_SIZE_K) + def test_max_sum_subarray_size_k_3(self, nums: List[int], k: int, expected: int): + actual = max_sum_subarray_3(nums, k) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/datastructures/trees/binary/test_utils.py b/datastructures/trees/binary/test_utils.py index fba86d26..0f470021 100644 --- a/datastructures/trees/binary/test_utils.py +++ b/datastructures/trees/binary/test_utils.py @@ -6,8 +6,13 @@ lowest_common_ancestor_ptr, connect_all_siblings, connect_all_siblings_ptr, + mirror_binary_tree, ) from datastructures.trees.binary.node import BinaryTreeNode +from datastructures.trees.binary.tree.tree_utils import ( + create_tree_from_nodes, + level_order_traversal, +) class LowestCommonAncestorTestCase(unittest.TestCase): @@ -222,5 +227,25 @@ def test_connect_all_siblings_ptr(self, root: BinaryTreeNode, expected: List[int self.assertIsNone(current) +MIRROR_BINARY_TREE_TEST_CASES = [ + ([100, 50, 200, 25, 75, 125, 350], [100, 200, 50, 350, 125, 75, 25]), + ([1, 2, None, 3, None, 4], [1, None, 2, None, 3, None, 4]), + ([25, 50, 75, 100, 125, 350], [25, 75, 50, None, 350, 125, 100]), + ([100], [100]), + ([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), + ([], []), + ([2, 1, 3], [2, 3, 1]), +] + + +class MirrorBinaryTreeTestCase(unittest.TestCase): + @parameterized.expand(MIRROR_BINARY_TREE_TEST_CASES) + def test_mirror_binary_tree(self, data: List[int], expected: List[int]): + root = create_tree_from_nodes(data) + actual = mirror_binary_tree(root) + actual_data = level_order_traversal(actual) + self.assertEqual(expected, actual_data) + + if __name__ == "__main__": unittest.main() diff --git a/datastructures/trees/binary/tree/README.md b/datastructures/trees/binary/tree/README.md index 4854ee18..85e637f0 100644 --- a/datastructures/trees/binary/tree/README.md +++ b/datastructures/trees/binary/tree/README.md @@ -391,3 +391,65 @@ Constraints: ![Example 1](./images/examples/minimum_camera_cover_binary_tree_example_1.png) ![Example 2](./images/examples/minimum_camera_cover_binary_tree_example_2.png) ![Example 3](./images/examples/minimum_camera_cover_binary_tree_example_3.png) + +--- + +## Invert Binary Tree + +Given the root node of a binary tree, transform the tree by swapping each node’s left and right subtrees, thus creating +a mirror image of the original tree. Return the root of the transformed tree. + +### Constraints + +- 0 <= `Number of nodes in the tree` <= 100 +- -1000 <= `node.value` <= 1000 + +### Examples + +![Example 1](./images/examples/mirror_binary_tree_example_1.png) +![Example 2](./images/examples/mirror_binary_tree_example_2.png) +![Example 3](./images/examples/mirror_binary_tree_example_3.png) + +### Solution + +For this solution, we do a post-order (left, right, parent) traversal of the binary tree. The algorithm is as follows: + +- Perform post-order traversal on the left child of the root node. +- Perform post-order traversal on the right child of the root node. +- Swap the left and right children of the root node. + +Since we perform a depth-first search traversal, the children of any node are already mirrored even before we return the +node itself. + +Let’s look at an example below: + +> Note: In this example, the node at the top of the stack is the current node. + +![Solution 1](./images/solutions/mirror_binary_tree_solution_1.png) +![Solution 2](./images/solutions/mirror_binary_tree_solution_2.png) +![Solution 3](./images/solutions/mirror_binary_tree_solution_3.png) +![Solution 4](./images/solutions/mirror_binary_tree_solution_4.png) +![Solution 5](./images/solutions/mirror_binary_tree_solution_5.png) +![Solution 6](./images/solutions/mirror_binary_tree_solution_6.png) +![Solution 7](./images/solutions/mirror_binary_tree_solution_7.png) +![Solution 8](./images/solutions/mirror_binary_tree_solution_8.png) +![Solution 9](./images/solutions/mirror_binary_tree_solution_9.png) +![Solution 10](./images/solutions/mirror_binary_tree_solution_10.png) +![Solution 11](./images/solutions/mirror_binary_tree_solution_11.png) +![Solution 12](./images/solutions/mirror_binary_tree_solution_12.png) +![Solution 13](./images/solutions/mirror_binary_tree_solution_13.png) +![Solution 14](./images/solutions/mirror_binary_tree_solution_14.png) +![Solution 15](./images/solutions/mirror_binary_tree_solution_15.png) +![Solution 16](./images/solutions/mirror_binary_tree_solution_16.png) + +#### Time Complexity + +The time complexity of this solution is linear O(n), where n is the number of nodes in the tree. + +> Note: Every subtree needs to be mirrored, so we visit every node once. Because of that, the runtime complexity is O(n). + +#### Space Complexity + +The space complexity of this solution is O(h), where h is the height of the tree. This is because our recursive +algorithm uses space on the call stack, which can grow to the height of the binary tree. The complexity will be O(log(n)) +for a balanced tree and O(n) for a degenerate tree. diff --git a/datastructures/trees/binary/tree/binary_tree.py b/datastructures/trees/binary/tree/binary_tree.py index 3502a2ca..b4e7562f 100644 --- a/datastructures/trees/binary/tree/binary_tree.py +++ b/datastructures/trees/binary/tree/binary_tree.py @@ -912,3 +912,24 @@ def dfs(node: BinaryTreeNode): camera_count[0] += 1 return camera_count[0] + + def invert_tree(self) -> Optional[BinaryTreeNode]: + """ + Inverts this binary tree by swapping each node's left and right subtrees thus creating a mirror image of the + original tree + """ + if not self.root: + return self.root + + def invert_tree_helper( + node: Optional[BinaryTreeNode], + ) -> Optional[BinaryTreeNode]: + if not node: + return node + + inverted_left = invert_tree_helper(node.left) + inverted_right = invert_tree_helper(node.right) + node.left, node.right = inverted_right, inverted_left + return node + + return invert_tree_helper(self.root) diff --git a/datastructures/trees/binary/tree/images/examples/mirror_binary_tree_example_1.png b/datastructures/trees/binary/tree/images/examples/mirror_binary_tree_example_1.png new file mode 100644 index 00000000..9f3047fd Binary files /dev/null and b/datastructures/trees/binary/tree/images/examples/mirror_binary_tree_example_1.png differ diff --git a/datastructures/trees/binary/tree/images/examples/mirror_binary_tree_example_2.png b/datastructures/trees/binary/tree/images/examples/mirror_binary_tree_example_2.png new file mode 100644 index 00000000..3cb47aac Binary files /dev/null and b/datastructures/trees/binary/tree/images/examples/mirror_binary_tree_example_2.png differ diff --git a/datastructures/trees/binary/tree/images/examples/mirror_binary_tree_example_3.png b/datastructures/trees/binary/tree/images/examples/mirror_binary_tree_example_3.png new file mode 100644 index 00000000..f6864202 Binary files /dev/null and b/datastructures/trees/binary/tree/images/examples/mirror_binary_tree_example_3.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_1.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_1.png new file mode 100644 index 00000000..da041be8 Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_1.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_10.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_10.png new file mode 100644 index 00000000..8dc51d58 Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_10.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_11.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_11.png new file mode 100644 index 00000000..2655a100 Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_11.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_12.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_12.png new file mode 100644 index 00000000..e49b27e3 Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_12.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_13.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_13.png new file mode 100644 index 00000000..21a8dbd6 Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_13.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_14.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_14.png new file mode 100644 index 00000000..81e8c4c3 Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_14.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_15.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_15.png new file mode 100644 index 00000000..1abe682a Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_15.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_16.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_16.png new file mode 100644 index 00000000..8816717e Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_16.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_2.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_2.png new file mode 100644 index 00000000..65adfb23 Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_2.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_3.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_3.png new file mode 100644 index 00000000..df9d31cf Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_3.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_4.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_4.png new file mode 100644 index 00000000..c6e54285 Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_4.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_5.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_5.png new file mode 100644 index 00000000..68928f75 Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_5.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_6.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_6.png new file mode 100644 index 00000000..d3aedd66 Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_6.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_7.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_7.png new file mode 100644 index 00000000..10783da0 Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_7.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_8.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_8.png new file mode 100644 index 00000000..ab2d46fd Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_8.png differ diff --git a/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_9.png b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_9.png new file mode 100644 index 00000000..58365c19 Binary files /dev/null and b/datastructures/trees/binary/tree/images/solutions/mirror_binary_tree_solution_9.png differ diff --git a/datastructures/trees/binary/tree/test_binary_tree.py b/datastructures/trees/binary/tree/test_binary_tree.py index f4550e19..eabb8c2e 100644 --- a/datastructures/trees/binary/tree/test_binary_tree.py +++ b/datastructures/trees/binary/tree/test_binary_tree.py @@ -1,6 +1,7 @@ import unittest -from . import BinaryTree, BinaryTreeNode +from datastructures.trees.binary.tree import BinaryTree +from datastructures.trees.binary.node import BinaryTreeNode class BinaryTreeCreateTreeTestCases(unittest.TestCase): diff --git a/datastructures/trees/binary/tree/test_binary_tree_deserialize.py b/datastructures/trees/binary/tree/test_binary_tree_deserialize.py index d96ed2d9..123c23b6 100644 --- a/datastructures/trees/binary/tree/test_binary_tree_deserialize.py +++ b/datastructures/trees/binary/tree/test_binary_tree_deserialize.py @@ -1,6 +1,7 @@ import unittest -from . import BinaryTree, BinaryTreeNode +from datastructures.trees.binary.tree import BinaryTree +from datastructures.trees.binary.node import BinaryTreeNode class BinaryTreeDeserializeTestCases(unittest.TestCase): diff --git a/datastructures/trees/binary/tree/test_binary_tree_invert_tree.py b/datastructures/trees/binary/tree/test_binary_tree_invert_tree.py new file mode 100644 index 00000000..793eab3b --- /dev/null +++ b/datastructures/trees/binary/tree/test_binary_tree_invert_tree.py @@ -0,0 +1,18 @@ +import unittest + +from datastructures.trees.binary.tree import BinaryTree +from datastructures.trees.binary.node import BinaryTreeNode + + +class BinaryTreeCreateTreeTestCases(unittest.TestCase): + def test_create_tree_1(self): + """should create a tree from [1, None, 1, 1, 1, None, None, 1, 1, None, 1, None, None, None, 1]""" + elements = [1, None, 1, 1, 1, None, None, 1, 1, None, 1, None, None, None, 1] + tree = BinaryTree.create_tree(elements) + + self.assertEqual(1, tree.root.data) + self.assertIsNone(tree.root.left) + + +if __name__ == "__main__": + unittest.main() diff --git a/datastructures/trees/binary/tree/test_binary_tree_serialize.py b/datastructures/trees/binary/tree/test_binary_tree_serialize.py index 88ee34ec..db6ce1fe 100644 --- a/datastructures/trees/binary/tree/test_binary_tree_serialize.py +++ b/datastructures/trees/binary/tree/test_binary_tree_serialize.py @@ -1,6 +1,7 @@ import unittest -from . import BinaryTree, BinaryTreeNode +from datastructures.trees.binary.tree import BinaryTree +from datastructures.trees.binary.node import BinaryTreeNode class BinaryTreeSerializeTestCases(unittest.TestCase): diff --git a/datastructures/trees/binary/tree/test_binary_tree_visible_nodes.py b/datastructures/trees/binary/tree/test_binary_tree_visible_nodes.py index 3275daaa..b06ecc74 100644 --- a/datastructures/trees/binary/tree/test_binary_tree_visible_nodes.py +++ b/datastructures/trees/binary/tree/test_binary_tree_visible_nodes.py @@ -1,6 +1,7 @@ import unittest -from . import BinaryTree, BinaryTreeNode +from datastructures.trees.binary.tree import BinaryTree +from datastructures.trees.binary.node import BinaryTreeNode class BinaryTreeVisibleNodesTestCases(unittest.TestCase): diff --git a/datastructures/trees/binary/tree/tree_utils.py b/datastructures/trees/binary/tree/tree_utils.py new file mode 100644 index 00000000..fa83a488 --- /dev/null +++ b/datastructures/trees/binary/tree/tree_utils.py @@ -0,0 +1,72 @@ +from typing import List, Optional, Any +from datastructures.trees.binary.node import BinaryTreeNode +from collections import deque +from itertools import chain + + +def create_tree_from_nodes(nodes: List[Any]) -> Optional[BinaryTreeNode]: + """ + Factory method to create a BinaryTreeNode given a list of values + Args: + nodes: List of values to be used to create the tree + Returns: + Optional[BinaryTreeNode]: The root of the created tree + """ + if not nodes or nodes[0] is None: + return None + + root = BinaryTreeNode(nodes[0]) + + queue = deque([root]) + + i = 1 + while i < len(nodes): + # Get the next node from the queue + curr = queue.popleft() + + # If the node is not None, create a new TreeNode object for its left child, + # set it as the left child of the current node, and add it to the queue + if nodes[i] is not None: + curr.left = BinaryTreeNode(nodes[i]) + queue.append(curr.left) + + i += 1 + # If there are more nodes in the list and the next node is not None, create a new BinaryTreeNode for its + # right child, set it as the right child of the current node, and add it to the queue + if i < len(nodes) and nodes[i] is not None: + curr.right = BinaryTreeNode(nodes[i]) + queue.append(curr.right) + + i += 1 + + # Return the root of the binary tree + return root + + +def level_order_traversal(root: Optional[BinaryTreeNode]) -> List[Any]: + if not root: + return [] + + current_level: List[Optional[BinaryTreeNode]] = [root] + levels: List[List[Any]] = [] + + while current_level: + level: List[Any] = [] + next_level: List[Optional[BinaryTreeNode]] = [] + + for node in current_level: + if not node: + level.append(None) + next_level.append(None) + else: + level.append(node.data) + + if node.left: + next_level.append(node.left) + if node.right: + next_level.append(node.right) + + levels.append(level) + current_level = next_level + + return list(chain.from_iterable(levels)) diff --git a/datastructures/trees/binary/utils.py b/datastructures/trees/binary/utils.py index 0f151cf0..53462ee4 100644 --- a/datastructures/trees/binary/utils.py +++ b/datastructures/trees/binary/utils.py @@ -162,3 +162,28 @@ def connect_all_siblings_ptr( # Return the root of the modified tree return root + + +def mirror_binary_tree(root: Optional[BinaryTreeNode]) -> Optional[BinaryTreeNode]: + """ + Inverts a binary tree such that the left subtree becomes the right and vice versa. + Args: + root(BinaryTreeNode): Root of the binary tree + Returns: + BinaryTreeNode: root of the inverted binary tree + """ + if not root: + return None + + # Perform a post order traversal of the tree, starting from the left subtree and then the right subtree and then + # the current node + if root.left: + mirror_binary_tree(root.left) + + if root.right: + mirror_binary_tree(root.right) + + # Swap left and right at the current level + root.left, root.right = root.right, root.left + + return root