Neetcode 150 Flashcards

1
Q

136.Single Number
Given a non-empty array of integers nums, every element appears twice except for one. Find that single one.

You must implement a solution with a linear runtime complexity and use only constant extra space.

Example 1:

Input: nums = [2,2,1]

Output: 1

Example 2:

Input: nums = [4,1,2,1,2]

Output: 4

Example 3:

Input: nums = [1]

Output: 1

Constraints:

1 <= nums.length <= 3 * 104
-3 * 104 <= nums[i] <= 3 * 104
Each element in the array appears twice except for one element which appears only once

A

Pattern: Bit Manipulation

Brute Force:

Iterate through numbers

if num is not in set:

add it to set and add in a total sum

else:

remove that number from total

return total

Complexity:

Time: O(n)

Space: O(n) → uses a hash map

More optimal approach: Sort it and compare neighbors

Time: O(n log n) → due to sorting

Space: O(1) if sorting in place

Optimal Approach:

Loop through all elements and xor them one by one.

Whats left if the single integer

Time: O(n) ✅

Space: O(1) ✅

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Number of 1 Bits

Given a positive integer n, write a function that returns the number of set bits in its binary representation (also known as the Hamming weight).

Example 1:

Input: n = 11

Output: 3

Explanation:

The input binary string 1011 has a total of three set bits.

Example 2:

Input: n = 128

Output: 1

Explanation:

The input binary string 10000000 has a total of one set bit.

Example 3:

Input: n = 2147483645

Output: 30

Explanation:

The input binary string 1111111111111111111111111111101 has a total of thirty set bits.

Constraints:

1 <= n <= 231 - 1

Follow up: If this function is called many times, how would you optimize it?

A

Pattern: Bit Manipulation
Brute Force:
Intuition: You can code the method to get binary numbers through decimal numbers by the standard method of division and getting the modulus.

Psuedo Code:
First take a list and append to it after % 2, then floor divide the number.

In another loop, just count the number of ones.

⏱️ Complexity:

Time: O(log n) because we're dividing by 2 each time (same as checking each bit)

Space: O(1)

More optimized solution: Iterate through all 32 bits and count how many are set to 1.

Code:
def hammingWeight(n):
count = 0
while n:
count += n & 1 # check least significant bit
n&raquo_space;= 1 # right shift
return count

Time: O(32) = O(1) constant time, because integers are ≤ 32-bit

Space: O(1)

Optimized solution:

Intuition : n &= (n - 1) solution. Brian Kernighan’s Algorithm

Time: O(k), where k is the number of set bits → much faster when n is sparse

Space: O(1)

Psuedo Code:
ans = 0
while n:
n &= (n - 1)
ans += 1
return ans

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q
  1. Counting Bits

Given an integer n, return an array ans of length n + 1 such that for each i (0 <= i <= n), ans[i] is the number of 1’s in the binary representation of i.

Example 1:

Input: n = 2
Output: [0,1,1]
Explanation:
0 –> 0
1 –> 1
2 –> 10
Example 2:

Input: n = 5
Output: [0,1,1,2,1,2]
Explanation:
0 –> 0
1 –> 1
2 –> 10
3 –> 11
4 –> 100
5 –> 101

Constraints:

0 <= n <= 105

Follow up:

It is very easy to come up with a solution with a runtime of O(n log n). Can you do it in linear time O(n) and possibly in a single pass?
Can you do it without using any built-in function (i.e., like __builtin_popcount in C++)?

A

Pattern: Bit Manipulation
Brute force:
Intuition: You could loop through every single element and do another loop which will XOR all the numbers until they are zero. But this is O(n^2) time complexity.

Optimized solution: You can use DP for this. maintain a 1D dp.

Intuition: There is repetition in the bits in a periodic fashion. What you can do is build a dp of the size n+ 1 with all elements 0. Then maintain an offset variable, as you get 2,4,8,16, etc (powers of 2, so you can keep multiplying them by 2 to get the next one), increase the offset. And for each i, do dp[i] = 1 + dp[i - offset]

psuedo code:
dp = [0] * (n + 1)
offset = 1

loop through 1 , n + 1 (dp[0] is already 0)
if offset * 2 == i:
offset = i
dp [i] = 1 + dp[i - offset]
return dp

Time and Space Complexity:

Time: O(n) → one pass, constant time per i

Space: O(n) for result array
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q
  1. Reverse Bits

Reverse bits of a given 32 bits unsigned integer.

Note:

Note that in some languages, such as Java, there is no unsigned integer type. In this case, both input and output will be given as a signed integer type. They should not affect your implementation, as the integer’s internal binary representation is the same, whether it is signed or unsigned.
In Java, the compiler represents the signed integers using 2’s complement notation. Therefore, in Example 2 above, the input represents the signed integer -3 and the output represents the signed integer -1073741825.

Example 1:

Input: n = 00000010100101000001111010011100
Output: 964176192 (00111001011110000010100101000000)
Explanation: The input binary string 00000010100101000001111010011100 represents the unsigned integer 43261596, so return 964176192 which its binary representation is 00111001011110000010100101000000.
Example 2:

Input: n = 11111111111111111111111111111101
Output: 3221225471 (10111111111111111111111111111111)
Explanation: The input binary string 11111111111111111111111111111101 represents the unsigned integer 4294967293, so return 3221225471 which its binary representation is 10111111111111111111111111111111.

Constraints:

The input must be a binary string of length 32

Follow up: If this function is called many times, how would you optimize it?

A

Pattern: Bit Manipulation

Intuition: Here, you need to right shift every bit to the units place and take every bit by anding it with 1, then put it back to its original place and ‘OR’ it with a result.

Code:
res = 0
for i in range (0,32):
bits = (n&raquo_space; i) & 1
res = res | (bits &laquo_space;(31 - i))
return res

⏱️ Time and Space Complexity

Time: O(32) = O(1)

Space: O(1)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q
  1. Missing Number

Given an array nums containing n distinct numbers in the range [0, n], return the only number in the range that is missing from the array.

Example 1:

Input: nums = [3,0,1]

Output: 2

Explanation:

n = 3 since there are 3 numbers, so all numbers are in the range [0,3]. 2 is the missing number in the range since it does not appear in nums.

Example 2:

Input: nums = [0,1]

Output: 2

Explanation:

n = 2 since there are 2 numbers, so all numbers are in the range [0,2]. 2 is the missing number in the range since it does not appear in nums.

Example 3:

Input: nums = [9,6,4,2,3,5,7,0,1]

Output: 8

Explanation:

n = 9 since there are 9 numbers, so all numbers are in the range [0,9]. 8 is the missing number in the range since it does not appear in nums.

Constraints:

n == nums.length
1 <= n <= 104
0 <= nums[i] <= n
All the numbers of nums are unique.

Follow up: Could you implement a solution using only O(1) extra space complexity and O(n) runtime complexity?

A

Pattern: Bit Manipulation

Brute Force:

Idea:

Check every number from 0 to n and see which one is missing.
Complexity:

Time: O(n)

Space: O(n) → uses a set

❌ Violates space constraint in follow-up

Optimized intuition:
For a single pass, xor the number itself in the result along with the i index that you are using. that will ensure that the missing number will remain in res as all others will become zero.

psuedo code:
loop through i to nums.length
res ^= 1
res ^= nums[i]

Time & Space:

Time: O(n)

Space: O(1)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q
  1. Sum of Two Integers

Given two integers a and b, return the sum of the two integers without using the operators + and -.

Example 1:

Input: a = 1, b = 2
Output: 3
Example 2:

Input: a = 2, b = 3
Output: 5

Constraints:

-1000 <= a, b <= 1000

A

Pattern: Bit Manipulation
Intuition: You need to xor both the numbers, then deal with the carries. After xoring, they update b with (a&b) &laquo_space;1, and keep xoring until b becomes zero. This ensures that all carries are taken care of.

psuedo code:
while b is not 0:
temp = (a & b) &laquo_space;1;
a = a ^ b
b = temp

⏱️ Time and Space Complexity

Time: O(1) in practice (fixed 32-bit loop, at most 32 iterations)

Space: O(1)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

48 . Rotate Image

You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).

You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

Example 1:

Input: matrix = [[1,2,3],[4,5,6],[7,8,9]]
Output: [[7,4,1],[8,5,2],[9,6,3]]
Example 2:

Input: matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
Output: [[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]

Constraints:

n == matrix.length == matrix[i].length
1 <= n <= 20
-1000 <= matrix[i][j] <= 1000

A

Pattern: Math and Geometry

Intuition: Think of processing all of the outer layer at once. There are multiple sub boxes in there. then think about what box where go where and there would be an offset which will be used. After one go, update the variables to go in the inner layer.

Code:
left = 0
right = len(matrix[0]) - 1
top = 0
bottom = len(matrix) - 1
offset = 0

    while left < right:
        for offset in range (right - left):
            temp = matrix[top][left + offset]

            matrix[top][left + offset] = matrix[bottom - offset][left]

            matrix[bottom - offset][left] = matrix[bottom][right - offset]

            matrix[bottom][right - offset] = matrix[top + offset][right]

            matrix[top + offset][right] = temp

        left = left + 1
        right = right - 1
        top = top + 1
        bottom = bottom - 1
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q
  1. Spiral Matrix

Given an m x n matrix, return all elements of the matrix in spiral order.

Example 1:

Input: matrix = [[1,2,3],[4,5,6],[7,8,9]]
Output: [1,2,3,6,9,8,7,4,5]
Example 2:

Input: matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
Output: [1,2,3,4,8,12,11,10,9,5,6,7]

Constraints:

m == matrix.length
n == matrix[i].length
1 <= m, n <= 10
-100 <= matrix[i][j] <= 100

A

Pattern: Math and Geometry
Intuition: Maintain top,bottom,left,right. Go through a row/column then update that variable accordingly. dont forget the condition in the middle to break, which doesnt come intuitively.

Code:

class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
left,right = 0, len(matrix[0]) - 1
top, bottom = 0, len(matrix) - 1
res = []

    while left < right and top < bottom:

        for i in range (left,right):
            res.append(matrix[top][i])
            print(matrix[top][i])
        left = left + 1
        
        for i in range(top,bottom + 1):
            res.append(matrix[i][right])
            print(matrix[i][right])
        right = right - 1

        if left < right and top < bottom:
            break
        
        for i in range(right,left-2,-1):
            res.append(matrix[bottom][i])
            print(matrix[bottom][i])
        bottom = bottom - 1
        for i in range(bottom,top-1,-1):
            res.append(matrix[i][left-1])
            print(matrix[i][left-1])
        left = left + 1

    return res

Time: O(m × n)

Space: O(1) extra space (excluding output list)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q
  1. Set Matrix Zeroes

Given an m x n integer matrix matrix, if an element is 0, set its entire row and column to 0’s.

You must do it in place.

Example 1:

Input: matrix = [[1,1,1],[1,0,1],[1,1,1]]
Output: [[1,0,1],[0,0,0],[1,0,1]]
Example 2:

Input: matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
Output: [[0,0,0,0],[0,4,5,0],[0,3,1,0]]

Constraints:

m == matrix.length
n == matrix[0].length
1 <= m, n <= 200
-231 <= matrix[i][j] <= 231 - 1

Follow up:

A straightforward solution using O(mn) space is probably a bad idea.
A simple improvement uses O(m + n) space, but still not the best solution.
Could you devise a constant space solution?

A

Pattern: Math and Geometry

Brute force: Make a deep copy, find all zeros and set corresponding rows and columns in the copy.

Time & Space:

Time: O(m × n × (m + n)) worst-case

Space: O(m × n)

Better approach: Use two arrays, one for rows and one for columns, then apply zero in the second pass.

Time & Space:

Time: O(m × n)

Space: O(m + n)

Optimized approach:

Intuition: Use the first row and column to maintain if there are zeros in there. For the first row and column, it can cause issues, because a single cell exists for row 0 and column 0, so maintain two separate variables for that.

Code:
m = len(matrix)
n = len(matrix[0])

    first_row_has_zero = False
    first_col_has_zero = False
    
    # iterate through matrix to mark the zero row and cols
    for row in range(m):
        for col in range(n):
            if matrix[row][col] == 0:
                if row == 0:
                    first_row_has_zero = True
                if col == 0:
                    first_col_has_zero = True
                matrix[row][0] = matrix[0][col] = 0

    # iterate through matrix to update the cell to be zero if it's in a zero row or col
    for row in range(1, m):
        for col in range(1, n):
            matrix[row][col] = 0 if matrix[0][col] == 0 or matrix[row][0] == 0 else matrix[row][col]
    
    # update the first row and col if they're zero
    if first_row_has_zero:
        for col in range(n):
            matrix[0][col] = 0
    
    if first_col_has_zero:
        for row in range(m):
            matrix[row][0] = 0

You said:
268. Missing Number

Given an array nums containing n distinct numbers in the range [0, n], return the only number in the range that is missing from the array.

Example 1:

Input: nums = [3,0,1]

Output: 2

Explanation:

n = 3 since there are 3 numbers, so all numbers are in the range [0,3]. 2 is the missing number in the range since it does not appear in nums.

Example 2:

Input: nums = [0,1]

Output: 2

Explanation:

n = 2 since there are 2 numbers, so all numbers are in the range [0,2]. 2 is the missing number in the range since it does not appear in nums.

Example 3:

Input: nums = [9,6,4,2,3,5,7,0,1]

Output: 8

Explanation:

n = 9 since there are 9 numbers, so all numbers are in the range [0,9]. 8 is the missing number in the range since it does not appear in nums.

Constraints:

n == nums.length
1 <= n <= 104
0 <= nums[i] <= n
All the numbers of nums are unique.

Follow up: Could you implement a solution using only O(1) extra space complexity and O(n) runtime complexity?
ChatGPT said:

Let’s walk through the solution to Leetcode 268. Missing Number step-by-step, optimizing progressively:
🔹 Problem Summary

You’re given an array nums of length n containing distinct integers from 0 to n (inclusive range), but one number in this range is missing. Your task is to find the missing number.
✅ Constraints Recap

All nums[i] are unique

nums contains n numbers out of the range [0, n], so exactly one number is missing

Must find the missing number

✅ Step 1: Naive Approach (Brute Force)
Idea:

Use a hash set to store all elements and check from 0 to n which one is missing.
Code:

def missingNumber(nums):
num_set = set(nums)
for i in range(len(nums) + 1):
if i not in num_set:
return i

Time & Space:

Time: O(n)

Space: O(n) for the set

Why it’s naive:

Uses extra space proportional to input size (O(n)), which violates the follow-up constraint.
✅ Step 2: Intermediate Optimization (Sorting)
Idea:

Sort the array and find the first index i where nums[i] != i.
Code:

def missingNumber(nums):
nums.sort()
for i in range(len(nums)):
if nums[i] != i:
return i
return len(nums)

Time & Space:

Time: O(n log n) for sorting

Space: O(1) if sorting in-place

Why it’s better:

Reduces space, but time is now worse than optimal O(n) for this problem.
✅ Step 3: Optimal Solution (O(n) time, O(1) space)
✅ Method 1: Sum Formula Trick (Arithmetic Series)
Idea:

Sum of numbers from 0 to n is n*(n+1)//2

Subtract sum of all elements in nums from this expected sum

Code:

def missingNumber(nums):
n = len(nums)
expected_sum = n * (n + 1) // 2
actual_sum = sum(nums)
return expected_sum - actual_sum

Time & Space:

Time: O(n)

Space: O(1)

Why it works:

Uses mathematical property of sum to find the missing value directly.
✅ Method 2: Bit Manipulation (XOR Trick)
Idea:

XOR all numbers from 0 to n and all numbers in nums

Duplicate values cancel out, leaving the missing number

Code:

def missingNumber(nums):
n = len(nums)
xor = 0
for i in range(n + 1):
xor ^= i
for num in nums:
xor ^= num
return xor

Time & Space:

Time: O(n)

Space: O(1)

Why it’s clever:

No math overflows, no extra space, and constant time operations per element.
🔚 Final Recommendation

Use the XOR trick or the sum formula trick for optimal performance:

Sum trick is intuitive and fast

XOR trick is elegant and avoids integer overflow in languages with fixed-size integers

Would you like to see this extended to finding two missing numbers, or how this fits into similar “missing element” problems?
You said:
54. Spiral Matrix

Given an m x n matrix, return all elements of the matrix in spiral order.

Example 1:

Input: matrix = [[1,2,3],[4,5,6],[7,8,9]]
Output: [1,2,3,6,9,8,7,4,5]
Example 2:

Input: matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
Output: [1,2,3,4,8,12,11,10,9,5,6,7]

Constraints:

m == matrix.length
n == matrix[i].length
1 <= m, n <= 10
-100 <= matrix[i][j] <= 100
ChatGPT said:

Let’s solve Leetcode 54. Spiral Matrix in progressive steps from naive to optimal.
🔹 Problem Summary

You are given a 2D m x n matrix. Traverse it in spiral order (clockwise) and return the elements in that order as a list.
✅ Step 1: Naive Idea (Simulation with visited matrix)
Idea:

Simulate movement in 4 directions (right → down → left → up) and mark visited cells using a boolean matrix.
Code:

def spiralOrder(matrix):
m, n = len(matrix), len(matrix[0])
visited = [[False] * n for _ in range(m)]
result = []

# Directions: right, down, left, up
dirs = [(0,1), (1,0), (0,-1), (-1,0)]
dir_idx = 0
r = c = 0

for _ in range(m * n):
    result.append(matrix[r][c])
    visited[r][c] = True

    # Calculate next position
    nr, nc = r + dirs[dir_idx][0], c + dirs[dir_idx][1]
    if 0 <= nr < m and 0 <= nc < n and not visited[nr][nc]:
        r, c = nr, nc
    else:
        # Change direction
        dir_idx = (dir_idx + 1) % 4
        r += dirs[dir_idx][0]
        c += dirs[dir_idx][1]

return result

Time & Space:

Time: O(m × n)

Space: O(m × n) for the visited matrix

Why it’s naive:

Requires extra space to track visited cells, even though we can avoid it by adjusting bounds.
✅ Step 2: Optimal Solution (Layer-by-layer traversal)
Idea:

Keep four pointers: top, bottom, left, and right. Traverse each layer of the matrix in spiral order and then shrink the boundaries inward.
Code:

def spiralOrder(matrix):
result = []
if not matrix:
return result

top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1

while top <= bottom and left <= right:
    # Traverse from left to right
    for col in range(left, right + 1):
        result.append(matrix[top][col])
    top += 1

    # Traverse down
    for row in range(top, bottom + 1):
        result.append(matrix[row][right])
    right -= 1

    if top <= bottom:
        # Traverse from right to left
        for col in range(right, left - 1, -1):
            result.append(matrix[bottom][col])
        bottom -= 1

    if left <= right:
        # Traverse up
        for row in range(bottom, top - 1, -1):
            result.append(matrix[row][left])
        left += 1

return result

Time & Space:

Time: O(m × n)

Space: O(1) extra space (excluding output list)

✅ Summary of Optimization
Approach Time Space Notes
Simulation with visited O(m×n) O(m×n) Easy to understand, uses extra space
Boundary shrinking O(m×n) O(1) Optimal, clean and elegant

Would you like a visual walkthrough or see how to generalize this to spiral matrix II (filling a matrix)?
You said:
73. Set Matrix Zeroes

Given an m x n integer matrix matrix, if an element is 0, set its entire row and column to 0’s.

You must do it in place.

Example 1:

Input: matrix = [[1,1,1],[1,0,1],[1,1,1]]
Output: [[1,0,1],[0,0,0],[1,0,1]]
Example 2:

Input: matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
Output: [[0,0,0,0],[0,4,5,0],[0,3,1,0]]

Constraints:

m == matrix.length
n == matrix[0].length
1 <= m, n <= 200
-231 <= matrix[i][j] <= 231 - 1

Follow up:

A straightforward solution using O(mn) space is probably a bad idea.
A simple improvement uses O(m + n) space, but still not the best solution.
Could you devise a constant space solution?
ChatGPT said:

Let’s solve Leetcode 73. Set Matrix Zeroes in progressive levels of optimization—from intuitive to optimal with constant space.
🔹 Problem Summary

Given an m x n matrix, if an element is 0, set its entire row and column to 0 — and do this in-place.
✅ Step 1: Naive Approach — Use an Extra Matrix
Idea:

Make a deep copy of the matrix, find all zeroes, and set corresponding rows and columns in the copy.
Code:

import copy

def setZeroes(matrix):
m, n = len(matrix), len(matrix[0])
copy_matrix = copy.deepcopy(matrix)

for i in range(m):
    for j in range(n):
        if copy_matrix[i][j] == 0:
            for k in range(n): matrix[i][k] = 0
            for k in range(m): matrix[k][j] = 0

Time & Space:

Time: O(m × n × (m + n)) worst-case

Space: O(m × n)

Why it’s naive:

Uses full space for a copy, and time is inefficient.
✅ Step 2: Improved Approach — Use O(m + n) Space
Idea:

Use two marker arrays: one for rows, one for columns. Mark rows/columns to zero, then apply zeroing in a second pass.
Code:

def setZeroes(matrix):
m, n = len(matrix), len(matrix[0])
rows, cols = set(), set()

# First pass: record which rows and columns to zero
for i in range(m):
    for j in range(n):
        if matrix[i][j] == 0:
            rows.add(i)
            cols.add(j)

# Second pass: apply zeroing
for i in range(m):
    for j in range(n):
        if i in rows or j in cols:
            matrix[i][j] = 0

Time & Space:

Time: O(m × n)

Space: O(m + n)

Why it’s better:

Avoids full copy, but still uses extra space for bookkeeping.
✅ Step 3: Optimal Approach — Constant Space O(1)
Key Insight:

Use first row and first column of the matrix as markers!
Steps:

Use first row to mark columns, first column to mark rows that should be zeroed.

Store separately whether the first row or column itself should be zeroed (to avoid conflicts).

Traverse matrix again to apply zeroing based on markers.

Code:

def setZeroes(matrix):
m, n = len(matrix), len(matrix[0])
first_row_zero = any(matrix[0][j] == 0 for j in range(n))
first_col_zero = any(matrix[i][0] == 0 for i in range(m))

# Mark rows and columns
for i in range(1, m):
    for j in range(1, n):
        if matrix[i][j] == 0:
            matrix[i][0] = 0
            matrix[0][j] = 0

# Apply marks (excluding first row/col)
for i in range(1, m):
    for j in range(1, n):
        if matrix[i][0] == 0 or matrix[0][j] == 0:
            matrix[i][j] = 0

# Handle first row
if first_row_zero:
    for j in range(n):
        matrix[0][j] = 0

# Handle first column
if first_col_zero:
    for i in range(m):
        matrix[i][0] = 0

Time & Space:

Time: O(m × n)

Space: O(1) ✅ in-place with no extra space
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q
  1. Plus One

You are given a large integer represented as an integer array digits, where each digits[i] is the ith digit of the integer. The digits are ordered from most significant to least significant in left-to-right order. The large integer does not contain any leading 0’s.

Increment the large integer by one and return the resulting array of digits.

Example 1:

Input: digits = [1,2,3]
Output: [1,2,4]
Explanation: The array represents the integer 123.
Incrementing by one gives 123 + 1 = 124.
Thus, the result should be [1,2,4].
Example 2:

Input: digits = [4,3,2,1]
Output: [4,3,2,2]
Explanation: The array represents the integer 4321.
Incrementing by one gives 4321 + 1 = 4322.
Thus, the result should be [4,3,2,2].
Example 3:

Input: digits = [9]
Output: [1,0]
Explanation: The array represents the integer 9.
Incrementing by one gives 9 + 1 = 10.
Thus, the result should be [1,0].

Constraints:

1 <= digits.length <= 100
0 <= digits[i] <= 9
digits does not contain any leading 0’s.

A

Pattern: Math and Geometry

Intuition: Go through the elements in reverse.
First store it in temp and calculate what the carry might be. then check if temp is 9, what the digit is going to be updated to. Finally, if there is an extra carry, add it to the very first index.
Code:

    carry = 0
    for loop in reverse
        if i == len(digits) - 1:
            temp = digits[i] + 1
        else:
            temp = digits[i] + carry
            carry = 0
        
        if temp > 9:
            digits[i] = temp % 10
            carry = 1
        else:
            digits[i] = temp
    if carry>0:
        add carry to the first index
    
    return digits

Time & Space Complexity:

Time: O(n), worst-case when all digits are 9

Space: O(1) extra (ignoring output list)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q
  1. Insert Interval

You are given an array of non-overlapping intervals intervals where intervals[i] = [starti, endi] represent the start and the end of the ith interval and intervals is sorted in ascending order by starti. You are also given an interval newInterval = [start, end] that represents the start and end of another interval.

Insert newInterval into intervals such that intervals is still sorted in ascending order by starti and intervals still does not have any overlapping intervals (merge overlapping intervals if necessary).

Return intervals after the insertion.

Note that you don’t need to modify intervals in-place. You can make a new array and return it.

Example 1:

Input: intervals = [[1,3],[6,9]], newInterval = [2,5]
Output: [[1,5],[6,9]]
Example 2:

Input: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
Output: [[1,2],[3,10],[12,16]]
Explanation: Because the new interval [4,8] overlaps with [3,5],[6,7],[8,10].

Constraints:

0 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti <= endi <= 105
intervals is sorted by starti in ascending order.
newInterval.length == 2
0 <= start <= end <= 105

A

Pattern: Intervals

Intuition: Make a new result array. There are three conditions you should be thinking about: the new interval comes before the current interval, in which case you should append and return return res + intervals[i:]. Second case is that the new interval comes after the current one, then just append the current one with the result. and finally that none of these conditions are true, then take the minimum and maximum for start and end., but dont append. Outside the loop append and return result.

Code:

def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
res = []
for i in range (len(intervals)):
if newInterval[1] < intervals[i][0]:
res.append(newInterval)
return res + intervals[i:]
elif newInterval[0] > intervals[i][1]:
res.append(intervals[i])
else:
newInterval = [min(intervals[i][0],newInterval[0]),max(intervals[i][1],newInterval[1])]
res.append(newInterval)
return res

Time & Space Complexity

Time: O(n) — one pass through intervals

Space: O(n) — result list holds output
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q
  1. Merge Intervals

Given an array of intervals where intervals[i] = [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.

Example 1:

Input: intervals = [[1,3],[2,6],[8,10],[15,18]]
Output: [[1,6],[8,10],[15,18]]
Explanation: Since intervals [1,3] and [2,6] overlap, merge them into [1,6].
Example 2:

Input: intervals = [[1,4],[4,5]]
Output: [[1,5]]
Explanation: Intervals [1,4] and [4,5] are considered overlapping.

Constraints:

1 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti <= endi <= 104

A

Pattern: Intervals

Intuition: Sort the intervals first, since they are unsorted. Essentially you are going to look at the last element of the result and check if it the current interval intersects with it or not. If yes, then update the end and continue without adding. Else just add the current one as a separate one.

class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort (key = lambda i: i[0])

    end = intervals[0][1]
    res = [intervals[0]]

    for i in range (1,len(intervals)):
        end = res[-1][1]
        if intervals[i][0] <= end:
            res[-1][1] = max (end,intervals[i][1])
        else:
            res.append(intervals[i])

    return res

Time & Space Complexity

Time: O(n log n) — for sorting

Space: O(n) — for the merged result
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q
  1. Non-overlapping Intervals

Given an array of intervals intervals where intervals[i] = [starti, endi], return the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping.

Note that intervals which only touch at a point are non-overlapping. For example, [1, 2] and [2, 3] are non-overlapping.

Example 1:

Input: intervals = [[1,2],[2,3],[3,4],[1,3]]
Output: 1
Explanation: [1,3] can be removed and the rest of the intervals are non-overlapping.
Example 2:

Input: intervals = [[1,2],[1,2],[1,2]]
Output: 2
Explanation: You need to remove two [1,2] to make the rest of the intervals non-overlapping.
Example 3:

Input: intervals = [[1,2],[2,3]]
Output: 0
Explanation: You don’t need to remove any of the intervals since they’re already non-overlapping.

Constraints:

1 <= intervals.length <= 105
intervals[i].length == 2
-5 * 104 <= starti < endi <= 5 * 104

A

Pattern: Intervals

Intuition: First sort the intervals as they are unsorted. Maintain a prevend, start from the second element, and see if the current interval overlaps with the last one, if no then update the prevend pointer. Else increase the count, take the minumum of end or prevend. The idea here is to only drop the one that ends later, so taking the minimum is as good as dropping the one after.

Code:

intervals.sort(key= lambda i : i[0])
prevend = intervals[0][1]
count = 0
for start,end in intervals[1:]:
if start >= prevend:
prevend = end
else:
count += 1
prevend = min(prevend,end)
return count

⏱ Time & Space Complexity

Time: O(n log n) for sorting

Space: O(1) extra (in-place iteration)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Meeting Rooms

Given an array of meeting time intervals where intervals[i] = [starti, endi], determine if a person could attend all meetings.

Example 1:

Input: intervals = [[0,30],[5,10],[15,20]]
Output: false
Example 2:

Input: intervals = [[7,10],[2,4]]
Output: true

Constraints:

0 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti < endi <= 106

A

Patterns: Intervals

Intuition: Just check if the current start is less than the previous end. If yes, then return false. When out of the loop return True.

Code:

intervals.sort(key = lambda i: i[0])

    for i in range(1,len(intervals)):
        if intervals[i][0] < intervals[i-1][1]:
            return False
    return True

⏱ Time & Space Complexity

Time: O(n log n) — for sorting

Space: O(1) — in-place processing
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Meeting Rooms II

Given an array of meeting time intervals intervals where intervals[i] = [starti, endi], return the minimum number of conference rooms required.

Example 1:

Input: intervals = [[0,30],[5,10],[15,20]]
Output: 2
Example 2:

Input: intervals = [[7,10],[2,4]]
Output: 1

Constraints:

1 <= intervals.length <= 104
0 <= start < end <= 106

A

Pattern: Intervals

Intution: Maintain two arrays, start and end. You have to see how many starts are there before the first end. then as the start goes equal and above the ends, increase the ends too. This will ensure you will count all of the meetings manually. and maintain a maxcount variable.

Code:

starts = []
ends = []
count = 0
maxcount = 0
for interval in intervals:
starts.append(interval[0])
ends.append(interval[1])
starts.sort()
ends.sort()
print(starts,ends)
i,j = 0, 0
while j < len(ends) and i < len(starts):
if starts[i] < ends[j]:
count+=1
i += 1
else:
count -= 1
j += 1
maxcount = max(count,maxcount)
return maxcount

⏱ Time & Space Complexity

Time: O(n log n) for sorting

Space: O(n) for start and end arrays
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q
  1. Maximum Subarray

Given an integer array nums, find the subarray with the largest sum, and return its sum.

Example 1:

Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
Output: 6
Explanation: The subarray [4,-1,2,1] has the largest sum 6.
Example 2:

Input: nums = [1]
Output: 1
Explanation: The subarray [1] has the largest sum 1.
Example 3:

Input: nums = [5,4,-1,7,8]
Output: 23
Explanation: The subarray [5,4,-1,7,8] has the largest sum 23.

Constraints:

1 <= nums.length <= 105
-104 <= nums[i] <= 104

Follow up: If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.

A

Pattern: Greedy

Intuition: maintain a sum, and if it goes below zero then start afresh. also maintain a maxsum. then return it. Basic Greedy problem.

Note: The reason that we are comparing cursum before and then adding the num to cursum is because, if you added it before, and reset it to zero, the maxsum will compare to 0 instead of the negative number, and the maxarea will be 0. Also one more intuition is that you need to start the maxsum with nums[0] instead of zero.

Code:

cursum = 0
maxsum = nums[0]

    for num in nums:
        if cursum < 0:
            cursum = 0
        cursum += num
        maxsum = max(cursum,maxsum)
    return maxsum

Time: O(n)

Space: O(1)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q
  1. Jump Game

You are given an integer array nums. You are initially positioned at the array’s first index, and each element in the array represents your maximum jump length at that position.

Return true if you can reach the last index, or false otherwise.

Example 1:

Input: nums = [2,3,1,1,4]
Output: true
Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index.
Example 2:

Input: nums = [3,2,1,0,4]
Output: false
Explanation: You will always arrive at index 3 no matter what. Its maximum jump length is 0, which makes it impossible to reach the last index.

Constraints:

1 <= nums.length <= 104
0 <= nums[i] <= 105

A

Pattern: Greedy

Brute force: Naive Backtracking (Too Slow) TLE for large inputs

Time: O(2^n) worst case

Space: O(n) recursion stack

Intuition: Maintain a goal variable, and go in reverse and check if you can go till that goal. if you can, then update that as the goal. if you reach this the first index then return True

Code:

def canJump(self, nums: List[int]) -> bool:
size = len(nums)
goal = nums[size - 1]
for i in range (size - 1, -1, -1):
if (i + nums[i]) >= goal:
goal = i
return goal == 0

Time & Space Complexity

Time: O(n)

Space: O(1)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q
  1. Jump Game II

You are given a 0-indexed array of integers nums of length n. You are initially positioned at nums[0].

Each element nums[i] represents the maximum length of a forward jump from index i. In other words, if you are at nums[i], you can jump to any nums[i + j] where:

0 <= j <= nums[i] and
i + j < n
Return the minimum number of jumps to reach nums[n - 1]. The test cases are generated such that you can reach nums[n - 1].

Example 1:

Input: nums = [2,3,1,1,4]
Output: 2
Explanation: The minimum number of jumps to reach the last index is 2. Jump 1 step from index 0 to 1, then 3 steps to the last index.
Example 2:

Input: nums = [2,3,0,1,4]
Output: 2

Constraints:

1 <= nums.length <= 104
0 <= nums[i] <= 1000
It’s guaranteed that you can reach nums[n - 1].

A

Pattern: Greedy

Intuition: Here we dont just want to find IF we can reach, but whats the minimum. So think of the ranges to which you can go at MAX, then from that MAX see whats the next MAX you can go to. This is a good greedy way to solve it.

def jump(self, nums: List[int]) -> int:
res = 0
l = r = 0

    while r < len(nums) - 1:
        farthest = 0
        for i in range(l,r + 1):
            farthest = max(farthest, i + nums[i])
        l = r + 1
        r = farthest
        res += 1
    return res

Time O(n) ✅
Space O(1) ✅

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q
  1. Gas Station

There are n gas stations along a circular route, where the amount of gas at the ith station is gas[i].

You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from the ith station to its next (i + 1)th station. You begin the journey with an empty tank at one of the gas stations.

Given two integer arrays gas and cost, return the starting gas station’s index if you can travel around the circuit once in the clockwise direction, otherwise return -1. If there exists a solution, it is guaranteed to be unique.

Example 1:

Input: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
Output: 3
Explanation:
Start at station 3 (index 3) and fill up with 4 unit of gas. Your tank = 0 + 4 = 4
Travel to station 4. Your tank = 4 - 1 + 5 = 8
Travel to station 0. Your tank = 8 - 2 + 1 = 7
Travel to station 1. Your tank = 7 - 3 + 2 = 6
Travel to station 2. Your tank = 6 - 4 + 3 = 5
Travel to station 3. The cost is 5. Your gas is just enough to travel back to station 3.
Therefore, return 3 as the starting index.
Example 2:

Input: gas = [2,3,4], cost = [3,4,3]
Output: -1
Explanation:
You can’t start at station 0 or 1, as there is not enough gas to travel to the next station.
Let’s start at station 2 and fill up with 4 unit of gas. Your tank = 0 + 4 = 4
Travel to station 0. Your tank = 4 - 3 + 2 = 3
Travel to station 1. Your tank = 3 - 3 + 3 = 3
You cannot travel back to station 2, as it requires 4 unit of gas but you only have 3.
Therefore, you can’t travel around the circuit once no matter where you start.

Constraints:

n == gas.length == cost.length
1 <= n <= 105
0 <= gas[i], cost[i] <= 104

A

Pattern: Greedy

Intuition: Greedy approach. make an array of the difference between gas and cost. Maintain a total, if that total goes below zero then reset it. there will be an index after which it is never reset, return that index. since you would have enough gas to keep moving forward after that one.

Code:

size = len(gas)
total = 0
start = 0
if sum (gas) < sum(cost):
return -1
for i in range(size):
total += (gas[i] - cost[i])
if total < 0:
total = 0
start = i + 1
return start

Time and Space Complexity
Metric Complexity
Time O(n)
Space O(1)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q
  1. Hand of Straights

Alice has some number of cards and she wants to rearrange the cards into groups so that each group is of size groupSize, and consists of groupSize consecutive cards.

Given an integer array hand where hand[i] is the value written on the ith card and an integer groupSize, return true if she can rearrange the cards, or false otherwise.

Example 1:

Input: hand = [1,2,3,6,2,3,4,7,8], groupSize = 3
Output: true
Explanation: Alice’s hand can be rearranged as [1,2,3],[2,3,4],[6,7,8]
Example 2:

Input: hand = [1,2,3,4,5], groupSize = 4
Output: false
Explanation: Alice’s hand can not be rearranged into groups of 4.

Constraints:

1 <= hand.length <= 104
0 <= hand[i] <= 109
1 <= groupSize <= hand.length

Note: This question is the same as 1296: https://leetcode.com/problems/divide-array-in-sets-of-k-consecutive-numbers/

A

Pattern: Greedy

Intuition: Here, you have to make a heap, and a count dictionary. The heap helps in determining which is the smallest number. Then you keep going through a loop until heap becomes empty. In a second loop inside, go through all of the elements from the current smallest to smallest + groupsize. Then check the following conditions:
1) if i was never in the count dict, then return false
2) Reduce the count of i in dict.
3) If count is now zero, pop the min element from the heap
4) also if count has become 0 and also the current element is not the smallest element then return false (which explains why the above condition never pops a non smallest elements ever)

Code:
def isNStraightHand(self, hand: List[int], groupSize: int) -> bool:
if len(hand) % groupSize:
return False
count = {}
for num in hand:
count[num] = 1 + count.get(num,0)

    minh = list (count.keys())
    heapq.heapify(minh)

    while minh:
        first = minh[0]

        for i in range(first,first+groupSize):
            if i not in count:
                return False
            
            count[i] -= 1
            if count[i] == 0:
                if i != minh[0]:
                    return False
                heapq.heappop(minh)
    return True

Metric Value
Time O(n log n)
Space O(n)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q
  1. Merge Triplets to Form Target Triplet

A triplet is an array of three integers. You are given a 2D integer array triplets, where triplets[i] = [ai, bi, ci] describes the ith triplet. You are also given an integer array target = [x, y, z] that describes the triplet you want to obtain.

To obtain target, you may apply the following operation on triplets any number of times (possibly zero):

Choose two indices (0-indexed) i and j (i != j) and update triplets[j] to become [max(ai, aj), max(bi, bj), max(ci, cj)].
For example, if triplets[i] = [2, 5, 3] and triplets[j] = [1, 7, 5], triplets[j] will be updated to [max(2, 1), max(5, 7), max(3, 5)] = [2, 7, 5].
Return true if it is possible to obtain the target triplet [x, y, z] as an element of triplets, or false otherwise.

Example 1:

Input: triplets = [[2,5,3],[1,8,4],[1,7,5]], target = [2,7,5]
Output: true
Explanation: Perform the following operations:
- Choose the first and last triplets [[2,5,3],[1,8,4],[1,7,5]]. Update the last triplet to be [max(2,1), max(5,7), max(3,5)] = [2,7,5]. triplets = [[2,5,3],[1,8,4],[2,7,5]]
The target triplet [2,7,5] is now an element of triplets.
Example 2:

Input: triplets = [[3,4,5],[4,5,6]], target = [3,2,5]
Output: false
Explanation: It is impossible to have [3,2,5] as an element because there is no 2 in any of the triplets.
Example 3:

Input: triplets = [[2,5,3],[2,3,4],[1,2,5],[5,2,3]], target = [5,5,5]
Output: true
Explanation: Perform the following operations:
- Choose the first and third triplets [[2,5,3],[2,3,4],[1,2,5],[5,2,3]]. Update the third triplet to be [max(2,1), max(5,2), max(3,5)] = [2,5,5]. triplets = [[2,5,3],[2,3,4],[2,5,5],[5,2,3]].
- Choose the third and fourth triplets [[2,5,3],[2,3,4],[2,5,5],[5,2,3]]. Update the fourth triplet to be [max(2,5), max(5,2), max(5,3)] = [5,5,5]. triplets = [[2,5,3],[2,3,4],[2,5,5],[5,5,5]].
The target triplet [5,5,5] is now an element of triplets.

Constraints:

1 <= triplets.length <= 105
triplets[i].length == target.length == 3
1 <= ai, bi, ci, x, y, z <= 1000

A

Pattern: Greedy

Intuition: You just need to filter out all the triplets which are more than the numbers that we are looking for. Then check if each number exists in there at least once. If all three flags are yes then return true.

Code:
def mergeTriplets(self, triplets: List[List[int]], target: List[int]) -> bool:
res = []
for triplet in triplets:
if triplet[0] <= target[0] and triplet[1] <= target[1] and triplet[2] <= target[2] :
res.append(triplet)
print(res)
flag1 = False
flag2 = False
flag3 = False
for triplet in res:
if (target[0] == triplet[0]):
flag1 = True
if (target[1] == triplet[1]):
flag2 = True
if (target[2] == triplet[2]):
flag3 = True

    return flag1 and flag2 and flag3

Time & Space Complexity
Metric Complexity
Time O(n)
Space O(1)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q
  1. Climbing Stairs

You are climbing a staircase. It takes n steps to reach the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Example 1:

Input: n = 2
Output: 2
Explanation: There are two ways to climb to the top.
1. 1 step + 1 step
2. 2 steps
Example 2:

Input: n = 3
Output: 3
Explanation: There are three ways to climb to the top.
1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step

Constraints:

1 <= n <= 45
Seen this question in a real interview before?
1/5

A

Pattern: 1D DP

Intuition: Since you can take two steps at once, Imagine creating a tree with all of the choices. The last step and second last step will have one way to reach the top. Then go in reverse and simply add the next two (ways). It means you are adding up the number of ways in reverse and return the lowest one as the answer.

Code:
class Solution:
def climbStairs(self, n: int) -> int:
dp = [0] * (n+1)
dp[n] = 1
dp [n-1] = 1
if n == 1:
return 1
if n == 2:
return 2
for i in range (n-2,-1,-1):
dp[i] = dp [i + 1] + dp [i + 2]
return dp [0]

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q
  1. Min Cost Climbing Stairs

You are given an integer array cost where cost[i] is the cost of ith step on a staircase. Once you pay the cost, you can either climb one or two steps.

You can either start from the step with index 0, or the step with index 1.

Return the minimum cost to reach the top of the floor.

Example 1:

Input: cost = [10,15,20]
Output: 15
Explanation: You will start at index 1.
- Pay 15 and climb two steps to reach the top.
The total cost is 15.
Example 2:

Input: cost = [1,100,1,1,1,100,1,1,100,1]
Output: 6
Explanation: You will start at index 0.
- Pay 1 and climb two steps to reach index 2.
- Pay 1 and climb two steps to reach index 4.
- Pay 1 and climb two steps to reach index 6.
- Pay 1 and climb one step to reach index 7.
- Pay 1 and climb two steps to reach index 9.
- Pay 1 and climb one step to reach the top.
The total cost is 6.

Constraints:

2 <= cost.length <= 1000
0 <= cost[i] <= 999
sion (239)

A

Pattern: 1D DP

Intuition: Here, top means the stair after the final one. So add a 0 to the entire array. Then You can take one step or two, and need to decide which one will cost less. So go in reverse, and from the last element, take the minimum from the sum of current and next or sum of current and next of next.

dp = cost + [0]
size = len (dp)
#print (dp)

    for i in range(size - 3, -1, -1):
        dp[i] = min (dp[i] + dp[i+1],dp[i] + dp[i+2])
    print(dp)
    res = min (dp[0],dp[1]) return res
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q
  1. House Robber

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security systems connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given an integer array nums representing the amount of money of each house, return the maximum amount of money you can rob tonight without alerting the police.

Example 1:

Input: nums = [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
Total amount you can rob = 1 + 3 = 4.
Example 2:

Input: nums = [2,7,9,3,1]
Output: 12
Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1).
Total amount you can rob = 2 + 9 + 1 = 12.

Constraints:

1 <= nums.length <= 100
0 <= nums[i] <= 400

A

Pattern: 1D DP

Intuition: Here you are either allowed to pick the current node or the skip the current one. Maintain a dp array starting from 0 and take in it the max of either the last one, or current one and the one before the last one. then return the last element.

Code:
size = len(nums)
if size == 1:
return nums[0]
nums[1] = max (nums[0],nums[1])
for i in range(2,len(nums)):
nums[i] = max (nums[i-1],nums[i-2]+nums[i])
return nums[size-1]

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
25
213. House Robber II You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. All houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, adjacent houses have a security system connected, and it will automatically contact the police if two adjacent houses were broken into on the same night. Given an integer array nums representing the amount of money of each house, return the maximum amount of money you can rob tonight without alerting the police. Example 1: Input: nums = [2,3,2] Output: 3 Explanation: You cannot rob house 1 (money = 2) and then rob house 3 (money = 2), because they are adjacent houses. Example 2: Input: nums = [1,2,3,1] Output: 4 Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3). Total amount you can rob = 1 + 3 = 4. Example 3: Input: nums = [1,2,3] Output: 3 Constraints: 1 <= nums.length <= 100 0 <= nums[i] <= 1000 Seen this question in a real interview before?
Intuition: Very similar to house robber 1. You have to go in ascending order and take the max of the last one or current one and the one before last. But the difference is that since this is circular, remove the first one first and take one value, then the remove the last element and take another value then return the max of both of those. Code: size = len(nums) dp1 = nums[1:] dp2 = nums[0:size-1] if size == 1: return nums[0] if size == 3 or size == 2: return max(nums) size = size - 1 dp1[1] = max (dp1[1],dp1[0]) dp2[1] = max (dp2[1],dp2[0]) for i in range(2,size): dp1[i] = max (dp1[i-1],dp1[i]+dp1[i-2]) for i in range(2,size): dp2[i] = max (dp2[i-1],dp2[i]+dp2[i-2]) return max (dp1[size-1],dp2[size-1])
26
5. Longest Palindromic Substring Given a string s, return the longest palindromic substring in s. Example 1: Input: s = "babad" Output: "bab" Explanation: "aba" is also a valid answer. Example 2: Input: s = "cbbd" Output: "bb" Constraints: 1 <= s.length <= 1000 s consist of only digits and English letters.
Pattern: 1D DP Intuition: Here the main thing to understand is that for each element you have to expand outwards on both sides and check if it is a palindrome. and do this for even and odd palindromes both. In case of even, left and right have to be adjacent elements, and in case of odd it has the be the same element. Code: size = len(s) if size == 1: return s if size == 2: if s[0] == s[1]: return s return s[0] res = [] for i in range(size): left,right = i,i+1 while left >= 0 and right < size and s[left] == s[right]: temp = s[left:right+1] if len(temp) > len(res): res = temp left -= 1 right += 1 for i in range(size): left,right = i,i while left >= 0 and right < size and s[left] == s[right]: temp = s[left:right+1] if len(temp) > len(res): res = temp left -= 1 right += 1 return res
27
647. Palindromic Substrings Given a string s, return the number of palindromic substrings in it. A string is a palindrome when it reads the same backward as forward. A substring is a contiguous sequence of characters within the string. Example 1: Input: s = "abc" Output: 3 Explanation: Three palindromic strings: "a", "b", "c". Example 2: Input: s = "aaa" Output: 6 Explanation: Six palindromic strings: "a", "a", "a", "aa", "aa", "aaa". Constraints: 1 <= s.length <= 1000 s consists of lowercase English letters.
Pattern: 1D DP Intuition: Same intuition as longest palindromic substring
28
763. Partition Labels You are given a string s. We want to partition the string into as many parts as possible so that each letter appears in at most one part. For example, the string "ababcc" can be partitioned into ["abab", "cc"], but partitions such as ["aba", "bcc"] or ["ab", "ab", "cc"] are invalid. Note that the partition is done so that after concatenating all the parts in order, the resultant string should be s. Return a list of integers representing the size of these parts. Example 1: Input: s = "ababcbacadefegdehijhklij" Output: [9,7,8] Explanation: The partition is "ababcbaca", "defegde", "hijhklij". This is a partition so that each letter appears in at most one part. A partition like "ababcbacadefegde", "hijhklij" is incorrect, because it splits s into less parts. Example 2: Input: s = "eccbbbbdec" Output: [10] Constraints: 1 <= s.length <= 500 s consists of lowercase English letters.
Pattern: Greedy Intuition: Here, we want to make sure every occurence of a character is in the same partition. So simply create a dictionary which maintains the last occurence of that character. Then continue to go through the elements and updating the end variable, until you reach a point that you surpass it. then create new end for the next partition, and append the size, re-setting it to 0. Code: class Solution: def partitionLabels(self, s: str) -> List[int]: last = {} res = [] for i,c in enumerate(s): last[c] = i size = 0 end = 0 for i,c in enumerate(s): size = size + 1 end = max (end,last[c]) if i == end: res.append(size) size = 0 return res
29
91. Decode Ways You have intercepted a secret message encoded as a string of numbers. The message is decoded via the following mapping: "1" -> 'A' "2" -> 'B' ... "25" -> 'Y' "26" -> 'Z' However, while decoding the message, you realize that there are many different ways you can decode the message because some codes are contained in other codes ("2" and "5" vs "25"). For example, "11106" can be decoded into: "AAJF" with the grouping (1, 1, 10, 6) "KJF" with the grouping (11, 10, 6) The grouping (1, 11, 06) is invalid because "06" is not a valid code (only "6" is valid). Note: there may be strings that are impossible to decode. Given a string s containing only digits, return the number of ways to decode it. If the entire string cannot be decoded in any valid way, return 0. The test cases are generated so that the answer fits in a 32-bit integer. Example 1: Input: s = "12" Output: 2 Explanation: "12" could be decoded as "AB" (1 2) or "L" (12). Example 2: Input: s = "226" Output: 3 Explanation: "226" could be decoded as "BZ" (2 26), "VF" (22 6), or "BBF" (2 2 6). Example 3: Input: s = "06" Output: 0 Explanation: "06" cannot be mapped to "F" because of the leading zero ("6" is different from "06"). In this case, the string is not a valid encoding, so return 0. Constraints: 1 <= s.length <= 100 s contains only digits and may contain leading zero(s).
Pattern: 1D DP Intuition: You need to add every number as its own solo self as 1 (except if it is zero), and in case of some conditions, add the next number as well. Code: def numDecodings(self, s: str) -> int: dp = {len(s) : 1} size = len (s) for i in range (size - 1, -1, -1): if s[i] == "0": dp[i] = 0 else: dp[i] = dp[i + 1] if (i + 1 < len(s)) and ((s[i] == "1") or (s[i] == "2" and s[i+1] in "0123456")): dp[i] += dp[i+2] return dp[0]
30
322. Coin Change You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money. Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1. You may assume that you have an infinite number of each kind of coin. Example 1: Input: coins = [1,2,5], amount = 11 Output: 3 Explanation: 11 = 5 + 5 + 1 Example 2: Input: coins = [2], amount = 3 Output: -1 Example 3: Input: coins = [1], amount = 0 Output: 0 Constraints: 1 <= coins.length <= 12 1 <= coins[i] <= 231 - 1 0 <= amount <= 104
Pattern: 1D DP Intuition: Initiate the dp array to something larger than the largest amount, and first one as zero. Then if i - coins is computed, take the minimum of the current one + 1, or i - coins. finally, return if it was updated. Code: def coinChange(self, coins: List[int], amount: int) -> int: dp = (amount + 1) * [amount + 1] dp [0] = 0 for i in range (1,len(dp)): for coin in coins: if (i - coin >= 0): dp[i] = min (dp[i], 1 + dp[i-coin]) return dp[len(dp) - 1] if dp[len(dp) - 1] < amount + 1 else -1
31
152. Maximum Product Subarray Given an integer array nums, find a subarray that has the largest product, and return the product. The test cases are generated so that the answer will fit in a 32-bit integer. Example 1: Input: nums = [2,3,-2,4] Output: 6 Explanation: [2,3] has the largest product 6. Example 2: Input: nums = [-2,0,-1] Output: 0 Explanation: The result cannot be 2, because [-2,-1] is not a subarray. Constraints: 1 <= nums.length <= 2 * 104 -10 <= nums[i] <= 10 The product of any subarray of nums is guaranteed to fit in a 32-bit integer.
Pattern: 1D DP Pattern: Dynamic Programming Intuition: You have to maintain a variable increasing or decreasing which determines the max. But in case of negative numbers, you may miss out a larger negative value suddenly becoming a large positive value if you do greedy. Thats why also maintain a curmin, then maintain the max and min from curmax * num, curmin * num, num Code: def maxProduct(self, nums: List[int]) -> int: res = max (nums) curmax, curmin = 1,1 for num in nums: if num == 0: curmax,curmin = 1,1 continue temp = curmax * num curmax = max (curmax * num, num * curmin, num) curmin = min (temp, num * curmin,num) res = max (res,curmax) return res
32
139. Word Break Given a string s and a dictionary of strings wordDict, return true if s can be segmented into a space-separated sequence of one or more dictionary words. Note that the same word in the dictionary may be reused multiple times in the segmentation. Example 1: Input: s = "leetcode", wordDict = ["leet","code"] Output: true Explanation: Return true because "leetcode" can be segmented as "leet code". Example 2: Input: s = "applepenapple", wordDict = ["apple","pen"] Output: true Explanation: Return true because "applepenapple" can be segmented as "apple pen apple". Note that you are allowed to reuse a dictionary word. Example 3: Input: s = "catsandog", wordDict = ["cats","dog","sand","and","cat"] Output: false Constraints: 1 <= s.length <= 300 1 <= wordDict.length <= 1000 1 <= wordDict[i].length <= 20 s and wordDict[i] consist of only lowercase English letters. All the strings of wordDict are unique.
Pattern: 1D DP Intuition: Take a dp array of size + 1, go in reverse with the final element being True. Iterate through all words per dp index, and if stuff matches, update with dp [i + len(word)] Code: for i in range (size - 1, -1 , -1): for word in wordDict: print("s",s[i:i+len(word)],"word",word) if (i+len(word)<=len(s) and s[i:i+len(word)] == word): dp[i] = dp[i + len(word)] print (dp) return dp[0]
33
300. Longest Increasing Subsequence Given an integer array nums, return the length of the longest strictly increasing subsequence. Example 1: Input: nums = [10,9,2,5,3,7,101,18] Output: 4 Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4. Example 2: Input: nums = [0,1,0,3,2,3] Output: 4 Example 3: Input: nums = [7,7,7,7,7,7,7] Output: 1 Constraints: 1 <= nums.length <= 2500 -104 <= nums[i] <= 104 Follow up: Can you come up with an algorithm that runs in O(n log(n)) time complexity?
Pattern: 1D DP Intuition: For every element, you are looking at the elements after and if they are larger, taking their tabularized value. Start the dp array as 1s of size. Then go in reverse from size - 2. Go through all of the elements after the current elements in an inner loop, and if the next element is larger than the current, make dp [i] max of 1,dp[i] or 1 + dp [j] Code: size = len (nums) dp = [1] * size dp [size - 1] = 1 for i in range (size - 2, - 1, - 1 ): for j in range (i + 1,size): if nums[j] > nums[i]: dp[i] = max (1,dp[i], 1+ dp[j]) return max(dp)
34
416. Partition Equal Subset Sum Given an integer array nums, return true if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or false otherwise. Example 1: Input: nums = [1,5,11,5] Output: true Explanation: The array can be partitioned as [1, 5, 5] and [11]. Example 2: Input: nums = [1,2,3,5] Output: false Explanation: The array cannot be partitioned into equal sum subsets. Constraints: 1 <= nums.length <= 200 1 <= nums[i] <= 100
Pattern: 1D DP Intuition: You need both sides to be equal to half of the sum. so that half is your target. make a set, in that set, add the current element and the sum of current and all other preexisting element in the set. Finally check if this put the target in the set, if yes then return. Code: dp = set() dp.add(0) size = len (nums) if sum(nums) % 2 == 1: return False target = sum(nums) / 2 for i in range (size - 1, -1, -1): temp = set() for eleset in dp: temp.add(nums[i]+eleset) temp.add(eleset) dp = temp return True if target in dp else False
35
217. Contains Duplicate Given an integer array nums, return true if any value appears at least twice in the array, and return false if every element is distinct. Example 1: Input: nums = [1,2,3,1] Output: true Explanation: The element 1 occurs at the indices 0 and 3. Example 2: Input: nums = [1,2,3,4] Output: false Explanation: All elements are distinct. Example 3: Input: nums = [1,1,1,3,3,4,3,2,4,2] Output: true Constraints: 1 <= nums.length <= 105 -109 <= nums[i] <= 109
Pattern: Arrays and Hashing Intuition: Basic set problem. Add to set and if already exists, then return true Code: def containsDuplicate(self, nums: List[int]) -> bool: total = 0 existing = set() for num in nums: if num in existing: return True existing.add(num) return False
36
242. Valid Anagram Given two strings s and t, return true if t is an anagram of s, and false otherwise. Example 1: Input: s = "anagram", t = "nagaram" Output: true Example 2: Input: s = "rat", t = "car" Output: false Constraints: 1 <= s.length, t.length <= 5 * 104 s and t consist of lowercase English letters. Follow up: What if the inputs contain Unicode characters? How would you adapt your solution to such a case?
Pattern: Arrays and Hashing Intuition: Two dictionaries of size 26, add the increment the letter frequency counts for both the strings. Then if both dicts are same, you found an anagram. Note: Character to integer conversion of letter is done by (ord) in Python. Ex: ord(char) Code: dic1 , dic2 = [0] * 26, [0] * 26 for char in s: index = ord(char) - ord('a') dic1[index] += 1 for char in t: index = ord(char) - ord('a') dic2[index] += 1 if dic1 == dic2: return True return False
37
Two Sum Hint Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target. You may assume that each input would have exactly one solution, and you may not use the same element twice. You can return the answer in any order. Example 1: Input: nums = [2,7,11,15], target = 9 Output: [0,1] Explanation: Because nums[0] + nums[1] == 9, we return [0, 1]. Example 2: Input: nums = [3,2,4], target = 6 Output: [1,2] Example 3: Input: nums = [3,3], target = 6 Output: [0,1] Constraints: 2 <= nums.length <= 104 -109 <= nums[i] <= 109 -109 <= target <= 109 Only one valid answer exists. Follow-up: Can you come up with an algorithm that is less than O(n2) time complexity?
Pattern: Arrays and Hashing Intuition: You have the target, go through every number and calculate the othernum. Then check if the othernum exists in the set and not itself. return that num. Add the current number to the set after doing all this in the loop. This also helps reduce the self check. Note: nums.index(num_to_find) gives you the index of a number in an array in python Code: numset = set() for i in range(len(nums)): othernum = target - nums[i] if othernum in numset and i!=nums.index(othernum): return ([i,nums.index(othernum)]) numset.add(nums[i])
38
Group Anagrams Given an array of strings strs, group the anagrams together. You can return the answer in any order. Example 1: Input: strs = ["eat","tea","tan","ate","nat","bat"] Output: [["bat"],["nat","tan"],["ate","eat","tea"]] Explanation: There is no string in strs that can be rearranged to form "bat". The strings "nat" and "tan" are anagrams as they can be rearranged to form each other. The strings "ate", "eat", and "tea" are anagrams as they can be rearranged to form each other. Example 2: Input: strs = [""] Output: [[""]] Example 3: Input: strs = ["a"] Output: [["a"]] Constraints: 1 <= strs.length <= 104 0 <= strs[i].length <= 100 strs[i] consists of lowercase English letters.
Pattern: Arrays and Hashing Intuition: Make a dictionary. Sort every string, keep that as the key and the original string as the value. Then go through all of the values and append them to a list and return that. Code: dict1 = {} for string in strs: temp = "".join(sorted(string)) if temp in dict1: dict1[temp].append(string) else: dict1[temp] = [string] final = [] for value in dict1.values(): print(value) final.append(value) #print(final) return final
39
347. Top K Frequent Elements Given an integer array nums and an integer k, return the k most frequent elements. You may return the answer in any order. Example 1: Input: nums = [1,1,1,2,2,3], k = 2 Output: [1,2] Example 2: Input: nums = [1], k = 1 Output: [1] Constraints: 1 <= nums.length <= 105 -104 <= nums[i] <= 104 k is in the range [1, the number of unique elements in the array]. It is guaranteed that the answer is unique. Follow up: Your algorithm's time complexity must be better than O(n log n), where n is the array's size.
Pattern: Arrays and Hashing Intuition: Bucket sort. You need to make to keep all the elements of a particular frequency in a list. and make a list of lists like that. The list can be of max size possible, which is len(nums) + 1 if all the elements are same. Go through these elements in reverse, and return the k elements. Code: count = {} bucket = [[] for i in range(len(nums) + 1)] for num in nums: count[num] = count.get(num,0) + 1 for num,freq in count.items(): bucket[freq].append(num) res = [] for i in range(len(bucket) -1, -1, -1): for num in bucket[i]: res.append(num) if len(res) == k: return res
40
271. Encode and Decode Strings Design an algorithm to encode a list of strings to a string. The encoded string is then sent over the network and is decoded back to the original list of strings. Machine 1 (sender) has the function: string encode(vector strs) { // ... your code return encoded_string; } Machine 2 (receiver) has the function: vector decode(string s) { //... your code return strs; } So Machine 1 does: string encoded_string = encode(strs); and Machine 2 does: vector strs2 = decode(encoded_string); strs2 in Machine 2 should be the same as strs in Machine 1. Implement the encode and decode methods. You are not allowed to solve the problem using any serialize methods (such as eval). Example 1: Input: dummy_input = ["Hello","World"] Output: ["Hello","World"] Explanation: Machine 1: Codec encoder = new Codec(); String msg = encoder.encode(strs); Machine 1 ---msg---> Machine 2 Machine 2: Codec decoder = new Codec(); String[] strs = decoder.decode(msg); Example 2: Input: dummy_input = [""] Output: [""] Constraints: 1 <= strs.length <= 200 0 <= strs[i].length <= 200 strs[i] contains any possible characters out of 256 valid ASCII characters. Follow up: Could you write a generalized algorithm to work on any possible set of characters?
Pattern: Arrays and Hashing Intuition: You need to put size and # before every string. Then in decode go through the string, and find the next #. then find the length after that. Try to use string.find(char,afterindex) here. code: def encode(self, strs: List[str]) -> str: """Encodes a list of strings to a single string. """ res = "" for string in strs: size = len(string) res += str(size) + '#' + string #print (res) return res def decode(self, s: str) -> List[str]: """Decodes a single string to a list of strings. """ decoded_list = [] i = 0 while i < len(s): j = s.find('#', i) # Find the separator length = int(s[i:j]) # Extract the length of the string i = j + 1 + length # Move the pointer to the next encoded string decoded_list.append(s[j+1:i]) return decoded_list
41
238. Product of Array Except Self Given an integer array nums, return an array answer such that answer[i] is equal to the product of all the elements of nums except nums[i]. The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer. You must write an algorithm that runs in O(n) time and without using the division operation. Example 1: Input: nums = [1,2,3,4] Output: [24,12,8,6] Example 2: Input: nums = [-1,1,0,-3,3] Output: [0,0,9,0,0] Constraints: 2 <= nums.length <= 105 -30 <= nums[i] <= 30 The input is generated such that answer[i] is guaranteed to fit in a 32-bit integer. Follow up: Can you solve the problem in O(1) extra space complexity? (The output array does not count as extra space for space complexity analysis.)
Pattern: Arrays and Hashing Intuition: Calculate prefix and suffix product for each position. Then multiply both of those for the output. Code: prefix = [0] * len(nums) suffix = [0] * len(nums) prefix[0] = 1 suffix[-1] = 1 #print(prefix,suffix) for i in range(1,len(nums)): prefix[i] = prefix[i-1] * nums[i-1] #print(prefix) for i in range(len(nums) - 2, -1, -1): suffix[i] = suffix[i + 1] * nums[i + 1] #print(suffix) res = [] for i in range (len(nums)): res.append(prefix[i]*suffix[i]) return res
42
36. Valid Sudoku Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be validated according to the following rules: Each row must contain the digits 1-9 without repetition. Each column must contain the digits 1-9 without repetition. Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 without repetition. Note: A Sudoku board (partially filled) could be valid but is not necessarily solvable. Only the filled cells need to be validated according to the mentioned rules.
Pattern: Arrays and Hashing Intuition: For rows and columns, you can simply use two sets and if it already exists, return false. For the 3 X 3 boxes, you need to use a dictionary. The tuple (i//3,j//3) will be key and the value is our a set. Code: class Solution: def isValidSudoku(self, board: List[List[str]]) -> bool: set1 = set() set2 = set() dict1= {} for i in range(len(board)): set1.clear() set2.clear() for j in range(len(board)): if (board[i][j] != "."): # cking rows if board[i][j] in set1: return False else: set1.add(board[i][j]) if (board[j][i] != "."): if board[j][i] in set2: return False else: set2.add(board[j][i]) # For checking columns if(board[i][j] == "."): continue # For checking boxes temp1 = i//3 temp2 = j//3 # key (temp1, temp2): set() if (temp1, temp2) not in dict1: dict1[(temp1, temp2)] = set() dict1[(temp1, temp2)].add(board[i][j]) else: if board[i][j] in dict1[(temp1, temp2)]: return False else: dict1[(temp1, temp2)].add(board[i][j]) return True
43
128. Longest Consecutive Sequence Given an unsorted array of integers nums, return the length of the longest consecutive elements sequence. You must write an algorithm that runs in O(n) time. Example 1: Input: nums = [100,4,200,1,3,2] Output: 4 Explanation: The longest consecutive elements sequence is [1, 2, 3, 4]. Therefore its length is 4. Example 2: Input: nums = [0,3,7,2,5,8,4,6,0,1] Output: 9 Example 3: Input: nums = [1,0,1,2] Output: 3 Constraints: 0 <= nums.length <= 105 -109 <= nums[i] <= 109
Pattern: Arrays and Hashing Intuition: you should find the elements that have nothing before them, these are the so called start elements. then from these start elements, check if there is + 1 available. keep record of the max and return. Keep start as a separate set so that there is no repeat calculations. This will remove the TLE. Code: setnums = set() for num in nums: setnums.add(num) starts = set() for num in nums: if num-1 not in setnums: starts.add(num) print(starts) size = 0 res = 0 for start in starts: itr = start temp = 1 while(itr+1 in setnums): temp+=1 itr+=1 res = max(res, temp) return res
44
125. Valid Palindrome A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers. Given a string s, return true if it is a palindrome, or false otherwise. Example 1: Input: s = "A man, a plan, a canal: Panama" Output: true Explanation: "amanaplanacanalpanama" is a palindrome. Example 2: Input: s = "race a car" Output: false Explanation: "raceacar" is not a palindrome. Example 3: Input: s = " " Output: true Explanation: s is an empty string "" after removing non-alphanumeric characters. Since an empty string reads the same forward and backward, it is a palindrome. Constraints: 1 <= s.length <= 2 * 105 s consists only of printable ASCII characters.
Pattern: Two pointers Intuition: Remove all spaces and non alpha numeric characters. Also keep all characters lower. Use two pointers to then check from both ends and come towards the middle. Note: char.isalnum() for removing all non alpha numeric characters. .lower() to make characters lowerspace Code: def isPalindrome(self, s: str) -> bool: res = "" for char in s: if char.isalnum(): res += char.lower() left = 0 right = len(res) - 1 print(res) while (left <= right): if res[left] != res[right]: return False left += 1 right -= 1 return True
45
167. Two Sum II - Input Array Is Sorted Given a 1-indexed array of integers numbers that is already sorted in non-decreasing order, find two numbers such that they add up to a specific target number. Let these two numbers be numbers[index1] and numbers[index2] where 1 <= index1 < index2 <= numbers.length. Return the indices of the two numbers, index1 and index2, added by one as an integer array [index1, index2] of length 2. The tests are generated such that there is exactly one solution. You may not use the same element twice. Your solution must use only constant extra space. Example 1: Input: numbers = [2,7,11,15], target = 9 Output: [1,2] Explanation: The sum of 2 and 7 is 9. Therefore, index1 = 1, index2 = 2. We return [1, 2]. Example 2: Input: numbers = [2,3,4], target = 6 Output: [1,3] Explanation: The sum of 2 and 4 is 6. Therefore index1 = 1, index2 = 3. We return [1, 3]. Example 3: Input: numbers = [-1,0], target = -1 Output: [1,2] Explanation: The sum of -1 and 0 is -1. Therefore index1 = 1, index2 = 2. We return [1, 2]. Constraints: 2 <= numbers.length <= 3 * 104 -1000 <= numbers[i] <= 1000 numbers is sorted in non-decreasing order. -1000 <= target <= 1000 The tests are generated such that there is exactly one solution.
Pattern: Two pointers Intuition: Since this is sorted, you can do two pointers approach here. If sum is less than current, increase left, else right. Code: left = 0 right = len(numbers) - 1 res = [] while left < right: print(left,right) sum1 = numbers[left] + numbers[right] if sum1 > target: right -= 1 elif sum1 < target: left += 1 else: res.append(left + 1) res.append(right + 1) break return res
46
15. 3Sum Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0. Notice that the solution set must not contain duplicate triplets. Example 1: Input: nums = [-1,0,1,2,-1,-4] Output: [[-1,-1,2],[-1,0,1]] Explanation: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0. nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0. nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0. The distinct triplets are [-1,0,1] and [-1,-1,2]. Notice that the order of the output and the order of the triplets does not matter. Example 2: Input: nums = [0,1,1] Output: [] Explanation: The only possible triplet does not sum up to 0. Example 3: Input: nums = [0,0,0] Output: [[0,0,0]] Explanation: The only possible triplet sums up to 0. Constraints: 3 <= nums.length <= 3000 -105 <= nums[i] <= 105
Pattern: Two Pointers Intuition: This is 2Sum for each number separately. You want to go in reverse order, skip the same ones. Then do left and right pointer approach on the remaining elements. Skip the left pointer duplicates when you append one value. This skipping is to avoid the duplicates of tuples in the problem. Code: def threeSum(self, nums: List[int]) -> List[List[int]]: nums.sort() res = [] for i in range(len(nums) - 2): print(nums[i]) if i > 0 and nums[i] == nums[i - 1]: continue left, right = i + 1, len(nums) - 1 target = -nums[i] while left < right: sum_lr = nums[left] + nums[right] if sum_lr < target: left += 1 elif sum_lr > target: right -= 1 else: res.append([nums[i], nums[left], nums[right]]) # Skip duplicates for left pointer while left < right and nums[left] == nums[left + 1]: left += 1 left += 1 right -= 1 return res
47
11. Container With Most Water You are given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the ith line are (i, 0) and (i, height[i]). Find two lines that together with the x-axis form a container, such that the container contains the most water. Return the maximum amount of water a container can store. Notice that you may not slant the container. Example 1: Input: height = [1,8,6,2,5,4,8,3,7] Output: 49 Explanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49. Example 2: Input: height = [1,1] Output: 1 Constraints: n == height.length 2 <= n <= 105 0 <= height[i] <= 104
Pattern: Two pointers Intuition: You move the pointer which is smaller, and keep a max area. But the reason that this works is, the smaller pointer determines the height, if you move the larger pointer, you keep reducing the width, while keeping the height locked in to the smaller value. So it makes sense to skip the possibilities with the larger value. Code: def maxArea(self, height: List[int]) -> int: l = 0 r = len(height) - 1 maxarea = 0 while l < r: area = min (height[l],height[r]) * (r - l) if height[l] <= height[r]: l += 1 else: r -= 1 maxarea = max(maxarea,area) return maxarea
48
11. Container With Most Water You are given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the ith line are (i, 0) and (i, height[i]). Find two lines that together with the x-axis form a container, such that the container contains the most water. Return the maximum amount of water a container can store. Notice that you may not slant the container. Example 1: Input: height = [1,8,6,2,5,4,8,3,7] Output: 49 Explanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49. Example 2: Input: height = [1,1] Output: 1 Constraints: n == height.length 2 <= n <= 105 0 <= height[i] <= 104 Seen this question in a real interview before?
Intuition: The brute-force approach would check all possible pairs, which is O(n²) time, but the two-pointer approach does it in O(n) time. How does that work? The solution starts with the left pointer at the beginning and the right pointer at the end. Then, it moves the pointer pointing to the shorter line inward, updating the maximum volume each time. But why does this work? Let me think. The key idea is that by moving the shorter pointer, we might find a taller line that could potentially lead to a larger area. If we have two lines, the height of the container is determined by the shorter one. The width is as large as possible at the start (since the pointers are at the ends). But as we move the pointers inward, the width decreases. So, the only way to possibly get a larger area is if the height increases enough to compensate for the reduced width. Suppose we have left and right pointers. If height[left] is less than height[right], moving the left pointer to the right skips all the pairs where left is fixed at the current position. But why is it safe to skip those? Because any container formed with the current left and a right between current right and left would have a width smaller than the current width and a height at most height[left] (since we're keeping the left the same and the right is moving left, so the min of left and new right can't be larger than before). Therefore, those containers can't have a larger area than the current one. So by moving the shorter pointer, we're effectively eliminating the need to check all those other possibilities, which allows the algorithm to run in linear time. Code: class Solution: def maxArea(self, height: List[int]) -> int: l = 0 r = len(height) - 1 maxarea = 0 while l < r: area = min (height[l],height[r]) * (r - l) if height[l] <= height[r]: l += 1 else: r -= 1 maxarea = max(maxarea,area) return maxarea
49
42. Trapping Rain Water Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it can trap after raining. Example 1: Input: height = [0,1,0,2,1,0,1,3,2,1,2,1] Output: 6 Explanation: The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Example 2: Input: height = [4,2,0,3,2,5] Output: 9 Constraints: n == height.length 1 <= n <= 2 * 104 0 <= height[i] <= 105
Pattern: Two Pointer Intuition: for every element, you want the amount of water that it can hold is the minimum of its left and right tallest. And subtract the blocks in itself. So calculate left and right maxes for every element using two pointers. Code: class Solution: def trap(self, height: List[int]) -> int: size = len(height) leftmax = [0] * size rightmax = [0] * size curmax = height[0] for i in range(size): if height[i] >= curmax: curmax = height[i] leftmax[i] = curmax curmax = height[size - 1] #print(curmax) for i in range(size - 1,-1,-1): if height[i] >= curmax: curmax = height[i] #print(curmax) rightmax[i] = curmax print(leftmax,rightmax) count = 0 for i in range(size): temp = min(leftmax[i],rightmax[i]) - height[i] count += temp return count
50
121. Best Time to Buy and Sell Stock You are given an array prices where prices[i] is the price of a given stock on the ith day. You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock. Return the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return 0. Example 1: Input: prices = [7,1,5,3,6,4] Output: 5 Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5. Note that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell. Example 2: Input: prices = [7,6,4,3,1] Output: 0 Explanation: In this case, no transactions are done and the max profit = 0. Constraints: 1 <= prices.length <= 105 0 <= prices[i] <= 104
Pattern: Two Pointer Intuition: Once stock goes below the current buyone. It makes to sense to not switch the buy stock to that, since it will lead to better profits than keeping the current buy. If you find a higher value, it makes no sense to switch to it, since the current lower value will always lead to better profits. Code: while left < size and right < size: profit = max(profit,prices[right] - prices[left]) if prices[left] > prices[right]: left = right right = left + 1 else: right = right + 1 return profit
51
3. Longest Substring Without Repeating Given a string s, find the length of the longest substring without duplicate characters. Example 1: Input: s = "abcabcbb" Output: 3 Explanation: The answer is "abc", with the length of 3. Example 2: Input: s = "bbbbb" Output: 1 Explanation: The answer is "b", with the length of 1. Example 3: Input: s = "pwwkew" Output: 3 Explanation: The answer is "wke", with the length of 3. Notice that the answer must be a substring, "pwke" is a subsequence and not a substring. Constraints: 0 <= s.length <= 5 * 104 s consists of English letters, digits, symbols and spaces.
Pattern: Sliding window Intuition: Increment the right pointer if the next character is unique. or keep reducing the left character. maintain a set for the current character list. Code: while left < size and right < size: if s[right] not in curset: curset.add(s[right]) right = right + 1 else: curset.remove(s[left]) left = left + 1 res = max(res,right - left) return res
52
567. Permutation in String Given two strings s1 and s2, return true if s2 contains a permutation of s1, or false otherwise. In other words, return true if one of s1's permutations is the substring of s2. Example 1: Input: s1 = "ab", s2 = "eidbaooo" Output: true Explanation: s2 contains one permutation of s1 ("ba"). Example 2: Input: s1 = "ab", s2 = "eidboaoo" Output: false Constraints: 1 <= s1.length, s2.length <= 104 s1 and s2 consist of lowercase English letters.
Pattern: Sliding window Intuition: You have to keep a sliding window of the size of first string on the second string and move it. First pre compute the initial matches before starting the loop. In the loop keep updating the count and matches. Whenever the matches are 26, return true. Code: class Solution: def checkInclusion(self, s1: str, s2: str) -> bool: if len(s1) > len (s2): return False s1Count, s2Count = [0] * 26, [0] *26 for i in range (len(s1)): s1Count[ord(s1[i]) - ord('a')] += 1 s2Count[ord(s2[i]) - ord('a')] += 1 matches = 0 for i in range (26): matches += (1 if s1Count[i] == s2Count[i] else 0) l = 0 for r in range(len(s1), len(s2)): if matches == 26: return True index = ord(s2[r]) - ord('a') s2Count[index] += 1 if s1Count[index] == s2Count[index]: matches += 1 elif s1Count[index] + 1 == s2Count[index]: matches -= 1 index = ord (s2[l]) - ord('a') s2Count[index] -= 1 if s1Count[index] == s2Count[index]: matches += 1 elif s1Count[index] - 1 == s2Count[index]: matches -= 1 l += 1 return matches == 26
53
200. Number of Islands Given an m x n 2D binary grid grid which represents a map of '1's (land) and '0's (water), return the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water. Example 1: Input: grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ] Output: 1 Example 2: Input: grid = [ ["1","1","0","0","0"], ["1","1","0","0","0"], ["0","0","1","0","0"] ["0","0","0","1","1"] ] Output: 3 Constraints: m == grid.length n == grid[i].length 1 <= m, n <= 300 grid[i][j] is '0' or '1'.
Pattern: Graph Intuition: Standard graph traversal problem. The dfs explores the entire island. So you can check how many islands exist from the number of calls you make in your for loop. Either by changing the contents to # or by keeping a visited set. Code: class Solution: def numIslands(self, grid: List[List[str]]) -> int: if not grid: return 0 visited = set() rows = len(grid) cols = len (grid[0]) count = 0 for i in range(rows): for j in range(cols): if (i,j) not in visited and grid[i][j] == '1': self.dfs(grid,i,j,visited) count += 1 return count def dfs (self,grid, i , j, visited): #print(visited) rows = len(grid) cols = len (grid[0]) if i < 0 or j < 0 or i >= rows or j >= cols or (i,j) in visited or grid[i][j] != '1': return visited.add((i,j)) self.dfs(grid,i + 1, j,visited) self.dfs(grid,i , j + 1, visited) self.dfs(grid,i - 1, j,visited) self.dfs(grid,i, j - 1,visited)
54
695. Max Area of Island Solved Medium Topics Companies You are given an m x n binary matrix grid. An island is a group of 1's (representing land) connected 4-directionally (horizontal or vertical.) You may assume all four edges of the grid are surrounded by water. The area of an island is the number of cells with a value 1 in the island. Return the maximum area of an island in grid. If there is no island, return 0. Example 1: Input: grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]] Output: 6 Explanation: The answer is not 11, because the island must be connected 4-directionally. Example 2: Input: grid = [[0,0,0,0,0,0,0,0]] Output: 0 Constraints: m == grid.length n == grid[i].length 1 <= m, n <= 50 grid[i][j] is either 0 or 1.
Pattern: Graph Intuition: Same as other dfs solutions. while calling the dfs, add all the calls to a local count and add 1 to it and return. You can also make all the calls in a return statement. Code: class Solution: def maxAreaOfIsland(self, grid: List[List[int]]) -> int: visited = set() rows = len(grid) cols = len(grid[0]) count = 0 maxcount = 0 def dfs (i,j,grid,visited): if i < 0 or j < 0 or i >= rows or j >= cols or grid[i][j] != 1 or (i,j) in visited : return 0 visited.add((i,j)) count = 1 count += dfs (i + 1 ,j,grid,visited) count += dfs (i ,j + 1,grid,visited) count += dfs (i - 1 ,j,grid,visited) count += dfs (i ,j - 1,grid,visited) return count for i in range(rows): for j in range(cols): if grid[i][j] == 1 and (i,j) not in visited: count = 0 count = dfs (i,j,grid,visited) maxcount = max (count,maxcount) return maxcount
55
286. Walls and Gates You are given an m x n grid rooms initialized with these three possible values. -1 A wall or an obstacle. 0 A gate. INF Infinity means an empty room. We use the value 231 - 1 = 2147483647 to represent INF as you may assume that the distance to a gate is less than 2147483647. Fill each empty room with the distance to its nearest gate. If it is impossible to reach a gate, it should be filled with INF. Example 1: Input: rooms = [[2147483647,-1,0,2147483647],[2147483647,2147483647,2147483647,-1],[2147483647,-1,2147483647,-1],[0,-1,2147483647,2147483647]] Output: [[3,-1,0,1],[2,2,1,-1],[1,-1,2,-1],[0,-1,3,4]] Example 2: Input: rooms = [[-1]] Output: [[-1]]
Pattern: Graph Intuition: BFS is preferred in path finding problems. This is because whenever it visits a particular node, you can be sure that it is the least distance to visit it. You can traverse it like normal bfs. But just make one change, you need to process all the nodes in the current queue, since the distances should be calculated paralelly in batches. So keep a loop inside of the while loop. This will ensure parallel processing. Code: queue = deque () visited = set() for i in range(len(rooms)): for j in range(len(rooms[0])): if rooms[i][j] == 0: queue.append((i,j)) visited.add((i,j)) distance = 0 def helper (i,j): if i < 0 or j < 0 or i >= len(rooms) or j >= len(rooms[0]) or (i,j) in visited or rooms[i][j] != 2147483647 or rooms[i][j] == -1 or rooms[i][j] == 0: return visited.add((i,j)) queue.append((i,j)) while queue: for i in range(len(queue)): #print(queue) temp = queue.popleft() #print(temp) i = temp[0] j = temp[1] rooms[i][j] = distance helper(i+1,j) helper(i,j+1) helper(i-1,j) helper(i,j-1) distance += 1 return rooms
56
994. Rotting Oranges You are given an m x n grid where each cell can have one of three values: 0 representing an empty cell, 1 representing a fresh orange, or 2 representing a rotten orange. Every minute, any fresh orange that is 4-directionally adjacent to a rotten orange becomes rotten. Return the minimum number of minutes that must elapse until no cell has a fresh orange. If this is impossible, return -1. Example 1: Input: grid = [[2,1,1],[1,1,0],[0,1,1]] Output: 4 Example 2: Input: grid = [[2,1,1],[0,1,1],[1,0,1]] Output: -1 Explanation: The orange in the bottom left corner (row 2, column 0) is never rotten, because rotting only happens 4-directionally. Example 3: Input: grid = [[0,2]] Output: 0 Explanation: Since there are already no fresh oranges at minute 0, the answer is just 0. Constraints: m == grid.length n == grid[i].length 1 <= m, n <= 10 grid[i][j] is 0, 1, or 2.
Pattern: Graph Intuition: BFS problem because order matters here, its a layer by layer problem. Start with the rotten applies and expand. Important to empty the whole queue with a loop before we pop the next ones as we want to minute by minute simulation simultaneously. Also maintain a count of the total and rotten apples. and return the minutes if they're equal. Code: def orangesRotting(self, grid: List[List[int]]) -> int: self.queue = deque () self.visited = set() numoforanges = 0 rotten = 0 for i in range(len(grid)): for j in range(len(grid[0])): if grid[i][j] == 1 or grid[i][j] == 2: numoforanges += 1 if grid[i][j] == 2: self.queue.append((i,j)) self.visited.add((i,j)) rotten += 1 fresh = numoforanges - rotten if fresh == 0: return 0 #print(queue) print("num of ranges", numoforanges) def helper (i,j): nonlocal rotten if i < 0 or j < 0 or i >= len(grid) or j >= len(grid[0]) or (i,j) in self.visited or grid[i][j] == 2 or grid[i][j] == 0: return grid[i][j] = 2 rotten += 1 print(rotten) self.visited.add((i,j)) self.queue.append((i,j)) minutes = 0 while len(self.queue)>0: curr_level = len(self.queue) for count in range(curr_level): indexes = self.queue.popleft() i = indexes[0] j = indexes[1] helper(i+1,j) helper(i,j+1) helper(i-1,j) helper(i,j-1) minutes += 1 if rotten == numoforanges: return minutes-1 else: return -1
57
133. Clone Graph Given a reference of a node in a connected undirected graph. Return a deep copy (clone) of the graph. Each node in the graph contains a value (int) and a list (List[Node]) of its neighbors. class Node { public int val; public List neighbors; } Test case format: For simplicity, each node's value is the same as the node's index (1-indexed). For example, the first node with val == 1, the second node with val == 2, and so on. The graph is represented in the test case using an adjacency list. An adjacency list is a collection of unordered lists used to represent a finite graph. Each list describes the set of neighbors of a node in the graph. The given node will always be the first node with val = 1. You must return the copy of the given node as a reference to the cloned graph. Example 1: Input: adjList = [[2,4],[1,3],[2,4],[1,3]] Output: [[2,4],[1,3],[2,4],[1,3]] Explanation: There are 4 nodes in the graph. 1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4). 2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3). 3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4). 4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3). Example 2: Input: adjList = [[]] Output: [[]] Explanation: Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors. Example 3: Input: adjList = [] Output: [] Explanation: This an empty graph, it does not have any nodes. Constraints: The number of nodes in the graph is in the range [0, 100]. 1 <= Node.val <= 100 Node.val is unique for each node. There are no repeated edges and no self-loops in the graph. The Graph is connected and all nodes can be visited starting from the given node.
Pattern: Graph Intuition: Here the problem is that when you make a new node, and traverse the others, how do you keep the information that node has already been processed? you will keep making infinite copies of the same node this way. So make a dictionary and once you make a new node, keep a copy of new and old nodes there. so after the recursive call you can make an immediate check if it already there. Code: newcopy = {} def clone (oldnode): if not oldnode: return None if oldnode in newcopy: return newcopy[oldnode] newnode = Node (oldnode.val) newcopy[oldnode] = newnode for neightbor in oldnode.neighbors: newnode.neighbors.append(clone(neightbor)) return newnode return clone(node)
58
Pacific and atlantic ocean There is an m x n rectangular island that borders both the Pacific Ocean and Atlantic Ocean. The Pacific Ocean touches the island's left and top edges, and the Atlantic Ocean touches the island's right and bottom edges. The island is partitioned into a grid of square cells. You are given an m x n integer matrix heights where heights[r][c] represents the height above sea level of the cell at coordinate (r, c). The island receives a lot of rain, and the rain water can flow to neighboring cells directly north, south, east, and west if the neighboring cell's height is less than or equal to the current cell's height. Water can flow from any cell adjacent to an ocean into the ocean. Return a 2D list of grid coordinates result where result[i] = [ri, ci] denotes that rain water can flow from cell (ri, ci) to both the Pacific and Atlantic oceans. Example 1: Input: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]] Output: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]] Explanation: The following cells can flow to the Pacific and Atlantic oceans, as shown below: [0,4]: [0,4] -> Pacific Ocean [0,4] -> Atlantic Ocean [1,3]: [1,3] -> [0,3] -> Pacific Ocean [1,3] -> [1,4] -> Atlantic Ocean [1,4]: [1,4] -> [1,3] -> [0,3] -> Pacific Ocean [1,4] -> Atlantic Ocean [2,2]: [2,2] -> [1,2] -> [0,2] -> Pacific Ocean [2,2] -> [2,3] -> [2,4] -> Atlantic Ocean [3,0]: [3,0] -> Pacific Ocean [3,0] -> [4,0] -> Atlantic Ocean [3,1]: [3,1] -> [3,0] -> Pacific Ocean [3,1] -> [4,1] -> Atlantic Ocean [4,0]: [4,0] -> Pacific Ocean [4,0] -> Atlantic Ocean Note that there are other possible paths for these cells to flow to the Pacific and Atlantic oceans. Example 2: Input: heights = [[1]] Output: [[0,0]] Explanation: The water can flow from the only cell to the Pacific and Atlantic oceans.
Pattern: Graph Intuition: You need to run dfs on the edges of atlantic and pacific and then find elements for both. then return the common ones. Code: ows, cols = len(heights), len(heights[0]) pacific = set() atlantic = set() final_res = [] def dfs (i,j,res,prev): if i < 0 or j < 0 or i >= rows or j >= cols or (i, j) in res or heights[i][j] < prev: return res.add((i,j)) dfs (i+1,j,res,heights[i][j]) dfs (i,j+1,res,heights[i][j]) dfs (i-1,j,res,heights[i][j]) dfs (i,j-1,res,heights[i][j]) for row in range(rows): dfs(row,0,pacific,float('-inf')) dfs(row,cols-1,atlantic,float('-inf')) for col in range(cols): dfs(0,col,pacific,float('-inf')) dfs(rows-1,col,atlantic,float('-inf')) print("pacific and atlantic",pacific,atlantic) for tuple1 in pacific: for tuple2 in atlantic: if tuple1 == tuple2: final_res.append(tuple1) return final_res
59
130. Surrounded Regions You are given an m x n matrix board containing letters 'X' and 'O', capture regions that are surrounded: Connect: A cell is connected to adjacent cells horizontally or vertically. Region: To form a region connect every 'O' cell. Surround: The region is surrounded with 'X' cells if you can connect the region with 'X' cells and none of the region cells are on the edge of the board. To capture a surrounded region, replace all 'O's with 'X's in-place within the original board. You do not need to return anything. Example 1: Input: board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]] Output: [["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]] Explanation: In the above diagram, the bottom region is not captured because it is on the edge of the board and cannot be surrounded. Example 2: Input: board = [["X"]] Output: [["X"]] Constraints: m == board.length n == board[i].length 1 <= m, n <= 200 board[i][j] is 'X' or 'O'.
Pattern: Graph Intution: First do a dfs on the corner edges of the matrix and mark them as T. Then turn all of the rest of the elements to 'X'. Then reconvert all of the Ts to Os. Code: def solve(self, board: List[List[str]]) -> None: """ Do not return anything, modify board in-place instead. """ rows = len(board) cols = len(board[0]) def dfs1(i,j): if i < 0 or j < 0 or i >= rows or j >= cols or board[i][j] != 'O': return board[i][j] = 'T' dfs1(i+1,j) dfs1(i,j+1) dfs1(i-1,j) dfs1(i,j-1) def dfs2(i,j): if i < 0 or j < 0 or i >= rows or j >= cols or board[i][j] != 'T': return board[i][j] = "O" dfs2(i+1,j) dfs2(i,j+1) dfs2(i-1,j) dfs2(i,j-1) for i in range(rows): dfs1(i,0) dfs1(i,cols-1) for j in range(cols): dfs1(0,j) dfs1(rows-1,j) for i in range(rows): for j in range(cols): if board[i][j] == 'O': board[i][j] = 'X' for i in range(rows): dfs2(i,0) dfs2(i,cols-1) for j in range(cols): dfs2(0,j) dfs2(rows-1,j) return board
60
207. Course Schedule There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai. For example, the pair [0, 1], indicates that to take course 0 you have to first take course 1. Return true if you can finish all courses. Otherwise, return false. Example 1: Input: numCourses = 2, prerequisites = [[1,0]] Output: true Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible. Example 2: Input: numCourses = 2, prerequisites = [[1,0],[0,1]] Output: false Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible. Constraints: 1 <= numCourses <= 2000 0 <= prerequisites.length <= 5000 prerequisites[i].length == 2 0 <= ai, bi < numCourses All the pairs prerequisites[i] are unique.
Pattern: Graph Intution: This is a directed graph, and we want to find out whether we have cycles or not, and return the order. The best algorithm to do this is Topological Sorting using Kahn's Algorithm (BFS). We essentially put all the nodes with indegree == 0 in the queue, and then append it's related ones, thus processing the ones. If we resolve all the ones using this method, we can be sure there is no cycle. Code: Step 1: Build the graph in_degree = [0] * numCourses graph = defaultdict(list) for dest, src in prerequisites: graph[src].append(dest) in_degree[dest] += 1 # Step 2: Queue all courses with no prerequisites (in-degree 0) queue = deque([i for i in range(numCourses) if in_degree[i] == 0]) completed_courses = 0 # Step 3: Process the queue while queue: course = queue.popleft() completed_courses += 1 for neighbor in graph[course]: in_degree[neighbor] -= 1 if in_degree[neighbor] == 0: queue.append(neighbor) # Step 4: If we've completed all courses, return True return completed_courses == numCourses
61
210. Course Schedule II There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai. For example, the pair [0, 1], indicates that to take course 0 you have to first take course 1. Return the ordering of courses you should take to finish all courses. If there are many valid answers, return any of them. If it is impossible to finish all courses, return an empty array. Example 1: Input: numCourses = 2, prerequisites = [[1,0]] Output: [0,1] Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0. So the correct course order is [0,1]. Example 2: Input: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]] Output: [0,2,1,3] Explanation: There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is [0,2,1,3]. Example 3: Input: numCourses = 1, prerequisites = [] Output: [0] Constraints: 1 <= numCourses <= 2000 0 <= prerequisites.length <= numCourses * (numCourses - 1) prerequisites[i].length == 2 0 <= ai, bi < numCourses ai != bi All the pairs [ai, bi] are distinct.
Pattern: Graph Intution: This is a directed graph, and we want to find out whether we have cycles or not, and return the order. The best algorithm to do this is Topological Sorting using Kahn's Algorithm (BFS). We essentially put all the nodes with indegree == 0 in the queue, and then append it's related ones, thus processing the ones. If we resolve all the ones using this method, we can be sure there is no cycle. Code: class Solution: def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: # Step 1: Build the adjacency list and in-degree count graph = defaultdict(list) in_degree = [0] * numCourses for dest, src in prerequisites: graph[src].append(dest) in_degree[dest] += 1 # Step 2: Queue all courses with in-degree 0 (no prerequisites) queue = deque([i for i in range(numCourses) if in_degree[i] == 0]) order = [] # Step 3: Process the queue while queue: course = queue.popleft() order.append(course) for neighbor in graph[course]: in_degree[neighbor] -= 1 if in_degree[neighbor] == 0: queue.append(neighbor) # Step 4: Check if all courses are processed return order if len(order) == numCourses else []
62
261. Graph Valid Tree You have a graph of n nodes labeled from 0 to n - 1. You are given an integer n and a list of edges where edges[i] = [ai, bi] indicates that there is an undirected edge between nodes ai and bi in the graph. Return true if the edges of the given graph make up a valid tree, and false otherwise. Example 1: Input: n = 5, edges = [[0,1],[0,2],[0,3],[1,4]] Output: true Example 2: Input: n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]] Output: false Constraints: 1 <= n <= 2000 0 <= edges.length <= 5000 edges[i].length == 2 0 <= ai, bi < n ai != bi There are no self-loops or repeated edges. Seen this question in a real interview before?
Pattern: Graph Intuition: A tree is a type of undirected graph with two conditions: 1) It is acyclic 2) It is a single component So you can solve this in two ways since this is an undirected graph, a dfs with no parent visit or union find. Solving this with dfs here. Make an adjacency list, then call dfs on 0 and just dont visit the parent node. if the cur node is already in visited, you can return False Code: #Convert edge list to adjacency list adj = {i: [] for i in range(n)} for node1, node2 in edges: adj[node1].append(node2) adj[node2].append(node1) print(adj) visited = set() # DFS Function : make sure not to visit the parent. Also if you find something in visited already, return false def dfs (cur_node,prev_node): print("cur_node",cur_node) if cur_node in visited: print(cur_node,prev_node) return False visited.add(cur_node) for value in adj[cur_node]: if value != prev_node: if not dfs(value,cur_node): return False return True #Call dfs for all the different nodes return dfs(0, -1) and len(visited) == n
63
323. Number of Connected Components You have a graph of n nodes. You are given an integer n and an array edges where edges[i] = [ai, bi] indicates that there is an edge between ai and bi in the graph. Return the number of connected components in the graph. Example 1: Input: n = 5, edges = [[0,1],[1,2],[3,4]] Output: 2 Example 2: Input: n = 5, edges = [[0,1],[1,2],[2,3],[3,4]] Output: 1 Constraints: 1 <= n <= 2000 1 <= edges.length <= 5000 edges[i].length == 2 0 <= ai <= bi < n ai != bi
Pattern: Graph Intuition: Since this is an undirected graph, you can solve this using union find. The basic idea goes that for every edge in the node, you establish that edge and update parents accordingly. Every node starts with it being its own parent. in each union operation you first find its current parent. The finding operation recursively updates the parents to its uppermost parent and then returns it. if both of the node's parents is same, it means a loop has been found, return 0. Here, the n - k is the amount of components that there are. Think about it pictorally, originally there are n components, with each union we reduce one component. so the number of components must be n-k. Code: par = [i for i in range(n)] rank = [1] * n def find (n1): res = n1 while res != par[res]: par[res] = par[par[res]] res = par[res] return res def union (n1,n2): p1,p2 = find(n1),find(n2) if p1 == p2: return 0 if rank[p2] > rank[p1]: par[p1] = p2 rank[p2] += rank[p1] else: par[p2] = p1 rank[p1] += rank[p2] return 1 res = n for edge in edges: res -= union(edge[0],edge[1]) return res
64
684. Redundant Connection In this problem, a tree is an undirected graph that is connected and has no cycles. You are given a graph that started as a tree with n nodes labeled from 1 to n, with one additional edge added. The added edge has two different vertices chosen from 1 to n, and was not an edge that already existed. The graph is represented as an array edges of length n where edges[i] = [ai, bi] indicates that there is an edge between nodes ai and bi in the graph. Return an edge that can be removed so that the resulting graph is a tree of n nodes. If there are multiple answers, return the answer that occurs last in the input. Example 1: Input: edges = [[1,2],[1,3],[2,3]] Output: [2,3] Example 2: Input: edges = [[1,2],[2,3],[3,4],[1,4],[1,5]] Output: [1,4] Constraints: n == edges.length 3 <= n <= 1000 edges[i].length == 2 1 <= ai < bi <= edges.length ai != bi There are no repeated edges. The given graph is connected.
Pattern: Graph Intuition: Since this is an undirected graph, you can solve this using union find. The basic idea goes that for every edge in the node, you establish that edge and update parents accordingly. Every node starts with it being its own parent. in each union operation you first find its current parent. The finding operation recursively updates the parents to its uppermost parent and then returns it. if both of the node's parents is same, it means a loop has been found, return False. At the end return true. Here whichever node pairs makes a cycle (union operation sends false), return that node. Code: nodes = set() for node1,node2 in edges: if node1 not in nodes: nodes.add(node1) if node2 not in nodes: nodes.add(node2) n = len(nodes) rank = [1] * (n + 1) par = [i for i in range(n + 1)] def find(n1): res = n1 while res != par[res]: par[res] = par[par[res]] res = par[res] return res def union (n1,n2): p1,p2 = find(n1),find(n2) if p1 == p2: return False if rank[p1] > rank[p2]: par[p2] = p1 rank[p1] += rank[p2] else: par[p1] = p2 rank[p2] += rank[p1] return True for node1,node2 in edges: if not union(node1,node2): return [node1,node2]
65
20. Valid Parentheses Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid. An input string is valid if: Open brackets must be closed by the same type of brackets. Open brackets must be closed in the correct order. Every close bracket has a corresponding open bracket of the same type. Example 1: Input: s = "()" Output: true Example 2: Input: s = "()[]{}" Output: true Example 3: Input: s = "(]" Output: false Example 4: Input: s = "([])" Output: true Constraints: 1 <= s.length <= 104 s consists of parentheses only '()[]{}'.
Pattern: Stack Intution: Maintain a dictionary with keys being the opening brackets and values being the closing brackets. Then loop through all elements, push the opening brackets else check if stack is empty and pop. once you pop, compare get the corresponding opening bracket to what just popped and compare that to the current bracket. then determine whether to return false. Code: def isValid(self, s: str) -> bool: brackets = {'(':')','{':'}','[':']'} stack = [] for i in range(len(s)): if s[i] in brackets.keys(): stack.append(s[i]) else: if stack: poppedchar = stack.pop() else: return False if brackets[poppedchar] != s[i]: print(s[i],poppedchar,"Returning false") return False return True if len(stack) == 0 else False
66
155. Min Stack Design a stack that supports push, pop, top, and retrieving the minimum element in constant time. Implement the MinStack class: MinStack() initializes the stack object. void push(int val) pushes the element val onto the stack. void pop() removes the element on the top of the stack. int top() gets the top element of the stack. int getMin() retrieves the minimum element in the stack. You must implement a solution with O(1) time complexity for each function. Example 1: Input ["MinStack","push","push","push","getMin","pop","top","getMin"] [[],[-2],[0],[-3],[],[],[],[]] Output [null,null,null,null,-3,null,0,-2] Explanation MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.getMin(); // return -3 minStack.pop(); minStack.top(); // return 0 minStack.getMin(); // return -2 Constraints: -231 <= val <= 231 - 1 Methods pop, top and getMin operations will always be called on non-empty stacks. At most 3 * 104 calls will be made to push, pop, top, and getMin.
Pattern: Stack Intuition: Because the minimum is an extra problem here, simply push the current minimum with the element you are wanting in the stack. Push both as a tuple. Code: def __init__(self): self.stack = [] def push(self, val: int) -> None: if len(self.stack) == 0: self.stack.append((val,val)) return prevmin = self.stack[-1][1] if val <= prevmin: self.stack.append((val,val)) else: self.stack.append((val,prevmin)) return def pop(self) -> None: self.stack.pop() def top(self) -> int: temp = self.stack[-1][0] return temp def getMin(self) -> int: temp = self.stack[-1][1] return temp
67
150. Evaluate Reverse Polish Notation You are given an array of strings tokens that represents an arithmetic expression in a Reverse Polish Notation. Evaluate the expression. Return an integer that represents the value of the expression. Note that: The valid operators are '+', '-', '*', and '/'. Each operand may be an integer or another expression. The division between two integers always truncates toward zero. There will not be any division by zero. The input represents a valid arithmetic expression in a reverse polish notation. The answer and all the intermediate calculations can be represented in a 32-bit integer. Example 1: Input: tokens = ["2","1","+","3","*"] Output: 9 Explanation: ((2 + 1) * 3) = 9 Example 2: Input: tokens = ["4","13","5","/","+"] Output: 6 Explanation: (4 + (13 / 5)) = 6 Example 3: Input: tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"] Output: 22 Explanation: ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = ((10 * (6 / (12 * -11))) + 17) + 5 = ((10 * (6 / -132)) + 17) + 5 = ((10 * 0) + 17) + 5 = (0 + 17) + 5 = 17 + 5 = 22 Constraints: 1 <= tokens.length <= 104 tokens[i] is either an operator: "+", "-", "*", or "/", or an integer in the range [-200, 200].
Pattern: Stack Intution: loop through the equation. Check if the element is not an operation, then push it in the stack (its a number). if the current element is a notation, then pop both and do an operation and then push the result back in the stack. if b is popped and then a is popped then do (a (operation) b). Code: for token in tokens: if token not in "+-*/": stack.append(int(token)) else: temp2 = stack.pop() temp1 = stack.pop() if token == '+': stack.append(temp1 + temp2) elif token == '-': stack.append(temp1 - temp2) elif token == '*': stack.append(temp1 * temp2) elif token == '/': stack.append(int(temp1 / temp2)) return stack.pop()
68
22. Generate Parentheses Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses. Example 1: Input: n = 3 Output: ["((()))","(()())","(())()","()(())","()()()"] Example 2: Input: n = 1 Output: ["()"] Constraints: 1 <= n <= 8
Pattern: Stack and Recursion Intuition: You need to realise that starting brackets can be upto 3, and closing brackets can be upto start. But we need something to keep a track and pop out the used letter for the backtracking, which is where a stack is useful. The base condition would be when start and close and n are equal. Code: start = 0 close = 0 res = [] stack = [] def generate (start, close): if start == close == n: res.append("".join(stack)) return if start < n: stack.append('(') generate(start + 1, close) stack.pop() if close < start: stack.append(')') generate(start,close + 1) stack.pop() generate(start,close) return res
69
704. Binary Search Given an array of integers nums which is sorted in ascending order, and an integer target, write a function to search target in nums. If target exists, then return its index. Otherwise, return -1. You must write an algorithm with O(log n) runtime complexity. Example 1: Input: nums = [-1,0,3,5,9,12], target = 9 Output: 4 Explanation: 9 exists in nums and its index is 4 Example 2: Input: nums = [-1,0,3,5,9,12], target = 2 Output: -1 Explanation: 2 does not exist in nums so return -1 Constraints: 1 <= nums.length <= 104 -104 < nums[i], target < 104 All the integers in nums are unique. nums is sorted in ascending order.
Pattern: Binary Search Intuition: Standard Binary search problem. It has a loop which runs till left <= right. calculate the mid as the floor division of left + right. see if the mid is the answer else update left or right. Code: def search(self, nums: List[int], target: int) -> int: left = 0 right = len(nums) - 1 while left <= right: mid = (left + right) // 2 if nums[mid] == target: return mid elif target < nums[mid]: right = mid - 1 else: left = mid + 1 return -1
70
Search a 2D Matrix You are given an m x n integer matrix matrix with the following two properties: Each row is sorted in non-decreasing order. The first integer of each row is greater than the last integer of the previous row. Given an integer target, return true if target is in matrix or false otherwise. You must write a solution in O(log(m * n)) time complexity. Example 1: Input: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3 Output: true Example 2: Input: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13 Output: false Constraints: m == matrix.length n == matrix[i].length 1 <= m, n <= 100 -104 <= matrix[i][j], target <= 104
Intution: You want to first identify the row in which the number exists. So the algorithm checks the if the target is bigger than the last element or smaller than the first element to update the rows. if it is range, the loop is broken, and row = (top + bottom) // 2. Also check a condition where it was never found. Then do a normal binary search on the row to find the element. Code: m, n = len(matrix), len(matrix[0]) # Step 1: Binary search to find the right row top, bottom = 0, m - 1 while top <= bottom: mid_row = (top + bottom) // 2 if target < matrix[mid_row][0]: bottom = mid_row - 1 elif target > matrix[mid_row][-1]: top = mid_row + 1 else: break if not (top <= bottom): return False row = (top + bottom) // 2 # Step 2: Binary search in the identified row left, right = 0, n - 1 while left <= right: mid_col = (left + right) // 2 if matrix[row][mid_col] == target: return True elif matrix[row][mid_col] < target: left = mid_col + 1 else: right = mid_col - 1 return False
71
875. Koko Eating Bananas Koko loves to eat bananas. There are n piles of bananas, the ith pile has piles[i] bananas. The guards have gone and will come back in h hours. Koko can decide her bananas-per-hour eating speed of k. Each hour, she chooses some pile of bananas and eats k bananas from that pile. If the pile has less than k bananas, she eats all of them instead and will not eat any more bananas during this hour. Koko likes to eat slowly but still wants to finish eating all the bananas before the guards return. Return the minimum integer k such that she can eat all the bananas within h hours. Example 1: Input: piles = [3,6,7,11], h = 8 Output: 4 Example 2: Input: piles = [30,11,23,4,20], h = 5 Output: 30 Example 3: Input: piles = [30,11,23,4,20], h = 6 Output: 23 Constraints: 1 <= piles.length <= 104 piles.length <= h <= 109 1 <= piles[i] <= 109
Pattern: Binary Search Intuition: You have 1 to n possibilities of k (eating speed). But to determine which is the right speed, you would need to do binary search. For each iteration, you need to calculate the total hours, check if it is less than equal to the hours we need. Remember <= because we are replacing the = condition here since we want to find the smallest value. Code: class Solution: def minEatingSpeed(self, piles: List[int], h: int) -> int: left, right = 1, max(piles) answer = right while left <= right: mid = (left + right) // 2 total_hours = sum(ceil(pile / mid) for pile in piles) if total_hours <= h: answer = mid right = mid - 1 # Try to find a smaller k else: left = mid + 1 # Need a larger k return answer
72
33. Search in Rotated Sorted Array There is an integer array nums sorted in ascending order (with distinct values). Prior to being passed to your function, nums is possibly rotated at an unknown pivot index k (1 <= k < nums.length) such that the resulting array is [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]] (0-indexed). For example, [0,1,2,4,5,6,7] might be rotated at pivot index 3 and become [4,5,6,7,0,1,2]. Given the array nums after the possible rotation and an integer target, return the index of target if it is in nums, or -1 if it is not in nums. You must write an algorithm with O(log n) runtime complexity. Example 1: Input: nums = [4,5,6,7,0,1,2], target = 0 Output: 4 Example 2: Input: nums = [4,5,6,7,0,1,2], target = 3 Output: -1 Example 3: Input: nums = [1], target = 0 Output: -1 Constraints: 1 <= nums.length <= 5000 -104 <= nums[i] <= 104 All values of nums are unique. nums is an ascending array that is possibly rotated. -104 <= target <= 104
Pattern: Binary Search Intuition: You could be in the right sorted array or the left sorted array. Make checks for that on the basis on l,m,r. Then make checks for whether it is in the right/left sorted array or not. Update left and right accordingly. Code: class Solution: def search(self, nums: List[int], target: int) -> int: l = 0 r = len(nums) - 1 while r>=l: m = (l + r) // 2 if target == nums[m]: return m #Left sorted array if nums[l] <= nums[m]: if target > nums[m] or target < nums[l]: l = m + 1 else: r = m - 1 # right sorted array else: if target < nums[m] or target > nums[r]: r = m - 1 else: l = m + 1 return -1
73
206. Reverse Linked List Given the head of a singly linked list, reverse the list, and return the reversed list. Example 1: Input: head = [1,2,3,4,5] Output: [5,4,3,2,1] Example 2: Input: head = [1,2] Output: [2,1] Example 3: Input: head = [] Output: [] Constraints: The number of nodes in the list is the range [0, 5000]. -5000 <= Node.val <= 5000 Follow up: A linked list can be reversed either iteratively or recursively. Could you implement both?
# class ListNode: Pattern: Linked List Intuition: start with a cur as the head and prev as None. the while loop should run until cur becomes None. Make sure to store the nextnode first (not the pointer in temp, but the node itself). Process the next pointer, then update the prev and cur. Code: Definition for singly-linked list. # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: # Start with the first node and prev as None cur = head prev = None # Condition should be while cur while(cur): # First store the nextnode nextnode = cur.next #Update the pointer cur.next = prev # Update prev and cur prev = cur cur = nextnode return prev
74
21. Merge Two Sorted Lists You are given the heads of two sorted linked lists list1 and list2. Merge the two lists into one sorted list. The list should be made by splicing together the nodes of the first two lists. Return the head of the merged linked list. Example 1: Input: list1 = [1,2,4], list2 = [1,3,4] Output: [1,1,2,3,4,4] Example 2: Input: list1 = [], list2 = [] Output: [] Example 3: Input: list1 = [], list2 = [0] Output: [0] Constraints: The number of nodes in both lists is in the range [0, 50]. -100 <= Node.val <= 100 Both list1 and list2 are sorted in non-decreasing order.
Pattern: Linked Lists Intuition: You need to use three pointers, a dummy pointer, one for each list. The next of the dummy pointer is what you're going to return. you need to check which value is smaller and update the dummy and the corresponding pointer you used. At the end add the unfinished lists. Code: def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: cur1 = list1 cur2 = list2 temp = ListNode(val = 0) cur = temp while cur1 and cur2: if cur1.val >= cur2.val: cur.next = cur2 cur2 = cur2.next else: cur.next = cur1 cur1 = cur1.next cur = cur.next if cur1: cur.next = cur1 if cur2: cur.next = cur2 return temp.next
75
143. Reorder List Solved Medium Topics Companies You are given the head of a singly linked-list. The list can be represented as: L0 → L1 → … → Ln - 1 → Ln Reorder the list to be on the following form: L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … You may not modify the values in the list's nodes. Only nodes themselves may be changed. Example 1: Input: head = [1,2,3,4] Output: [1,4,2,3] Example 2: Input: head = [1,2,3,4,5] Output: [1,5,2,4,3] Constraints: The number of nodes in the list is in the range [1, 5 * 104]. 1 <= Node.val <= 1000
Intuition: Three parts to the algorithm. 1) Find the middle pointer. 2) Reverse the second part of the list. 3) Combine both lists 1) Initiate the slow pointer at head and fast at head.next. The while conditions should be while fast and fast.next 2) Reverse the second list. 3) First save the next two pointers in temp, update the next pointers, then update the first and second Code: lass Solution: def reorderList(self, head: Optional[ListNode]) -> None: """ Do not return anything, modify head in-place instead. """ #Find the middle pointer slow = head fast = head.next while fast and fast.next: slow = slow.next fast = fast.next.next # Reverse the second list second = slow.next slow.next = None prev = None while second: tmp = second.next second.next = prev prev = second second = tmp # combine both lists first,second = head,prev while second: tmp1,tmp2 = first.next, second.next first.next = second second.next = tmp1 first = tmp1 second = tmp2
76
19. Remove Nth Node From End of List Solved Medium Topics Companies Hint Given the head of a linked list, remove the nth node from the end of the list and return its head. Example 1: Input: head = [1,2,3,4,5], n = 2 Output: [1,2,3,5] Example 2: Input: head = [1], n = 1 Output: [] Example 3: Input: head = [1,2], n = 1 Output: [1] Constraints: The number of nodes in the list is sz. 1 <= sz <= 30 0 <= Node.val <= 100 1 <= n <= sz Follow up: Could you do this in one pass?
Intuition: You need to move one pointer for n times. then another pointer until the first one reaches None. There are some edge cases which are better handled if you start from dummy and move the pointer n + 1 times. Code: dummy = ListNode(0, head) # dummy node to simplify edge cases first = second = dummy # Move first pointer n + 1 steps to maintain a gap for _ in range(n + 1): first = first.next # Move both pointers until first reaches the end while first: first = first.next second = second.next # Skip the desired node second.next = second.next.next return dummy.next
77
138. Copy List with Random Pointer Solved Medium Topics Companies Hint A linked list of length n is given such that each node contains an additional random pointer, which could point to any node in the list, or null. Construct a deep copy of the list. The deep copy should consist of exactly n brand new nodes, where each new node has its value set to the value of its corresponding original node. Both the next and random pointer of the new nodes should point to new nodes in the copied list such that the pointers in the original list and copied list represent the same list state. None of the pointers in the new list should point to nodes in the original list. For example, if there are two nodes X and Y in the original list, where X.random --> Y, then for the corresponding two nodes x and y in the copied list, x.random --> y. Return the head of the copied linked list. The linked list is represented in the input/output as a list of n nodes. Each node is represented as a pair of [val, random_index] where: val: an integer representing Node.val random_index: the index of the node (range from 0 to n-1) that the random pointer points to, or null if it does not point to any node. Your code will only be given the head of the original linked list. Example 1: Input: head = [[7,null],[13,0],[11,4],[10,2],[1,0]] Output: [[7,null],[13,0],[11,4],[10,2],[1,0]] Example 2: Input: head = [[1,1],[2,1]] Output: [[1,1],[2,1]] Example 3: Input: head = [[3,null],[3,0],[3,null]] Output: [[3,null],[3,0],[3,null]] Constraints: 0 <= n <= 1000 -104 <= Node.val <= 104 Node.random is null or is pointing to some node in the linked list.
Intuition: Two basic parts to this solution. 1) Make new nodes and copy the node in a dictionary which also has None:None. 2) Retrieve the node and and assign random and next pointers to it. Code: oldtonew = {None:None} oldnode = head while (oldnode): newnode = Node (oldnode.val) oldtonew[oldnode] = newnode oldnode = oldnode.next oldnode = head while (oldnode): newnode = oldtonew[oldnode] newnode.next = oldtonew[oldnode.next] newnode.random = oldtonew[oldnode.random] oldnode = oldnode.next return oldtonew[head]
78
2. Add Two Numbers You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list. You may assume the two numbers do not contain any leading zero, except the number 0 itself. Example 1: Input: l1 = [2,4,3], l2 = [5,6,4] Output: [7,0,8] Explanation: 342 + 465 = 807. Example 2: Input: l1 = [0], l2 = [0] Output: [0] Example 3: Input: l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9] Output: [8,9,9,9,0,0,0,1] Constraints: The number of nodes in each linked list is in the range [1, 100]. 0 <= Node.val <= 9 It is guaranteed that the list represents a number that does not have leading zeros.
Pattern: Linked List Intuition: The algorithm's flow should be, take the values of the both the lists in two numbers, or take the value 0. Take the sum and calculate the value and carry. Use the dummy method to create the linked list. and move next in the l1 and l2 lists. Code: def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]: dummy = ListNode() cur = dummy carry = 0 while l1 or l2 or carry: v1 = l1.val if l1 else 0 v2 = l2.val if l2 else 0 val = v1 + v2 + carry carry = val//10 val = val % 10 cur.next = ListNode(val) cur = cur.next l1 = l1.next if l1 else None l2 = l2.next if l2 else None return dummy.next
79
226. Invert Binary Tree Given the root of a binary tree, invert the tree, and return its root. Example 1: Input: root = [4,2,7,1,3,6,9] Output: [4,7,2,9,6,3,1] Example 2: Input: root = [2,1,3] Output: [2,3,1] Example 3: Input: root = [] Output: [] Constraints: The number of nodes in the tree is in the range [0, 100]. -100 <= Node.val <= 100
Pattern: Trees Intuition: Best approach is recursive approach. Base condition is if root is None. Assign roots to reverse positions using temp method. and cursively call left and right. Return root. Code: def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: if root == None: return left = root.left root.left = root.right root.right = left self.invertTree(root.left) self.invertTree(root.right) return root
80
104. Maximum Depth of Binary Tree Given the root of a binary tree, return its maximum depth. A binary tree's maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node. Example 1: Input: root = [3,9,20,null,null,15,7] Output: 3 Example 2: Input: root = [1,null,2] Output: 2 Constraints: The number of nodes in the tree is in the range [0, 104]. -100 <= Node.val <= 100
Pattern: Tree Intuition: Base condition is if root is None, return 0. do recursive searches and return maximum of both sides. Code: class Solution: def maxDepth(self, root: Optional[TreeNode]) -> int: if root == None: return 0 depthleft = self.maxDepth(root.left) + 1 depthright = self.maxDepth(root.right) + 1 return max(depthleft,depthright)
81
146. LRU Cache Design a data structure that follows the constraints of a Least Recently Used (LRU) cache. Implement the LRUCache class: LRUCache(int capacity) Initialize the LRU cache with positive size capacity. int get(int key) Return the value of the key if the key exists, otherwise return -1. void put(int key, int value) Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key. The functions get and put must each run in O(1) average time complexity. Example 1: Input ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"] [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]] Output [null, null, null, 1, null, -1, null, -1, 3, 4] Explanation LRUCache lRUCache = new LRUCache(2); lRUCache.put(1, 1); // cache is {1=1} lRUCache.put(2, 2); // cache is {1=1, 2=2} lRUCache.get(1); // return 1 lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3} lRUCache.get(2); // returns -1 (not found) lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3} lRUCache.get(1); // return -1 (not found) lRUCache.get(3); // return 3 lRUCache.get(4); // return 4 Constraints: 1 <= capacity <= 3000 0 <= key <= 104 0 <= value <= 105 At most 2 * 105 calls will be made to get and put.
Pattern: Linked List Intuition: On a high level, we are going to use two dummy pointers, left and right as pointers to the LRU and MRU pointers in a doubly linked list. We are also going to use a cache which has information about whether that key exists in the linked list for easy lookup, and the value will be the actual value of the node. Actual algorithm overview: 1) Node class constructor that has key, val, prev, next 2) LRU Cache should have the constructor that starts capacity cache, left and right nodes. Connect left and right pointers to each other as there's no node yet. 3) Get Function: If key is in cache dict, then remove and insert. This will call functions that will remove it from the node connected to left and put it to the node connected to the right. else return -1 4) Put Function: If key in cache, then remove first. Then make a new node and put it in the cache dict. and call the insert function. There is an extra condition where cache exceeds the limit given, then get the lru element and remove it from the list by invoking the remove function and delete it from the cache. Code: class Node: def __init__(self,key,val): self.key = key self.val = val self.prev = self.next = None class LRUCache: def __init__(self, capacity: int): self.cap = capacity self.cache = {} self.left, self.right = Node(0,0), Node(0,0) self.left.next = self.right self.right.prev = self.left def remove(self,node): prev,nxt = node.prev, node.next prev.next = nxt nxt.prev = prev def insert(self,node): prev,nxt = self.right.prev,self.right prev.next = nxt.prev = node node.next, node.prev = nxt,prev def get(self, key: int) -> int: if key in self.cache: self.remove(self.cache[key]) self.insert(self.cache[key]) return self.cache[key].val return -1 def put(self, key: int, value: int) -> None: if key in self.cache: self.remove(self.cache[key]) self.cache[key] = Node(key,value) self.insert(self.cache[key]) if len(self.cache) > self.cap: lru = self.left.next self.remove(lru) del self.cache[lru.key]
82
110. Balanced Binary Tree Given a binary tree, determine if it is height-balanced. Example 1: Input: root = [3,9,20,null,null,15,7] Output: true Example 2: Input: root = [1,2,2,3,3,null,null,4,4] Output: false Example 3: Input: root = [] Output: true Constraints: The number of nodes in the tree is in the range [0, 5000]. -104 <= Node.val <= 104
Pattern: Tree Intuition: Base case is if the node is None, return 0. left and right are variables that will hold the max depth on left and right, calculate the diff using that and if its more than 1 then set balanced to be False. return max (left,right) + 1. class Solution: balanced = True def isBalanced(self, root: Optional[TreeNode]) -> bool: def helper (root): if root == None: return 0 left = helper(root.left) right = helper(root.right) diff = abs (left - right) print(root.val,left,right,diff) if diff > 1: self.balanced = False return max (left,right) + 1 helper (root) return self.balanced
83
100. Same Tree Solved Easy Topics Companies Given the roots of two binary trees p and q, write a function to check if they are the same or not. Two binary trees are considered the same if they are structurally identical, and the nodes have the same value. Example 1: Input: p = [1,2,3], q = [1,2,3] Output: true Example 2: Input: p = [1,2], q = [1,null,2] Output: false Example 3: Input: p = [1,2,1], q = [1,1,2] Output: false Constraints: The number of nodes in both trees is in the range [0, 100]. -104 <= Node.val <= 104
Intuition: Base case is if both p and q are None. If p or q is None while the other is not, then return fasle. If both values dont match, return false. call both left and right nodes of p and q and return true only if both are true. Code: def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool: if p == None and q == None: return True if (p == None and q != None) or (p != None and q == None): return False if p.val != q.val: return False return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
84
572. Subtree of Another Tree Solved Easy Topics Companies Hint Given the roots of two binary trees root and subRoot, return true if there is a subtree of root with the same structure and node values of subRoot and false otherwise. A subtree of a binary tree tree is a tree that consists of a node in tree and all of this node's descendants. The tree tree could also be considered as a subtree of itself. Example 1: Input: root = [3,4,5,1,2], subRoot = [4,1,2] Output: true Example 2: Input: root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2] Output: false Constraints: The number of nodes in the root tree is in the range [1, 2000]. The number of nodes in the subRoot tree is in the range [1, 1000]. -104 <= root.val <= 104 -104 <= subRoot.val <= 104
Pattern: Tree Intuition: Here, you need to check if the tree iterates for every single node in the root. This can be done by the help of two functions: 1) Traverse: Base condition: if r is none then return false. for each node, call isSameTree. then call both left and right nodes. return the OR operation of both, since the base is false, it needs to be turned into true just once. 2) isSameTree: Base condition is both are None, then return true. If either is false and other is not then return false. if both values are not same then return false. The return condition should traverse left of both and right of both and return AND operation of both. Since once it's proven false once, we want to keep returning that. Code: class Solution: def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool: def isSameTree(s, t): if not s and not t: return True if not s or not t: return False if s.val != t.val: return False return isSameTree(s.left, t.left) and isSameTree(s.right, t.right) def traverse(r): if not r: return False if isSameTree(r, subRoot): return True return traverse(r.left) or traverse(r.right) return traverse(root)
85
235. Lowest Common Ancestor of a Binary Search Tree Given a binary search tree (BST), find the lowest common ancestor (LCA) node of two given nodes in the BST. According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).” Example 1: Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 Output: 6 Explanation: The LCA of nodes 2 and 8 is 6. Example 2: Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 Output: 2 Explanation: The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition. Example 3: Input: root = [2,1], p = 2, q = 1 Output: 2 Constraints: The number of nodes in the tree is in the range [2, 105]. -109 <= Node.val <= 109 All Node.val are unique. p != q p and q will exist in the BST.
Pattern: Binary Search Tree Intuition: Since its a binary search tree, the node that you want is the node where the p value is to left of a node and q to the right. That is the 'split' node. So you write a function which goes through the tree. if p and q are both less than root, then move cur to left. Vice versa. But else, return current node. Code: class Solution: LCA = None def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': cur = root while cur: if p.val < cur.val and q.val < cur.val: cur = cur.left elif p.val > cur.val and q.val > cur.val: cur = cur.right else: return cur
86
102. Binary Tree Level Order Traversal Given the root of a binary tree, return the level order traversal of its nodes' values. (i.e., from left to right, level by level). Example 1: Input: root = [3,9,20,null,null,15,7] Output: [[3],[9,20],[15,7]] Example 2: Input: root = [1] Output: [[1]] Example 3: Input: root = [] Output: [] Constraints: The number of nodes in the tree is in the range [0, 2000]. -1000 <= Node.val <= 1000
Intuition: This basically graph traversal by BFS. Do a iterative approach with queue. in the queue, process all the nodes in the queue present with a for loop, and append its children. Note: Make sure not to iterate over the nodes but the size, this is because the nodes may change with popping. Code: class Solution: def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: if not root: return [] queue = [] queue.append(root) queue = deque (queue) res = [] while queue: level = [] size = len (queue) for i in range (size): node = queue.popleft() level.append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) res.append(level) return res
87
199. Binary Tree Right Side View Given the root of a binary tree, imagine yourself standing on the right side of it, return the values of the nodes you can see ordered from top to bottom. Example 1: Input: root = [1,2,3,null,5,null,4] Output: [1,3,4] Explanation: Example 2: Input: root = [1,2,3,4,null,null,null,5] Output: [1,3,4,5] Explanation: Example 3: Input: root = [1,null,3] Output: [1,3] Example 4: Input: root = [] Output: [] Constraints: The number of nodes in the tree is in the range [0, 100]. -100 <= Node.val <= 100
Pattern: Tree Intuition: It is a graph BFS traversal problem, where the last element in each graph traversal is the node that we want, so in the for loop for an iterative queue based bfs traversal, whenever we find i == size, we append that element. Code: if not root: return [] queue = [] queue.append(root) queue = deque (queue) res = [] while queue: size = len(queue) for i in range (size): node = queue.popleft() if i == size - 1: res.append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) return res
88