KK's blog

每天积累多一些

0%

LeetCode

<div>

You are given an array routes representing bus routes where routes[i] is a bus route that the i<sup>th</sup> bus repeats forever.

  • For example, if routes[0] = [1, 5, 7], this means that the 0<sup>th</sup> bus travels in the sequence 1 -> 5 -> 7 -> 1 -> 5 -> 7 -> 1 -> ... forever.

You will start at the bus stop source (You are not on any bus initially), and you want to go to the bus stop target. You can travel between bus stops by buses only.

Return the least number of buses you must take to travel from source to target. Return -1 if it is not possible.

Example 1:

<pre>Input: routes = [[1,2,7],[3,6,7]], source = 1, target = 6 Output: 2 Explanation: The best strategy is take the first bus to the bus stop 7, then take the second bus to the bus stop 6. </pre>

Example 2:

<pre>Input: routes = [[7,12],[4,5,15],[6],[15,19],[9,12,13]], source = 15, target = 12 Output: -1 </pre>

Constraints:

  • 1 <= routes.length <= 500.
  • 1 <= routes[i].length <= 10<sup>5</sup>
  • All the values of routes[i] are unique.
  • sum(routes[i].length) <= 10<sup>5</sup>
  • 0 <= routes[i][j] < 10<sup>6</sup>
  • 0 <= source, target < 10<sup>6</sup>

</div>

题目大意:

求公交路线中最小换站次数

解题思路:

最值题且涉及到图,容易想到BFS。但此题难点在于不能将每个站作为一个节点,这样代码复杂且TLE。优化的做法是将路线作为节点,因为同一路线换站次数是一样的。属于一组节点作为一层的BFS

解题步骤:

N/A

注意事项:

  1. 将整条路线作为图的节点。这些节点都位于同一层,换乘不同路线才会换到下一层,路径+1
  2. source和target所在的路线可能是多个,所以要将target所在的所有路线放在set中
  3. 邻接表计算是用两条路线是否有交集,有交集才能换乘,才能进入下一层访问,Python用set(a) & set(b)
  4. 如果target和source相等,返回0 (相等test case)
  5. 站点不存在或者不存在任何路线,就返回-1

Python代码:

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
def numBusesToDestination(self, routes: List[List[int]], source: int, target: int) -> int:
if target == source: # remember
return 0
graph = collections.defaultdict(list) # both set or list are fine
queue, targets, visited, distance = collections.deque(), set(), set(), collections.defaultdict(int)
for i in range(len(routes)):
if source in set(routes[i]):
queue.append(i)
visited.add(i)
distance[i] = 1
if target in set(routes[i]):
targets.add(i)
for j in range(i + 1, len(routes)):
if set(routes[i]) & set(routes[j]):
graph[i].append(j)
graph[j].append(i)

while queue:
node = queue.popleft()
if node in targets:
return distance[node]
for neighbor in graph[node]:
if neighbor in visited:
continue
queue.append(neighbor)
visited.add(neighbor)
distance[neighbor] = distance[node] + 1
return -1 # remember

TLE版本用station作为节点

Python代码:

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
def numBusesToDestination2(self, routes: List[List[int]], source: int, target: int) -> int:
if target == source: # remember
return 0
graph, station_to_route = collections.defaultdict(list), collections.defaultdict(set)
for j in range(len(routes)):
r = routes[j] + [routes[j][0]]
for i in range(1, len(r)):
# graph[r[i - 1]].append(r[i])
station_to_route[r[i]].add(j)

queue = collections.deque()
visited = set()
bus_num = collections.defaultdict(int) # remember collections.defaultdict(lambda: 1)
for j in station_to_route[source]:
for i in routes[j]:
if i in visited:
continue
queue.append(i)
visited.add(i)
bus_num[i] = 1

while queue:
node = queue.popleft()
if node == target:
return bus_num[node]
for j in station_to_route[node]:
for i in routes[j]:
if i in visited:
continue
queue.append(i)
visited.add(i)
bus_num[i] = bus_num[node] + 1
return -1 # remember

算法分析:

时间复杂度为<code>O(n<sup>2</sup>)</code>,空间复杂度<code>O(n<sup>2</sup>)</code>, n = num of routes

题目大意:

长URL变成短URL方便传输和阅读,特别是很多社交网站对字数有限制如Twitter。

解题步骤:

解题思路:

  1. 沟通清楚需要,用户数(都会很大)
  2. 数据量估计。如火车售票系统,估计西雅图总人口,高峰乘坐人数。
  3. 先完成一个功能。如buy ticket。画图,每个部件high level细节包括数据库的schema和组件间的API接口。
  4. 自觉加上优化,如cache,master-slave数据库,LB等等。不要等面试官提醒
  5. 完成一个功能后,再画图扩展到其他功能。

短网址长度:

短网址若只含数字,也就是十进制整数还是不够短。可以考虑加入大小写字母,总共有26x2+10=62,也就是一个62进制数。
网站总数是45亿个,62^7就远远大于45亿,7位就够。
若long表示的64位整数,log62(2^64-1)=11,大约是对应11位。

网站 十进制 62进制
amazon.com 0854 a5G

存储方法:

写操作:长网址到短网址
读操作:短网址到长网址
读操作远远大于写操作,所以key(或primary key)选在短网址, value在长网址。
每个新的长网址,对应一个短网址还是多个?考虑一下几点:

  1. 若对应一个短网址,必须再产生一个unique key在长网址上来决定该长网址对应的短网址是否存在。大大降低写操作速度。
  2. 长网址虽然一样,但可以带不同的header, user agent,从而知道进入该长网址的入口(其他网站),短网址商的盈利来源。
    所以长网址对应多个短网址,Google Maps就采取这个设计。
网站 十进制 62进制
amazon.com 0854 a5G
amazon.com 17922 bYd

数据库选择可以是关系型数据库SQL Server,或者KV数据库如Redis,dynamoDB。可以详细讨论关系型数据库与No SQL的区别。
此题目用No sql比较好,因为从分布性考虑和是否需要复杂的Join操作来考虑,No sql有明显优势。

计算短网址:

另一个核心问题就是如何计算短网址,具体而言是怎么从URL转化为一个十进制整数。有几个方案:

  1. 最简单的是维护一个最大值,每个新的请求,对此值加1。缺点是分布式系统中,维护单一最大值(所有机器中)大大降低性能。
  2. 取URL的hash值得到64位整数再取前7位,但会有冲突。
  3. 参考分布式发号器

十进制到62进制用短除法来做,
796%62=52, (796-52)/62=12. 12%62=12, (12-12)/62=0. 结果为(12)(52) = cP

DDOS:

这是一个细节考虑,若黑客大量发请求,耗尽所有ID怎么办?

  1. 限制IP单日请求总数,超过直接拒绝。
  2. 限制长网址的单一性。限制IP还不够,因为用proxy provider服务可以绕过这个限制。用Redis来cache长网址到短网址的一日数据,
    然后LRU淘汰旧的数据。这样如果此URL的请求超过一定数量,比如100次,就返回最新的短网址。 长网址->次数+短URL

301还是302:

301是永久重定向,302是临时重定向。如果用了301, Google,百度等搜索引擎,搜索的时候会直接展示真实地址,那我们就无法统计
到短地址被点击的次数了,也无法收集用户的Cookie, User Agent等信息。这是短网址商的盈利来源。

Ref:

https://soulmachine.gitbooks.io/system-design/content/cn/tinyurl.html
https://segmentfault.com/a/1190000006140476

LeetCode 253 Meeting Rooms II

Given an array of meeting time intervals consisting of start and end times [[s1,e1],[s2,e2],...] (si < ei), find the minimum number of conference rooms required.

For example, Given [[0, 30],[5, 10],[15, 20]], return 2.

题目大意:

输入[[0, 30],[5, 10],[15, 20]]表示每个会议的开始结束时间,求最少需要多少会议室能够安排所有的会议。

最小堆解题思路:

基于merging interval题目,首先按start排序。并且merge条件是start小于上一个会议的end。

  1. 写几个例子感受一下。 有两个重叠的会议,现在插入新的会议。是否再需要一个会议室取决于该新会议的开始时间小于这两个目前会议的终止时间的最小值。
    所以思路是用End time min-heap维护目前会议End time。若新会议start time小于堆顶元素,入栈且activeMeeting++,否则循环地出栈且activeMeeting--直到 start time小于堆顶。
    这过程activeMeeting的最大值即所求。 最坏情况是所有会议都重叠,复杂度为n* 2logn因为n个元素入堆出堆各一次,所以复杂度为nlogn,但会议一般不会集中,平均情况比排序法稍优。

注意事项:

  1. Heap为结束时间的heap。
  2. 类似于递减栈模板,用开始时间与堆顶的结束时间比较(表示这些会议均已结束),若大于堆顶,连续出堆。

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
def minMeetingRooms(self, intervals: List[List[int]]) -> int:
intervals.sort()
heap = [] # heap for end time
max = 0
for i in range(len(intervals)):
# start time of the new meeting is earlier than on-going endtime
while heap and intervals[i][0] >= heap[0]:
heappop(heap)
heappush(heap, (intervals[i][1]))
if len(heap) > max:
max = len(heap)
return max

算法分析:

由于输入无序,所以先要排序O(nlogn), 而循环复杂度为O(nlogk), 所以总时间复杂度为O(nlogn),空间复杂度O(k), k为所求也就是需要的会议室数。

排序法解题思路(推荐):

  1. 证明解与具体间隔无关,只与end time的值有关。
  2. 基于1和2,对end time进行排序,题解只与start-end的相对顺序有关。既然这样,我们可以把所有start,end一起排序,也就是按时间轴排列,排成一个2n大小的数组,
    遇到start,activeMeeting++,遇到end,activeMeeting--。 这过程activeMeeting的最大值即所求。 当然,上述方法直观,但实现起来需要建立一个class Node{value, startOrEnd}。本质上等价于对排序后的start数组和排序后的end数组进行合并排序。 合并排序的结果等价于时间轴上两个数组的统一排序。当然,不需要剩余部分的合并排序,因为这部分不会增加activeMeeting的值。

解题步骤:

  1. 排序start
  2. 排序end
  3. 合并排序,start小就activeMeeting++,否则activeMeeting--。求activeMeeting的最大值。

注意事项:

  1. 若endpoint值相同情况下,要确保第二个排序先结束点,再出发点,因为相同点不算有重复飞机

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def minMeetingRooms(self, intervals: List[List[int]]) -> int:
start = [(i[0], 1) for i in intervals]
ends = [(i[1], -1) for i in intervals]
endpoints = start + ends
endpoints.sort()
active_meetings, max = 0, 0
for i in range(len(endpoints)):
if endpoints[i][1] == 1:
active_meetings += 1
else:
active_meetings -= 1
if active_meetings > max:
max = active_meetings
return max

算法分析:

时间复杂度为O(nlogn),空间复杂度O(n)

LeetCode

<div>

You are given an undirected graph. You are given an integer n which is the number of nodes in the graph and an array edges, where each edges[i] = [u<sub>i</sub>, v<sub>i</sub>] indicates that there is an undirected edge between u<sub>i</sub> and v<sub>i</sub>.

A connected trio is a set of three nodes where there is an edge between every pair of them.

The degree of a connected trio is the number of edges where one endpoint is in the trio, and the other is not.

Return the minimum degree of a connected trio in the graph, or -1 if the graph has no connected trios.

Example 1:

<pre>Input: n = 6, edges = [[1,2],[1,3],[3,2],[4,1],[5,2],[3,6]] Output: 3 Explanation: There is exactly one trio, which is [1,2,3]. The edges that form its degree are bolded in the figure above. </pre>

Example 2:

<pre>Input: n = 7, edges = [[1,3],[4,1],[4,3],[2,5],[5,6],[6,7],[7,5],[2,6]] Output: 0 Explanation: There are exactly three trios:

  1. [1,4,3] with degree 0.
  2. [2,5,6] with degree 2.
  3. [5,6,7] with degree 2. </pre>

Constraints:

  • 2 <= n <= 400
  • edges[i].length == 2
  • 1 <= edges.length <= n * (n-1) / 2
  • 1 <= u<sub>i</sub>, v<sub>i</sub> <= n
  • u<sub>i</sub> != v<sub>i</sub>
  • There are no repeated edges.

</div>

题目大意:

给定一个图,trio是三个节点直接互相相连,而度数表示连着trio的边的个数

解题思路:

根据定义,找出所有三个节点的组合,判断是否trio,然后根据trio的每个节点的度数总和 - 6即为所求
遍历所有三个节点组合时,会重复了两次。所以一个优化是,先按度数排序节点,若节点度数大于等于最小度数除以3,跳出循环。因为这个最小度数的节点已经大于等于3,trio里其他两个度数比它大的节点的度数更加会大于最小度数除以3,这样总度数肯定大于此时的最小度数

解题步骤:

N/A

注意事项:

  1. 根据定义,找出所有三个节点的组合u -> v, v -> w, w是否在u中,判断是否trio。先按度数排序节点,若节点度数大于等于最小度数除以3,跳出循环
  2. 邻接图用set,因为Line 13查找w是否在u中可以提高效率
  3. min_degree / 3不是// 3

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def minTrioDegree(self, n: int, edges: List[List[int]]) -> int:
graph = collections.defaultdict(set) # use set coz if w in graph[u]
for u, v in edges:
graph[u].add(v)
graph[v].add(u)

min_degree = float('inf')
for u in sorted(range(1, n + 1), key=lambda x: len(graph[x])):
if len(graph[u]) >= min_degree / 3: # remember / 3 not // 3
break
for v in graph[u]:
for w in graph[v]:
if w in graph[u]:
min_degree = min(min_degree, len(graph[u]) + len(graph[v]) + len(graph[w]))
return min_degree - 6 if min_degree != float('inf') else -1

算法分析:

时间复杂度为<code>O(n<sup>3</sup>)</code>,空间复杂度<code>O(n<sup>2</sup>)</code>

LeetCode

<div>

You are given two integers, x and y, which represent your current location on a Cartesian grid: (x, y). You are also given an array points where each points[i] = [a<sub>i</sub>, b<sub>i</sub>] represents that a point exists at (a<sub>i</sub>, b<sub>i</sub>). A point is valid if it shares the same x-coordinate or the same y-coordinate as your location.

Return the index (0-indexed) of the valid point with the smallest Manhattan distance from your current location. If there are multiple, return the valid point with the smallest index. If there are no valid points, return -1.

The Manhattan distance between two points (x<sub>1</sub>, y<sub>1</sub>) and (x<sub>2</sub>, y<sub>2</sub>) is abs(x<sub>1</sub> - x<sub>2</sub>) + abs(y<sub>1</sub> - y<sub>2</sub>).

Example 1:

<pre>Input: x = 3, y = 4, points = [[1,2],[3,1],[2,4],[2,3],[4,4]] Output: 2 Explanation: Of all the points, only [3,1], [2,4] and [4,4] are valid. Of the valid points, [2,4] and [4,4] have the smallest Manhattan distance from your current location, with a distance of 1. [2,4] has the smallest index, so return 2.</pre>

Example 2:

<pre>Input: x = 3, y = 4, points = [[3,4]] Output: 0 Explanation: The answer is allowed to be on the same location as your current location.</pre>

Example 3:

<pre>Input: x = 3, y = 4, points = [[2,3]] Output: -1 Explanation: There are no valid points.</pre>

Constraints:

  • 1 <= points.length <= 10<sup>4</sup>
  • points[i].length == 2
  • 1 <= x, y, a<sub>i</sub>, b<sub>i</sub> <= 10<sup>4</sup>

</div>

题目大意:

给定一个坐标和一堆坐标,这个坐标与某个点在同一条y轴或x轴上叫合法点,求它到这些点的最小曼哈顿距离对应的点的下标,若有多个结果,返回最小的数组下标。

解题思路:

Easy题,根据题意求

解题步骤:

N/A

注意事项:

Python代码:

1
2
3
4
5
6
7
8
9
10
11
def nearestValidPoint(self, x: int, y: int, points: List[List[int]]) -> int:
min_dis = float('inf')
res = -1
for i, (_x, _y) in enumerate(points):
if x == _x or y == _y:
if abs(x - _x) + abs(y - _y) < min_dis:
min_dis = abs(x - _x) + abs(y - _y)
res = i
elif abs(x - _x) + abs(y - _y) == min_dis and i < res:
res = i
return res

算法分析:

时间复杂度为O(n),空间复杂度O(1)

Free mock interview