diff --git a/src/my_project/interviews/amazon_high_frequency_23/common_algos/two_sum_round_2.py b/src/my_project/interviews/amazon_high_frequency_23/common_algos/two_sum_round_2.py new file mode 100644 index 00000000..e03fe46f --- /dev/null +++ b/src/my_project/interviews/amazon_high_frequency_23/common_algos/two_sum_round_2.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 [answer[v], k] + 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_2.py b/src/my_project/interviews/amazon_high_frequency_23/common_algos/valid_palindrome_round_2.py new file mode 100644 index 00000000..e9e656ac --- /dev/null +++ b/src/my_project/interviews/amazon_high_frequency_23/common_algos/valid_palindrome_round_2.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=r'[^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_5/jump_game_ii.py b/src/my_project/interviews/amazon_high_frequency_23/round_5/jump_game_ii.py new file mode 100644 index 00000000..ef0cd8b6 --- /dev/null +++ b/src/my_project/interviews/amazon_high_frequency_23/round_5/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_5/jump_game_vii.py b/src/my_project/interviews/amazon_high_frequency_23/round_5/jump_game_vii.py new file mode 100644 index 00000000..483e38c5 --- /dev/null +++ b/src/my_project/interviews/amazon_high_frequency_23/round_5/jump_game_vii.py @@ -0,0 +1,61 @@ +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(smallStack) + print(largeStack) + + + 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_5/k_free_subsets.py b/src/my_project/interviews/amazon_high_frequency_23/round_5/k_free_subsets.py new file mode 100644 index 00000000..87afd6dc --- /dev/null +++ b/src/my_project/interviews/amazon_high_frequency_23/round_5/k_free_subsets.py @@ -0,0 +1,111 @@ +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