Leetcode Hot 150
😇

Leetcode Hot 150

Table of Contents
 
 
 
 

Merge Sorted Array

Problem Description:

You are given two integer arrays nums1 and nums2, sorted in non-decreasing order, and two integers m and n, representing the number of elements in nums1 and nums2 respectively.
Merge nums1 and nums2 into a single array sorted in non-decreasing order.
The final sorted array should not be returned by the function, but instead be stored inside the array nums1. To accommodate this, nums1 has a length of m + n, where the first m elements denote the elements that should be merged, and the last n elements are set to 0 and should be ignored. nums2 has a length of n.

Solution:

class Solution: def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: """ Do not return anything, modify nums1 in-place instead. """ # Set p1 and p2 to point to the end of their respective arrays. p1 = m - 1 p2 = n - 1 # And move p backward through the array, each time writing # the largest value pointed at by p1 or p2. for p in range(n + m - 1, -1, -1): if p2 < 0: break if p1 >= 0 and nums1[p1] > nums2[p2]: nums1[p] = nums1[p1] p1 -= 1 else: nums1[p] = nums2[p2] p2 -= 1

Analysis

  • time complexity: O(m+n)
  • space complexity: O(1)
  • Algo category: 3 pointers
 

Climbing Stairs

Problem 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?

Solution1:

class Solution: def climbStairs(self, n: int) -> int: # fib x, y = 1,2 if n == 1: return 1 elif n==2: return 2 for i in range(n-2): tmp_y = y y = x + y x = tmp_y return y

Analysis

  • time complexity: O(n)
  • space complexity: O(1)
  • Algo category: Fib fast

Solution2:

class Solution: def climbStairs(self, n: int) -> int: q = [[1, 1], [1, 0]] res = self.pow(q, n) return res[0][0] def pow(self, a: [[int]], n: int) -> [[int]]: ret = [[1, 0], [0, 1]] while n > 0: if (n & 1) == 1: ret = self.multiply(ret, a) n >>= 1 a = self.multiply(a, a) return ret def multiply(self, a: [[int]], b: [[int]]) -> [[int]]: c = [[0, 0], [0, 0]] for i in range(2): for j in range(2): c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] return c

Analysis

  • time complexity: O(log(n))
  • space complexity: O(1)
  • Algo category: Binets Method, Use Matrix
 

House Robber

Problem Description:

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.

Idea:

it’s a DP question. Transition Function:

Solution—my ver.:

class Solution: def rob(self, nums: List[int]) -> int: memo = nums if len(nums) == 1: return nums[0] elif len(nums) == 2: return max(nums[0], nums[1]) memo[1] = max(nums[0], nums[1]) for i in range(2, len(nums)): memo[i] = max(memo[i-1], memo[i-2]+nums[i]) return memo[-1]

Analysis

  • time complexity: O(n)
  • space complexity: O(n)
  • Algo category: DP, memoization

Solution—official1:

class Solution: def rob(self, nums: List[int]) -> int: # Special handling for empty case. if not nums: return 0 maxRobbedAmount = [None for _ in range(len(nums) + 1)] N = len(nums) # Base case initialization. maxRobbedAmount[N], maxRobbedAmount[N - 1] = 0, nums[N - 1] # DP table calculations. for i in range(N - 2, -1, -1): # Same as recursive solution. maxRobbedAmount[i] = max( maxRobbedAmount[i + 1], maxRobbedAmount[i + 2] + nums[i] ) return maxRobbedAmount[0]
Same as my code, just more elegant with addition 0 at the end.

Solution—official2:

class Solution: def rob(self, nums: List[int]) -> int: # Special handling for empty case. if not nums: return 0 N = len(nums) rob_next_plus_one = 0 rob_next = nums[N - 1] # DP table calculations. for i in range(N - 2, -1, -1): # Same as recursive solution. current = max(rob_next, rob_next_plus_one + nums[i]) # Update the variables rob_next_plus_one = rob_next rob_next = current return rob_next
Simplify Space complexity to O(1). Only need to keep track of 2 previous values.
 

Word Break

Problem Description:

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.

Solution—Top-Down DP:

class Solution: def wordBreak(self, s: str, wordDict: List[str]) -> bool: @cache def dp(i): if i < 0: return True for word in wordDict: if s[i - len(word) + 1 : i + 1] == word and dp(i - len(word)): return True return False return dp(len(s) - 1)

Solution—Bottom-Up DP:

class Solution: def wordBreak(self, s: str, wordDict: List[str]) -> bool: dp = [False] * len(s) for i in range(len(s)): for word in wordDict: # Handle out of bounds case if i < len(word) - 1: continue if i == len(word) - 1 or dp[i - len(word)]: if s[i - len(word) + 1 : i + 1] == word: dp[i] = True break return dp[-1]
 

Valid Palindrome

Problem Description:

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.

Solution—2 pointers:

class Solution: def isPalindrome(self, s: str) -> bool: s = s.lower() print(s) a, b = 0, len(s)-1 while a < b: while a < b and not s[a].isalnum(): a += 1 while a < b and not s[b].isalnum(): b -= 1 if s[a] != s[b]: return False a += 1 b -= 1 return True
 

Container with most water

Problem Description:

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.

Solution—2 pointers:

class Solution: def maxArea(self, height: List[int]) -> int: maxarea = 0 left = 0 right = len(height) - 1 while left < right: width = right - left maxarea = max(maxarea, min(height[left], height[right]) * width) if height[left] <= height[right]: left += 1 else: right -= 1 return maxarea
Idea: just always move the shorter one closer.
 
 

Ransom Note

Problem Description:

Given two strings ransomNote and magazine, return true if ransomNote can be constructed by using the letters from magazine and falseotherwise.
Each letter in magazine can only be used once in ransomNote.

Solution—defaultdict:

class Solution: def canConstruct(self, ransomNote: str, magazine: str) -> bool: from collections import defaultdict maga = defaultdict(int) for x in magazine: maga[x] += 1 for x in ransomNote: maga[x] -= 1 for x in maga: if maga[x] < 0: return False return True
 
 

Two Sum

class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]: hashmap = {} for i in range(len(nums)): complement = target - nums[i] if complement in hashmap: return [i, hashmap[complement]] hashmap[nums[i]] = i # Return an empty list if no solution is found return []
Use a hash table. O(n) time, O(n) space.
 
 

Two Sum 2 (sorted)

class Solution: def twoSum(self, numbers: List[int], target: int) -> List[int]: low = 0 high = len(numbers) - 1 while low < high: sum = numbers[low] + numbers[high] if sum == target: return [low + 1, high + 1] elif sum < target: low += 1 else: high -= 1 # In case there is no solution, return [-1, -1]. return [-1, -1]
Use two pointers. Left and right. O(n) time, O(1) space.
 

3 Sum

 

sort +1 outer for-loop + two pointer

class Solution: def threeSum(self, nums: List[int]) -> List[List[int]]: res = [] nums.sort() for i in range(len(nums)): if nums[i] > 0: break if i == 0 or nums[i - 1] != nums[i]: self.twoSumII(nums, i, res) return res def twoSumII(self, nums: List[int], i: int, res: List[List[int]]): lo, hi = i + 1, len(nums) - 1 while lo < hi: sum = nums[i] + nums[lo] + nums[hi] if sum < 0: lo += 1 elif sum > 0: hi -= 1 else: res.append([nums[i], nums[lo], nums[hi]]) lo += 1 hi -= 1 while lo < hi and nums[lo] == nums[lo - 1]: lo += 1
time O(n^2), space O(n)

Hast set + sorting

 
class Solution: def threeSum(self, nums: List[int]) -> List[List[int]]: res = [] nums.sort() for i in range(len(nums)): if nums[i] > 0: break if i == 0 or nums[i - 1] != nums[i]: self.twoSum(nums, i, res) return res def twoSum(self, nums: List[int], i: int, res: List[List[int]]): seen = set() j = i + 1 while j < len(nums): complement = -nums[i] - nums[j] if complement in seen: res.append([nums[i], nums[j], complement]) while j + 1 < len(nums) and nums[j] == nums[j + 1]: j += 1 seen.add(nums[j]) j += 1
O(n^2), O(n)

Hash Set no sorting

class Solution: def threeSum(self, nums: List[int]) -> List[List[int]]: res, dups = set(), set() seen = {} for i, val1 in enumerate(nums): if val1 not in dups: dups.add(val1) for j, val2 in enumerate(nums[i + 1 :]): complement = -val1 - val2 if complement in seen and seen[complement] == i: res.add(tuple(sorted((val1, val2, complement)))) seen[val2] = i return [list(x) for x in res]
O(n^2), O(n)
 

3 Sum closest

class Solution: def threeSumClosest(self, nums: List[int], target: int) -> int: diff = float("inf") nums.sort() for i in range(len(nums)): lo, hi = i + 1, len(nums) - 1 while lo < hi: sum = nums[i] + nums[lo] + nums[hi] if abs(target - sum) < abs(diff): diff = target - sum if sum < target: lo += 1 else: hi -= 1 if diff == 0: break return target - diff
O(n^2), O(n)
 

4 Sum

 
class Solution: def fourSum(self, nums: List[int], target: int) -> List[List[int]]: def kSum(nums: List[int], target: int, k: int) -> List[List[int]]: res = [] # If we have run out of numbers to add, return res. if not nums: return res # We cannot obtain a sum of target if the smallest value # in nums is greater than target // k or if the largest # value in nums is smaller than target // k. average_value = target // k if average_value < nums[0] or nums[-1] < average_value: return res if k == 2: return twoSum(nums, target) for i in range(len(nums)): if i == 0 or nums[i - 1] != nums[i]: for subset in kSum(nums[i + 1 :], target - nums[i], k - 1): res.append([nums[i]] + subset) return res def twoSum(nums: List[int], target: int) -> List[List[int]]: res = [] lo, hi = 0, len(nums) - 1 while lo < hi: curr_sum = nums[lo] + nums[hi] if curr_sum < target or (lo > 0 and nums[lo] == nums[lo - 1]): lo += 1 elif curr_sum > target or ( hi < len(nums) - 1 and nums[hi] == nums[hi + 1] ): hi -= 1 else: res.append([nums[lo], nums[hi]]) lo += 1 hi -= 1 return res nums.sort() return kSum(nums, target, 4)
 
 

Largest Rectangle in Histogram

 
Given an array of integers heights representing the histogram's bar height where the width of each bar is 1, return the area of the largest rectangle in the histogram.
 
Example 1:
notion image
Input: heights = [2,1,5,6,2,3] Output: 10 Explanation: The above is a histogram where width of each bar is 1. The largest rectangle is shown in the red area, which has an area = 10 units.
 
 
class Solution: def largestRectangleArea(self, heights: List[int]) -> int: stack = [-1] max_area = 0 for i in range(len(heights)): while stack[-1] != -1 and heights[stack[-1]] >= heights[i]: current_height = heights[stack.pop()] current_width = i - stack[-1] - 1 max_area = max(max_area, current_height * current_width) stack.append(i) while stack[-1] != -1: current_height = heights[stack.pop()] current_width = len(heights) - stack[-1] - 1 max_area = max(max_area, current_height * current_width) return max_area
First thing to note is that there are N rectangles that need to be considered. This isn't immediately obvious, it helps to draw them out on a piece of paper. But once you see that there are N rectangles, the problem simply boils down to iterating through each one, which of course is linear.
How do we iterate through N rectangles? The obvious approach would be to scan left-to-right and have the index represent the right side of the rectangle. To derive the left side / height, we need a data structure to keep track of columns we've already seen. A stack is usually the choice for these increasing / decreasing columns type of problems.
So we know we're gonna scan left-to-right and keep putting something or other on the stack. Now to work out the specifics.
The column heights in the example are [6, 7, 5, ...]. What happens when we get to 5? The thing to note is that from perspective of any columns to the right of it, 6 and 7 don't matter. The height will be 5 or smaller. So come 5, that's the only thing that should be on the stack.
What about 6 and 7? It looks like we'll need both on the stack, cause we'll need to consider rectangles of heights 6 and 7. We don't know how far to the right those rectangles will extend, so we'll just put (column, height) onto the stack. Once we see 5, that's the right-hand border for these rectangles and that's when we can consider them.
So the algorithm boils down to:
  1. Maintain a stack such that heights are always in increasing order.
  1. When we see a column that's lower than what's on the stack
      • Use it as the right side and compute all the possible rectangles using what's on the stack to derive left side and height.
      • Remove each considered rectangle / column from the stack
 

Maximal Rectangle

 
Given a rows x cols binary matrix filled with 0's and 1's, find the largest rectangle containing only 1's and return its area.
Example 1:
notion image
Input: matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]] Output: 6 Explanation: The maximal rectangle is shown in the above picture.
 
class Solution: # Get the maximum area in a histogram given its heights def leetcode84(self, heights): stack = [-1] maxarea = 0 for i in range(len(heights)): while stack[-1] != -1 and heights[stack[-1]] >= heights[i]: maxarea = max( maxarea, heights[stack.pop()] * (i - stack[-1] - 1) ) stack.append(i) while stack[-1] != -1: maxarea = max( maxarea, heights[stack.pop()] * (len(heights) - stack[-1] - 1) ) return maxarea def maximalRectangle(self, matrix: List[List[str]]) -> int: if not matrix: return 0 maxarea = 0 dp = [0] * len(matrix[0]) for i in range(len(matrix)): for j in range(len(matrix[0])): # update the state of this row's histogram using the last row's histogram # by keeping track of the number of consecutive ones dp[j] = dp[j] + 1 if matrix[i][j] == "1" else 0 # update maxarea with the maximum area from this row's histogram maxarea = max(maxarea, self.leetcode84(dp)) return maxarea
Here just use the solution of the last problem.
 
 
 
 

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.
 
class Solution: def numIslands(self, grid): if not grid: return 0 num_islands = 0 for i in range(len(grid)): for j in range(len(grid[0])): if grid[i][j] == "1": self.dfs(grid, i, j) num_islands += 1 return num_islands def dfs(self, grid, r, c): if ( r < 0 or c < 0 or r >= len(grid) or c >= len(grid[0]) or grid[r][c] != "1" ): return grid[r][c] = "0" self.dfs(grid, r - 1, c) self.dfs(grid, r + 1, c) self.dfs(grid, r, c - 1) self.dfs(grid, r, c + 1)
 
O(m*n), O(m*n)
 
 

Target Sum

Problem Description

You are given an integer array nums and an integer target.
You want to build an expression out of nums by adding one of the symbols '+' and '-' before each integer in nums and then concatenate all the integers.
  • For example, if nums = [2, 1], you can add a '+' before 2 and a '-' before 1 and concatenate them to build the expression "+2-1".
Return the number of different expressions that you can build, which evaluates to target.
 

Solution: dp top down

class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int: @lru_cache(maxsize=None) def check(idx, target): if idx == 0: return int(target == nums[0]) + int(target == -nums[0]) value = nums[idx] return check(idx-1, target - value)+check(idx-1, target + value) return check(len(nums)-1, target)

Solution: dp bottom up

class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int: total_sum = sum(nums) dp = [[0] * (2 * total_sum + 1) for _ in range(len(nums))] # Initialize the first row of the DP table dp[0][nums[0] + total_sum] = 1 dp[0][-nums[0] + total_sum] += 1 # Fill the DP table for index in range(1, len(nums)): for sum_val in range(-total_sum, total_sum + 1): if dp[index - 1][sum_val + total_sum] > 0: dp[index][sum_val + nums[index] + total_sum] += dp[ index - 1 ][sum_val + total_sum] dp[index][sum_val - nums[index] + total_sum] += dp[ index - 1 ][sum_val + total_sum] # Return the result if the target is within the valid range return ( 0 if abs(target) > total_sum else dp[len(nums) - 1][target + total_sum] )

Solution: freq. map

class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int: d = defaultdict(int) d[0] = 1 for num in nums: dp = defaultdict(int) for k, v in d.items(): dp[k + num] += v dp[k - num] += v d = dp return d[target]
 
 

Largest Divisible Subset

 

Problem Description

 
Given a set of distinct positive integers nums, return the largest subset answer such that every pair (answer[i], answer[j]) of elements in this subset satisfies:
  • answer[i] % answer[j] == 0, or
  • answer[j] % answer[i] == 0
If there are multiple solutions, return any of them.
 
也就是,先sort,然后找最长的整除链。

Solution — DP

def largestDivisibleSubset(nums): if not nums: return [] nums.sort() n = len(nums) dp = [1] * n # 每个数至少能形成一个长度为1的链 prev = [-1] * n # 记录前驱索引 max_len = 1 max_index = 0 for i in range(n): for j in range(i): if nums[i] % nums[j] == 0: if dp[j] + 1 > dp[i]: dp[i] = dp[j] + 1 prev[i] = j if dp[i] > max_len: max_len = dp[i] max_index = i # 回溯构造结果 res = [] while max_index != -1: res.append(nums[max_index]) max_index = prev[max_index] return res[::-1]
关键点是有一个前驱。
 
 

Path With Maximum Probability

Problem Description

You are given an undirected weighted graph of n nodes (0-indexed), represented by an edge list where edges[i] = [a, b] is an undirected edge connecting the nodes a and b with a probability of success of traversing that edge succProb[i].
Given two nodes start and end, find the path with the maximum probability of success to go from start to end and return its success probability.
If there is no path from start to endreturn 0. Your answer will be accepted if it differs from the correct answer by at most 1e-5.

Dijkstra (min-heap) + log(x)

 
import math import heapq from collections import defaultdict class Solution: def maxProbability(self, n, edges, succProb, start, end): graph = defaultdict(list) for (a, b), p in zip(edges, succProb): if p > 0: w = -math.log(p) graph[a].append((b, w)) graph[b].append((a, w)) dist = [float('inf')] * n dist[start] = 0.0 heap = [(0.0, start)] while heap: cur_cost, node = heapq.heappop(heap) if node == end: return math.exp(-cur_cost) if cur_cost > dist[node]: continue for nei, w in graph[node]: new_cost = cur_cost + w if new_cost < dist[nei]: dist[nei] = new_cost heapq.heappush(heap, (new_cost, nei)) return 0.0
 
 

Majority Element

Problem Description

Given an array nums of size n, return the majority element.
The majority element is the element that appears more than ⌊n / 2⌋ times. You may assume that the majority element always exists in the array.

Solution — Boyer-Moore Voting Algorithm

class Solution: def majorityElement(self, nums): count = 0 candidate = None for num in nums: if count == 0: candidate = num count += 1 if num == candidate else -1 return candidate
 
LeetCode Hot 150 Tracker15 / 15
TopicsDifficultySpace ComplexityStatusSolution LinkProblem LinkTime ComplexityIDQuestion
String, Hash TableEasyO(n)Not Startedhttps://leetcode.com/problems/valid-parentheses/O(n)20Valid Parentheses
ArrayMediumO(1)Not Startedhttps://leetcode.com/problems/3sum/O(n²)153Sum
Hash Table, StringEasyO(1)FinishedO(n)383Ransom Note
String, Two PointersEasyO(1)Finishedhttps://leetcode.com/problems/valid-palindrome/O(n)125Valid Palindrome
Array, GreedyMediumO(1)Finishedhttps://leetcode.com/problems/container-with-most-water/O(n)11Container With Most Water
String, Hash TableMediumO(1)Not Startedhttps://leetcode.com/problems/longest-substring-without-repeating-characters/O(n)3Longest Substring Without Repeating Characters
Array, Dynamic ProgrammingEasyO(1)Not Startedhttps://leetcode.com/problems/best-time-to-buy-and-sell-stock/O(n)121Best Time to Buy and Sell Stock
Array, Hash TableEasyO(n)Not Startedhttps://leetcode.com/problems/two-sum/O(n)1Two Sum
Array, Dynamic ProgrammingHardO(1)Not Startedhttps://leetcode.com/problems/trapping-rain-water/O(n)42Trapping Rain Water
Array, Hash Table, Dynamic Programming, String, Memoization, TrieMediumIn Progresshttps://leetcode.com/problems/word-break/139Word Break
Linked ListHardO(n)Not Startedhttps://leetcode.com/problems/merge-k-sorted-lists/O(n log n)23Merge k Sorted Lists
Array, Dynamic ProgrammingMediumO(1)Finishedhttps://www.notion.so/Leetcode-Hot-150-2699cef30c0f80938f11deea5ba90048?source=copy_link#26a9cef30c0f8068a05dded39a2e40f7https://leetcode.com/problems/house-robber/O(n)198House Robber
Linked ListEasyO(1)Not Startedhttps://leetcode.com/problems/merge-two-sorted-lists/O(n)21Merge Two Sorted Lists
Math, Dynamic Programming, MemoizationEasyO(1)Finishedhttps://www.notion.so/Leetcode-Hot-150-2699cef30c0f80938f11deea5ba90048?source=copy_link#2699cef30c0f802bb121f0f8cb617637https://leetcode.com/problems/climbing-stairs/O(log n)70Climbing Stairs
Array, Two Pointers, SortingEasyO(1)Finishedhttps://www.notion.so/Leetcode-Hot-150-2699cef30c0f80938f11deea5ba90048?source=copy_link#2699cef30c0f8081aaafc9b22a479027https://leetcode.com/problems/merge-sorted-array/O(n+m)88Merge Sorted Array