-
Notifications
You must be signed in to change notification settings - Fork 2
feat(algorithms): sliding window #155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3cab43b
4f77531
eff6568
23a91a1
dbcbcb5
fae454f
b88d25b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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: | ||
|
|
||
|  | ||
|
|
||
| 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. | ||
|
|
||
|  | ||
|
|
||
| ### 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. | ||
|
|
||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. | ||
|
|
||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
|
Comment on lines
+230
to
+247
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find and inspect level_order_traversal implementation
find . -name "tree_utils.py" -type f | head -5Repository: BrianLusina/PythonSnips Length of output: 115 🏁 Script executed: #!/bin/bash
# Search for level_order_traversal implementation
rg -nA 30 'def level_order_traversal' --type pyRepository: BrianLusina/PythonSnips Length of output: 13160 🏁 Script executed: #!/bin/bash
# Search for mirror_binary_tree implementation
rg -nA 30 'def mirror_binary_tree' --type pyRepository: BrianLusina/PythonSnips Length of output: 1738 🏁 Script executed: #!/bin/bash
# Search for create_tree_from_nodes implementation
rg -nA 50 'def create_tree_from_nodes' --type pyRepository: BrianLusina/PythonSnips Length of output: 4198 🏁 Script executed: #!/bin/bash
# Look at the test file to understand imports and context
head -50 datastructures/trees/binary/test_utils.pyRepository: BrianLusina/PythonSnips Length of output: 1751 🏁 Script executed: #!/bin/bash
# Check if there are other usages of level_order_traversal in tests
rg -n 'level_order_traversal' datastructures/trees/binary/test_utils.pyRepository: BrianLusina/PythonSnips Length of output: 152 🏁 Script executed: #!/bin/bash
# Trace through test case [25, 50, 75, 100, 125, 350] with a simple Python script
python3 << 'EOF'
# Simulating tree construction from [25, 50, 75, 100, 125, 350]
# Using level-order construction like create_tree_from_nodes
nodes = [25, 50, 75, 100, 125, 350]
print("Tree construction from nodes:", nodes)
print("Index mapping (level-order):")
print(" 0:25")
print(" / \\")
print("1:50 2:75")
print("/ \\ / \\")
print("3:100 4:125 5:350")
print()
# After mirroring (swap left/right at each node)
print("After mirroring (swapping left/right):")
print(" 0:25")
print(" / \\")
print("2:75 1:50")
print("/ \\ / \\")
print("5:350 4:125 3:100")
print()
print("Expected from test: [25, 75, 50, None, 350, 125, 100]")
print("Actual from level_order_traversal should be: [25, 75, 50, 350, 125, 100]")
print("(no None values)")
EOFRepository: BrianLusina/PythonSnips Length of output: 153 Test expectations contain incorrect The Additionally, Affected test cases will fail:
Update expected values to remove 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix MD007 list indentation for the new bullets.
markdownlint reports list indentation issues at these lines; please align indentation with the surrounding list nesting to satisfy MD007.
Also applies to: 489-493
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
234-234: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
235-235: Unordered list indentation
Expected: 4; Actual: 6
(MD007, ul-indent)
236-236: Unordered list indentation
Expected: 2; Actual: 4
(MD007, ul-indent)
237-237: Unordered list indentation
Expected: 4; Actual: 6
(MD007, ul-indent)
🤖 Prompt for AI Agents