diff --git a/k_diff_pairs.py b/k_diff_pairs.py new file mode 100644 index 00000000..d832d13e --- /dev/null +++ b/k_diff_pairs.py @@ -0,0 +1,121 @@ +class Solution: + """ + ------------------------------------------------------------ + K-diff Pairs in an Array + ------------------------------------------------------------ + Given: + - nums[] : array of integers + - k : integer (non-negative difference value) + + Goal: + Count the number of UNIQUE k-diff pairs (nums[i], nums[j]) where: + - i ≠ j (different indices) + - |nums[i] - nums[j]| == k (absolute difference equals k) + - Pairs are unique by VALUES, not indices + - (1, 3) and (3, 1) are considered the SAME pair + + Key Insights: + 1. k = 0 Special Case: + - We need pairs where nums[i] == nums[j] with i ≠ j + - This means the value must appear AT LEAST TWICE + - Each duplicate value forms exactly ONE unique pair + - Example: [1,1,1] with k=0 → only 1 pair: (1,1) + + 2. k > 0 Case: + - For each unique value x, check if (x + k) exists + - No need to check (x - k) separately due to iteration + - Example: nums=[1,3], k=2 → when we check 1, we find 3 + - Duplicates don't matter: [1,1,3,3] still just 1 pair + + 3. Why Counter Works: + - Reduces problem to unique values + frequencies + - Eliminates index management complexity + - Natural separation of k=0 vs k>0 logic + + Examples Walkthrough: + + Example 1: nums = [3,1,4,1,5], k = 2 + - Counter: {3:1, 1:2, 4:1, 5:1} + - k > 0, so check each unique value: + - 3+2=5 ✓ exists → count=1 + - 1+2=3 ✓ exists → count=2 + - 4+2=6 ✗ + - 5+2=7 ✗ + - Result: 2 pairs (1,3) and (3,5) + + Example 2: nums = [1,3,1,5,4], k = 0 + - Counter: {1:2, 3:1, 5:1, 4:1} + - k = 0, so count values with freq ≥ 2: + - 1 appears 2 times ✓ → count=1 + - Result: 1 pair (1,1) + + Approaches: + 1. Brute Force (Check all pairs) – O(n²) + 2. Sort + Two Pointers – O(n log n) + 3. Hash Map + Set for uniqueness – O(n) + 4. Counter (Frequency Map) – O(n) ✔ Optimal & Cleanest + + ------------------------------------------------------------ + Algorithm (Counter Approach): + + Step 1: Count frequency of each unique value + Step 2: If k = 0: + Count how many values appear ≥ 2 times + If k > 0: + For each unique value, check if (value + k) exists + Step 3: Return count + + Why This is Better: + - No index management needed + - Clear separation of edge cases + - Only iterates unique values (not all elements) + - More intuitive and readable + + Time Complexity: O(n) + - Building Counter: O(n) + - Iterating unique values: O(u) where u = unique count ≤ n + - Counter membership check: O(1) average + - Total: O(n) + + Space Complexity: O(n) + - Counter storage: O(u) where u ≤ n unique values + - Worst case all elements unique: O(n) + ------------------------------------------------------------ + """ + + def findPairs(self, nums: List[int], k: int) -> int: + # Edge case: negative k is mathematically invalid + # (absolute value cannot be negative) + if k < 0: + return 0 + + # Count frequency of each number in the array + # This reduces the problem to unique values + their counts + counter = Counter(nums) + count = 0 + + if k == 0: + # Special case: k=0 means we need identical pairs + # A number can form a pair with itself only if it appears ≥ 2 times + # Each such number contributes exactly ONE unique pair + for num, freq in counter.items(): + if freq >= 2: + count += 1 + else: + # General case: k>0 means we need distinct values + # For each unique number x, check if (x + k) exists + # + # Why only check forward (x + k) and not backward (x - k)? + # - We iterate through ALL unique values + # - For pair (a, b) where b = a + k: + # → We find it when iterating at x=a (checking a+k) + # → No need to find it again at x=b (checking b-k) + # → Checking both directions would double count! + for num in counter: + if num + k in counter: + count += 1 + + return count + + # Time Complexity: O(n) - counting + iterating unique values + # Space Complexity: O(n) - counter storage \ No newline at end of file diff --git a/pascals_traingle.py b/pascals_traingle.py new file mode 100644 index 00000000..688fb85b --- /dev/null +++ b/pascals_traingle.py @@ -0,0 +1,61 @@ +class Solution: + """ + ------------------------------------------------------------ + Pascal's Triangle + ------------------------------------------------------------ + Given: + - numRows : integer representing number of rows to generate + + Goal: + Return the first numRows of Pascal's triangle as a 2D list. + + Pascal's Triangle Properties: + - First and last element of each row is 1 + - Each interior element is the sum of two elements above it + - Row i has (i+1) elements + + Example: + Row 0: 1 + Row 1: 1 1 + Row 2: 1 2 1 + Row 3: 1 3 3 1 + Row 4: 1 4 6 4 1 + + Approaches: + 1. Brute Force (Recalculate each element) – O(n^3) + 2. Dynamic Programming (Build row by row) – O(n^2) ✔ Optimal + - Space: O(n^2) for output, O(1) auxiliary space + 3. Combinatorial Formula (nCr) – O(n^2) with O(n) per element + + ------------------------------------------------------------ + Algorithm (Dynamic Programming): + 1. Initialize triangle with all 1s (correct for first/last elements) + 2. For each row i starting from row 2: + For each position j from 1 to i-1: + dp[i][j] = dp[i-1][j-1] + dp[i-1][j] + 3. Return the complete triangle + + Time Complexity: O(n^2) + - We have n rows + - Row i has i elements to potentially compute + - Total: 1 + 2 + 3 + ... + n = n(n+1)/2 = O(n^2) + + Space Complexity: O(n^2) + - Output requires O(n^2) space + - Auxiliary space: O(1) (only loop variables) + ------------------------------------------------------------ + """ + + def generate(self, numRows: int) -> List[List[int]]: + # Initialize triangle with all 1s + # Row i has (i+1) elements + dp = [[1 for _ in range(i + 1)] for i in range(numRows)] + + # Fill interior elements starting from row 2 (index 2) + for i in range(2, numRows): + # Only update interior elements (skip first and last) + for j in range(1, i): + # Each element is sum of two elements from row above + dp[i][j] = dp[i-1][j-1] + dp[i-1][j] + + return dp \ No newline at end of file