KK's blog

每天积累多一些

0%

LeetCode 140 Word Break II

<div>

Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, add spaces in s to construct a sentence where each word is a valid dictionary word. Return all such possible sentences.

Note:

  • The same word in the dictionary may be reused multiple times in the segmentation.
  • You may assume the dictionary does not contain duplicate words.

Example 1:

<pre>Input: s = "catsanddog" wordDict = ["cat", "cats", "and", "sand", "dog"] Output: [   "cats and dog",   "cat sand dog" ] </pre>

Example 2:

<pre>Input: s = "pineapplepenapple" wordDict = ["apple", "pen", "applepen", "pine", "pineapple"] Output: [   "pine apple pen apple",   "pineapple pen apple",   "pine applepen apple" ] Explanation: Note that you are allowed to reuse a dictionary word. </pre>

Example 3:

<pre>Input: s = "catsandog" wordDict = ["cats", "dog", "sand", "and", "cat"] Output: []</pre>

</div>

题目大意:

一个字符串s,求被“字典集合”(wordDict)中的单词拼接的所有方案。

解题思路:

这是经典题。求所有可能性想到DFS,前面Lintcode 683提到可能会有重复解。所以用Cache。

Cache模板:

  1. key为子问题索引st,value为子问题的解。不含path和res因为类似于Catalan(属于单边Catalan),用子问题返回结果来组成此轮结果。f(input, st, endIndex, cache)
  2. 紧跟终结条件,若在cache中,返回子问题的解。
  3. 循环结束,将子问题的结果存于cache。

注意事项:

  1. 终止条件返回['']而不是[],正如L017,空字符串作为初始结果。返回到上层要strip(), 因为ss可能为空
  2. 子问题用f=word + f并不是f=f + word, 这样最后结果避免反转。
  3. s[:i + 1]判断是否在字典中,而不是s[:i],单词包括整个字符串。

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def wordBreak(self, s: str, wordDict: List[str]) -> List[str]:
word_set = set(wordDict)
res = []
cache = {}
res = self.dfs(s, word_set, cache) #cat
return res
# dfs(s) = word + dfs(s[i+1:])
def dfs(self, s, word_set, cache):
if s == "":
return [""]
if s in cache:
return cache[s]
res = []
for i in range(len(s)):
if s[:i + 1] not in word_set:
continue
cur_list = self.dfs(s[i + 1:], word_set, cache) # i = 2, dfs("")
for _s in cur_list: # [""]
res.append((s[:i + 1] + " " + _s).strip()) #cat
cache[s] = res
return res

注意事项:

  1. 将两个输入都转换成小写。
  2. 复制子问题的解,不能直接在解List<String>上编辑。

Java代码:

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
public List<String> wordBreak(String s, List<String> wordDict) {
List<String> res = new ArrayList<>();
if(s == null || s.isEmpty())
return res;
Set<String> wordDictLower = new HashSet<>();
for(String c : wordDict)
wordDictLower.add(c.toLowerCase());
s = s.toLowerCase();
Map<Integer, List<String>> cache = new HashMap<>();
return dfs(s, wordDictLower, s.length(), cache);
}

List<String> dfs(String s, Set<String> wordDict, int st, Map<Integer, List<String>> cache) {
if(st == 0)
return new ArrayList<>(Arrays.asList(""));
if(cache.containsKey(st))
return cache.get(st);
List<String> result = new ArrayList<>();
for(int i = 0; i < st; i++) {
String word = s.substring(i, st);
if(!wordDict.contains(word))
continue;

List<String> sub = dfs(s, wordDict, i, cache);
// copy solution for subproblem, don't edit on sub
for(int j = 0; j < sub.size(); j++)
result.add((sub.get(j) + " " + word).trim());
}
cache.put(st, result);
return result;
}

算法分析:

时间复杂度为O(解大小),空间复杂度为O(解大小)

LeetCode

<div>

Given a string s which represents an expression, evaluate this expression and return its value.

The integer division should truncate toward zero.

You may assume that the given expression is always valid. All intermediate results will be in the range of [-2<sup>31</sup>, 2<sup>31</sup> - 1].

Note: You are not allowed to use any built-in function which evaluates strings as mathematical expressions, such as eval().

Example 1:

<pre>Input: s = "3+2*2" Output: 7 </pre>

Example 2:

<pre>Input: s = " 3/2 " Output: 1 </pre>

Example 3:

<pre>Input: s = " 3+5 / 2 " Output: 5 </pre>

Constraints:

  • 1 <= s.length <= 3 * 10<sup>5</sup>
  • s consists of integers and operators ('+', '-', '*', '/') separated by some number of spaces.
  • s represents a valid expression.
  • All the integers in the expression are non-negative integers in the range [0, 2<sup>31</sup> - 1].
  • The answer is guaranteed to fit in a 32-bit integer.

</div>

题目大意:

实现字符串加减乘除,但无括号。

算法思路:

逆波兰式的实现用Stack。Stack只存数,而且只存加号操作符的数,也就是说,
核心思想是只有出现运算符,才能计算前一个数[op]num. 所以op和num是记录前一个符号和数字 字符三种类别:空格,数字和运算符
若遇到运算符,就处理四种的op,目标是都要把num压栈,但是op是乘除要计算积或商后才能压栈。压栈后num=0,

如果是减,就将-num入栈,
如果是乘除,立刻计算stack[-1]乘除num的结果再压入栈,因为乘除是最高优先级可以直接计算,而加减不可以。
所以用一个stack

举一个例子: 2+3

  1. 2-3+
  2. char="-"的时候, op = "+", num="2". op和num是一对的。
    char="+"的时候, op = "-", num="3". op和num是一对的。

若5*6+, char="+"的时候, prev = "5", op = "*", num="6".
[prev][op][num][char]
stack=只存加号操作符

代码中含三种情况:空格,运算符,数字

LeetCode 224 Basic Calculator 括号加减法, 同一层括号内求和遇括号入栈
LeetCode 227 Basic Calculator II 加减乘除, 和的每一项入栈,方便出栈计乘除
LeetCode 772 Basic Calculator III 加减乘除括号, L227的递归版

注意事项:

此题没有括号,不能用模板,要借用op来记录前一个操作符

  1. op记录前一个运算符,char为运算符或当前字符。计算时候根据op,因为char(+)只是第二个操作数(1-2+)的终结字符,此时表明操作数stack[-1], num以及操作符op均已完成,可以计算
  2. 最容易错的是向下取整Line 18, 题目返回要求整数。所以要除法后取整int(prev / num)
  3. s末尾加入加号,方便parse最后一个num

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
def calculate(self, s: str) -> int:
# prev[op]+num[char]
# 2-3
stack, num, op, res = [], 0, '+', 0
s += "+"
for c in s:
if c == " ":
continue
if c.isdigit():
num = 10 * num + int(c) #2
if c in "+-*/":
if op == "+":
stack.append(num)
if op == "-":
stack.append(-num)
if op == "*":
prev = stack.pop()
stack.append(prev * num)
if op == "/":
prev = stack.pop()
stack.append(int(prev / num))
op = c # *
num = 0
return sum(stack)

算法分析:

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

LeetCode

<div>

You are given a nested list of integers nestedList. Each element is either an integer or a list whose elements may also be integers or other lists. Implement an iterator to flatten it.

Implement the NestedIterator class:

  • NestedIterator(List<NestedInteger> nestedList) Initializes the iterator with the nested list nestedList.
  • int next() Returns the next integer in the nested list.
  • boolean hasNext() Returns true if there are still some integers in the nested list and false otherwise.

Your code will be tested with the following pseudocode:

<pre>initialize iterator with nestedList res = [] while iterator.hasNext() append iterator.next() to the end of res return res </pre>

If res matches the expected flattened list, then your code will be judged as correct.

Example 1:

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

Example 2:

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

Constraints:

  • 1 <= nestedList.length <= 500
  • The values of the integers in the nested list is in the range [-10<sup>6</sup>, 10<sup>6</sup>].

</div>

题目大意:

实现Nested List的Iterator。Nested List是NestedInteger的数组,NestedInteger可以是int,也可以是Nested List
nestedList = [NestedInteger]
NestedInteger 用isInteger()来判断
-> 2 by getInteger()
-> [2, 3] by getList()

解题思路:

有点似括号题但区别是从前往后读取。用Queue或Stack都可以,插入或删除其一需要反序,删除nestedinteger(=list)后要将它里面所有nestedinteger加入。Queue要从队首加入和删除,Stack要从栈顶加入和删除,用Stack比较方便。

Nested List题目:
LeetCode 341 Flatten Nested List Iterator Iterator - Stack LeetCode 339 Nested List Weight Sum - BFS LeetCode 364 Nested List Weight Sum II - BFS

解题步骤:

  1. Iterator题目都是用Stack,比如BST Iterator
  2. init和next都可以假设是用int来完成,用reversed加入
  3. next或hasNext要迭代到第一个integer为止
  4. hasNext和next要对第三步保持一致: next要call self.hasNext()

注意事项:

  1. 用Stack,逆序将nestedList中nestedinteger加入到stack,直到栈顶元素为int,hasNext才算结束

Python代码:

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

def __init__(self, nestedList: [NestedInteger]):
self.stack = []
for n in reversed(nestedList):
self.stack.append(n)

def next(self) -> int:
return self.stack.pop() if self.hasNext() else None


def hasNext(self) -> bool:
while self.stack and not self.stack[-1].isInteger():
n = self.stack.pop()
for m in reversed(n.getList()):
self.stack.append(m)
return self.stack

算法分析:

next操作时间复杂度为O(V/N)O(1),空间复杂度O(L + N), N为所有数,V为nested list数,O(N + V)/N. O(1)如果不存在nested list

LeetCode

<div>

Given an encoded string, return its decoded string.

The encoding rule is: k[encoded_string], where the encoded_string inside the square brackets is being repeated exactly k times. Note that k is guaranteed to be a positive integer.

You may assume that the input string is always valid; No extra white spaces, square brackets are well-formed, etc.

Furthermore, you may assume that the original data does not contain any digits and that digits are only for those repeat numbers, k. For example, there won't be input like 3a or 2[4].

Example 1:

<pre>Input: s = "3[a]2[bc]" Output: "aaabcbc" </pre>

Example 2:

<pre>Input: s = "3[a2[c]]" Output: "accaccacc" </pre>

Example 3:

<pre>Input: s = "2[abc]3[cd]ef" Output: "abcabccdcdcdef" </pre>

Example 4:

<pre>Input: s = "abc3[cd]xyz" Output: "abccdcdcdxyz" </pre>

Constraints:

  • 1 <= s.length <= 30
  • s consists of lowercase English letters, digits, and square brackets '[]'.
  • s is guaranteed to be a valid input.
  • All the integers in s are in the range [1, 300].

</div>

题目大意:

将循环体表示展开

Stack解题思路(推荐):

见到括号就考虑用Stack,此题有字符和数字,所以考虑用两个Stack

解题步骤:

运用模板

注意事项:

  1. 同级为括号外(包括括号以左,以右),但数字和字符分别存于两个stack。括号内为另一级,入栈。 prev_res+prev_num*[res]
    两个stack存储优先级较低的字符以及数字,类似于多重括号2*(3*(4))

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def decodeString(self, s: str) -> str:
stack_num, stack_char, num, char_str = [], [], 0, ""
# char_str + num[<>]
for c in s:
if c.isdigit():
num = 10 * num + int(c)
if c.isalpha():
char_str += c
if c == "[":
stack_num.append(num)
stack_char.append(char_str)
num = 0
char_str = ""
if c == "]":
prev_char = stack_char.pop()
prev_num = stack_num.pop()
char_str = prev_char + prev_num * char_str
return char_str

算法分析:

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


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

用index作为全局变量,扫每一个字符,类似于Leetcode 297。
遇到字符append到result,遇到数字记录次数k,遇到左括号就递归decodeString(s), 递归返回decodedString, 跳过右括号,result.append(decodedString) k次,最后返回result。

如3[a2[c]]

伪代码:

1
2
3
4
5
6
7
8
decodeString('3[a2[c]]')
k = 3
acc = decodeString('a2[c]')
result = a
k = 2
'c' = decodeString('c')
result = acc
result = accaccacc

LeetCode

<div>

There are n cities connected by some number of flights. You are given an array flights where flights[i] = [from<sub>i</sub>, to<sub>i</sub>, price<sub>i</sub>] indicates that there is a flight from city from<sub>i</sub> to city to<sub>i</sub> with cost price<sub>i</sub>.

You are also given three integers src, dst, and k, return the cheapest price from src to dst with at most k stops. If there is no such route, return-1.

Example 1:

<pre>Input: n = 3, flights = [[0,1,100],[1,2,100],[0,2,500]], src = 0, dst = 2, k = 1 Output: 200 Explanation: The graph is shown. The cheapest price from city 0 to city 2 with at most 1 stop costs 200, as marked red in the picture. </pre>

Example 2:

<pre>Input: n = 3, flights = [[0,1,100],[1,2,100],[0,2,500]], src = 0, dst = 2, k = 0 Output: 500 Explanation: The graph is shown. The cheapest price from city 0 to city 2 with at most 0 stop costs 500, as marked blue in the picture. </pre>

Constraints:

  • 1 <= n <= 100
  • 0 <= flights.length <= (n * (n - 1) / 2)
  • flights[i].length == 3
  • 0 <= from<sub>i</sub>, to<sub>i</sub> < n
  • from<sub>i</sub> != to<sub>i</sub>
  • 1 <= price<sub>i</sub> <= 10<sup>4</sup>
  • There will not be any multiple flights between two cities.
  • 0 <= src, dst, k < n
  • src != dst

</div>

题目大意:

求只允许停k个站情况下,最便宜机票价格

解题思路:

BFS + Heap
这是单源最短路径的典型应用。可以用Dijkstra,机票价格相当于单源最短路径问题中的路径大小。一开始我用BFS,但得到TLE,因为存在循环,导致节点被重复访问(同一路径)。但一个节点的确可以被用不同路径访问。所以引入visited[node] = dis

解题步骤:

N/A

注意事项:

  1. node需要被多次访问,所以跟模板不同,visited的检测要放在neighbor循环之外且用node且初始化为空。visited不再是set,它需要记录node离src的距离。一方面用于循环检测,因为如果存在循环,会出现dist >= visited[node]。若该节点的当前距离小于之前的最小距离,此时也要加入到heap,因为贪婪法,虽然此路径费用较高,但它距离更近,当k限制比较小时,此路径可能满足要求。这就是为什么一个节点会被多次访问的原因。
  2. 若路径不存在返回-1

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
graph = collections.defaultdict(list)
for pair in flights:
graph[pair[0]].append((pair[1], pair[2]))
heap = ([(0, src, 0)]) # price, node_id, distance
visited = {}
while heap:
p, node, dist = heapq.heappop(heap)
if node == dst and dist <= k + 1:
return p
if node in visited and dist >= visited[node]:
continue
visited[node] = dist
for neighbor, _price in graph[node]:
heapq.heappush(heap, (p + _price, neighbor, dist + 1))
return -1

算法分析:

时间复杂度为O(VlogV),空间复杂度O(V)

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

算法一比较难想,如果我们用BFS+Heap模板,我们用visited记录单点最贵price。但是此题由于有边数限制的最值问题,所以类似于DP,将限制条件引入作为参数或state。
visited[(node, distance)]=total_price
两处剪枝:

  1. 价格超过visited
  2. 路径长度超过k
    其实法一也是用了两个剪枝条件,因为price低肯定是先出堆,所以只要比较某点最短路径visited即可,后出堆的点如果路径更长(价格肯定更高了),肯定可以剪枝。

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
graph = collections.defaultdict(list)
for pair in flights:
graph[pair[0]].append((pair[1], pair[2]))
heap = ([(0, src, 0)]) # price, node_id, distance
visited = {(src, 0): 0} # (node, distance): price
while heap:
p, node, dist = heapq.heappop(heap)
if node == dst:
return p
if dist == k + 1:
continue
for neighbor, _price in graph[node]:
if (neighbor, dist + 1) in visited and visited[(neighbor, dist + 1)] <= p + _price:
continue
heapq.heappush(heap, (p + _price, neighbor, dist + 1))
visited[(neighbor, dist + 1)] = p + _price
return -1

Free mock interview