KK's blog

每天积累多一些

0%

LeetCode

<div>

Given two vectors of integers v1 and v2, implement an iterator to return their elements alternately.

Implement the ZigzagIterator class:

  • ZigzagIterator(List<int> v1, List<int> v2) initializes the object with the two vectors v1 and v2.
  • boolean hasNext() returns true if the iterator still has elements, and false otherwise.
  • int next() returns the current element of the iterator and moves the iterator to the next element.

Example 1:

<pre>Input: v1 = [1,2], v2 = [3,4,5,6] Output: [1,3,2,4,5,6] Explanation: By calling next repeatedly until hasNext returns false, the order of elements returned by next should be: [1,3,2,4,5,6]. </pre>

Example 2:

<pre>Input: v1 = [1], v2 = [] Output: [1] </pre>

Example 3:

<pre>Input: v1 = [], v2 = [1] Output: [1] </pre>

Constraints:

  • 0 <= v1.length, v2.length <= 1000
  • 1 <= v1.length + v2.length <= 2000
  • -2<sup>31</sup> <= v1[i], v2[i] <= 2<sup>31</sup> - 1

Follow up: What if you are given k vectors? How well can your code be extended to such cases?

Clarification for the follow-up question:

The "Zigzag" order is not clearly defined and is ambiguous for k > 2 cases. If "Zigzag" does not look right to you, replace "Zigzag" with "Cyclic".

Follow-up Example:

<pre>Input: v1 = [1,2,3], v2 = [4,5,6,7], v3 = [8,9] Output: [1,4,8,2,5,9,3,6,7] </pre>

</div>

题目大意:

求两数组轮替取值的Iterator

解题思路:

将数组和数组下标分别存于新数组中。用一个list_index来记录要取哪个数组

解题步骤:

N/A

注意事项:

  1. 用Iterator模板,hasNext也是找到下一个元素为止,由于只有两个数组,所以不用循环。取值是一个二维数组val = self.input[self.list_index][self.index[self.list_index]]
  2. next中取值后指针要后移。

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ZigzagIterator(TestCases):

def __init__(self, v1: List[int], v2: List[int]):
self.input = [v1, v2]
self.index = [0, 0]
self.list_index = 0

def next(self) -> int:
if self.hasNext():
val = self.input[self.list_index][self.index[self.list_index]]
self.index[self.list_index] += 1
self.list_index = (self.list_index + 1) % 2
return val
return None

def hasNext(self) -> bool:
if self.index[self.list_index] < len(self.input[self.list_index]):
return True
self.list_index = (self.list_index + 1) % 2
return self.index[self.list_index] < len(self.input[self.list_index])

算法分析:

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

LeetCode

<div>

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:

<pre>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) (2, 1, 1) (2, 2) (3, 1) Note that different sequences are counted as different combinations. </pre>

Example 2:

<pre>Input: nums = [9], target = 3 Output: 0 </pre>

Constraints:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 1000
  • All the elements of nums are unique.
  • 1 <= target <= 1000

Follow up: What if negative numbers are allowed in the given array? How does it change the problem? What limitation we need to add to the question to allow negative numbers?

</div>

题目大意:

求所有可能组合的和等于target。元素可以复用且顺序在组合中可以任意。

LeetCode 377 Combination Sum IV 题目基本一样,唯一区别是结果元素有序,属于排列 LeetCode 518 Coin Change 2 题目基本一样,唯一区别是结果元素无序,属于组合

解题思路:

这题似组合又似排列,是组合的结果再全排列。求种数另一种的方法是DP. dp[n]为target=n所有的所有组合种数。属于数值->个数型DP
递归公式

1
dp[n + num[i]] = dp[n], n = [1, tgt], i = [0, len(nums) - 1]

解题步骤:

N/A

注意事项:

  1. dp[i + nums[j]] += dp[i] 而不是dp[i] + 1
  2. dp[0] = 1表示数值为0,可以不用任何数就能获得,所以是1种
  3. 先排序,否则如[3, 1, 2, 4],返回dp[1] = 0, 但应该是dp[1] = 1

Python代码:

1
2
3
4
5
6
7
8
9
10
11
# dp[n + num[i]] = dp[n], n = [1, tgt], i = [0, len(nums) - 1]
def combinationSum4(self, nums: List[int], target: int) -> int:
nums.sort() # remember
dp = [0] * (target + 1)
dp[0] = 1
for i in range(len(dp)):
for j in range(len(nums)):
if i + nums[j] > target:
break
dp[i + nums[j]] += dp[i] # remember no +1
return dp[-1]

算法分析:

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

LeetCode

<div>

Given a string s and an integer k, return the length of the longest substring of s such that the frequency of each character in this substring is greater than or equal to k.

Example 1:

<pre>Input: s = "aaabb", k = 3 Output: 3 Explanation: The longest substring is "aaa", as 'a' is repeated 3 times. </pre>

Example 2:

<pre>Input: s = "ababbc", k = 2 Output: 5 Explanation: The longest substring is "ababb", as 'a' is repeated 2 times and 'b' is repeated 3 times. </pre>

Constraints:

  • 1 <= s.length <= 10<sup>4</sup>
  • s consists of only lowercase English letters.
  • 1 <= k <= 10<sup>5</sup>

</div>

题目大意:

求最长每种字符至少k个的子串

解题思路:

类似于L003 Longest Substring Without Repeating Characters用双指针法,难点是每种字符,用26字母存储法解决。
前面的sliding window题都要求字符种数是固定的,否则不单调,例如本题。如aabbcc, i=1, "aa",满足题目条件,但向右移就不满足条件,但事实再移一位就满足条件
若修改为n=2, 也就是收缩条件为字符窗口必须有两种字符,若出现3个就shrink直到只有两个字符,而这个窗口忽略了每个字符都要出现k个的条件。
举例aabb, i=3, res=4. i=4, aabbc, 出现3种字符,shrink到bbc, 再扩展到bbcc.

此题关键在于将收缩条件:每个字符出现k次转化成字符种数=n再计算k

解题步骤:

N/A

注意事项:

  1. 按多少种不同字符来做sliding window。有1-26种。
  2. 子函数求给定种数下的最长子串,所以满足条件在外循环不在内循环,还需进一步统计每种字符是否符合k个。内循环为不满足条件的情况len(char_to_count) == n + 1
  3. char_to_count记录每种字符个数, valid_count是子串[left, i]之间满足题意中个数大于等于k的种数。

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def longestSubstring(self, s: str, k: int) -> int:
res = 0
for i in range(1, 27):
res = max(res, self.longest_substring(s, k, i))
return res

def longest_substring(self, s, k, n) -> int:
left, char_to_count = 0, collections.defaultdict(int)
res = 0
for i in range(len(s)):
char_to_count[ord(s[i]) - ord('a')] += 1
while len(char_to_count) == n + 1:
char_to_count[ord(s[left]) - ord('a')] -= 1 # use left not i
if char_to_count[ord(s[left]) - ord('a')] == 0:
char_to_count.pop(ord(s[left]) - ord('a'))
left += 1
valid_count = len([_ for _ in char_to_count.values() if _ >= k])
if len(char_to_count) == valid_count:
res = max(res, i - left + 1)
return res

算法分析:

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

LeetCode

<div>

Given a n * n matrix grid of 0's and 1's only. We want to represent the grid with a Quad-Tree.

Return the root of the Quad-Tree representing the grid.

Notice that you can assign the value of a node to True or False when isLeaf is False, and both are accepted in the answer.

A Quad-Tree is a tree data structure in which each internal node has exactly four children. Besides, each node has two attributes:

  • val: True if the node represents a grid of 1's or False if the node represents a grid of 0's.
  • isLeaf: True if the node is leaf node on the tree or False if the node has the four children.

<pre>class Node { public boolean val; public boolean isLeaf; public Node topLeft; public Node topRight; public Node bottomLeft; public Node bottomRight; }</pre>

We can construct a Quad-Tree from a two-dimensional area using the following steps:

  1. If the current grid has the same value (i.e all 1's or all 0's) set isLeaf True and set val to the value of the grid and set the four children to Null and stop.
  2. If the current grid has different values, set isLeaf to False and set val to any value and divide the current grid into four sub-grids as shown in the photo.
  3. Recurse for each of the children with the proper sub-grid.

If you want to know more about the Quad-Tree, you can refer to the wiki.

Quad-Tree format:

The output represents the serialized format of a Quad-Tree using level order traversal, where null signifies a path terminator where no node exists below.

It is very similar to the serialization of the binary tree. The only difference is that the node is represented as a list [isLeaf, val].

If the value of isLeaf or val is True we represent it as 1 in the list [isLeaf, val] and if the value of isLeaf or val is False we represent it as 0.

Example 1:

<pre>Input: grid = [[0,1],[1,0]] Output: [[0,1],[1,0],[1,1],[1,1],[1,0]] Explanation: The explanation of this example is shown below: Notice that 0 represnts False and 1 represents True in the photo representing the Quad-Tree. </pre>

Example 2:

<pre>Input: grid = [[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0]] Output: [[0,1],[1,1],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]] Explanation: All values in the grid are not the same. We divide the grid into four sub-grids. The topLeft, bottomLeft and bottomRight each has the same value. The topRight have different values so we divide it into 4 sub-grids where each has the same value. Explanation is shown in the photo below: </pre>

Constraints:

  • n == grid.length == grid[i].length
  • n == 2<sup>x</sup> where 0 <= x <= 6

</div>

题目大意:

由矩阵建四叉树。矩阵有0和1组成。按以下步骤:若子矩阵(变成为2的幂)只含1或0,生成一个叶子节点,值为该值;子矩阵含0和1混合,值为0或1(均为答案),非叶子节点,递归四个同样大小的矩阵生成相应节点。
矩阵大小为2的幂,最小长度为1.

单格DFS算法II解题思路(推荐):

也是DFS,但递归终止条件为长度1,也就是每个cell都是叶子节点,先递归然后再归纳,若四个儿子节点都是叶子节点且值都相等,合并为一个叶子节点。否则为非叶子节点。 此算法实现更简单,但比较难想出。上述方法思想是按照题意。

注意事项:

  1. size = 1作为终止条件
  2. 两个条件该轮递归的节点为叶子节点,第一值相等,第二儿子节点都是叶子节点

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def construct(self, grid: List[List[int]]) -> 'Node':
return self.dfs(grid, 0, 0, len(grid))

def dfs(self, grid, start_x, start_y, n):
if n == 1:
return Node(grid[start_x][start_y], True, None, None, None, None)
top_left = self.dfs(grid, start_x, start_y, n // 2)
top_right = self.dfs(grid, start_x, start_y + n // 2, n // 2)
bottom_left = self.dfs(grid, start_x + n // 2, start_y, n // 2)
bottom_right = self.dfs(grid, start_x + n // 2, start_y + n // 2, n // 2)
if top_left.val == top_right.val == bottom_left.val == bottom_right.val and \
top_left.isLeaf and top_right.isLeaf and bottom_left.isLeaf and bottom_right.isLeaf: # remmember
return Node(top_left.val, True, None, None, None, None)
else:
return Node(1, False, top_left, top_right, bottom_left, bottom_right)

算法分析:

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


presum解题思路(不推荐):

这是我的方法,按照定义求解,定义是递归的,所以用DFS。而统计子矩阵和用presum提高效率。但实现比较复杂

解题步骤:

N/A

注意事项:

  1. 子矩阵presum用模板
  2. 终止条件为子矩阵sum是0或n平方

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
34
def construct(self, grid: List[List[int]]) -> 'Node':
presum = self.get_presum(grid)
return self.dfs(grid, (0, 0), (len(grid) - 1, len(grid[0]) - 1), presum)

def dfs(self, grid, top_left, bottom_right, presum):
grim_sum = self.get_grid_sum(top_left, bottom_right, presum)
if grim_sum == 0:
return Node(0, True, None, None, None, None)
if grim_sum == (bottom_right[0] - top_left[0] + 1) * (bottom_right[0] - top_left[0] + 1):
return Node(1, True, None, None, None, None)

node = Node(1, False, None, None, None, None)
row_mid = top_left[0] + (bottom_right[0] - top_left[0]) // 2
col_mid = top_left[1] + (bottom_right[1] - top_left[1]) // 2
node.topLeft = self.dfs(grid, top_left, (row_mid, col_mid), presum)
node.topRight = self.dfs(grid, (top_left[0], col_mid + 1), (row_mid, bottom_right[1]), presum)
node.bottomLeft = self.dfs(grid, (row_mid + 1, top_left[1]), (bottom_right[0], col_mid), presum)
node.bottomRight = self.dfs(grid, (row_mid + 1, col_mid + 1), bottom_right, presum)
return node

def get_grid_sum(self, top_left, bottom_right, presum):
left = 0 if top_left[1] < 1 else presum[bottom_right[0]][top_left[1] - 1]
top = 0 if top_left[0] < 1 else presum[top_left[0] - 1][bottom_right[1]]
diag = 0 if top_left[0] < 1 or top_left[1] < 1 else presum[top_left[0] - 1][top_left[1] - 1]
return presum[bottom_right[0]][bottom_right[1]] - left - top + diag

def get_presum(self, grid):
presum = [[0 for _ in range(len(grid[0]))] for _ in range(len(grid))]
for i in range(len(grid)):
row_sum = 0
for j in range(len(grid[0])):
row_sum += grid[i][j]
presum[i][j] = row_sum + (presum[i - 1][j] if i > 0 else 0)
return presum

算法分析:

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

LeetCode

<div>

You are given an n x n integer matrix board where the cells are labeled from 1 to n<sup>2</sup> in a Boustrophedon style starting from the bottom left of the board (i.e. board[n - 1][0]) and alternating direction each row.

You start on square 1 of the board. In each move, starting from square curr, do the following:

  • Choose a destination square next with a label in the range [curr + 1, min(curr + 6, n<sup>2</sup>)].
    • This choice simulates the result of a standard 6-sided die roll: i.e., there are always at most 6 destinations, regardless of the size of the board.
  • If next has a snake or ladder, you must move to the destination of that snake or ladder. Otherwise, you move to next.
  • The game ends when you reach the square n<sup>2</sup>.

A board square on row r and column c has a snake or ladder if board[r][c] != -1. The destination of that snake or ladder is board[r][c]. Squares 1 and n<sup>2</sup> do not have a snake or ladder.

Note that you only take a snake or ladder at most once per move. If the destination to a snake or ladder is the start of another snake or ladder, you do not follow the subsequent snake or ladder.

  • For example, suppose the board is [[-1,4],[-1,3]], and on the first move, your destination square is 2. You follow the ladder to square 3, but do not follow the subsequent ladder to 4.

Return the least number of moves required to reach the square n<sup>2</sup>. If it is not possible to reach the square, return -1.

Example 1:

<pre>Input: board = [[-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1],[-1,35,-1,-1,13,-1],[-1,-1,-1,-1,-1,-1],[-1,15,-1,-1,-1,-1]] Output: 4 Explanation: In the beginning, you start at square 1 (at row 5, column 0). You decide to move to square 2 and must take the ladder to square 15. You then decide to move to square 17 and must take the snake to square 13. You then decide to move to square 14 and must take the ladder to square 35. You then decide to move to square 36, ending the game. This is the lowest possible number of moves to reach the last square, so return 4. </pre>

Example 2:

<pre>Input: board = [[-1,-1],[-1,3]] Output: 1 </pre>

Constraints:

  • n == board.length == board[i].length
  • 2 <= n <= 20
  • grid[i][j] is either -1 or in the range [1, n<sup>2</sup>].
  • The squares labeled 1 and n<sup>2</sup> do not have any ladders or snakes.

</div>

题目大意:

二维版上每格label从1到n^2, 从左到右或从右到左(梅花间竹),从下到上。每次走1-6步,格上可能有梯子和蛇,梯子是快进,蛇是回退直接到达目标格。求从1到n^2所需要步数。始点和目标不含梯子和蛇。

BFS解题思路(推荐):

求最值两个方法:DP和BFS。一开始考虑用DP,但状态很复杂,因为存在回退,这样回退后要重新计算回退之后的DP值。
由于此题没有方向性而且似jump game,所以考虑用DP。

解题步骤:

N/A

注意事项:

  1. 题意:对于梯子和蛇,它不能停留在梯子和蛇的起点,只能够停在终点,所以梯子和蛇的起点到1的距离为无穷大。其实可以留在起点,比如一个格同时是蛇的终点和梯子的起点。题意表明不能在同一步中两次用梯子或蛇。
  2. 根据上述题意,程序中对应是如碰到儿子中有梯子和蛇的起点,完全忽略它,立刻转换成终点,也就是不入列,不入visited,不计算距离,完全当其透明。开始犯的错误是将其入列,出列才计算梯子终点。此算法仍然可以满足上述题意,此时梯子的起点会被加入到visited和distance,queue中,因为它确实停在那里了。
  3. visited在计算完梯子和蛇的终点后才处理,而不是进入for loop后
  4. neighbor不能超过n,达不到目标返回-1
  5. 另一个难点在label转成坐标从而查找是否有梯子和蛇

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
def snakesAndLadders(self, board: List[List[int]]) -> int:
n = len(board) * len(board)
queue = collections.deque([1])
visited = set([1])
distance = {1: 0}
while queue:
node = queue.popleft()
if node == n:
return distance[node]
for neighbor in range(node + 1, node + 7):
if neighbor > n: # remember
continue

board_x, board_y = self.get_board_cell(len(board), neighbor)
dest_label = board[board_x][board_y]
next_step = dest_label if dest_label != -1 else neighbor

if next_step in visited: # remember to put it after dest_label
continue

queue.append(next_step)
visited.add(next_step)
distance[next_step] = distance[node] + 1
return -1 # remember

def get_board_cell(self, n, label): # 6, 6
label -= 1 # rememeber
row_id = label // n # 0
col_id = label % n
return n - 1 - row_id, n - 1 - col_id if row_id % 2 == 1 else col_id

算法分析:

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

另一种优化是只入最远的节点和蛇梯子的终点,类似于jump两种,类似于jump game。

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
34
35
36
37
38
def snakesAndLadders(self, board: List[List[int]]) -> int:
n = len(board) * len(board)
queue = collections.deque([1])
visited = set([1])
distance = {1: 0}
while queue:
node = queue.popleft()
if node == n:
return distance[node]
max_non_jump = node
for neighbor in range(node + 1, node + 7):
if neighbor > n: # remember
continue

board_x, board_y = self.get_board_cell(len(board), neighbor)
dest_label = board[board_x][board_y]
next_step = dest_label if dest_label != -1 else neighbor

if next_step in visited: # remember to put it after dest_label
continue
if dest_label != -1:
queue.append(next_step)
visited.add(next_step)
distance[next_step] = distance[node] + 1
else:
max_non_jump = next_step
if max_non_jump in visited: # remember to put it after dest_label
continue
queue.append(max_non_jump)
visited.add(max_non_jump)
distance[max_non_jump] = distance[node] + 1
return -1 # remember

def get_board_cell(self, n, label): # 6, 6
label -= 1 # rememeber
row_id = label // n # 0
col_id = label % n
return n - 1 - row_id, n - 1 - col_id if row_id % 2 == 1 else col_id

算法分析:

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


DP算法II解题思路(不推荐):

非常容易错,且效率更低,需要回退重新计算dp值。 dp[i] + 1 < dp[dest_label]保证不会在无限回退,i = dest_label - 1要在break前做,而不是更前,否二影响dp[dest_label]计算

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def snakesAndLadders_dp(self, board: List[List[int]]) -> int:
N = len(board) * len(board) + 1
dp = [float('inf') for _ in range(N)]
dp[1] = 0 # remember
# i is label id
i = 1
# for i in range(2, N):
while i < N:
for k in range(1, 7):
if i + k < N:
board_x, board_y = self.get_board_cell(len(board), i + k)
dest_label = board[board_x][board_y]
next_step = dest_label if dest_label != -1 else i + k
if dest_label != -1:
if dest_label < i and dp[i] + 1 < dp[dest_label]: # remember
dp[dest_label] = min(dp[dest_label], dp[i] + 1)
i = dest_label - 1 # remember to assign at the end
break

dp[next_step] = min(dp[next_step], dp[i] + 1) # remember + 1 inside min
i += 1
return dp[-1] if dp[-1] != float('inf') else -1 # remember

算法分析:

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

Free mock interview