Introduction
Grind 75 is an improved version of Blind 75 by the same author. While Blind 75 is a great list to practice coding skills and enter the world of programming algorithms, it includes too few questions and the list is static.
Grind 75 provides 169 top LeetCode questions (by popularity, frequency, like rating, etc.) and the 75 questions with higher priorities are called new Blind 75, or Grind 75. After completing the top 75 questions, you can go beyond that and solve the remaining questions.
This series plans to cover all 169 questions in Grind 75 as a practice of coding algorithms and preparation for tech interviews. It should be completed within this June.
For each question, I will provide a thinking process, a brief description of the algorithm, time and space complexity, and then the actual code.
1. Two Sum
The brute force way would be to iteratively check all combinations and calculate their summary, the time complexity of which would be O(n^2).
Each time we go through the array, we only check one pair of numbers and discard all other information. That’s why the brute force method is slow.
We can use a hashmap to record more useful information when we go through the array. For each number, we record target - num
as the key and its index as the value in the hashmap. Later, we can search the hashmap to see whether the current number equals the required number.
The time complexity would be O(n) as we only traverse the array once.
The space complexity would also be O(n) for the created hashmap.
|
|
121. Best Time to Buy and Sell Stock
To get the highest profit, we should find one lowest price and one highest price after the low price. However, if we go through the prices
, it’s not convenient to judge the positions of the lowest and highest prices.
We can use a profit
to record current max profit. For each price, we will update the lowest price and calculate the max profit.
The time complexity is O(n) as we only traverse the array once.
The space complexity is O(1) as no extra data structures are created.
|
|
57. Insert Interval
There are several possibilities for the relationship between intervals:
- Interval A outside of interval B: they will become two intervals in the result. They can be identified by comparing right side of interval A and left side of interval B. (or reverse)
- Interval A crossing with interval B: they will merge into one interval
- Interval A contains interval B: they will also merge into one interval (A)
We can go through the intervals
and compare each interval with the newInterval
. If merge is needed, directly append the interval to the result. Otherwise, merge it into newInterval
and append the merged interval.
One issue here is (which is different from 56. Merge Intervals) that we should update the newInterval
before appending it to the result, rather than updating the last interval in the result. In practice, we keep updating (merging) the newInterval
and append it into the result when the interval has come to the right side of the newInterval
.
The time complexity is O(n) as we only traverse intervals once.
The space complexity is also O(n) for the new result list.
|
|
238. Product of Array Except Self
The time is limited to O(n), which means the brute force method wouldn’t work (which is O(n^2)). Also, the division operation is not allowed, which means we can’t get the total product and divide each element one by one.
Each item in answer
can be calculated by multiplying the product of all prefix items and the product of all suffix items. We can traverse the array two times: the first time to record prefix product at each position, and the second time to record suffix product at each position. Then we build the answer
. The entire time complexity is limited to 3 rounds of traverse, i.e. O(n).
The space complexity is O(n) as we created a new answer
array.
|
|
39. Combination Sum
As each number can be chosen an unlimited number of times, which means for each step, we can choose the next number from all the numbers in the array. This creates a tree where each node has all numbers as its children.
As we want all possible combinations, we can use DFS to get one combination and backtrack to get all possible ones. We should keep track of the current path using temp
, validate it at each step, and record valid combinations in res
list.
- If the sum of
temp
equals the target, we should record it inres
and backtrack to the upper level. - If the sum of
temp
exceeds the target, which means this path can’t work, we should backtrack directly to the upper level. - If the sum of
temp
is less than the target, we should append current node totemp
and explore the next level. When the next level returns, it means the path has been searched, and we should pop the current node and move to the next node.
The time complexity is O(n^l), where n is the length of array and l is the longest path (target / minInArray). In the worst case, we need to visit a tree of l levels and the bottom level would have n*l choices. Each level have n*level choices, and the sum would be O(n^l).
The space complexity would be the O(n*l). The bottom level occupies n*l nodes. When backtracking, the space is freed and the upper level occupies less space. So the most space needed is O(n*l).
|
|
56. Merge Intervals
There are several possibilities for the relationship between intervals:
- Interval A outside of interval B: they will become two intervals in the result. They can be identified by comparing right side of interval A and left side of interval B. (or reverse)
- Interval A crossing with interval B: they will merge into one interval
- Interval A containing interval B: they will also merge into one interval (A)
If we sort the intervals (note the given array is not sorted), go through each interval, and compare it with the last interval in the result, we can decide whether to merge it into the last interval or append it into the result directly.
The time complexity is O(nlogn) + O(n) = O(nlogn) for sorting.
The space complexity is O(1) as no extra data structures are created.
|
|
169. Majority Element
The straightforward way would be to use a hashmap to record the counts of elements and return the element once its count exceeds n/2.
The time complexity is O(n), as we traverse the list.
The space complexity is O(n), as the hashmap would store at most n items (unique list).
|
|
There is also an easier solution: after sorting the array, the majority element with more than n/2 counts would always appear at the middle point of the array. The time complexity is O(nlogn) for sorting, and the space complexity is O(1).
|
|
75. Sort Colors
This is the problem of Dutch Flag, the key is to put two different colors at the two sides and leave the 3rd color in the middle.
As the question requires in-place sorting, swapping should be applied. There are two moving edges (pointers) between the 3 colors, and swapping happens at these 2 edges. This can also be seen as a two-pointer problem. In addition, there should be a 3rd pointer going through the list to check values of items.
Swapping current with the left side would make the current element a smaller one, while swapping current element with the right side would make current an un-known one. Thus, the iterating index should not increase when swapping with right side, and we should check the new current element again. For the middle color, just increase the iterating index without swapping.
The time complexity is O(n), as all three pointers traverse at most twice the entire list.
The space complexity is O(1), as no new data structures are created.
|
|
There is also an easier solution: as there are mostly 3 kinds of items. We can count the number of each item, and directly assign values to the original list based on the count numbers. The time complexity is still O(n), but the space complexity becomes O(n) for the new hashmap.
|
|
217. Contains Duplicate
Use a hashmap to record the appearance of items. Return false when encounter the 2nd appearance of an item.
The time complexity is O(n) due to the iteration through the list.
The space complexity is O(n) for the new hashmap.
|
|
11. Container With Most Water
There are two moving endpoints (pointers) of the container, and the shorter line limits the size of the container. The issue with two pointers is: which one is fixed and which one is floating. For each step, the floating one should always move in a direction that can lead to a better result.
If two pointers start from the left side or from some point within the array, expanding the width will not necessarily lead to a larger container (neither the short nor the longer line). However, shrinking the longer line will always lead to a smaller container (either renewing the shorter line or keeping the original shorter line but with a smaller width).
Thus, we can start from two sides of the array and always shrink the shorter line. This will not guarantee a better result at each step, but it will have a chance to achieve one.
The time complexity is O(n), as the left and right pointers traverse the entire list together.
The space complexity is O(1), as no new structures are created.
|
|
Last modified on 2024-06-05