From d957bddd90f45c3e9dd340b52c65868925e6fc10 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 11 Nov 2025 04:27:39 -0600 Subject: [PATCH] adding algo --- .../common_algos/two_num_round_3.py | 16 +++ .../common_algos/valid_palindrome_round_3.py | 23 ++++ .../round_2/jump_game_ii.py | 77 ++++--------- .../round_2/jump_game_vii.py | 60 ++++++++++ .../round_2/k_free_subsets_round_1.py | 46 -------- .../round_3/jump_game_ii.py | 33 ++++++ .../round_3/jump_game_vii.py | 60 ++++++++++ .../round_3/k_free_subsets.py | 108 ++++++++++++++++++ .../contains_duplicate.py | 22 ++++ .../test_contains_duplicate_round_21.py | 1 + 10 files changed, 348 insertions(+), 98 deletions(-) create mode 100644 src/my_project/interviews/amazon_high_frequency_23/common_algos/two_num_round_3.py create mode 100644 src/my_project/interviews/amazon_high_frequency_23/common_algos/valid_palindrome_round_3.py create mode 100644 src/my_project/interviews/amazon_high_frequency_23/round_2/jump_game_vii.py delete mode 100644 src/my_project/interviews/amazon_high_frequency_23/round_2/k_free_subsets_round_1.py create mode 100644 src/my_project/interviews/amazon_high_frequency_23/round_3/jump_game_ii.py create mode 100644 src/my_project/interviews/amazon_high_frequency_23/round_3/jump_game_vii.py create mode 100644 src/my_project/interviews/amazon_high_frequency_23/round_3/k_free_subsets.py create mode 100644 src/my_project/interviews/top_150_questions_round_21/contains_duplicate.py create mode 100644 tests/test_150_questions_round_21/test_contains_duplicate_round_21.py diff --git a/src/my_project/interviews/amazon_high_frequency_23/common_algos/two_num_round_3.py b/src/my_project/interviews/amazon_high_frequency_23/common_algos/two_num_round_3.py new file mode 100644 index 00000000..5f1b2b04 --- /dev/null +++ b/src/my_project/interviews/amazon_high_frequency_23/common_algos/two_num_round_3.py @@ -0,0 +1,16 @@ +from typing import List, Union, Collection, Mapping, Optional +from abc import ABC, abstractmethod + +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + + answer = dict() + + for k, v in enumerate(nums): + + if v in answer: + return [k, answer[v]] + else: + answer[target - v] = k + + return [] \ No newline at end of file diff --git a/src/my_project/interviews/amazon_high_frequency_23/common_algos/valid_palindrome_round_3.py b/src/my_project/interviews/amazon_high_frequency_23/common_algos/valid_palindrome_round_3.py new file mode 100644 index 00000000..51abc51d --- /dev/null +++ b/src/my_project/interviews/amazon_high_frequency_23/common_algos/valid_palindrome_round_3.py @@ -0,0 +1,23 @@ +from typing import List, Union, Collection, Mapping, Optional +from abc import ABC, abstractmethod +import re + +class Solution: + def isPalindrome(self, s: str) -> bool: + + # To lowercase + s = s.lower() + + # Remove non-alphanumeric characters + s = re.sub(pattern='[^a-zA-Z0-9]', repl='', string=s) + + # Determine if s is palindrome or not + + len_s = len(s) + + for i in range(len_s//2): + + if s[i] != s[len_s - 1 - i]: + return False + + return True \ No newline at end of file diff --git a/src/my_project/interviews/amazon_high_frequency_23/round_2/jump_game_ii.py b/src/my_project/interviews/amazon_high_frequency_23/round_2/jump_game_ii.py index 72fdeb7d..1ef43897 100644 --- a/src/my_project/interviews/amazon_high_frequency_23/round_2/jump_game_ii.py +++ b/src/my_project/interviews/amazon_high_frequency_23/round_2/jump_game_ii.py @@ -1,60 +1,33 @@ -from typing import List, Union, Collection, Mapping, Optional - +from typing import List class Solution: - def minCost(self, nums: List[int], costs: List[int]) -> int: + def jump(self, nums: List[int]) -> int: """ - Example: nums = [3, 1, 2, 4], costs = [1, 2, 3, 4] - - From index 0 (value=3): - - Next smaller: index 1 (value=1) - - Next larger: index 3 (value=4) + Greedy approach: At each position, jump to the farthest reachable index - DP builds cost backwards: - - dp[3] = 0 (at end, no cost) - - dp[2] = dp[3] + costs[3] = 0 + 4 = 4 (can jump to 4) - - dp[1] = dp[2] + costs[2] = 4 + 3 = 7 (can jump to 2) - - dp[0] = min(dp[1]+costs[1], dp[3]+costs[3]) = min(7+2, 0+4) = 4 + Example: [2,3,1,1,4] + - From index 0 (value=2): can reach indices 1,2 + - Greedy choice: Jump to index 1 (value=3) because it reaches farthest + - From index 1: can reach indices 2,3,4 (end) + - Answer: 2 jumps """ + + if len(nums) <= 1: + return 0 - smallStack = [] # Monotonic increasing (next smaller element) - largeStack = [] # Monotonic decreasing (next larger element) - dp = [0] * len(nums) # dp[i] = min cost from i to end - - # Process backwards (right to left) - for i in range(len(nums) - 1, -1, -1): - - # Maintain monotonic increasing stack (for next smaller) - # Remove elements >= current (they can't be "next smaller") - while smallStack and nums[smallStack[-1]] >= nums[i]: - smallStack.pop() - - # Maintain monotonic decreasing stack (for next larger) - # Remove elements < current (they can't be "next larger") - while largeStack and nums[largeStack[-1]] < nums[i]: - largeStack.pop() - - # Calculate minimum cost for this position - nxtCost = [] - - if largeStack: - lid = largeStack[-1] # Next larger index - nxtCost.append(dp[lid] + costs[lid]) - - if smallStack: - sid = smallStack[-1] # Next smaller index - nxtCost.append(dp[sid] + costs[sid]) - - # Min cost from current position to end - dp[i] = min(nxtCost) if nxtCost else 0 - - # Add current index to both stacks - largeStack.append(i) - smallStack.append(i) + jumps = 0 + current_end = 0 # End of current jump range + farthest = 0 # The farthest position we can reach - print(largeStack) - print(smallStack) + for i in range(len(nums) - 1): + # Update farthest position reachable + farthest = max(farthest, i + nums[i]) - print(dp) - - return dp[0] # Minimum cost from start \ No newline at end of file + # If we've reached the end of current jump range + if i == current_end: + jumps += 1 + current_end = farthest # Make the greedy choice + + if current_end > len(nums) - 1: + break + return jumps \ No newline at end of file diff --git a/src/my_project/interviews/amazon_high_frequency_23/round_2/jump_game_vii.py b/src/my_project/interviews/amazon_high_frequency_23/round_2/jump_game_vii.py new file mode 100644 index 00000000..72fdeb7d --- /dev/null +++ b/src/my_project/interviews/amazon_high_frequency_23/round_2/jump_game_vii.py @@ -0,0 +1,60 @@ +from typing import List, Union, Collection, Mapping, Optional + + +class Solution: + def minCost(self, nums: List[int], costs: List[int]) -> int: + """ + Example: nums = [3, 1, 2, 4], costs = [1, 2, 3, 4] + + From index 0 (value=3): + - Next smaller: index 1 (value=1) + - Next larger: index 3 (value=4) + + DP builds cost backwards: + - dp[3] = 0 (at end, no cost) + - dp[2] = dp[3] + costs[3] = 0 + 4 = 4 (can jump to 4) + - dp[1] = dp[2] + costs[2] = 4 + 3 = 7 (can jump to 2) + - dp[0] = min(dp[1]+costs[1], dp[3]+costs[3]) = min(7+2, 0+4) = 4 + """ + + smallStack = [] # Monotonic increasing (next smaller element) + largeStack = [] # Monotonic decreasing (next larger element) + dp = [0] * len(nums) # dp[i] = min cost from i to end + + # Process backwards (right to left) + for i in range(len(nums) - 1, -1, -1): + + # Maintain monotonic increasing stack (for next smaller) + # Remove elements >= current (they can't be "next smaller") + while smallStack and nums[smallStack[-1]] >= nums[i]: + smallStack.pop() + + # Maintain monotonic decreasing stack (for next larger) + # Remove elements < current (they can't be "next larger") + while largeStack and nums[largeStack[-1]] < nums[i]: + largeStack.pop() + + # Calculate minimum cost for this position + nxtCost = [] + + if largeStack: + lid = largeStack[-1] # Next larger index + nxtCost.append(dp[lid] + costs[lid]) + + if smallStack: + sid = smallStack[-1] # Next smaller index + nxtCost.append(dp[sid] + costs[sid]) + + # Min cost from current position to end + dp[i] = min(nxtCost) if nxtCost else 0 + + # Add current index to both stacks + largeStack.append(i) + smallStack.append(i) + + print(largeStack) + print(smallStack) + + print(dp) + + return dp[0] # Minimum cost from start \ No newline at end of file diff --git a/src/my_project/interviews/amazon_high_frequency_23/round_2/k_free_subsets_round_1.py b/src/my_project/interviews/amazon_high_frequency_23/round_2/k_free_subsets_round_1.py deleted file mode 100644 index 930bee81..00000000 --- a/src/my_project/interviews/amazon_high_frequency_23/round_2/k_free_subsets_round_1.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import List, Union, Collection, Mapping, Optional -from collections import defaultdict - -class Solution: - def countTheNumOfKFreeSubsets(self, nums: List[int], k: int) -> int: - - groups = defaultdict(list) - - for num in nums: - groups[num % k].append(num) - - res = 1 - - for group in groups.values(): - group.sort() - - i = 0 - while i < len(group): - chain = [group[i]] - - j = i + 1 - - while j < len(group) and group[j] == chain[-1] + k: - chain.append(group[j]) - j += 1 - - m = len(chain) - if m == 1: - chain_res = 2 # {} and {chain[0]} - else: - take = 1 - skip = 1 - - for i in range(1,m): - new_take = skip - new_skip = take + skip - take, skip = new_take, new_skip - - chain_res = take + skip - - - res *= chain_res - - i = j - - return res diff --git a/src/my_project/interviews/amazon_high_frequency_23/round_3/jump_game_ii.py b/src/my_project/interviews/amazon_high_frequency_23/round_3/jump_game_ii.py new file mode 100644 index 00000000..ef0cd8b6 --- /dev/null +++ b/src/my_project/interviews/amazon_high_frequency_23/round_3/jump_game_ii.py @@ -0,0 +1,33 @@ +from typing import List + +class Solution: + def jump(self, nums: List[int]) -> int: + """ + Greedy approach: At each position, jump to the farthest reachable index + + Example: [2,3,1,1,4] + - From index 0 (value=2): can reach indices 1,2 + - Greedy choice: Jump to index 1 (value=3) because it reaches farthest + - From index 1: can reach indices 2,3,4 (end) + - Answer: 2 jumps + """ + + if len(nums) <= 1: + return 0 + + jumps = 0 + current_end = 0 # End of current jump range + farthest = 0 # The farthest position we can reach + + for i in range(len(nums) - 1): + # Update farthest position reachable + farthest = max(farthest, i + nums[i]) + + # If we've reached the end of current jump range + if i == current_end: + jumps += 1 + current_end = farthest # Make the greedy choice + + if current_end > len(nums) - 1: + break + return jumps diff --git a/src/my_project/interviews/amazon_high_frequency_23/round_3/jump_game_vii.py b/src/my_project/interviews/amazon_high_frequency_23/round_3/jump_game_vii.py new file mode 100644 index 00000000..72fdeb7d --- /dev/null +++ b/src/my_project/interviews/amazon_high_frequency_23/round_3/jump_game_vii.py @@ -0,0 +1,60 @@ +from typing import List, Union, Collection, Mapping, Optional + + +class Solution: + def minCost(self, nums: List[int], costs: List[int]) -> int: + """ + Example: nums = [3, 1, 2, 4], costs = [1, 2, 3, 4] + + From index 0 (value=3): + - Next smaller: index 1 (value=1) + - Next larger: index 3 (value=4) + + DP builds cost backwards: + - dp[3] = 0 (at end, no cost) + - dp[2] = dp[3] + costs[3] = 0 + 4 = 4 (can jump to 4) + - dp[1] = dp[2] + costs[2] = 4 + 3 = 7 (can jump to 2) + - dp[0] = min(dp[1]+costs[1], dp[3]+costs[3]) = min(7+2, 0+4) = 4 + """ + + smallStack = [] # Monotonic increasing (next smaller element) + largeStack = [] # Monotonic decreasing (next larger element) + dp = [0] * len(nums) # dp[i] = min cost from i to end + + # Process backwards (right to left) + for i in range(len(nums) - 1, -1, -1): + + # Maintain monotonic increasing stack (for next smaller) + # Remove elements >= current (they can't be "next smaller") + while smallStack and nums[smallStack[-1]] >= nums[i]: + smallStack.pop() + + # Maintain monotonic decreasing stack (for next larger) + # Remove elements < current (they can't be "next larger") + while largeStack and nums[largeStack[-1]] < nums[i]: + largeStack.pop() + + # Calculate minimum cost for this position + nxtCost = [] + + if largeStack: + lid = largeStack[-1] # Next larger index + nxtCost.append(dp[lid] + costs[lid]) + + if smallStack: + sid = smallStack[-1] # Next smaller index + nxtCost.append(dp[sid] + costs[sid]) + + # Min cost from current position to end + dp[i] = min(nxtCost) if nxtCost else 0 + + # Add current index to both stacks + largeStack.append(i) + smallStack.append(i) + + print(largeStack) + print(smallStack) + + print(dp) + + return dp[0] # Minimum cost from start \ No newline at end of file diff --git a/src/my_project/interviews/amazon_high_frequency_23/round_3/k_free_subsets.py b/src/my_project/interviews/amazon_high_frequency_23/round_3/k_free_subsets.py new file mode 100644 index 00000000..6188ef27 --- /dev/null +++ b/src/my_project/interviews/amazon_high_frequency_23/round_3/k_free_subsets.py @@ -0,0 +1,108 @@ +from typing import List, Union, Collection, Mapping, Optional +from collections import defaultdict + +class Solution: + def countTheNumOfKFreeSubsets(self, nums: List[int], k: int) -> int: + """ + Count k-Free subsets using dynamic programming. + + Approach: + 1. Group elements by (num % k) to find independent groups + 2. Within each group, sort and build chains where elements differ by k + 3. For each chain, use House Robber DP to count valid subsets + 4. Multiply results across all independent chains + + Time: O(n log n), Space: O(n) + """ + # Group numbers by their remainder when divided by k + groups = defaultdict(list) + for num in nums: + groups[num % k].append(num) + + res = 1 + + # Process each group independently + for group in groups.values(): + group.sort() + + # Build chains within this group + i = 0 + while i < len(group): + chain = [group[i]] + j = i + 1 + + # Build chain where each element is exactly k more than previous + while j < len(group) and group[j] == chain[-1] + k: + chain.append(group[j]) + j += 1 + + # House Robber DP for this chain + m = len(chain) + if m == 1: + chain_res = 2 # {} or {chain[0]} + else: + take = 1 # Take first element + skip = 1 # Skip first element + + for idx in range(1, m): + new_take = skip # Can only take current if we skipped previous + new_skip = take + skip # Can skip current regardless + take, skip = new_take, new_skip + + chain_res = take + skip + + res *= chain_res + i = j + + return res + + +''' +Detailed Algorithm Explanation +Part 1: Why Group by num % k? +Two numbers can have a difference of exactly k only if they have the same remainder when divided by k. + +Mathematical proof: + +If a - b = k, then a = b + k +Therefore: a % k = (b + k) % k = b % k +Example: nums = [2, 3, 5, 8], k = 5 + +num | num % 5 | group +----|---------|------- +2 | 2 | Group A +3 | 3 | Group B +5 | 0 | Group C +8 | 3 | Group B + + +Why this matters: Elements from different groups can never differ by k, so they're independent. We can combine any subset from Group A with any subset from Group B. + +Part 2: Building Chains +Within each group, we sort and find chains where consecutive elements differ by exactly k. + +Example with Group B: [3, 8] + +Sorted: [3, 8] +Check: 8 - 3 = 5 ✓ +Chain: 3 → 8 + +Another example: nums = [1, 6, 11, 21], k = 5 (all have remainder 1) + +Sorted: [1, 6, 11, 21] +Check: 6-1=5 ✓, 11-6=5 ✓, 21-11=10 ✗ +Chains: [1 → 6 → 11], [21] + + +Part 3: House Robber DP - The Core Logic +For a chain like [3 → 8], we can't pick both 3 and 8 (they differ by k). This is the House Robber problem: count all subsets where we don't pick adjacent elements. + +DP State Variables +take = number of valid subsets that INCLUDE the current element +skip = number of valid subsets that EXCLUDE the current element + +DP Transitions +new_take = skip # To take current, we MUST have skipped previous +new_skip = take + skip # To skip current, we can take or skip previous + +''' \ No newline at end of file diff --git a/src/my_project/interviews/top_150_questions_round_21/contains_duplicate.py b/src/my_project/interviews/top_150_questions_round_21/contains_duplicate.py new file mode 100644 index 00000000..f7e53715 --- /dev/null +++ b/src/my_project/interviews/top_150_questions_round_21/contains_duplicate.py @@ -0,0 +1,22 @@ +from typing import List, Union, Collection, Mapping, Optional +from abc import ABC, abstractmethod +from collections import defaultdict + + +class Solution: + def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool: + + answer_dic = defaultdict(int) + len_nums = len(nums) + + for i in range(len_nums): + + answer_dic[nums[i]] += 1 + + if i > k: + answer_dic[nums[i - (k+1)]] -= 1 + + if answer_dic[nums[i]] > 1: + return True + + return False diff --git a/tests/test_150_questions_round_21/test_contains_duplicate_round_21.py b/tests/test_150_questions_round_21/test_contains_duplicate_round_21.py new file mode 100644 index 00000000..10e61596 --- /dev/null +++ b/tests/test_150_questions_round_21/test_contains_duplicate_round_21.py @@ -0,0 +1 @@ +import unittest