In this project, I will try to solve 75 Leetcode questions as listed in this link.

1. Two Sum

Description

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.

Example 1:

Input: nums = [2,7,11,15], target = 9
Output: [0,1]
Output: Because nums[0] + nums[1] == 9, we return [0, 1].

Example 2:

Input: nums = [3,2,4], target = 6
Output: [1,2]
Solution
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        dic = {}
        for i, num in enumerate(nums):
            if num in dic:
                return [dic[num], i]
            else:
                dic[target - num] = i

121. Best Time to Buy and Sell Stock

Description

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.
Solution

Two pointers starting from both ends (i.e., move left or right pointer based on which side has a better profit)

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        l, r = 0, len(prices)-1
        minL = prices[l]
        maxR = prices[r]

        while l<r: 
            if prices[l] < minL: 
                minL = prices[l]

            if prices[r] > maxR: 
                maxR = prices[r]

            if prices[r-1] - minL > maxR - prices[l+1]: 
                r -= 1 
            else: 
                l += 1 

        return maxR - minL if maxR >= minL else 0

Two pointers starting from right (i.e., find highest price and then find the lowest price before it, fastest solution)

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        max_profit = 0
        max_price = 0
        for i in range(len(prices)-1, -1, -1):
            if prices[i] > max_price:
                max_price = prices[i]
            else:
                max_profit = max(max_profit, max_price - prices[i])
        return max_profit

Two pointers starting from left (i.e., find lowest price and then find the highest price after it)

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        min_price = float('inf')
        max_profit = 0
        for i in range(len(prices)):
            if prices[i] < min_price:
                min_price = prices[i]
            else:
                max_profit = max(max_profit, prices[i] - min_price)
        return max_profit

217. Contains Duplicate

Description

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.

Solution
class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        dic = {}
        for num in nums:
            if num in dic:
                return True
            else:
                dic[num] = 1

238. Product of Array Except Self

Description

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]
Solution

We will use two arrays to store the product of all elements to the left and right of each element. Then, we will multiply the two arrays to get the final result.

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        left = [1] * len(nums)
        right = [1] * len(nums)
        for i in range(1, len(nums)):
            left[i] = left[i-1] * nums[i-1]
            right[len(nums)-1-i] = right[len(nums)-i] * nums[len(nums)-i]
        return [left[i] * right[i] for i in range(len(nums))]

53. Maximum Subarray

Description

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

A subarray is a contiguous part of an array.

Example 1:

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

Example 2:

Input: nums = [1]
Output: 1

Example 3:

Input: nums = [5,4,-1,7,8]
Output: 23
Solution

Use Kadane’s algorithm to find the maximum subarray sum. The idea is to keep track of the maximum sum of subarrays ending at each index. If the sum is negative, we reset it to 0. Refer to this video and https://www.geeksforgeeks.org/largest-sum-contiguous-subarray/ article for more details.

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        res = nums[0]
        accsum = nums[0]
        for n in nums[1:]: 
            accsum += n
            
            if accsum <= n:
                accsum = n 
                        
            if accsum >= res: 
                res = accsum 

        return res 

Dynamic programming solution (from greeksforgeeks)

Observation: DP[i] = max(DP[i-1] + nums[i], nums[i])

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        DP = [0] * len(nums)
        DP[0] = nums[0]
        for i in range(1, len(nums)):
            DP[i] = max(DP[i-1] + nums[i], nums[i])
        return max(DP)

152. Maximum Product Subarray

Description

Given an integer array nums, find a contiguous non-empty subarray within the array that has the largest product, and return the product.

It is guaranteed 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.
Solution
class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        res = max(nums)
        cumMax = 1 
        cumMin = 1 
        
        for n in nums: 
            
            if n == 0: 
                cumMax = 1 
                cumMin = 1 
                continue 
            
            temp = max(cumMax * n, cumMin * n, n)
            cumMin = min(cumMax * n, cumMin * n, n)
            cumMax = temp 
            
            res = max(res, cumMax)
            
        return res

153. Find Minimum in Rotated Sorted Array

Description

Suppose an array of length n sorted in ascending order is rotated between 1 and n times. For example, the array nums = [0,1,2,4,5,6,7] might become:

  • [4,5,6,7,0,1,2] if it was rotated 4 times.
  • [0,1,2,4,5,6,7] if it was rotated 7 times.

Notice that rotating an array [a[0], a[1], a[2], ..., a[n-1]] 1 time results in the array [a[n-1], a[0], a[1], a[2], ..., a[n-2]].

Given the sorted rotated array nums of unique elements, return the minimum element of this array.

You must write an algorithm that runs in O(log n) time.

Example 1:

Input: nums = [3,4,5,1,2]
Output: 1
Explanation: The original array was [1,2,3,4,5] rotated 3 times.

Example 2:

Input: nums = [4,5,6,7,0,1,2]
Output: 0
Explanation: The original array was [0,1,2,4,5,6,7] and it was rotated 4 times.

Example 3:

Input: nums = [11,13,15,17]
Output: 11
Explanation: The original array was [11,13,15,17] and it was rotated 4 times.
Solution
class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums)-1

        if nums[left] < nums[right]:
            return nums[left]
        
        while left < right:
            mid = (left + right) // 2
            if nums[mid] > nums[right]:
                left = mid + 1
            else:
                right = mid
        
        return nums[left]

33. Search in Rotated Sorted Array

Description

There is an integer array nums sorted in ascending order (with distinct values).

Prior to being passed to your function, nums is rotated at an unknown pivot index k (0 <= 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 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
Solution
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums)-1

        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == target:
                return mid
            elif nums[mid] >= nums[left]:
                if nums[left] <= target < nums[mid]:
                    right = mid -1
                else:
                    left = mid + 1
            else:
                if nums[mid] < target <= nums[right]:
                    left = mid + 1
                else:
                    right = mid - 1
        
        return -1

15. 3Sum

Description

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]]

Example 2:

Input: nums = [0,1,1]
Output: []
Solution
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        if len(nums) == 3:
            return [nums] if sum(nums) == 0 else []
        
        nums.sort()

        n = len(nums)
        res = []

        for i in range(n):
            if i > 0 and nums[i] == nums[i-1]:
                continue
            
            left, right = i+1, n-1

            while left < right:
                sum3 = nums[i] + nums[left] + nums[right]
                if sum3 == 0:
                    res.append([nums[i], nums[left], nums[right]])
                    left += 1
                    right -= 1
                    while left < right and nums[left] == nums[left-1]:
                        left += 1
                    while left < right and nums[right] == nums[right+1]:
                        right -= 1
                elif sum3 < 0:
                    left += 1
                else:
                    right -= 1
        
        return res

11. Container With Most Water

Description

Given n non-negative integers a1, a2, ..., an , where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of the line i is at (i, ai) and (i, 0). Find two lines, which, together with the x-axis forms a container, such that the container contains the most water.

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
Solution
class Solution:
    def maxArea(self, height: List[int]) -> int:
        left, right = 0, len(height)-1
        res = 0
        while left < right:
            res = max(res, min(height[left], height[right]) * (right - left))
            if height[left] < height[right]:
                left += 1
            else:
                right -= 1
        return res

70. Climbing Stairs

Description

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
Solution

Key Observation: DP[i] = DP[i-1] + DP[i-2] which means the number of ways to reach the i-th step is the sum of the number of ways to reach the (i-1)-th step and the number of ways to reach the (i-2)-th step.

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

322. Coin Change

Description

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
Solution

Key Observation: DP[i] = min(DP[i-c1], DP[i-c2], ...) + 1 which means the minimum number of coins to make up the amount i is the minimum number of coins to make up the amount i-c1, i-c2, … plus 1.

This problem can be visualized as a tree with the root node as the amount and the children nodes as the amount - coin values. The problem is to find the shortest path from the root to the target amount.

    def coinChange(self, coins: List[int], amount: int) -> int:
        BIG_NUM = amount + 1
        dp = [BIG_NUM] * (amount+1)
        if amount == 0: 
            return 0 
        
        dp[0] = 0
        for i in range(0,amount+1):
            for c in coins:
                if i + c <= amount:
                    dp[i+c] = min(dp[i] + 1,dp[i+c])
        
        if dp[amount] == BIG_NUM:
            return -1 
        else:
            return dp[amount]

300. Longest Increasing Subsequence

Given an integer array nums, return the length of the longest strictly increasing subsequence.

A subsequence is a sequence that can be derived from an array by deleting some or no elements without changing the order of the remaining elements. For example, [3,6,2,7] is a subsequence of the array [0,3,1,6,2,2,7].

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
Solution

Key Observation:

  • When considering the i-th element, we need to find the longest increasing subsequence that ends with nums[i].
  • Straightforward case is when nums[i] is the largest element, then the longest `dp[i] = dp[i-1] + 1
  • The addition of nums[i] to the subsequence causes the change of all previous dp[j] where nums[j] < nums[i].
    def lengthOfLIS(self, nums: List[int]) -> int:
        n = len(nums)
        dp = [1] * n 

        for i in range(n-2,-1,-1):
            for j in range(i+1,n,1):
                if nums[i] < nums[j]:
                    dp[i] = max(dp[i], dp[j]+1)
        
        return max(dp)

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

Example 2:

Input: s = "applepenapple", wordDict = ["apple","pen"]
Output: true

Example 3:

Input: s = "catsandog", wordDict = ["cats","dog","sand","and","cat"]
Output: false
Solution
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
    
    def _valid(s, wordDict):

        is_detect = False
        for word in wordDict:  
            if len(word) > len(s): 
                continue
            elif len(word) == len(s):
                if word == s: 
                    is_detect = True
            else:
                # if word appear in the top of the string s 
                if word == s[:len(word)]:
                    return _valid(s[len(word):], wordDict)
        
        return is_detect
    
    return _valid(s, wordDict)
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
    
    n = len(s)
    dp = [False] * (n + 1)
    dp[-1] = True

    for i in range(n-1, -1, -1): 
        for w in wordDict: 
            # check whether the word w in the head of the sub string s 
            if len(w) <= len(s[i:]):
                if w == s[i:i+len(w)]:
                    dp[i] = any([dp[i+len(w)], dp[i]])
    
    return dp[0]

377. Combination Sum IV

Given an array of distinct integers nums and a target integer target, return the number of possible combinations that add up to target.

The test cases are generated so that the answer can fit in a 32-bit integer.

Example 1:

Input: nums = [1,2,3], target = 4
Output: 7
Explanation:
The possible combination ways are:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3, 1)
(2, 1, 1)
(2, 2, 1)
(3, 1, 1)

Example 2:

Input: nums = [9], target = 3
Output: 0
    def combinationSum4(self, nums: List[int], target: int) -> int:
        dp = [0] * (target + 1)
        dp[0] = 1 
         
        for i in range(target+1):
            for num in nums: 
                if i + num <= target:
                    dp[i+num] = dp[i+num] + dp[i]
        
        return dp[-1]

198. 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
    def rob(self, nums: List[int]) -> int:
        cur, pre = 0, 0 

        # [pre, cur, n, n+1, ...]
        for n in nums: 
            temp = max(pre + n, cur)
            pre = cur 
            cur = temp 
        
        return cur 

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.
Solution
def rob(self, nums: List[int]) -> int:
    
    def _rob(nums):
        cur, pre = 0, 0 

        # [pre, cur, n, n+1, ...]
        for n in nums:
            temp = max(pre + n, cur)
            pre = cur 
            cur = temp 
        
        return cur 
    
    if len(nums)== 1:
        return nums[0]
        
    return max(_rob(nums[:-1]), _rob(nums[1:]))

62. Unique Paths

There is a robot on an m x n grid. The robot is initially located at the top-left corner (i.e., grid[0][0]). The robot tries to move to the bottom-right corner (i.e., grid[m-1][n-1]). The robot can only move either down or right at any point in time.

Given the two integers m and n, return the number of possible unique paths that the robot can take to reach the bottom-right corner.

Example 1:

Input: m = 3, n = 7
Output: 28
Solution
def uniquePaths(self, m: int, n: int) -> int:
    grid = [[0 for _ in range(n)] for _ in range(m)]
    # set the last row and last col to be 1 
    for i in range(n): 
        grid[m-1][i] = 1 

    for i in range(m): 
        grid[i][n-1] = 1

    for row in range(m-2, -1, -1):
        for col in range(n-2, -1, -1): 
            grid[row][col] = grid[row][col+1] + grid[row+1][col]
    
    return grid[0][0]

55. 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
Solution
    def canJump(self, nums: List[int]) -> bool:
        dp = [False] * len(nums)
        dp[-1] = True

        for i in range(len(nums)-2,-1,-1): # start at len(nums)-2, stop at 0 
            # any(dp[i + 0], dp[i + 1], ..., dp[i + nums[i]]) 
            furthest = min(i + nums[i], len(nums)) + 1
            dp[i] = any(dp[i:furthest])

    return dp[0] 

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 of its neighbors.

class Node {
    public int val;
    public List<Node> neighbors;
}

Test case:

  • 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.

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).

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.

Example 1:

Input: numCourses = 2, prerequisites = [[1,0]]
Output: true
Solution
def canFinish(numCourses: int, prerequisites: List[List[int]]) -> bool:
    mapPre = {c:[] for c in range(numCourses)}
    for c, p in prerequisites:
        mapPre[c].append(p)
    
    visited = {}

    def dfs(c):
        res = []

        if mapPre[c] == []:
            return True 

        visited[c] = True

        for p in mapPre[c]:
            if p in visited: 
                return False 
            else: 
                res.append(dfs(p))
        
        mapPre[c] = []
        del visited[c]
        return all(res)
    
    res = [dfs(c) for c,p in prerequisites]
    return all(res)

417. Pacific Atlantic Water Flow

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]]
Solution
class Solution:
    def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
        m = len(heights)
        n = len(heights[0])

        po_map = [[0 for _ in range(n)] for _ in range(m)] 
        ao_map = [[0 for _ in range(n)] for _ in range(m)]

        po_map[0] = [1 for _ in range(n)]
        ao_map[-1] = [1 for _ in range(n)]
        
        for i in range(m):
            po_map[i][0] = 1 
            ao_map[i][-1] = 1 
        
        def valid(r, c): 
            if r < 0 or r >= m: 
                return False 
            if c < 0 or c >= n: 
                return False 
            return True 

        def reverse_flow(r,c, flow_map):
            # going reverse flow 
            # if lower --> stop 
            # if higher --> mark as 1, mark as visited 
            # directions [up, down, left, right]
            ds = [[-1,0], [1,0], [0,-1], [0,1]]

            for d in ds:
                r_, c_ = r + d[0], c + d[1]
                
                if not valid(r_, c_): 
                    continue 
                
                if flow_map[r_][c_] == 1:
                    continue

                if heights[r_][c_] >= heights[r][c]: 
                    flow_map[r_][c_] = 1 
                    flow_map = reverse_flow(r_, c_, flow_map)
            
            return flow_map 
        
        for i in range(n):
            po_map = reverse_flow(0, i, po_map)
            ao_map = reverse_flow(m-1,i, ao_map)
        
        for i in range(m):
            po_map = reverse_flow(i, 0, po_map)
            ao_map = reverse_flow(i, n-1, ao_map)
        
        # merge two map 
        res = []
        for r in range(m):
            for c in range(n):
                if po_map[r][c] == 1 and ao_map[r][c] == 1:
                    res.append([r,c])
        
        return res

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
Solution
    def numIslands(self, grid: List[List[str]]) -> int:
        m = len(grid)
        n = len(grid[0])

        visited = [[0 for _ in range(n)] for _ in range(m)]

        def valid(r, c): 
            if r < 0 or r >= m:
                return False
            if c < 0 or c >= n: 
                return False 
            return True 

        def dfs(r,c,ni): 

            # directions [up, down, left, right]
            ds = [[-1,0], [1,0], [0,-1], [0,1]]

            if grid[r][c] == "0":
                return 0 
            
            if visited[r][c] == 1:
                return 0 
            
            visited[r][c] = 1 
            ni += 1

            for d in ds: 
                r_, c_ = r + d[0], c + d[1]

                if not valid(r_, c_): 
                    continue 
                
                if grid[r_][c_] == "0":
                    continue 

                if visited[r_][c_] == 1:
                    continue 

                #  grid[r_][c_] == "1" and not visited 
                # print(r, c, r_, c_, ni, visited)
                ni = dfs(r_, c_, ni)

            return ni 
        
        num_islands = 0 

        for r in range(m): 
            for c in range(n): 

                num_islands += 1 if dfs(r,c,0) > 0 else 0 

        return num_islands 

212. Word Search II

Given an m x n board of characters board and a list of strings words, return all words on the board.

Each word must be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.

Example 1:

Input: board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
Output: ["eat","oath"]
Solution
def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:

    def valid(r, c):
        m, n = len(board), len(board[0])
        if r < 0 or r >= m: 
            return False 
        if c < 0 or c >= n: 
            return False 
        return True 

    def dfs(r, c, word, cur_i, found_next_word, visited):
        directions = [[-1,0], [1,0], [0,-1], [0,1]]

        # reach the last character 
        if cur_i == len(word):
            return found_next_word, visited

        found_cur_word = False 

        for d in directions: 
            r_, c_ = r + d[0], c + d[1]
            
            if not valid(r_, c_):
                continue 

            if (board[r_][c_] == word[cur_i]) and (not visited[r_][c_]):
                found_cur_word = True
                visited[r_][c_] = True
                temp, visited = dfs(r_, c_, word, cur_i + 1, True, visited)
                found_next_word = all([temp, found_next_word])
        
        if found_cur_word:
            return found_next_word, visited
        else:
            return False, visited

    def findWord(board, word):
        m, n = len(board), len(board[0])
        visited = [[False for _ in range(n)] for _ in range(m)]

        for r in range(m):
            for c in range(n):
                if board[r][c] == word[0]:
                    copy_visited = visited
                    copy_visited[r][c] = True
                    found, copy_visited = dfs(r,c,word,1,True,copy_visited)
                    if found: 
                        return True 
        
        return False

    output = []
    for word in words: 
        if findWord(board, word):
            output.append(word)

    return output

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]]
Solution

Key idea is to use the first row and first column as the marker to indicate if the row or column should be set to 0. However, we need to be careful when the first row itself contains a 0.

  • To handle this case, we need a flag rowZERO to indicate if the first row itself contains a 0.
  • We iterate columns from right to left to avoid overwriting the marker.
class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        ROWS = len(matrix)
        COLS = len(matrix[0])
        rowZERO = False 

        for r in range(ROWS):
            for c in range(COLS):
                if matrix[r][c] == 0:
                    if r > 0: 
                        matrix[0][c] = 0
                        matrix[r][0] = 0
                    else:
                        rowZERO = True 
                        matrix[0][c] = 0

        
        for r in range(1,ROWS): 
            for c in range(COLS-1,-1,-1):
                if matrix[0][c] == 0:
                    matrix[r][c] = 0
                if matrix[r][0] == 0:
                    matrix[r][c] = 0
        

        if rowZERO: 
            for c in range(COLS):
                matrix[0][c] = 0

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]
Solution
class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        def move_right(r, c):
            return r, c+1
        
        def move_left(r, c):
            return r, c-1
        
        def move_up(r, c):
            return r-1, c
        
        def move_down(r, c):
            return r+1, c
        
        def move(r, c, direction):
            if direction == 'move_right':
                return move_right(r, c)
            elif direction == 'move_down':
                return move_down(r, c)
            elif direction == 'move_up':
                return move_up(r, c)
            elif direction == 'move_left':
                return move_left(r, c)
            else: 
                raise ValueError
        
        def valid(r, c, left_col, right_col, top_row, bot_row):
            if r < top_row or r > bot_row:
                return False 
            if c < left_col or c > right_col: 
                return False 
            return True
        
        def change_direction(direction, left_col, right_col, top_row, bot_row):
            if direction == 'move_right':
                return 'move_down', left_col, right_col, top_row + 1, bot_row
            elif direction == 'move_down':
                return 'move_left', left_col, right_col - 1, top_row, bot_row
            elif direction == 'move_left':
                return 'move_up', left_col, right_col, top_row, bot_row - 1 
            elif direction == 'move_up':
                return 'move_right', left_col + 1, right_col, top_row, bot_row
            else:
                raise ValueError 
        
        
        def next_move(r, c, direction, left_col, right_col, top_row, bot_row):
            temp_r, temp_c = move(r, c, direction)
            if valid(temp_r, temp_c, left_col, right_col, top_row, bot_row):
                return direction, left_col, right_col, top_row, bot_row
            else:
                return change_direction(direction, left_col, right_col, top_row, bot_row)
                
        output = []
        direction = 'move_right'
        r, c = 0, 0 

        ROWS = len(matrix)
        COLS = len(matrix[0])
        left_col = 0 
        right_col = COLS - 1 
        top_row = 0 
        bot_row = ROWS - 1 

        while len(output) < ROWS * COLS:
            output.append(matrix[r][c])
            direction, left_col, right_col, top_row, bot_row = next_move(r, c, direction, left_col, right_col, top_row, bot_row)
            r, c = move(r, c, direction)
        
        return output