KK's blog

每天积累多一些

0%

算法思路:

  1. 循环条件start + 1 < end。 当跳出循环时,start和end的关系只能是相等或相邻。
    相等是若数组只有一个元素,没有进入循环时出现。当进入过循环,一定是相邻。
  2. 跳出循环后比较start和end的关系从而判断答案。

这可以满足二分法找first position或者last position, peak element的题目。
first position中若等于target,end = mid,因为要在左半部分找,相反last
position在右半部分找,所以start = mid。

应用:

  1. 有序数组找目标
  2. 没给定目标情况下,找峰值, 缺失数(元素间关系)
  3. 没给定目标情况下,求第k小的数,如求根号(数值关系)

找目标

注意事项:

  1. 判断数组是否为空。
  2. 如果只有唯一的target的话,target==nums[mid]可以并入任何一种。end和target的顺序也没关系。其他注意循环后要根据题目条件(如小于或者大于或者等于tgt),
    再比较一次start和end上的元素,详见下表
类型 if target = nums[mid] 循环之后 备注
binary_search 任意 任意 N/A
last_position 向右start = mid 先end且是否等于tgt 贪婪法,找最后一个target,所以尽量靠后,先end
first_position 向左end = mid 先start是否等于tgt 贪婪法,找第一个target,所以尽量靠前,先start
smaller(_or_equal)_position 向左end = mid 先end是否小于tgt 贪婪法,找最后一个小于target的数,所以尽量靠近target,先end
greater(_or_equal)_position 向右start = mid 先start是否大于tgt 贪婪法,找第一个大于target的数,所以尽量靠近target,先start

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def binary_search(self, nums: List[int], target: int) -> int:
if not nums:
return -1
start, end = 0, len(nums) - 1
while start + 1 < end:
mid = start + (end - start) // 2
if target < nums[mid]:
end = mid
else:
start = mid

if nums[end] == target:
return end
elif nums[start] == target:
return start
else:
return -1

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def last_position(self, nums: List[int], target: int) -> int:
if not nums:
return -1
start, end = 0, len(nums) - 1
while start + 1 < end:
mid = start + (end - start) // 2
if target < nums[mid]:
end = mid
elif target > nums[mid]:
start = mid
else: # Depends on the target on the right side or left side. For fist pos, use end = mid
start = mid

if nums[end] == target:
return end
elif nums[start] == target:
return start
else:
return -1

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def first_position(self, nums: List[int], target: int) -> int:
if not nums:
return -1
start, end = 0, len(nums) - 1
while start + 1 < end:
mid = start + (end - start) // 2
if target < nums[mid]:
end = mid
elif target > nums[mid]:
start = mid
else:
end = mid

if nums[start] == target:
return start
elif nums[end] == target:
return end
else:
return -1

Python代码:

如果是smaller_or_equal_position,Line 13和15取等号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def smaller_position(self, nums: List[int], target: int) -> int:
if not nums:
return -1
start, end = 0, len(nums) - 1
while start + 1 < end:
mid = start + (end - start) // 2
if target > nums[mid]:
start = mid
elif target < nums[mid]:
end = mid
else:
end = mid
if nums[end] < target: # nums[end] <= target for smaller_or_equal_position
return end
if nums[start] < target: # nums[start] < target for smaller_or_equal_position
return start
return -1

Python代码:

如果是greater_or_equal_position,Line 13和15取等号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def greater_position(self, nums: List[int], target: int) -> int:
if not nums:
return -1
start, end = 0, len(nums) - 1
while start + 1 < end:
mid = start + (end - start) // 2
if target > nums[mid]:
start = mid
elif target < nums[mid]:
end = mid
else:
start = mid
if nums[start] > target: # nums[start] >= target for greater_or_equal_position
return start
if nums[end] > target: # nums[end] >= target for greater_or_equal_position
return end
return -1

找峰值

注意事项:

  1. 判断mid+1的元素不越界
  2. 最后返回start和end之中较大者

Python代码:

1
2
3
4
5
6
7
8
9
10
11
def find_peak(self, nums: List[int]) -> int:
if not nums:
return -1
start, end = 0, len(nums) - 1
while start + 1 < end:
mid = start + (end - start) // 2
if mid + 1 <= end and nums[mid] < nums[mid + 1]:
start = mid
else:
end = mid
return start if nums[start] > nums[end] else end

找第k小的数

注意事项:

  1. 数值比较,所以mid是除2获得不是//2。start,end,mid都是数值而不是索引
  2. k是从0开始,count==k的时候,k在mid的右边,如数组1-10, k=3, mid=3.5, count=3,k是前4个数,所以在mid右边。nums[i] <= mid这个等号有没有也没关系,因为mid是小数,不会相等的。
  3. 最后start,end区间内是一个整数正是所求,所以返回end向下取整

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import math
def binary_select(self, nums: List[int], k: int) -> int:
if not nums:
return -1
start, end, epsilon = min(nums), max(nums), 0.5
while end - start > epsilon:
mid = start + (end - start) / 2
count = len([n for n in nums if n <= mid])
if k < count:
end = mid
elif k > count:
start = mid
else:
start = mid
return int(end)

若nums有序,count的统计可以用bisect(nums, mid)完成,记得不需要用+1,而且空数组也无问题
上面line 8可以改成

1
count = bisect.bisect(matrix[i], mid)

算法分析:

时间复杂度为O(nlog(|hi-lo|/delta)),如果数据比较平均也就是差值相当的情况下(比如1,2,3),复杂度为O(nlogn). 空间复杂度O(1)
若不需要搜索全数组,前面的n会变成logn

Java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int lastPosition(int[] nums, int target) {
if(nums == null || nums.length == 0)
return -1;

int start = 0, end = nums.length - 1;
while(start + 1 < end) {
int mid = start + (end - start) / 2;
if(nums[mid] < target)
start = mid;
else if(nums[mid] == target)
// Depends on the target on the right side or left side. For fist pos, use end = mid
start = mid;
else
end = mid;
}

if(nums[end] == target)
return end;
if(nums[start] == target)
return start;

return -1;
}

算法分析:

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

LeetCode 1197 Minimum Knight Moves

In an infinite chess board with coordinates from -infinity to +infinity, you have a knight at square [0, 0].

A knight has 8 possible moves it can make, as illustrated below. Each move is two squares in a cardinal direction, then one square in an orthogonal direction.

Return the minimum number of steps needed to move the knight to the square [x, y]. It is guaranteed the answer exists.

Example 1:

Input: x = 2, y = 1
Output: 1
Explanation: [0, 0] → [2, 1]

Example 2:

Input: x = 5, y = 5
Output: 4
Explanation: [0, 0] → [2, 1] → [4, 2] → [3, 4] → [5, 5]

Constraints:

  • |x| + |y| <= 300

Because x and y are constrained to be in range[-300, 300], we can use BFS to find the minimum steps needed to reach target(x, y). Furthermore, we can only consider the case that x >=0 && y >=0 since the chess board is symmetric. The bfs implementation is pretty straightforward. There are two important points you need to be careful with.

  1. Pruning. We can limit the search dimension within 310 * 310. Any moves that lead to a position that is outside this box will not yield an optimal result.

2. Initially, you used a Set of type int[] to track visited positions. This caused TLE because you didn’t overwrite the hashCode and equals methods for int[]. As a result, Set uses the default hashCode and equals method when checking if an element is already in the set. For equals(), The default implementation provided by the JDK is based on memory location — two objects are equal if and only if they are stored in the same memory address. For a comprehensive reading, refer to https://dzone.com/articles/working-with-hashcode-and-equals-in-java

O(x * y) runtime and space

题目大意:

象棋一样,走日字到达目标点的最小次数。

解题思路:

这题是最短路径题,第一时间想到BFS。

解题步骤:

这题是最短路径题,第一时间想到BFS。这是一条典型的单源最短路径问题。用Distance BFS模板

  1. 建距离map。
  2. BFS访问。

注意事项:

  1. 有边界限制

Python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def minKnightMoves(self, x: int, y: int) -> int:
return self.bfs(0, 0, x, y)

def bfs(self, start_x, start_y, target_x, target_y):
directions = {(2, 1), (1, 2), (-1, 2), (-2, 1), (-2, -1), (-1, -2), (1, -2), (2, -1)}
queue = deque([(start_x, start_y)])
visited = {(start_x, start_y)}
distance = {(start_x, start_y): 0}
while queue:
node = queue.popleft()
if node == (target_x, target_y):
return distance[node]

for direction in directions:
neighbor = (node[0] + direction[0], node[1] + direction[1])
if neighbor in visited:
continue
queue.append(neighbor)
visited.add(neighbor)
distance[neighbor] = distance[node] + 1

注意事项:

  1. 用map记录距离一定要将首节点加入到map中,否则求距离时候会NPE。
  2. visited我一开始实现用HashSet但因为没有实现equals导致LTE,改成矩阵即可。

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
32
33
34
35
36
37
38
39
40
41
42
43
int[] directX = new int[]{1, 1,-1,-1,2,2,-2,-2};
int[] directY = new int[]{2,-2,2,-2,1,-1,1,-1};

public int shortestPath(boolean[][] grid, Point source, Point destination) {
if(grid == null || grid.length == 0 || grid[0].length == 0)
return -1;

Queue<Point> q = new LinkedList<>();
Map<Point, Integer> map = new HashMap<>();
map.put(source, 0); // remember
boolean[][] visited = new boolean[grid.length][grid[0].length];
q.offer(source);
visited[source.x][source.y] = true; // use hashSet is wrong.
while(!q.isEmpty()) {
Point p = q.poll();
if(p.x == destination.x && p.y == destination.y)
return map.get(p);
for(Point neighbor : getNeighbors(p)) {
if(!isValid(grid, neighbor) || visited[neighbor.x][neighbor.y])
continue;
q.offer(neighbor);
visited[neighbor.x][neighbor.y] = true;
map.put(neighbor, map.get(p) + 1);
}
}

return -1;
}

List<Point> getNeighbors(Point point) {
List<Point> result = new ArrayList<>();
for(int i = 0; i < 8; i++) {
result.add(new Point(point.x + directX[i], point.y + directY[i]));
}
return result;
}

boolean isValid(boolean[][] grid, Point point) {
if(point.x >= 0 && point.x < grid.length && point.y >= 0 && point.y < grid[0].length
&& !grid[point.x][point.y])
return true;
else return false;
}

算法分析:

时间复杂度为棋盘大小O(n*m),空间复杂度O(n)

LeetCode 127 Word Ladder



Given two words (beginWord and endWord), and a dictionary’s word list, find the length of shortest transformation sequence from beginWord to endWord, such that:

1. Only one letter can be changed at a time.
2. Each transformed word must exist in the word list.

Note:

Return 0 if there is no such transformation sequence. All words have the same length.
All words contain only lowercase alphabetic characters. You may assume no duplicates in the word list.
You may assume beginWord and endWord are non-empty and are not the same.

Example 1:

Input:
beginWord = “hit”,
endWord = “cog”,
wordList = [“hot”,”dot”,”dog”,”lot”,”log”,”cog”]

Output: 5

Explanation: As one shortest transformation is “hit” -> “hot” -> “dot” -> “dog” -> “cog”,
return its length 5.


Example 2:

Input:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,”dot”,”dog”,”lot”,”log”]

Output: 0

*Explanation:
The endWord “cog” is not in wordList, therefore no possibletransformation.


题目大意:

给定一个字典和两个单词。每次变换一个字母的得到新单词且该词要在字典中。求最少变换次数。

解题思路:

这题是最短路径题,第一时间想到BFS。这是一条典型的单源最短路径问题。

  1. 这是图,所以要有visited记录是否重复访问。
  2. 字典的实现两个作用: 快速查找,以及记录距离可以省下一轮循环。总共两重循环。
  3. getNextWords的实现。通过变换每位上字母,比较巧妙。

解题步骤:

这题是最短路径题,第一时间想到BFS。这是一条典型的单源最短路径问题。

  1. 建字典。
  2. BFS访问。
  3. 求所有距离为1的相邻单词getNextWords。

注意事项:

  1. 注意题目条件,开始词不在字典中(终结词默认在,否则无结果),要将它加入字典中且距离为1。
  2. 用Map来记录解(儿子节点,参考按层搜索),visited用于记录父节点
  3. getNextWords的实现不含自己。
  4. Python中用popleft出列,不是pop

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
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
if not beginWord or not endWord:
return 0
distance = {}
for word in wordList:
distance[word] = 0
distance[beginWord] = 1
return self.bfs(beginWord, endWord, distance, set())

def bfs(self, beginWord, endWord, dict, visited):
queue = deque([beginWord])
visited.add(beginWord)
while queue:
word = queue.popleft()
if word == endWord:
return dict[word]

neighbors = self.get_next_words(word, dict)
for neighbor in neighbors:
if neighbor not in visited:
queue.append(neighbor)
visited.add(neighbor)
dict[neighbor] = dict[word] + 1

return 0

def get_next_words(self, word, dict):
res = []
for i in range(len(word)):
for c in string.ascii_lowercase: # or use 'abcdefghijklmnopqrstuvwxyz'
if c == word[i]:
continue
new_word = word[:i] + c + word[i + 1:]
if new_word in dict:
res.append(new_word)
return res

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
// This is a dict and also keeps track of distance
Map<String, Integer> dict = getDict(wordList);
// Make sure endWord is in the dict and can be the next word
//dict.put(endWord, 0);
dict.put(beginWord, 1);

Set<String> visited = new HashSet<>();
Queue<String> q = new LinkedList<>();
q.offer(beginWord);
visited.add(beginWord);
while(!q.isEmpty()) {
String word = q.poll();
if(endWord.equals(word))
return dict.get(word);

List<String> nextWords = getNextWords(word, dict);
for(String s : nextWords) {
if(visited.contains(s))
continue;

q.offer(s);
visited.add(s);
dict.put(s, dict.get(word) + 1);

}
}
return 0;
}

Map<String, Integer> getDict(List<String> wordList) {
Map<String, Integer> map = new HashMap<>();
for(String word : wordList) {
map.put(word, 0);
}
return map;
}

List<String> getNextWords(String word, Map<String, Integer> dict) {
List<String> result = new ArrayList<>();
for(int i = 0; i < word.length(); i++) {
for(int j = 0; j < 26; j++) {
char newChar = (char)('a' + j);
if(word.charAt(i) == newChar) // exclude itself
continue;
String newWord = word.substring(0, i) +
newChar + word.substring(i + 1, word.length());
if(dict.containsKey(newWord))
result.add(newWord);

}
}
return result;
}

算法分析:

getNextWords是L26L=O(L2)产生新字符串需要L
时间复杂度为O(n*L2),空间复杂度O(n),n为单词数。


算法II解题思路:

利用双向BFS,参考双向BFS概念
如果搜索不够广的话(例如类似于一条直线),BFS会较慢,用双向BFS可解决此问题。双向BFS就是同时从起点和终点两个方向开始搜索,结果分存在map中,如果节点在另一个map中,
就意味着找到了一条连接起点和终点的最短路径。若任一queue为空,表明不会存在路径。如下图,前向BFS找到了结果。

搜索方式分为同步搜索和队列容量较少的先搜。本法采取前者

解题步骤:

  1. 与单向BFS类似,但由于现在是双向,所以将BFS的通用部分提取出来,变量为queue, visited, distance, target_distance, target_distance是查找本方向
    的节点是否在对方搜索过的路径上代替endWord,距离为本方向的路径+对方的路径。while循环移到调用函数中。
  2. while循环用两个queue不为空
  3. 为了优化get_next_words重复调用,将结果存在graph中形成邻接表。这样的话,distance不用初始化为0,还可以用于记录重复节点代替visited。
  4. 其他初始化步骤给endWord的BFS复制一次。

注意事项:

  1. graph = collections.defaultdict(list)避免NPE,dict都尽量用此法

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
def ladderLength2(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
if not beginWord or not endWord:
return 0
wordList.append(beginWord)
graph, word_dict = collections.defaultdict(list), set(wordList)
for word in wordList:
graph[word] = self.get_next_words(word, word_dict)
forward_distance, backward_distance = collections.defaultdict(int), collections.defaultdict(int)
forward_distance[beginWord], backward_distance[endWord] = 1, 0

forward_queue, backward_queue = deque([beginWord]), deque([endWord])
forward_visited, backward_visited = set([beginWord]), set([endWord])
while forward_queue and backward_queue:
total_dis = self.bfs_from_start_or_end(graph, forward_queue, forward_distance, backward_distance)
if total_dis > 0:
return total_dis
total_dis = self.bfs_from_start_or_end(graph, backward_queue, backward_distance, forward_distance)
if total_dis > 0:
return total_dis
return 0

def bfs_from_start_or_end(self, graph, queue, distance, target_dict):
word = queue.popleft()
if word in target_dict and target_dict[word] > 0: # the forward distance has all words initially
return distance[word] + target_dict[word]

neighbors = graph[word]
for neighbor in neighbors:
#if neighbor in visited:
if neighbor in distance:
continue
queue.append(neighbor)
# visited.add(neighbor)
distance[neighbor] = distance[word] + 1
return 0

LeetCode 297 Serialize and Deserialize Binary Tree

Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.

Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.

Example:

You may serialize the following tree:

    1
   / \
  2   3
     / \
    4   5

as `"[1,2,3,null,null,4,5]"`

Clarification: The above format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself.

Note: Do not use class member/global/static variables to store states. Your serialize and deserialize algorithms should be stateless.

题目大意:

序列化和反序列化二叉树。

Python解法

算法思路:

N/A

注意事项:

  1. BFS解码,空节点也要入列,因为要转成#,且不让代码往下执行
  2. 难点是用#补充空节点,令每个非空节点必有左右儿子,这样解码就可以固定地每轮扫描两个。出列一个父节点,p扫描两个儿子且生成节点,若为#即空节点不入列,这和编码不同。主要因为编码的长度比节点数多,所以生成节点时,不需要再处理空节点。
    Line 25 - 32有重复,这里放在一起方便理解,也可以封装成函数
  3. 类型转换int和str, Python用popleft不是pop
  4. Line 11非空节点值要记得加入
  5. 空节点或空字符单独处理

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
def serialize(self, root):
if not root:
return ''
queue = collections.deque([root])
res = []
while queue:
node = queue.popleft()
if node:
res.append(str(node.val))
else:
res.append('#')
continue

queue.append(node.left)
queue.append(node.right)
return ','.join(res)

def deserialize(self, data):
if not data:
return None
vals = data.split(',')
p = 0
root = TreeNode(int(vals[0]))
queue = collections.deque([root])
while queue:
node = queue.popleft()

p += 1
if vals[p] != '#':
node.left = TreeNode(int(vals[p]))
queue.append(node.left)
p += 1
if vals[p] != '#':
node.right = TreeNode(int(vals[p]))
queue.append(node.right)
return root

Java解法

解题思路:

BFS可以涉及三重循环

  1. q不为空
  2. 是否按层遍历
  3. 是否为图

这题不需要按层遍历,所以不用第二重。而且只是二叉树,不用第三重循环。

编码方式:

1
2
3
4
5
6
7
8
	      1
/ \
# 3
/ \
2 #
/ \
# #
=> 1,#,3,2,#,#,#

BFS解题步骤:

serialize:

  1. 建queue,然后首节点入列
  2. 进入q的非空循环,队首出列,分别加入左右子树。由于空子树也会被遍历,所以左右子树可能为空,队首为空时continue
    且val加入到结果字符串
  3. 用#代替null且删去末尾的#和,

deserialize:
这方法难实现点。用两个指针来代表遍历上一层和该层节点们。q出列的节点是上一层节点head,而idx指向的是
该层节点。这样head.left = Node(tokens[idx])就建立了它们的关系。两指针分别向后一位。每轮循环父指针
向后一位,而idx向后两位,因为有左右儿子。

  1. 建queue,然后首节点入列
  2. 进入q的非空循环,队首出列,分别生成非空左右子树,且建立父子关系。idx走两步,非空儿子加入q。

注意事项:

  1. #也要入栈,因为结果需要
  2. 解码需要一个字符串扫描指针(类全局指针),左右儿子无条件扫两位。这点DFS也是一样的。
  3. deserialize中循环条件要加入idx < tokens.length因为serialize末尾#已经删除。
  4. 字符串相等判断用equals,不用==。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public String serialize2(TreeNode root){
if(root == null)
return "{}";

StringBuilder sb = new StringBuilder();
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
while(!q.isEmpty()) {
TreeNode n = q.poll();
sb.append(n == null ? "null" : n.val);
sb.append(",");
if(n == null)
continue;
q.add(n.left);
q.add(n.right);
}
String res = sb.toString().replaceAll("null", "#");
int endIdx = res.length() - 1;
while(res.charAt(endIdx) == ',' || res.charAt(endIdx) == '#')
endIdx--;
return "{" + res.substring(0, endIdx + 1) + "}";
}

public TreeNode deserialize2(String data) {
String str = data.substring(1, data.length() - 1);
if("".equals(str))
return null;

String[] tokens = str.split(",");
int idx = 1;
Queue<TreeNode> q = new LinkedList<>();
TreeNode root = new TreeNode(Integer.parseInt(tokens[0]));
q.offer(root);
while(!q.isEmpty() && idx < tokens.length) {
TreeNode head = q.poll();
if(head == null)
continue;
head.left = generateChildNode(idx++, tokens, q);
head.right = generateChildNode(idx++, tokens, q);
}
return root;
}

TreeNode generateChildNode(int idx, String[] tokens, Queue<TreeNode> q) {
TreeNode root = null;
if(idx < tokens.length && !"#".equals(tokens[idx])) {
root = new TreeNode(Integer.parseInt(tokens[idx]));
q.offer(root);
}
return root;
}

DFS算法II解题思路:

DFS的serialize很简单,但deserialize比较难。有点类似于前序遍历的递归版。因为编码时候就是前序遍历,解码时候也是先root再左右。
需要维护一个指针p来记录已处理的字符串。

编码方式:

1
2
3
4
    1
2 3
5 6
=> 1,2,5,#,#,6,#,#,3,#,#

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
32
33
34
35
36
37
38
public String serialize(TreeNode root){
if(root==null)
return "#";
String rootStr = root.val+"";
String lStr = serialize(root.left);
String rStr = serialize(root.right);
return rootStr+","+lStr+","+rStr;
}

int p=0;
String[] items = null;

public TreeNode deserialize(String data){
p = 0;
items = null;
return deserializeR(data);
}

public TreeNode deserializeR(String data){
if(data==null||data.length()==0)
return null;
if(p>=data.length())
return null;
String curVal = getNext(data);
if(curVal.equals("#"))
return null;

TreeNode newRoot = new TreeNode(Integer.parseInt(curVal));
newRoot.left = deserializeR(data);
newRoot.right = deserializeR(data);
return newRoot;
}

public String getNext(String s){
if(items==null)
items = s.split(",");
return items[p++];
}

LeetCode 133 Clone Graph

Given a reference of a node in a connected#Connected_graph) undirected graph.

Return a deep copy (clone) of the graph.

Each node in the graph contains a val (int) and a list (List[Node]) of its neighbors.

class Node {
    public int val;
    public List neighbors;
}

Test case format:

For simplicity sake, each node’s value is the same as the node’s index (1-indexed). For example, the first node with val = 1, the second node with val = 2, and so on. The graph is represented in the test case using an adjacency list.

Adjacency list is a collection of unordered lists used to represent a finite graph. Each list describes the set of neighbors of a node in the graph.

The given node will always be the first node with val = 1. You must return the copy of the given node as a reference to the cloned graph.

Example 1:

**Input:** adjList = [[2,4],[1,3],[2,4],[1,3]]
**Output:** [[2,4],[1,3],[2,4],[1,3]]
**Explanation:** There are 4 nodes in the graph.
1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).

Example 2:

**Input:** adjList = [[]]
**Output:** [[]]
**Explanation:** Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors.

Example 3:

**Input:** adjList = []
**Output:** []
**Explanation:** This an empty graph, it does not have any nodes.

Example 4:

**Input:** adjList = [[2],[1]]
**Output:** [[2],[1]]

Constraints:

  • 1 <= Node.val <= 100
  • Node.val is unique for each node.
  • Number of Nodes will not exceed 100.
  • There is no repeated edges and no self-loops in the graph.
  • The Graph is connected and all nodes can be visited starting from the given node.

题目大意:

深度复制图。注意要复制所有邻接节点。

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

三步走。分开写逻辑会显得清晰点。

  1. BFS搜索所有节点,变成邻接表节点列表。
  2. 复制节点。旧新节点映射存在dict中
  3. 根据node.neighbors复制边。

注意事项:

  1. 空节点判断Line 2-3
  2. BFS访问是收集节点列表,并不是变成邻接表。如果是含循环的图,由于用了visited,所以邻接表只能复制一半的边,不能用邻接表

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 cloneGraph(self, node: 'Node') -> 'Node':
if not node:
return None
di = {}
node_list = self.bfs(node)
for n in node_list:
di[n] = Node(n.val)
for n in node_list:
for n2 in n.neighbors:
di[n].neighbors.append(di[n2])
return di[node]

def bfs(self, input):
queue = deque([input])
visited = {input}
# graph = collections.defaultdict(list)
res = []
while queue:
node = queue.popleft()
# graph[node] = []
res.append(node)
for neighbor in node.neighbors:
if neighbor in visited:
continue
queue.append(neighbor)
visited.add(neighbor)
# graph[node].append(neighbor)
return res

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
32
33
34
35
36
37
// another bfs3 method uses 3 steps, convert graph to adjacent list by bfs (flatten the graph), 
//clone vertices, clone edges
public void bfs3(UndirectedGraphNode node, HashMap<UndirectedGraphNode, UndirectedGraphNode> map) {
ArrayList<UndirectedGraphNode> nodes = getNodes(node);

// Copy vertices
for(UndirectedGraphNode old : nodes) {
UndirectedGraphNode newNode = new UndirectedGraphNode(old.label);
map.put(old, newNode);
}

// Copy edges
for(UndirectedGraphNode old : nodes) {
for(UndirectedGraphNode neighbor : old.neighbors) {
map.get(old).neighbors.add(map.get(neighbor));
}
}
}

public ArrayList<UndirectedGraphNode> getNodes(UndirectedGraphNode node) {
Queue<UndirectedGraphNode> q = new LinkedList<>();
Set<UndirectedGraphNode> result = new HashSet<>();
q.offer(node);
result.add(node); // Use result set so we can save the visited set
while(!q.isEmpty()) {
UndirectedGraphNode n = q.poll();
for(UndirectedGraphNode neighbor : n.neighbors) {
if(result.contains(neighbor))
continue;
q.offer(neighbor);
result.add(neighbor);
}
}
ArrayList<UndirectedGraphNode> reList = new ArrayList<UndirectedGraphNode>();
reList.addAll(result);
return reList;
}

算法II解题思路:

不分开三步写

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
public UndirectedGraphNode cloneGraph2(UndirectedGraphNode node) {
if(node == null)
return null;

HashMap<UndirectedGraphNode, UndirectedGraphNode> map = new HashMap<>();
bfs(node, map);
return map.get(node);
}

public void bfs(UndirectedGraphNode node, HashMap<UndirectedGraphNode, UndirectedGraphNode> map) {
Queue<UndirectedGraphNode> q = new LinkedList<>();
q.offer(node);
map.put(node, new UndirectedGraphNode(node.label));
while(!q.isEmpty()) {
UndirectedGraphNode head = q.poll();
for(UndirectedGraphNode neighbor : head.neighbors) {
if(!map.containsKey(neighbor)) {
q.offer(neighbor);
// Clone children's vertex
map.put(neighbor, new UndirectedGraphNode(neighbor.label));
}
// Clone edge
map.get(head).neighbors.add(map.get(neighbor));
}
}
}

算法II解题思路:

DFS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) {
HashMap<Integer, UndirectedGraphNode> map = new HashMap<Integer, UndirectedGraphNode>();
return cloneGraphR(node, map);
}

public UndirectedGraphNode cloneGraphR(UndirectedGraphNode node,
HashMap<Integer, UndirectedGraphNode> map) {
if (node == null)
return node;
if (map.containsKey(node.label))
return map.get(node.label);

UndirectedGraphNode result = new UndirectedGraphNode(node.label);
map.put(node.label, result);
for (UndirectedGraphNode child : node.neighbors) {
result.neighbors.add(cloneGraphR(child, map));
}
return result;
}

算法分析:

时间复杂度为O(# of results),空间复杂度O(lengh(high))

Free mock interview