KK's blog

每天积累多一些

0%

LeetCode

<div>

Let's define a function countUniqueChars(s) that returns the number of unique characters on s.

  • For example if s = "LEETCODE" then "L", "T", "C", "O", "D" are the unique characters since they appear only once in s, therefore countUniqueChars(s) = 5.

Given a string s, return the sum of countUniqueChars(t) where t is a substring of s.

Notice that some substrings can be repeated so in this case you have to count the repeated ones too.

Example 1:

<pre>Input: s = "ABC" Output: 10 Explanation: All possible substrings are: "A","B","C","AB","BC" and "ABC". Evey substring is composed with only unique letters. Sum of lengths of all substring is 1 + 1 + 1 + 2 + 2 + 3 = 10 </pre>

Example 2:

<pre>Input: s = "ABA" Output: 8 Explanation: The same as example 1, except countUniqueChars("ABA") = 1. </pre>

Example 3:

<pre>Input: s = "LEETCODE" Output: 92 </pre>

Constraints:

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

</div>

题目大意:

求所有子串的唯一字符的个数的总和

解题思路:

暴力法是所有子串O(n^2),统计唯一字符个数O(n), 复杂度为O(n^3). 尝试优化统计那一步,用presum map来详见可以O(1)求得,但内存过大,仍然TLE。
求个个数且是字符串题,考虑用DP。此题还有点似Leetcode 003 Longest Substring Without Repeating Characters。

写几个找规律且从简单开始,也就是没有重复

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
A: 1
AB: 1 + 2 + 1 = 4, 1是dp[1], 2是以B结尾的2个子串有两个B,最后一个1表示AB串中有一个A
B
AB
ABC: 4 + 3 + 2 + 1 = 10, 4是dp[2], 2是以C结尾的3个子串有三个C,2个B,1个A. Delta = 6
C
BC
ABC
ABCB:10 + 2 + 3 + 0 + 1 = 16, 同理是上一个DP结果和从后往前每个字母在新子串中的唯一数。由于出现重复,B从4个变成2个,前一个B变成0个,其他加法项是不变的。Delta = 6 + 4 - 2 x 2 = 6 公式为Delta = Delta + 当前长度 - (i - 上一个重复元素下标) x 2
B
CB
BCB
ABCB
ABCBA:16 + 4 + 2 + 3 + 0 + 0 = 25 = 16 + delta 验证公式delta = 6 + 5 - 1 x 2 = 9
A
BA
CBA
BCBA
ABCBA
ABCBAC:25 + 3 + 4 + 2 + 0 + 0 + 0 = 34 = 25 + delta 验证公式delta = 9 + 6 - (6 - 3) x 2 = 9
C
AC
BAC
CBAC
BCBAC
ABCBAC
ABCBACA:34 + 2 + 3 + 0 + 2 + 0 + 0 + 0 = 41 = 34 + delta 验证公式delta = 9 + 7 - (7 - 2) x 2 = 6不匹配,新A本来是7个变成2个,而次新A上一轮有4个最多减4个并不能减5个,所以x 2是不对的。
A
CA
ACA
BACA
CBACA
BCBACA
ABCBACA

公式为:

1
2
Delta = Delta + 当前下标 - 上一个重复元素下标 - (上一个重复元素下标 - 上个重复元素对应的下标)
Res += Delta
公式解释:
delta是每增加一个字符的增量。若每一个字符都不重复,Delta = Delta + 当前长度表示增加了最后一个字符的数量如AB -> ABC
若有重复,就只增加遇到前一个重复前的个数,如ABCB -> ABCBA的4个
若前面重复有2个,就还要减去在算前一个重复时的增量如上图ABCBACA中BACA和CBACA中在BA和CBA时候当时A没有重复,计算了2个

解题步骤:

delta_sum为上一轮的增加的唯一元素个数
delta[i]为下标为i的元素的唯一个数的增量

注意事项:

  1. 公式中减去重复个数不能乘以2,因为上一个重复元素的增量可能不够减

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def uniqueLetterString(self, s: str) -> int:
res, delta_sum, delta, char_to_index = 0, 0, [0] * len(s), collections.defaultdict(lambda: -1)
for i in range(len(s)):
cur_len = i + 1
delta[i] = cur_len
if s[i] in char_to_index:
delta[i] -= char_to_index[s[i]] + 1
delta_sum += delta[i] - delta[char_to_index[s[i]]]
delta[char_to_index[s[i]]] = 0
else:
delta_sum += delta[i]
res += delta_sum
char_to_index[s[i]] = i
return res

算法分析:

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


两个Map算法II解题思路(推荐):

公式中上个重复元素对应的加法项也就是上个重复元素与上上个重复元素的距离,所以引入另一个map来记录,避免用delta[i],算法更加简单。

Python代码:

1
2
3
4
5
6
7
8
9
10
def uniqueLetterString(self, s: str) -> int:
last_char_to_index = collections.defaultdict(lambda: -1)
last_last_char_to_index = collections.defaultdict(lambda: -1)
res, delta = 0, 0
for i, c in enumerate(s):
delta += i - last_char_to_index[c] - (last_char_to_index[c] - last_last_char_to_index[c])
last_last_char_to_index[c] = last_char_to_index[c]
last_char_to_index[c] = i
res += delta
return res

算法分析:

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

LeetCode

<div>

Given the root of a binary tree, calculate the vertical order traversal of the binary tree.

For each node at position (row, col), its left and right children will be at positions (row + 1, col - 1) and (row + 1, col + 1) respectively. The root of the tree is at (0, 0).

The vertical order traversal of a binary tree is a list of top-to-bottom orderings for each column index starting from the leftmost column and ending on the rightmost column. There may be multiple nodes in the same row and same column. In such a case, sort these nodes by their values.

Return the vertical order traversal of the binary tree.

Example 1:

<pre>Input: root = [3,9,20,null,null,15,7] Output: [[9],[3,15],[20],[7]] Explanation: Column -1: Only node 9 is in this column. Column 0: Nodes 3 and 15 are in this column in that order from top to bottom. Column 1: Only node 20 is in this column. Column 2: Only node 7 is in this column.</pre>

Example 2:

<pre>Input: root = [1,2,3,4,5,6,7] Output: [[4],[2],[1,5,6],[3],[7]] Explanation: Column -2: Only node 4 is in this column. Column -1: Only node 2 is in this column. Column 0: Nodes 1, 5, and 6 are in this column. 1 is at the top, so it comes first. 5 and 6 are at the same position (2, 0), so we order them by their value, 5 before 6. Column 1: Only node 3 is in this column. Column 2: Only node 7 is in this column. </pre>

Example 3:

<pre>Input: root = [1,2,3,4,6,5,7] Output: [[4],[2],[1,5,6],[3],[7]] Explanation: This case is the exact same as example 2, but with nodes 5 and 6 swapped. Note that the solution remains the same since 5 and 6 are in the same location and should be ordered by their values. </pre>

Constraints:

  • The number of nodes in the tree is in the range [1, 1000].
  • 0 <= Node.val <= 1000

</div>

题目大意:

按列顺序打印二叉树,若列号同,同一行的节点按值排序

解题思路:

LeetCode 314 Binary Tree Vertical Order Traversal类似,用BFS

LeetCode 314 Binary Tree Vertical Order Traversal 同一列,从上到下,从左到右排序 LeetCode 987 Vertical Order Traversal of a Binary Tree 同一列,从上到下,同一行值从小到大排序

解题步骤:

N/A

注意事项:

与LeetCode 314实现的区别

  1. 一开始以为同一列的同一行的节点在queue是一个紧接一个出列。但同一行节点可能先出列col=3, col=4, col=3。而且同一列同一行的节点有多个,不止两个。所以将row_id也加入到queue节点和map中
  2. 遍历结果时,map中的value排序**, value是先row_id再node.val,所以直接可以排序,最后直接取出第二维度

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def verticalTraversal(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
col_to_node_list = collections.defaultdict(list)
min_col, max_col = float('inf'), float('-inf')
queue = collections.deque([(root, 0, 0)])
while queue:
node, row_id, col_id = queue.popleft()
col_to_node_list[col_id].append((row_id, node.val))
min_col, max_col = min(min_col, col_id), max(max_col, col_id)
if node.left:
queue.append((node.left, row_id + 1, col_id - 1))
if node.right:
queue.append((node.right, row_id + 1, col_id + 1))
res = []
for i in range(min_col, max_col + 1):
col_to_node_list[i].sort()
res.append([_[1] for _ in col_to_node_list[i]])
return res

算法分析:

时间复杂度为O(n),空间复杂度O(1),稍大于O(n), 因为同一列同一行节点要排序

LeetCode

<div>

A binary string is monotone increasing if it consists of some number of 0's (possibly none), followed by some number of 1's (also possibly none).

You are given a binary string s. You can flip s[i] changing it from 0 to 1 or from 1 to 0.

Return the minimum number of flips to make s monotone increasing.

Example 1:

<pre>Input: s = "00110" Output: 1 Explanation: We flip the last digit to get 00111. </pre>

Example 2:

<pre>Input: s = "010110" Output: 2 Explanation: We flip to get 011111, or alternatively 000111. </pre>

Example 3:

<pre>Input: s = "00011000" Output: 2 Explanation: We flip to get 00000000. </pre>

Constraints:

  • 1 <= s.length <= 10<sup>5</sup>
  • s[i] is either '0' or '1'.

</div>

题目大意:

01字符串中Flip其中一些将它变成00111,0和1的个数是任意。

DP解题思路(推荐):

求最小值考虑用BFS或者DP。BFS的复杂度可能比较大,DP定义为以s[i]为结尾的最小flip数,但由于不知道具体排列(末状态)是什么或者结尾是什么,所以比较难从子问题推导出来。
不妨用两个dp来计算,
dp为以0为结尾的最小flip数
dp2为以1为结尾的最小flip数

1
2
3
4
5
dp = dp     if s[i] = '0'
= dp + 1 if s[i] = '1'

dp2 = min(dp2 + 1, dp + 1) if s[i] = '0'
= min(dp2, dp) if s[i] = '1'
公式不是对称,因为题意是先0再1。

解题步骤:

N/A

注意事项:

  1. 用Python的dp和dp2同时由前状态赋值,这样避免用临时变量

Python代码:

1
2
3
4
5
6
7
8
def minFlipsMonoIncr(self, s: str) -> int:
dp, dp2 = 0, 0
for i in range(len(s)):
if s[i] == '0':
dp, dp2 = dp, min(dp, dp2) + 1
else:
dp, dp2 = dp + 1, min(dp2, dp)
return min(dp, dp2)

算法分析:

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


presum算法II解题思路:

统计1的个数,若是0同时统计从0 flip到1的个数,取两者较小为新flip数。较难理解,不推荐

Python代码:

1
2
3
4
5
6
7
8
9
def minFlipsMonoIncr2(self, s: str) -> int:
ones, flips = 0, 0
for c in s:
if c == '1':
ones += 1
else:
flips += 1
flips = min(ones, flips)
return flips

算法分析:

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

LeetCode

<div>

A valid number can be split up into these components (in order):

  1. A decimal number or an integer.
  2. (Optional) An 'e' or 'E', followed by an integer.

A decimal number can be split up into these components (in order):

  1. (Optional) A sign character (either '+' or '-').
  2. One of the following formats:
    1. One or more digits, followed by a dot '.'.
    2. One or more digits, followed by a dot '.', followed by one or more digits.
    3. A dot '.', followed by one or more digits.

An integer can be split up into these components (in order):

  1. (Optional) A sign character (either '+' or '-').
  2. One or more digits.

For example, all the following are valid numbers: ["2", "0089", "-0.1", "+3.14", "4.", "-.9", "2e10", "-90E3", "3e+7", "+6e-1", "53.5e93", "-123.456e789"], while the following are not valid numbers: ["abc", "1a", "1e", "e3", "99e2.5", "--6", "-+3", "95a54e53"].

Given a string s, return true if s is a valid number.

Example 1:

<pre>Input: s = "0" Output: true </pre>

Example 2:

<pre>Input: s = "e" Output: false </pre>

Example 3:

<pre>Input: s = "." Output: false </pre>

Constraints:

  • 1 <= s.length <= 20
  • s consists of only English letters (both uppercase and lowercase), digits (0-9), plus '+', minus '-', or dot '.'.

</div>

题目大意:

求合法小数指数形式

类括号法解题思路(推荐):

有四种symbol,要保证先后关系。

解题步骤:

  1. 先写基本框架:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    def isNumber(self, s: str) -> bool:
    seen_sign, seen_num, seen_exp, seen_dot = False, False, False, False
    for char in s:
    if char = '+-':
    if seen_sign:
    return False
    seen_sign = True
    elif char.isdigit():

    seen_num = True
    elif char in 'eE':
    if seen_exp:
    return False
    seen_exp = True
    elif char == '.':
    if seen_dot:
    return False
    seen_dot = True
    else:
    return False
    return True
  2. if语句加入前面字符不能出现什么,每种其他字符过一遍。还有字符必须出现什么,此情况只有一种: e字符前必须有数字
  3. for循环后return语句检查单个字符

注意事项:

  1. 有四种symbol: 符号,数字,dot,exp。保证先后关系。exp的前后部分是独立的,唯一区别是后部分不能有dot,如1e2.2
  2. 实现类似于括号题用if语句来分别处理每种symbol:前面不能出现什么符号(e后面不能出现小数,也就是小数前面不能出现e),或必须出现什么符号(仅一种情况:e前面必须出现数字),如1e2. 然后该符号赋True
  3. for循环后检查单个字符且不含数字情况 见解题步骤

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def isNumber(self, s: str) -> bool:
seen_sign, seen_num, seen_exp, seen_dot = False, False, False, False
for char in s:
if char in '+-':
if seen_sign or seen_num or seen_dot:
return False
seen_sign = True
elif char.isdigit():
seen_num = True
elif char in 'eE':
if seen_exp or not seen_num:
return False
seen_exp = True
seen_sign = False
seen_num = False
seen_dot = False
elif char == '.':
if seen_dot or seen_exp:
return False
seen_dot = True
else:
return False
return False if (seen_sign or seen_exp or seen_dot) and not seen_num else True

算法分析:

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


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

Deterministic Finite Automaton (DFA)状态机,也就是将状态写入一个map中作为config,代码较简洁,但很难想。

算法分析:

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

LeetCode

<div>

You are given an array of strings products and a string searchWord.

Design a system that suggests at most three product names from products after each character of searchWord is typed. Suggested products should have common prefix with searchWord. If there are more than three products with a common prefix return the three lexicographically minimums products.

Return a list of lists of the suggested products after each character of searchWord is typed.

Example 1:

<pre>Input: products = ["mobile","mouse","moneypot","monitor","mousepad"], searchWord = "mouse" Output: [ ["mobile","moneypot","monitor"], ["mobile","moneypot","monitor"], ["mouse","mousepad"], ["mouse","mousepad"], ["mouse","mousepad"] ] Explanation: products sorted lexicographically = ["mobile","moneypot","monitor","mouse","mousepad"] After typing m and mo all products match and we show user ["mobile","moneypot","monitor"] After typing mou, mous and mouse the system suggests ["mouse","mousepad"] </pre>

Example 2:

<pre>Input: products = ["havana"], searchWord = "havana" Output: [["havana"],["havana"],["havana"],["havana"],["havana"],["havana"]] </pre>

Example 3:

<pre>Input: products = ["bags","baggage","banner","box","cloths"], searchWord = "bags" Output: [["baggage","bags","banner"],["baggage","bags","banner"],["baggage","bags"],["bags"]] </pre>

Constraints:

  • 1 <= products.length <= 1000
  • 1 <= products[i].length <= 3000
  • 1 <= sum(products[i].length) <= 2 * 10<sup>4</sup>
  • All the strings of products are unique.
  • products[i] consists of lowercase English letters.
  • 1 <= searchWord.length <= 1000
  • searchWord consists of lowercase English letters.

</div>

题目大意:

实现搜索结果为3个autocomplete的功能

Prefix解题思路(推荐):

Prefix

解题步骤:

N/A

注意事项:

  1. 用Trie,另一种思路是用prefix,此法采用prefix法,将所有单词按前缀加入到字典中

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
def suggestedProducts(self, products: List[str], searchWord: str) -> List[List[str]]:
products.sort()
prefix_dict = collections.defaultdict(list)
for word in products:
for i in range(len(word)):
prefix = word[:i + 1]
if len(prefix_dict[prefix]) < 3:
prefix_dict[prefix].append(word)
res = []
for i in range(len(searchWord)):
prefix = searchWord[:i + 1]
res.append(prefix_dict[prefix])
return res

算法分析:

时间复杂度为O(nL1 + L2),空间复杂度O(nL1), L1为单词列表中最长的长度,L2为搜索单词长度,n为单词个数


Trie + DFS算法II解题思路:

建Trie,然后根据搜索的前缀定位到Trie节点,然后对此节点做DFS找到3个单词,因为DFS和字母顺序是一致的,所以DFS可行
具体参考Leetcode solution


Two pointers算法III解题思路:

先排序,用双指针相向搜索,根据搜索单词的每一个字母,不断收缩搜索范围,左指针和右指针之间即为满足条件的结果。每轮将左指针往后三个结果加到结果集
具体参考Leetcode discussion

Free mock interview