posted on July 09, 2024, last updated on Saturday, November 23, 2024 at 10:51 AM
1701. Average waiting time
A restaurant with single chef,
customers[i] = [arrival_i, time_i]
Chef prepares food for customers in the order they were given in the input.
return the average waitiing time of all customers
this question can be modelled as a non-preemptive single process at a time scheduling model.
Intuitative solution (in Python3):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution:
def averageWaitingTime(self, customers: List[List[int]]) -> float:
# FCFS scheduling
customers[0].append(customers[0][0] + customers[0][1])
total_wait_time = customers[0][1]
for i in range(1, len(customers)):
prev_customer = customers[i-1]
customer = customers[i]
if customer[0] < prev_customer[-1]:
customer.append(prev_customer[-1] + customer[1])
else:
customer.append(customer[0]+customer[1])
total_wait_time += customer[2] - customer[0]
return total_wait_time/len(customers)
Java Tips:
1
2
// to initialize an array with the length of an existing array arr
int newArr[] = new int[arr.length];
Optimized:
- it is not necessary to save all the finish times of each customer
- in the line
total_wait_time += customer[2] - customer[0], we just need to save the finish time of the previous customer, use a variable to iterate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public double averageWaitingTime(int[][] customers) {
// finish time of the prev customer
int ft = 0;
double allWaitingTime = 0;
for(int i=0; i<customers.length; i++) {
// current customer arrived later than the prev customer ends
if (customers[i][0] > ft) {
ft = customers[i][0] + customers[i][1];
}
// current customer arrived no later than the prev customer ends
else {
ft = ft + customers[i][1];
}
allWaitingTime += ft-customers[i][0];
}
return allWaitingTime/customers.length;
}
}
1598. Crawler log folder
A very easy question…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int minOperations(String[] logs) {
int layer = 0;
for(int i=0; i<logs.length; i++) {
if(logs[i].equals("../")) {
layer = Math.max(0, layer-1);
}
else if (logs[i].equals("./")) {
// no action - current dir
} else {
layer++;
}
}
return layer;
}
}
1717. Maximum score from removing substrings
Preservation of Opportunities:
- Removing higher-scoring pairs first does not diminish the opportunity to remove lower-scoring pairs later. For instance, after all “ba” pairs are removed from the string, it still contains “ab” pairs if they existed initially.
- If we reverse this order, i.e., remove lower-scoring pairs first, we might miss out on higher-scoring pairs that could have been formed by the initial configuration of the string.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class Solution:
def maximumGain(self, s: str, x: int, y: int) -> int:
# intuitive - recursion and search
ab = "ab"
ba = "ba"
first = ""
second = ""
maxxy = max(x,y)
minxy = min(x,y)
if x>y:
first = ab
second = ba
else:
first = ba
second = ab
st = []
it = 0
score = 0
while it < len(s):
curr = s[it]
it += 1
if len(st) == 0:
st.append(curr)
continue
prev = st[-1]
if prev+curr == first:
score += maxxy
st.pop()
else:
st.append(curr)
s = "".join(st)
st = []
it = 0
while it < len(s):
curr = s[it]
it += 1
if len(st) == 0:
st.append(curr)
continue
prev = st[-1]
if prev+curr == second:
score += minxy
st.pop()
else:
st.append(curr)
return score
1530. Number of good leaf node pairs
- use BFS - “distance” indicates that we need to search for pairs layer by layer
- construct a graph is necessary since we need to search bidirectionally – than tree having one-way references
- when searching in each layer, use the length of the queue as the number of nodes of this layer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def _traverse_tree(self, curr_node: TreeNode, prev_node: TreeNode, graph: dict, leaf_nodes: set):
if curr_node is None:
return
if curr_node.left is None and curr_node.right is None:
leaf_nodes.add(curr_node)
if prev_node is not None:
if prev_node not in graph:
graph[prev_node] = []
graph[prev_node].append(curr_node)
if curr_node not in graph:
graph[curr_node] = []
graph[curr_node].append(prev_node)
self._traverse_tree(curr_node.left, curr_node, graph, leaf_nodes)
self._traverse_tree(curr_node.right, curr_node, graph, leaf_nodes)
def countPairs(self, root: TreeNode, distance: int) -> int:
graph = {}
leaf_nodes = set()
self._traverse_tree(root, None, graph, leaf_nodes)
count = 0
# begin of DFS
for leaf in leaf_nodes:
queue = []
seen = set()
queue.append(leaf)
seen.add(leaf)
# visiting all nodes, start from the leaf node (distance is 0 at this time)
for i in range(distance+1):
size = len(queue)
#print(f"leaf:{leaf.val}, distance:{i}, size:{size}")
# nodes of this distance
# visit all nodes of i distance
# add all nodes of i+1 distance
for j in range(size):
curr_node = queue.pop(0) # bfs -- queue -> popleft
if curr_node in leaf_nodes and curr_node != leaf:
count += 1 # a satisfied leaf node has been visited
if curr_node in graph:
for _ in graph[curr_node]:
if _ not in seen:
queue.append(_)
seen.add(_)
return count//2
1380. Lucky numbers in a matrix
-
Since matrix contains distinct numbers – we just need to get two sets, one is max values in cols and another is min values in rows.
-
The intersection of this two sets is the answer.
Proof that there is only one lucky numbers in a matrix – proof by contradicary
-
suppose that in a matrix, if two lucky numbers, X and Y exist
1 2
a b X c d Y e f
-
since X is a lucky number - min of the row and max of the col: X < b and X > e
- in an axis: —-e—-X—-b—-
-
Y is also a lucky number: Y < e and Y > b
- in the above axis: –Y?—e—X—b—Y?—
-
a contradiction arise - hence, only one lucky number can exist.
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution:
def luckyNumbers (self, matrix: List[List[int]]) -> List[int]:
col_maxs = set()
row_mins = set()
for i in range(len(matrix)):
row_mins.add(min(matrix[i]))
m_T = list(zip(*matrix))
for i in range(len(m_T)):
col_maxs.add(max(m_T[i]))
"""
for i in range(len(matrix[0])):
col_max = -1
for j in range(len(matrix)):
elem = matrix[j][i]
if elem > col_max:
col_max = elem
col_maxs.add(col_max)
"""
"""
class Solution:
def luckyNumbers (self, matrix: List[List[int]]) -> List[int]:
return set(map(min,matrix)) & set(map(max,zip(*matrix)))
"""
return list(col_maxs.intersection(row_mins))
912. Sort an array - o(nlogn) complexity
-
although quicksort is an optimized sorting algorithm, however in worst case, it takes o(n^2) times.
-
quicksort
- quicksort() and partiton()
- use index i to label the position that all elem before index i is less than the pivot.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
class Solution { public int[] sortArray(int[] nums) { quicksort(nums, 0, nums.length-1); return nums; } private static void quicksort(int[] nums, int low, int high) { if (low < high) { int partitionIndex = partition(nums, low, high); quicksort(nums, low, partitionIndex-1); quicksort(nums, partitionIndex+1, high); } } private static int partition(int[] nums, int low, int high) { int pivot = nums[high]; int i = (low-1); for(int j=low; j<= high; j++) { if (nums[j] <= pivot) { i++; // all elements before index at i is less than the pivot // therefore, if elem at j is less than the pivot // swap elem at j with elem at i // in special circumstance - low...j is all less than the pivot, the swapping swaps the element by itself int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } } return i; } }
-
2418. Sort the people
- easy solution - “distinct value in heights” - create a map mapping height to name, then sort the keys (heights), output the names in the sorted order.
- Or, sorting manually with quicksort.
1
2
3
4
5
6
7
class Solution:
def sortPeople(self, names: List[str], heights: List[int]) -> List[str]:
height_dict = {h:n for h, n in zip(heights, names)}
sorted_keys = sorted(list(k for k in height_dict.keys()), reverse=True)
return [height_dict[height] for height in sorted_keys]
1636. Sort array by increasing frequency
-
Count the frequency for each number into pairs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
# initial attempt class Solution: def frequencySort(self, nums: List[int]) -> List[int]: reversedlookup = {} n_min = -100 n_max = 100 frequency = [0 for _ in range(n_min, n_max+1)] for num in nums: frequency[num-n_min] += 1 for i in range(len(frequency)): num = i+n_min freq = frequency[i] if freq > 0: if freq in reversedlookup: reversedlookup[freq].append(num) else: reversedlookup[freq] = [num] result = [] sorted_keys = sorted(reversedlookup.keys()) for k in sorted_keys: for elem in sorted(reversedlookup[k], reverse=True): for i in range(k): result.append(elem) return result
-
Sort the frequency by key
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
class Solution: def frequencySort(self, nums: List[int]) -> List[int]: freq = {} for n in nums: if n in freq: freq[n] += 1 else: freq[n] = 1 result = sorted(nums, key = lambda x: (freq[x], -x)) # by the frequency first, if they have the same frequency, sort the numbers in decreasing order return result
2058. Find the Minimum and Maximum Number of Nodes Between Critical Points
2191. Sort the jumbled numbers
- design a key that jumbles numbers.
- use sorted to sort
numsusing that key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution:
def convert(self, mapping, num):
if num == 0:
return mapping[0]
val = 0
i = 0
while num != 0:
digit = num % 10
num = num//10
val += mapping[digit] * 10**i
i+=1
return val
def sortJumbled(self, mapping: List[int], nums: List[int]) -> List[int]:
return sorted(nums, key=lambda x: self.convert(mapping, x))
164. Maximum gap
-
Vanilla approach - sort then count
-
however this violates the restriction that you must write an algorithm that runs in linear time and uses linear extra space.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class Solution: def maximumGap(self, nums: List[int]) -> int: if len(nums) < 2: return 0 max_gap = -1 sorted_nums = sorted(nums) for i in range(len(sorted_nums)-1): curr_n = sorted_nums[i] next_n = sorted_nums[i+1] max_gap = max(next_n-curr_n, max_gap) return max_gap
-
-
Hence we can consider bucket sort - where we compare the gap between adjacent buckets
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
class Solution: def maximumGap(self, nums: List[int]) -> int: low, high, n = min(nums), max(nums), len(nums) if n <= 2 or high == low: return high - low buckets = defaultdict(list) for num in nums: bucket_index = n-2 if num == high else ((num - low)*(n-1))//(high-low) # the index of the bucket buckets[bucket_index].append(num) candidates = [[min(buckets[i]), max(buckets[i])] for i in range(n-1) if buckets[i]] max_gap = -1 #for k in buckets.keys(): #print(k, buckets[k]) for i in range(len(candidates)-1): # min: next bucket #max: curr bucket max_gap = max(max_gap, candidates[i+1][0] - candidates[i][1]) return max_gap
1334. Find the city with the smallest number of neighbors at a threshold distance
-
BFS won’t suffice for this question! – it can only visit a node once but there will be circumstance that two paths using the same node.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
class Solution: def findTheCity(self, n: int, edges: List[List[int]], distanceThreshold: int) -> int: # solution - BFS with threshold cut graph = {} candidates = [] # notes: init the graph map first, in case there may have city with no neighbour -- wont be added to keys when building the graph for city_no in range(n): graph[city_no] = [] candidates.append([city_no, 0]) for edge in edges: node_0 = edge[0] node_1 = edge[1] distance = edge[2] if node_0 in graph: graph[node_0].append([node_1, distance]) else: graph[node_0] = [[node_1, distance]] if node_1 in graph: graph[node_1].append([node_0, distance]) else: graph[node_1] = [[node_0, distance]] print(graph) for node in graph: queue = [] visited = set() queue.append([node, 0, 0]) # node, accumulated distance, city_visited most_city_visited = -1 while (len(queue) > 0): curr = queue.pop(0) city = curr[0] if city in visited: continue acc_dist = curr[1] if acc_dist > distanceThreshold: continue visited.add(city) most_city_visited += 1 city_visited = curr[2] print("startcity", node, "visiting", city, "city_visited", most_city_visited, 'acc_dist', acc_dist) for edge in graph[city]: neighbour = edge[0] distance = edge[1]+acc_dist if neighbour not in visited: queue.append([neighbour, distance, city_visited+1]) candidates[node] = [node, most_city_visited] #dsc city_visited #asc city_no sorted_by_name = list(sorted(candidates, key=lambda pair: [pair[1], -pair[0]])) print(sorted_by_name) return sorted_by_name[0][0]
-
The best solution should be using the Floyd-Warshall algorithm
- with $O(n^3)$ time complexity and $O(n^2)$ space complexity.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
class Solution: def findTheCity(self, n: int, edges: List[List[int]], distanceThreshold: int) -> int: # large value to represent inf INF = int(1e9+7) distance_matrix = [[INF] * n for _ in range(n)] for i in range(n): distance_matrix[i][i] = 0 for start, end, weight in edges: distance_matrix[start][end] = weight distance_matrix[end][start] = weight self.floyd(n, distance_matrix) return self.get_city_with_fewest_reachable(n, distance_matrix, distanceThreshold) # a n^3 algorithm def floyd(self, n, distance_matrix): for k in range(n): for i in range(n): for j in range(n): # compare the distance of previous path and the path passing by node k distance_matrix[i][j] = min( distance_matrix[i][j], distance_matrix[i][k] + distance_matrix[k][j] ) def get_city_with_fewest_reachable(self, n, distance_matrix, distanceThreshold): fewest_city_id = -1 fewest_reachable_cities = n+1 for start_city in range(n): count = 0 for dest_city in range(n): if start_city != dest_city and distance_matrix[start_city][dest_city] <= distanceThreshold: count += 1 if count <= fewest_reachable_cities: fewest_reachable_cities = count fewest_city_id = start_city return fewest_city_id
2976. Minimum cost to convert string I
- actually it is a application of searching the shortest path between each two nodes in a graph
- the tuple
(original, changed, cost)describes the edges in the graph- need to notice that: there may exist indices
i,jsuch thatoriginal[j] == original[i]andchanged[j] == changed[i].
- need to notice that: there may exist indices
- it is possibly that one letter cannot reach another letter through exchanges, so If it is impossible to convert
sourcetotarget, return-1.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class Solution:
# consider using floyd-warshall
def minimumCost(self, source: str, target: str, original: List[str], changed: List[str], cost: List[int]) -> int:
self.INF = 10**9
alphabet_lookup, alphabet_matrix = self.floyd(source ,target, original, changed, cost)
print(alphabet_lookup, alphabet_matrix)
cost = 0
for i in range(len(source)):
source_a = source[i]
target_a = target[i]
source_index = alphabet_lookup[source_a]
target_index = alphabet_lookup[target_a]
change_cost = alphabet_matrix[source_index][target_index]
if change_cost == self.INF:
cost = -1
break
cost += change_cost
return cost
def floyd(self, source, target, original, changed, cost):
alphabet_original = set(original)
alphabet_change = set(changed)
alphabet_source = set(source)
alphabet_target = set(target)
alphabet = list(alphabet_original.union(alphabet_change, alphabet_source, alphabet_target))
lookup = {}
for i in range(len(alphabet)):
lookup[alphabet[i]] = i
alphabet_matrix = [[self.INF for j in range(len(alphabet))] for i in range(len(alphabet)) ]
for i in range(len(alphabet)):
alphabet_matrix[i][i] = 0
for i in range(len(cost)):
from_index = lookup[original[i]]
to_index = lookup[changed[i]]
alphabet_matrix[from_index][to_index] = min(cost[i], alphabet_matrix[from_index][to_index])
# to avoid cases as: Note that there may exist indices i, j such that original[j] == original[i] and changed[j] == changed[i].
for k in range(len(alphabet)):
for i in range(len(alphabet)):
for j in range(len(alphabet)):
alphabet_matrix[i][j] = min(
alphabet_matrix[i][j],
alphabet_matrix[i][k] + alphabet_matrix[k][j]
)
return lookup, alphabet_matrix
-
alternatively, using dijkstra’s algorithm (since all the edges are all non-negative)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
class Solution: def minimumCost(self, source: str, target: str, original: List[str], changed: List[str], cost: List[int]) -> int: def _ord(ch): return ord(ch)-ord('a') graph = [[] for i in range(26)] for i in range(len(original)): from_ch = _ord(original[i]) to_ch = _ord(changed[i]) graph[from_ch].append((to_ch, cost[i])) def dijkstra(start_ch, graph): distances = [-1 for i in range(26)] pq = [(0, start_ch)] # correctness -- in the first iteration, the algorithms will find the first shortest path -- the path between the start node to its nearest adjacent node while len(pq) > 0: curr_dist, curr_ch = heapq.heappop(pq) if distances[curr_ch] != -1: continue # already found shortest path distances[curr_ch] = curr_dist for edge in graph[curr_ch]: node, dist = edge if distances[node] == -1: # the shortest path is not found new_dist = curr_dist + dist heapq.heappush(pq, (new_dist, node)) return distances min_dists = [ dijkstra(ch, graph) for ch in range(26) ] total_cost = 0 for i in range(len(source)): from_ch = _ord(source[i]) to_ch = _ord(target[i]) cost = min_dists[from_ch][to_ch] if cost == -1: return -1 total_cost += cost return total_cost
2134. Minimum swaps to group all 1’s together II
- use suffix sum
3243. Shortest distance after road addition queries I
-
Floyd-warshall will exceed the time limit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
class Solution: def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]: INF = 10000 distances = [[INF for i in range(n)] for j in range(n)] for i in range(n): distances[i][i] = 0 for i in range(n-1): distances[i][i+1] = 1 # build the initial graph for i in range(n): for j in range(i+1, n): distances[i][j] = j-i ans = [] # adding routes for query in queries: u, v = query distances[u][v] = 1 for k in [u,v]: for i in range(n): for j in range(i+1, n): dist_i_j = distances[i][j] dist_i_k_j = distances[i][k] + distances[k][j] distances[i][j] = min(dist_i_j, dist_i_k_j) #count current min distance ans.append(distances[0][n-1]) return ans
-
Using Dijkstra with priority queue (heap queue):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
class Solution: def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]: graph = {i: [] for i in range(n)} for i in range(n-1): graph[i].append((i+1, 1)) results = [] def dijkstra(graph, start, end): distances = {node: float('infinity') for node in range(n)} distances[start] = 0 priority_queue = [(0, start)] while priority_queue: current_distance, current_node = heapq.heappop(priority_queue) # the current_distance is greater than the known shortest distance between start and c_node if current_distance > distances[current_node]: continue for neighbor, weight in graph[current_node]: distance = current_distance + weight if distance < distances[neighbor]: distances[neighbor] = distance heapq.heappush(priority_queue, (distance, neighbor)) return distances[end] for u, v in queries: graph[u].append((v, 1)) shortest_path_length = dijkstra(graph, 0, n-1) results.append(shortest_path_length) return results
2053. Kth distinct string in an array
- We can organize two sets, one is distinct string set and another is duplicated string set. (set is unordered)
- To find the kth distinct string, we need to follow the order of the original array. We can iterate over the original array and check if each string is in the distinct string set, and use a counter to count the number of distinct string we’ve seen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution:
def kthDistinct(self, arr: List[str], k: int) -> str:
dist_s = set()
dup_s = set()
for s in arr:
if s in dup_s:
continue
if s in dist_s:
dist_s.remove(s)
dup_s.add(s)
else:
dist_s.add(s)
n = 0
for s in arr:
if s in dist_s:
n+=1
if n==k:
return s
return ""
1460. Make two arrays equal by reversing subarrays
-
Think of swapping two adjacent elements – the same as reversing a subarray with length of 2.
-
Therefore, as long as two arrays have the amount of elements, they can be adjusted to be equal.
-
Hence, either we count the elements in two arrays, or we sort them to see if the two arrays are the same after sorting.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
# count the elements class Solution: def canBeEqual(self, target: List[int], arr: List[int]) -> bool: target_map = {} arr_map = {} for num in arr: if num in arr_map: arr_map[num] += 1 else: arr_map[num] = 1 for num in target: if num in target_map: target_map[num] += 1 else: target_map[num] = 1 for key in arr_map.keys(): if key not in target_map.keys(): return False if arr_map[key] != target_map[key]: return False return True
1 2 3 4 5 6 7 8 9 10 11 12 13 14
class Solution: def canBeEqual(self, target: List[int], arr: List[int]) -> bool: if len(target) != len(arr): return False arr_sorted = sorted(arr) target_sorted = sorted(target) for i in range(len(arr_sorted)): if arr_sorted[i] != target_sorted[i]: return False return True
1508. Range sum of sorted subarray sums
-
Use two for-loops to iterate all the ranges, then, sort the array fo subarray sums, get the sum of the sorted array with given indexes.
- this is a brute force solution with $O(n^2 \cdot \log n)$ time complexity – since the solution is sorting an array with $n^2$ elements, at best it need $O(n^2 \cdot \log n^2) = O(n^2 \cdot 2 \log n) = O(n^2 \cdot \log n)$ time.
1 2 3 4 5 6 7 8 9 10 11 12 13
class Solution: def rangeSum(self, nums: List[int], n: int, left: int, right: int) -> int: subsums = [] for i in range(n): subsum = 0 for j in range(i, n): subsum += nums[j] subsums.append(subsum) _sorted = sorted(subsums) return sum(_sorted[left-1: right]) % (10**9 + 7)
-
Alternatively, we can use a priority queue to maintain the order of subarray sums.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
class Solution: def rangeSum(self, nums: List[int], n: int, left: int, right: int) -> int: pq = [] for i in range(n): heapq.heappush(pq, (nums[i], i)) ans = 0 mod = 1e9+7 print(pq) for i in range(1, right+1): p = heapq.heappop(pq) if i >= left: ans = (ans + p[0]) % mod if p[1] < n-1: p = (p[0] + nums[p[1]+1], p[1]+1) heapq.heappush(pq, p) return int(ans)
-
Java, using
PriorityQueue1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
class Solution { public int rangeSum(int[] nums, int n, int left, int right) { PriorityQueue<int[]> pq = new PriorityQueue<>( new Comparator<int[]>() { @Override public int compare(int[] o1, int[] o2) { return o1[0]-o2[0]; } } ); for (int i=0; i<n; i++) { pq.offer(new int[] {nums[i], i}); } int mod = 1000000007; int ans = 0; for(int i=1; i<right+1; i++) { int[] top = pq.poll(); if (i >= left) { ans = (ans + top[0]) % mod; } if (top[1] < n-1) { pq.offer(new int[] {top[0]+ nums[top[1]+1], top[1]+1}); } } return ans; } }
pq.offer()– add a new element into the priority queuepq.poll()– pop the top of the priority queue
-
3016. Minimum number to pushes to type word II
- Count the frequency of each character in the word.
- Sort the items in the frequency map in descending order, then get the key press times for each character by
math.ceil(ch_freq/numpad_n)(prioritize high-frequency characters in the keypad, once reached the number of total available keys, characters are placed in the second, third, … place of a key – we don’t have to assign them just need to count them).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution:
def minimumPushes(self, word: str) -> int:
freq = {}
for ch in word:
if ch in freq:
freq[ch] += 1
else:
freq[ch] = 1
numpad_n = 8
items = sorted(freq.items(), key=lambda item: -item[1])
count = 0
for i, item in enumerate(items):
pressing = (math.ceil((i+1)/numpad_n))
count += item[1] * pressing
print(item, i, pressing, count)
return count
2134. Minimum swaps to group all 1’s together II
- using sliding window - window size is the total number of 1s in the array
840. Magic squares in grid
- DON’T OVERTHINK! Here is the consequence of overthinking.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
class Solution:
def numMagicSquaresInside(self, grid: List[List[int]]) -> int:
count = 0
H = len(grid)
L = len(grid[0])
X_MAX = L-3+1
Y_MAX = H-3+1
if H < 3 or L < 3:
return count
steps = X_MAX*Y_MAX
x = 0
y = 0
occur = {}
non_magic_count = 0
row_sums = [0, 0, 0]
col_sums = [0, 0, 0]
left_diag = 0
right_diag = 0
for i in range(y+3):
for j in range(x+3):
val = grid[i][j]
row_sums[i] += val
col_sums[j] += val
if val in occur:
occur[val] += 1
else:
occur[val] = 1
if i == j:
left_diag += val
if i+j == 2:
right_diag += val
if val > 9 or val < 1:
non_magic_count += 1
if left_diag == right_diag and len(occur) == 9 and non_magic_count == 0 and len(set(row_sums)) == 1 and len(set(col_sums)) == 1:
count += 1
#print(left_diag, right_diag, occur)
#print(grid[0],'\n',grid[1], '\n', grid[2])
steps -= 1
east= [0, 1] # east
south= [1, 0] # south
west= [0, -1] # west
north= [-1, 0] # north -- not used
direction = east
x += direction[1]
y += direction[0]
while steps > 0:
# remove vals out of square
if direction == east:
col_sums = col_sums[1:]
col_sums.append(0)
for i in range(3):
val = grid[y+i][x-1]
row_sums[i] -= val
if occur[val] == 1:
del occur[val]
else:
occur[val] -= 1
if val > 9 or val < 1:
non_magic_count -= 1
new_val = grid[y+i][x+2]
row_sums[i] += new_val
col_sums[2] += new_val
if new_val in occur:
occur[new_val] += 1
else:
occur[new_val] = 1
if new_val > 9 or new_val <1:
non_magic_count += 1
if direction == south:
row_sums = row_sums[1:]
row_sums.append(0)
for i in range(3):
val = grid[y-1][x+i]
col_sums[i] -= val
if occur[val] == 1:
del occur[val]
else:
occur[val] -= 1
if val > 9 or val < 1:
non_magic_count -= 1
new_val = grid[y+2][x+i]
row_sums[2] += new_val
col_sums[i] += new_val
if new_val in occur:
occur[new_val] += 1
else:
occur[new_val] = 1
if new_val > 9 or new_val <1:
non_magic_count += 1
if direction == west:
col_sums = col_sums[:2]
col_sums.insert(0, 0)
for i in range(3):
val = grid[y+i][x+1]
row_sums[i] -= val
if occur[val] == 1:
del occur[val]
else:
occur[val] -= 1
if val > 9 or val < 1:
non_magic_count -= 1
new_val = grid[y+i][x]
row_sums[i] += val
col_sums[0] += val
if new_val in occur:
occur[new_val] += 1
else:
occur[new_val] = 1
if new_val > 9 or new_val <1:
non_magic_count += 1
# get diags
left_diag = 0
right_diag = 0
for i in range(3):
left_diag += grid[y+i][x+i]
right_diag += grid[y+i][2+x+y-(y+i)]
if left_diag == right_diag and len(occur) == 9 and non_magic_count == 0 and len(set(row_sums)) == 1 and len(set(col_sums)) == 1 and left_diag == row_sums[0] and left_diag == col_sums[0]:
count += 1
#decide next direction
if x == X_MAX and direction == east:
direction = south
if x == X_MAX and direction == south:
direction = west
if x == 0 and direction == west:
direction = south
if x == 0 and direction == south:
direction = east
steps -= 1
x += direction[1]
y += direction[0]
return count
-
Simply scan each 3*3 block through the grids. Also, remember that x, y relationships in diagonal entries.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
class Solution: def numMagicSquaresInside(self, grid: List[List[int]]) -> int: count = 0 X_MAX, Y_MAX = len(grid[0])-3+1, len(grid)-3+1 for i in range(Y_MAX): for j in range(X_MAX): lookup = {} diag_left = 0 diag_right = 0 rowsums = [0, 0, 0] colsums = [0, 0, 0] non_magic = 0 for y in range(i, i+3): for x in range(j, j+3): val = grid[y][x] if val not in range(1, 10): non_magic += 1 if val in lookup: lookup[val] += 1 else: lookup[val] = 1 if (x-j) == (y-i): diag_left += val if (x-j)+(y-i) == 2: diag_right += val rowsums[y-i] += val colsums[x-j] += val if len(lookup) == 9 and diag_left == diag_right and diag_left == rowsums[0] and diag_left == colsums[0] and len(set(rowsums)) == 1 and len(set(colsums)) == 1 and non_magic == 0: count += 1 return count
959. Regions cut by slashes
-
A novel question, need to use its geometrical properties to make the shapes.
-
An demo, it can animate the exploring steps in finding number of regions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
import copy class Solution: def regionsBySlashes(self, grid): n = len(grid) expanded_grid = [[0 for j in range(n*3)] for i in range(n*3)] for i in range(n): for j in range(n): if grid[i][j] == '/': expanded_grid[i*3][j*3+2] = 1 expanded_grid[i*3+1][j*3+1] = 1 expanded_grid[i*3+2][j*3] = 1 if grid[i][j] == '\\': expanded_grid[i*3][j*3] = 1 expanded_grid[i*3+1][j*3+1] = 1 expanded_grid[i*3+2][j*3+2] = 1 N = len(expanded_grid) regions = 0 steps = [{ 'grid': copy.deepcopy(expanded_grid), 'regions': regions }] def explore(x, y): # check the boundaries if x in range(0, N) and y in range(0, N) and expanded_grid[x][y] == 0: expanded_grid[x][y] = 1 steps.append({'grid': copy.deepcopy(expanded_grid), 'regions': regions}) explore(x-1, y) explore(x+1, y) explore(x, y-1) explore(x, y+1) for i in range(N): for j in range(N): if expanded_grid[i][j] == 0: # coloring this region until meets the boundary explore(i, j) regions += 1 return regions, steps import random import math import os import time fill = [ '**', '░░', '▒▒', '▓▓', '██', ] def print_grid(grid, regions=0): print('exploring region ', regions+1) for i in range(len(grid)): for j in range(len(grid[i])): if grid[i][j] == 0: print(' ', end='') else: print(fill[min(regions, len(fill)-1)], end='') print() if __name__ == '__main__': candidates = [' ', '/', '\\'] testcase = [random.sample(candidates, 2) for _ in range(2)] print(testcase) solver = Solution() region, steps = solver.regionsBySlashes(testcase) print(region) time.sleep(1) os.system('clear') print_grid(steps[0]['grid']) time.sleep(1) for step in steps: os.system('clear') print_grid(step['grid'], step['regions']) time.sleep(0.1)
40. Combination sum II
719. Find k-th smallest pair distance
1
860. Lemonade change
1140. Stone game II
-
EACH PLAYER PLAYS OPTIMALLY – not greedy, alice would like to maximize her score, Bob will try to minimize Alice’s score
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
class Solution: def stoneGameII(self, piles: List[int]) -> int: n = len(piles) # memo saves the states of (m, index) memo = [[0] * n for _ in range(n)] suffix_sums = piles[:] for i in range(n-2, -1, -1): suffix_sums[i] += suffix_sums[i+1] def minimax(m, index): if index + 2*m >= n: # player can take all remaining piles of stone, to maximize their gain return suffix_sums[index] if memo[m][index] > 0: return memo[m][index] opponent_results = float('inf') for i in range(1, 2*m+1): opponent_results = min(opponent_results, minimax(max(m, i), index+i)) memo[m][index] = suffix_sums[index]-opponent_results return memo[m][index] minimax(1, 0) for row in memo: print(row) return memo[1][0]
Find the longest non-consecutive common substring
-
using 2D DP for looking up sub problems
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
def nonconseqsubstr(s1, s2): m, n = len(s1), len(s2) dp = [["" for _ in range(n+1)] for __ in range(m+1)] for i in range(1, m+1): for j in range(1, n+1): if s1[i-1] == s2[j-1]: dp[i][j] = dp[i-1][j-1] + s1[i-1] else: # choose the longer lcs for previous found if len(dp[i-1][j]) > len(dp[i][j-1]): dp[i][j] = dp[i-1][j] else: dp[i][j] = dp[i][j-1] return dp[m][n], dp
264. Ugly number II
- use a set to mark seen ugly numbers
- use a heap queue to receive newly added ugly numbers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution:
def nthUglyNumber(self, n: int) -> int:
uglynumbers = [1]
uglyset = set(uglynumbers)
if n == 1:
return 1
counter = 0
result = -1
heapq.heapify(uglynumbers)
while counter < n:
result = heapq.heappop(uglynumbers)
#print(result)
counter += 1
mul2 = result*2
if mul2 not in uglyset:
heapq.heappush(uglynumbers, mul2)
uglyset.add(mul2)
mul3 = result*3
if mul3 not in uglyset:
heapq.heappush(uglynumbers, mul3)
uglyset.add(mul3)
mul5 = result*5
if mul5 not in uglyset:
heapq.heappush(uglynumbers, mul5)
uglyset.add(mul5)
return result
f
1004. Max consecutive ones III
- Use sliding window and effectively count number of 0s inside the window:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution:
def longestOnes(self, nums: List[int], k: int) -> int:
n = len(nums)
left = 0
right = 0
penalty = 0
# by default, not selecting any numbers in the array
longest_ones = 0
if nums[0] == 0:
penalty += 1
# consider the special case: nums[0] is 0 but k is also 0, therefore we won't count nums[0] into our result.
if penalty <= k:
longest_ones = 1
# we dont need to count number of ones and zeroes in the window, we see all of them are 1s and count them by subtracting left and right indexes
# if right = left+1 means we don't pick any number from the array
while right != n-1:
# first we need to move start index the if penalty is higher than the limit k.
# the maximum total moves of the left point is less or equal n, so the time complexity is O(n) as we traverse the array.
while penalty > k:
if nums[left] == 0:
penalty -= 1
left += 1
right += 1
if nums[right] == 0: # rightend is 0
penalty += 1
if penalty <= k:
longest_ones = max(longest_ones, right-left+1)
return longest_ones
539. Minimum time difference
- Add 24hr for earlier time if the time difference between two time point is larger than 720 mins (12hr)
- After sorting the time points, don’t forget to compare the first and the last one (since time point is a circular data type: 23:59 -> 00:00)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution:
def findMinDifference(self, timePoints: List[str]) -> int:
times = []
for time_point in timePoints:
times.append( tuple(int(entry) for entry in time_point.split(":")))
times.sort()
def diff(time1, time2):
hourdiff = time1[0] - time2[0]
mindiff = time1[1]-time2[1]
if abs(hourdiff*60 + mindiff) > 12*60 :
if time1[0] < time2[0]:
time1 = (time1[0]+24, time1[1])
else:
time2 = (time2[0]+24, time2[1])
mindiff = abs((time1[0]-time2[0])*60 + (time1[1]-time2[1]))
return mindiff
min_mindiff = 24*60
n = len(times)
for i in range(1, n+1):
min_mindiff = min(diff(times[i%n], times[i-1]), min_mindiff)
return min_mindiff
884. Uncommon words from two sentences
- Understand the question – uncommon word is acutally a word that only appear once from both sentences.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution:
def uncommonFromSentences(self, s1: str, s2: str) -> List[str]:
counter = defaultdict(int)
words = s1.split(' ') + s2.split(' ')
for word in words:
counter[word] += 1
uncommon_words = []
for word in counter:
if counter[word] == 1:
uncommon_words.append(word)
return uncommon_words
386. Lexicographical numbers
- Imagine the result as a forest of trees
- 1-10, 11, 12, 13, …, 19
- 10-100, 101, …
- 2, …
- 3, …
- …
- 1-10, 11, 12, 13, …, 19
- Then, we can use DFS to traverse all nodes, by the limitation that numbers are in range [1, n].
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution:
def lexicalOrder(self, n: int) -> List[int]:
numbers = []
def generate_next_lexical_number(curr_number, limit, number_list):
if curr_number > limit:
return
number_list.append(curr_number)
for digit in range(10):
next_number = curr_number*10 + digit
generate_next_lexical_number(next_number, limit, number_list)
for digit in range(1,10):
generate_next_lexical_number(digit, n, numbers)
return numbers
151. Reverse words in a string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution:
def reverseWords(self, s: str) -> str:
s = s.strip()
cur_word = []
words = []
for i in range(len(s)):
ch = s[i]
if ch == " " and len(cur_word) != 0:
words.append(''.join(cur_word))
cur_word = []
if ch != " ":
cur_word.append(ch)
if len(cur_word) != 0:
words.append(''.join(cur_word))
return ' '.join(words[::-1])
# or use a stack to maintain a LIFO list
334. Increasing Triplet Subsequence
-
Maintain two sequences, leftMins and rightMaxs, for every i - corresponding to nums[i] - we need to know the minimum value before nums[i], and maximum value after nums[i].
- therefore, if we found that leftMins[i] < nums[i] < rightMaxs[i] when we iterate over i, an increasing triplet subsequence is found.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
class Solution: def increasingTriplet(self, nums: List[int]) -> bool: N = len(nums) if N <= 2: return False leftmins = [nums[0]] * N rightmaxs = [nums[-1]] * N for i in range(2, N): leftmins[i] = min(leftmins[i-1], nums[i-1]) for i in range(N-1-2, -1, -1): rightmaxs[i] = max(rightmaxs[i+1], nums[i+1]) for i in range(1, N-1): if leftmins[i] < nums[i] and nums[i] < rightmaxs[i]: return True return False
-
The above solution is using O(N) time complexity and O(N) space complexity.
- using its greedy nature of this question, we can reduce the space complexity to O(1), by maintaining a triplet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
class Solution: def increasingTriplet(self, nums: List[int]) -> bool: N = len(nums) if N <= 2: return False first = nums[0] second = float('inf') for i in range(1, N): num = nums[i] if num > second: return True # num > first but < second, num as second if num > first: second = num # num <= first, place it as the lowest value else: first = num return False
1448. Count good nodes in binary tree
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def goodNodes(self, root: TreeNode) -> int:
goodNodes = []
def traverse(node, prevMax, goodNodes):
if node is None:
return
if node.val >= prevMax:
goodNodes.append(node.val)
traverse(node.left, max(prevMax, node.val), goodNodes)
traverse(node.right, max(prevMax, node.val), goodNodes)
traverse(root, -float('inf'), goodNodes)
return len(goodNodes)
use generator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def goodNodes(self, root: TreeNode) -> int:
def traverse(node, prevMax):
if node is None:
return
if node.val >= prevMax:
yield node.val
yield from traverse(node.left, max(prevMax, node.val))
yield from traverse(node.right, max(prevMax, node.val))
return len(list(traverse(root, -float('inf'))))
use a counter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def goodNodes(self, root: TreeNode) -> int:
def traverse(node, prevMax):
count = 0
if node is None:
return count
if node.val >= prevMax:
count += 1
prevMax = max(prevMax, node.val)
count += traverse(node.left, prevMax) + traverse(node.right, prevMax)
return count
return traverse(root, -float('inf'))
437. Path sum III
-
brute force – O(N^2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int: def rootSum(root, targetSum): if root is None: return 0 result = 0 if root.val == targetSum: result += 1 result += rootSum(root.left, targetSum-root.val) result += rootSum(root.right, targetSum-root.val) return result result = rootSum(root, targetSum) result += rootSum(root.left, targetSum) result += rootSum(root.right, targetSum) return result
-
Hashset
1
23. Merge k-sorted lists
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
N = len(lists)
heap = []
for i in range(len(lists)):
if lists[i]:
heapq.heappush(heap, (lists[i].val, i, lists[i]))
dummy = ListNode(0)
current = dummy
while heap:
val, index, node = heapq.heappop(heap)
current.next = ListNode(val)
current = current.next
if node.next:
heapq.heappush(heap, (node.next.val, index, node.next))
return dummy.next
Table of Content
- 1701. Average waiting time
- 1598. Crawler log folder
- 1717. Maximum score from removing substrings
- 1530. Number of good leaf node pairs
- 1380. Lucky numbers in a matrix
- 912. Sort an array - o(nlogn) complexity
- 2418. Sort the people
- 1636. Sort array by increasing frequency
- 2058. Find the Minimum and Maximum Number of Nodes Between Critical Points
- 2191. Sort the jumbled numbers
- 164. Maximum gap
- 1334. Find the city with the smallest number of neighbors at a threshold distance
- 2976. Minimum cost to convert string I
- 2134. Minimum swaps to group all 1’s together II
- 3243. Shortest distance after road addition queries I
- 2053. Kth distinct string in an array
- 1460. Make two arrays equal by reversing subarrays
- 1508. Range sum of sorted subarray sums
- 3016. Minimum number to pushes to type word II
- 2134. Minimum swaps to group all 1’s together II
- 840. Magic squares in grid
- 959. Regions cut by slashes
- 40. Combination sum II
- 719. Find k-th smallest pair distance
- 860. Lemonade change
- 1140. Stone game II
- Find the longest non-consecutive common substring
- 264. Ugly number II
- 1004. Max consecutive ones III
- 539. Minimum time difference
- 884. Uncommon words from two sentences
- 386. Lexicographical numbers
- 151. Reverse words in a string
- 334. Increasing Triplet Subsequence
- 1448. Count good nodes in binary tree
- 437. Path sum III
- 23. Merge k-sorted lists