拼扯惹斯特

迎难而上,祝我好运。面朝大海,春暖花开。

2018.8.9

269. alien-dictionary x3
  • 给一个按照某种外星人字典序排好序的String数组,只包含小写字母,求这些出现过的字母的顺序。若有多种可能(平级)则任意顺序均可。
  • topo排序问题,前后两个字符串逐个字符找到第一对不同的字符,确定先后顺序(前->后)构成一条边,两两字符串全部遍历完之后就形成了一个graph。维护一个inDegree,每次BFS时从入度为0的节点出发,将它可达的所有邻接点的入度都减1(删掉这些边),最后如果全部字符都遍历到了即可。如果出现了环,则一定会又入度不为0的点残留。
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
55
56
57
58
59
60
class Solution {
public String alienOrder(String[] words) {
if (words == null || words.length == 0) {
return "";
}
Map<Character, Set<Character>> graph = new HashMap<>();
Map<Character, Integer> inDegree = new HashMap<>();
initInDegree(words, inDegree);
buildGraph(words, graph, inDegree);
Queue<Character> q = new LinkedList<>();
for (char c : inDegree.keySet()) {
if (inDegree.get(c) == 0) {
q.offer(c);
}
}
StringBuilder sb = new StringBuilder();
while (!q.isEmpty()) {
char c = q.poll();
sb.append(c);
if (graph.containsKey(c)) {
Set<Character> neighbors = graph.get(c);
for (char neighbor : neighbors) {
inDegree.put(neighbor, inDegree.get(neighbor) - 1);
if (inDegree.get(neighbor) == 0) {
q.offer(neighbor);
}
}
}
}
if (sb.length() != inDegree.size()) {
return "";
}
return sb.toString();
}
private void initInDegree(String[] words, Map<Character, Integer> inDegree) {
for (String word : words) {
for (char c : word.toCharArray()) {
inDegree.put(c, 0);
}
}
}
private void buildGraph(String[] words, Map<Character, Set<Character>> graph, Map<Character, Integer> inDegree) {
for (int i = 1; i < words.length; i++) {
String prev = words[i - 1];
String curr = words[i];
int len = Math.min(prev.length(), curr.length());
for (int j = 0; j < len; j++) {
char c1 = prev.charAt(j);
char c2 = curr.charAt(j);
if (c1 != c2) { // 找到第一个不同的字母
graph.putIfAbsent(c1, new HashSet<Character>());
if (graph.get(c1).add(c2)) {
inDegree.put(c2, inDegree.get(c2) + 1);
}
break;
}
}
}
}
}
271. encode-and-decode-strings
  • 实现encode和decode函数,将字符串List和单一条字符串互相转换。
  • 字符含有哪些?(所有ASCII字符都可能出现)
  • 由于每个符号都有可能出现,所以不能想着用某个特殊符号来拼接和拆分。因此需要额外的信息进行标记,这里用的是在/之前加上所跟字符串的长度的方式,decode时每次从特定位置开始找/,然后取出它前面的长度信息直接截取后面的子字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Codec {
// Encodes a list of strings to a single string.
public String encode(List<String> strs) {
StringBuilder sb = new StringBuilder();
for (String str : strs) {
sb.append(str.length()).append("/").append(str); // 长度 + / + 字符串内容
}
return sb.toString();
}

// Decodes a single string to a list of strings.
public List<String> decode(String s) {
List<String> ans = new ArrayList<>();
int i = 0;
while (i < s.length()) {
int index = s.indexOf("/", i);
int len = Integer.valueOf(s.substring(i, index));
ans.add(s.substring(index + 1, index + 1 + len));
i = index + 1 + len;
}
return ans;
}
}

2018.8.4 电面

  • 给一个n,返回所有可能的factor组合。lc 254 x2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public List<List<Integer>> getFactors(int n) {
List<List<Integer>> ans = new ArrayList<>();
if (n < 4) {
return ans;
}
getFactors(n, 2, new ArrayList<Integer>(), ans);
return ans;
}
private void getFactors(int n, int start, List<Integer> path, List<List<Integer>> ans) {
if (n <= 1) { // 已经除尽了,说明没有更多factor
if (path.size() > 1) { // 当前path就是一路走过来用过的factor
ans.add(new ArrayList<Integer>(path));
}
return;
}
for (int i = start; i <= n; i++) {
if (n % i == 0) { // 可以整除,说明i是n的一个factor
path.add(i); // 将i加入path,将n除掉i,再递归从i开始继续往后找大于等于i的factor
getFactors(n / i, i, path, ans);
path.remove(path.size() - 1);
}
}
}

2018.9.14 Onsite

  • 给一个连接关系{A - > B; B - > A,C; C - > B; D - > E; E -> D},group这些node并返回这些group:[(A,B,C), (D,E)].
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
public static List<List<Node>> getGroups(Map<Node, List<Node>> graph) {
if (graph == null) {
return null;
}
Set<Node> nodeSet = new HashSet<>();
for (Node key : graph) {
nodeSet.add(key);
for (Node neighbor : graph.get(key)) {
nodeSet.add(neighbor);
}
}
List<List<Node>> ans = new ArrayList<>();
while (!nodeSet.isEmpty()) {
List<Node> group = new ArrayList<>();
Iterator<Node> it = nodeSet.iterator();
dfs(it.next(), graph, nodeSet, group);
ans.add(group);
}
return ans;
}
private void dfs(Node curr, Map<Node, List<Node>> graph, Set<Node> nodeSet, List<Node> group) {
group.add(curr);
nodeSet.remove(curr); // 移除表示已访问
List<Node> neighbors = graph.get(curr);
if (neighbors == null) {
return;
}
for (Node neighbor : neighbors) {
if (nodeSet.contains(neighbor)) {
dfs(neighbor, graph, nodeSet, group);
}
}
}
425. word-squares x2
  • 给一个wordList,将List中的String放入matrix中使得行、列的单词都来自于这个List。
  • Trie + DFS,对于每个Trie节点,除了正常的nexts数组、isWord布尔值,额外维护一个List保存以「到达当前TrieNode路径」为prefix的所有word。固定一个word之后,下一个词的prefix可以通过纵向append得到,具体规律见这个discussion.
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class Solution {
class TrieNode {
TrieNode[] nexts;
List<String> prefixWords;
boolean isWord;
public TrieNode() {
nexts = new TrieNode[26];
prefixWords = new ArrayList<>();
isWord = false;
}
}
class Trie {
TrieNode root;
public Trie(String[] words) {
root = new TrieNode();
for (String word : words) {
TrieNode curr = root;
for (int i = 0; i < word.length(); i++) {
int index = word.charAt(i) - 'a';
if (curr.nexts[index] == null) {
curr.nexts[index] = new TrieNode();
}
curr.prefixWords.add(word);
curr = curr.nexts[index];
}
curr.isWord = true;
}
}

public List<String> getByPrefix(String prefix) {
List<String> ans = new ArrayList<>();
TrieNode curr = root;
for (int i = 0; i < prefix.length(); i++) {
int index = prefix.charAt(i) - 'a';
if (curr.nexts[index] == null) {
return ans;
}
curr = curr.nexts[index];
}
ans.addAll(curr.prefixWords);
return ans;
}
}
private void dfs(int len, Trie trie, List<List<String>> ans, List<String> curr) {
if (curr.size() == len) {
ans.add(new ArrayList<>(curr));
return;
}
int index = curr.size();
StringBuilder prefix = new StringBuilder();
for (String s : curr) {
prefix.append(s.charAt(index));
}
List<String> candidates = trie.getByPrefix(prefix.toString());
for (String candidate : candidates) {
curr.add(candidate);
dfs(len, trie, ans, curr);
curr.remove(curr.size() - 1);
}
}

public List<List<String>> wordSquares(String[] words) {
List<List<String>> ans = new ArrayList<>();
if (words == null || words.length == 0) {
return ans;
}
int len = words[0].length();
Trie root = new Trie(words);
List<String> curr = new ArrayList<>();
for (String word : words) {
curr.add(word);
dfs(len, root, ans, curr);
curr.remove(curr.size() - 1);
}
return ans;
}
}

2018.4 Onsite

index sum
  • 一个array自己跟自己的element可以组成一个pair,这个pair有个sum,求the kth sum分别对应的index。应该类似于lc 215 k-th element吧,只不过需要多存一个index作为返回。
  • 方法一:PriorityQueue维护top K元素即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
public int findKthLargest(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return 0;
}
PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int i = 0; i < nums.length; i++) {
pq.offer(nums[i]);
if (pq.size() > k) {
pq.poll();
}
}
return pq.peek();
}
  • 方法二:quickSelect,用快排的partition来找到元素。
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 int findKthLargest(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return 0;
}
shuffle(nums);
int lo = 0, hi = nums.length - 1;
k = nums.length - k; // 转换成第k小的
while (lo < hi) {
int j = partition(nums, lo, hi);
if (j == k) {
break;
} else if (j < k) { // 说明第k小的还在后面
lo = j + 1;
} else { // 说明第k小的还在前面
hi = j - 1;
}
}
return nums[k];
}
private int partition(int[] nums, int lo, int hi) {
int i = lo, j = hi + 1;
while (i < j) {
while (nums[++i] < nums[lo]) {
if (i == hi) {
break;
}
}
while (nums[--j] > nums[lo]) {
if (j == lo) {
break;
}
}
if (i >= j) {
break;
}
swap(nums, i, j);
}
swap(nums, lo, j);
return j;
}
public void shuffle(int[] arr) {
Random r = new Random();
for (int i = 0; i < arr.length; i++) {
swap(arr, i, r.nextInt(i + 1));
}
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
463. island-perimeter x2
  • 给一个0/1二维矩阵,求其中为1的island的周长。
  • 方法一:BFS,每个1都先算它有四条边,然后根据邻接情况减掉不是边的即可。
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
class Solution {
public int islandPerimeter(int[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
boolean[][] visited = new boolean[grid.length][grid[0].length];
int perimeter = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (grid[i][j] == 1 && !visited[i][j]) {
perimeter += bfs(grid, i, j, visited);
}
}
}
return perimeter;
}
private int[][] dirs = new int[][] {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
private int bfs(int[][] grid, int i, int j, boolean[][] visited) {
Queue<int[]> q = new LinkedList<>();
q.add(new int[] {i, j});
visited[i][j] = true;
int perimeter = 0;
while (!q.isEmpty()) {
int[] curr = q.poll();
perimeter += 4;
for (int[] dir : dirs) {
int row = curr[0] + dir[0], col = curr[1] + dir[1];
if (isIsland(grid, row, col)) {
perimeter--;
if (!visited[row][col]) {
q.add(new int[] {row, col});
visited[row][col] = true;
}
}
}
}
return perimeter;
}
private boolean isIsland(int[][] grid, int i, int j) {
return i >= 0 && i < grid.length
&& j >= 0 && j < grid[0].length
&& grid[i][j] == 1;
}
}
  • 方法二:找规律。对于每一个1的cell,看它的下方和右方neighbor是不是1,记录neighbor数,最后周长就是islands * 4 - neighbours * 2。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int islandPerimeter(int[][] grid) {
int islands = 0, neighbours = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
if (grid[i][j] == 1) {
islands++; // count islands
if (i < grid.length - 1 && grid[i + 1][j] == 1) neighbours++; // count down neighbours
if (j < grid[i].length - 1 && grid[i][j + 1] == 1) neighbours++; // count right neighbours
}
}
}

return islands * 4 - neighbours * 2;
}

2018.9 Onsite

permutation系列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
backtrack(list, new ArrayList<>(), nums, new boolean[nums.length]);
return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int[] nums, boolean[] used) {
if (tempList.size() == nums.length) {
list.add(new ArrayList<>(tempList));
} else {
for (int i = 0; i < nums.length; i++) {
if (used[i]) continue;
tempList.add(nums[i]);
used[i] = true;
backtrack(list, tempList, nums);
used[i] = false;
tempList.remove(tempList.size() - 1);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
Arrays.sort(nums);
backtrack(list, new ArrayList<>(), nums, new boolean[nums.length]);
return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int[] nums, boolean[] used) {
if (tempList.size() == nums.length) {
list.add(new ArrayList<>(tempList));
} else {
for (int i = 0; i < nums.length; i++) {
if (used[i] || i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;
tempList.add(nums[i]);
used[i] = true;
backtrack(list, tempList, nums, used);
used[i] = false;
tempList.remove(tempList.size() - 1);
}
}
}

2016.8 onsite

Letter Combinations of a Phone Number with isWord() API
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
final char[][] num2Char = new char [][] {
{},
{},
{'a', 'b', 'c'},
{'d', 'e', 'f'},
{'g', 'h', 'i'},
{'j', 'k', 'l'},
{'m', 'n', 'o'},
{'p', 'q', 'r', 's'},
{'t', 'u', 'v'},
{'w', 'x', 'y', 'z'}
};

// 判断是否在给定的dict中
// public boolean isWord(String word);

public List<String> letterCombinations(String digits) {
List<String> ans = new ArrayList<>();
if (digits == null || digits.length() == 0
|| digits.indexOf('1') >= 0 || digits.indexOf('0') >= 0) {
return ans;
}
char[] str = digits.toCharArray();
dfs(str, 0, num2Char, new StringBuilder(), ans);
return ans;
}
private void dfs(char[] str, int index, final char[][] num2Char,
StringBuilder sb, List<String> ans) {
if (index == str.length && isWord(sb.toString())) {
ans.add(sb.toString());
return;
}
int temp = str[index] - '0';
for (int i = 0; i < num2Char[temp].length; i++) {
sb.append(num2Char[temp][i]);
dfs(str, index + 1, num2Char, sb, ans);
sb.deleteCharAt(sb.length() - 1);
}
}
Duplicate Files in directory
  • 给一个文件目录,设计一个方法能够找出该目录下(包括子目录)所有的duplicate files(文件内容一样但文件名不一定一样),
  • 先DFS把所有file找出来,然后用MD5哈希值作为Map的key、file的Set作为value,将相同key的Set中的文件两两比较,防止是不同文件因为collision而放到这里。
  • 但如果目录中只有少量的重复文件也要全部扫一遍算MD5,这样很不划算。因此考虑利用文件大小来归类,然后只有文件大小相同的才有可能出现duplicate,但这样可能还是要计算很多个相同文件大小的MD5。
  • 再考虑是不是可以不求整个文件的MD5,而是只取一部分来MD5求哈希作为key(例如前1k个bytes),然后对于size > 1的组再根据[1k + 1, 2k]部分内容求MD5.这样如果只有少量的dulicate,通过这样拆分求key在前面几步就会把大量文件给排除了,后面求MD5的时候就少很多了。
走到0
  • 可能类似于lc 457 ,但是不是找loop,也不要求移动方向一致,只是找是否存在arr[index] == 0。直接用Set存走过的index即可。或者也可以快慢指针,每次判断快指针是否指向了一个0即可,若快慢指针相遇都没有遇到0,说明不存在。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public boolean hasZero(int[] nums, int start) {
if (nums == null || nums.length == 0 || start < 0 || start >= nums.length) {
return false;
}
if (nums[start] == 0) { // 防止一个元素的时候slow == fast进不去while-loop
return true;
}
int n = nums.length, slow = start, fast = getNextIndex(start, nums[start], n);
while (slow != fast) {
if (nums[slow] == 0 || nums[fast] == 0) {
return true;
}
slow = getNextIndex(slow, nums[slow], n); // 慢快指针分别移动一步两步
fast = getNextIndex(fast, nums[fast], n);
fast = getNextIndex(fast, nums[fast], n);
}
return false;
}
private int getNextIndex(int index, int step, int len) {
int nextIndex = index + step;
return nextIndex >= 0 ? nextIndex % len : nextIndex % len + len; // java的%求的是余数而不是真正的modulo
}
merge K sorted list
  • 可以直接把元素通通存入PriorityQueue,然后从头取元素即可。或者用分治法递归来搞,
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
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) {
return null;
}
// last two params are used for dividing into sub problems
return mergeKLists(lists, 0, lists.length);
}

private ListNode mergeKLists(ListNode[] lists, int start, int end) {
if (end - start == 1) { // only one list
return lists[start];
} else if (end - start == 2) { // merge two lists
return mergeTwoLists(lists[start], lists[start + 1]);
} else {
int mid = start + (end - start) / 2;
// cut into first and second halves
return mergeTwoLists(mergeKLists(lists, start, mid),
mergeKLists(lists, mid, end)); // warning not mid + 1
}
}
private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
if (l1.val <= l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}

2018.4 Onsite x2

battle ship游戏
  • lc有个算battleship个数的,直接O(NM)求第一次出现的ship坐标即可。但游戏应该是给一个坐标进行轰炸,如果反复调用O(NM)太慢了,可以考虑存坐标-船的map,这样就是O(1)了。
277. find-the-celebrity x2
  • 给一个API函数判断i是否认识j,celebrity的定义是在n个人中,n-1个人都认识他、他却完全不认识他们。
  • naive方法是O(n^2)搞定,对于每个人,一旦他认识别人、或者别人不认识他就跳出循环。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* The knows API is defined in the parent class Relation.
boolean knows(int a, int b); */
public class Solution extends Relation {
public int findCelebrity(int n) {
for (int i = 0; i < n; i++) {
boolean found = true;
for (int j = 0; j < n; j++) {
if (j == i) {
continue;
}
if (!knows(j, i) || knows(i, j)) { // 看别人是否都认识i且i完全不认识别人
found = false;
break;
}
}
if (found) {
return i;
}
}
return -1;
}
}
  • 更巧妙的办法,从头开始一个pass,判断如果i认识j则把candidate设为j,如果i不认识j则j不可能是candidate;完了之后再一个pass看这个candidate是不是不认识所有人、且所有人都认识他。
1
2
3
4
5
6
7
8
9
10
public int findCelebrity(int n) {
int candidate = 0; // 先假设第一个人是名人
for (int i = 1; i < n; i++) { // 他认识别人就,把他排除掉了
if (knows(candidate, i)) candidate = i;
}
for (int i = 0; i < n; i++) { // 确认他是否认识某个人或者某个人不认识他,那他也不是名人
if (i != candidate && (knows(candidate, i) || !knows(i, candidate))) return -1;
}
return candidate;
}
759. employee-free-time
  • 给一个List of List,每一个子List存放每个employee的工作时间,求所有employee的共同空闲时间。没有则返回空List。
  • 直接将List flatten,然后按照工作开始时间从小到大排序,再往后依次取工作时间,若当前开始时间比之前的结束时间长,说明出现了空闲时间;否则需要更新prev的end,保证prev的结束时间cover到所有结束时间,即取max。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public List<Interval> employeeFreeTime(List<List<Interval>> schedule) {
List<Interval> ans = new ArrayList<>();
if (schedule == null || schedule.size() == 0) {
return ans;
}
List<Interval> workingTimes = new ArrayList<>();
schedule.forEach(e -> workingTimes.addAll(e));
Collections.sort(workingTimes, (a, b) -> a.start - b.start);
Interval prev = workingTimes.get(0);
for (int i = 1; i < workingTimes.size(); i++) {
if (workingTimes.get(i).start > prev.end) {
ans.add(new Interval(prev.end, workingTimes.get(i).start));
prev = workingTimes.get(i);
} else {
prev.end = Math.max(prev.end, workingTimes.get(i).end);
}
}
return ans;
}
求出现次数多于n/4的元素
  • 169. majority-element是求n/2、229. majority-element-ii是求n/3。区别是这些给的都不是sorted array. 这题已经排序了,可以想到的是用两个相隔n/4的指针,若两端元素相等说明存在n/4 + 1个相同元素,这个就是其中一个答案了。

2018.5 Oniste

blacklist of words
  • scan 某个query里面存不存在blacklist里面的word。借此机会复习一下trie吧。
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
55
56
57
58
59
class Trie {
class TrieNode {
boolean isWord;
TrieNode[] next;
public TrieNode() {
isWord = false;
next = new TrieNode[26];
}
}
TrieNode root;
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
if (word == null || word.length() == 0) {
return;
}
char[] wordChar = word.toCharArray();
TrieNode curr = root;
for (int i = 0; i < wordChar.length; i++) {
int index = wordChar[i] - 'a';
if (curr.next[index] == null) {
curr.next[index] = new TrieNode();
}
curr = curr.next[index];
}
curr.isWord = true;
}
public boolean search(String word) {
if (word == null || word.length() == 0) {
return false;
}
char[] wordChar = word.toCharArray();
TrieNode curr = root;
for (int i = 0; i < wordChar.length; i++) {
int index = wordChar[i] - 'a';
if (curr.next[index] == null) {
return false;
}
curr = curr.next[index];
}
return curr.isWord;
}
public boolean startsWith(String prefix) {
if (prefix == null || prefix.length() == 0) {
return false;
}
char[] prefixChar = prefix.toCharArray();
TrieNode curr = root;
for (int i = 0; i < prefixChar.length; i++) {
int index = prefixChar[i] - 'a';
if (curr.next[index] == null) {
return false;
}
curr = curr.next[index];
}
return true;
}
}

2018.3 Onsite x2

Longest increasing path in the binary tree
  • 具体题意不明,可能是从root到leaf的上升序列(类似lc 298),或者是可以转折的(类似lc 549)。
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
55
56
57
class Solution {    // 从上到下的单向路径
private int max = 0;
public int longestConsecutive(TreeNode root) {
helper(root);
return max;
}
private int helper(TreeNode root) {
if (root == null) {
return 0;
}
int len = 1; // 以当前节点结束,则长度为1
int left = helper(root.left), right = helper(root.right);
if (root.left != null && root.left.val == root.val + 1) {
len = left + 1; // 根据孩子节点val判断长度是否累加
}
if (root.right != null && root.right.val == root.val + 1) {
len = Math.max(len, right + 1); // 取两个孩子长度的max
}
max = Math.max(max, len);
return len;
}
}
class Solution { // 允许转折的路径
int max = 0;
public int longestConsecutive(TreeNode root) {
if (root == null) {
return 0;
}
helper(root);
return max;
}
private int[] helper(TreeNode root) {
if (root == null) {
return new int[] {0, 0};
}
int[] left = helper(root.left), right = helper(root.right);
int inc = 1, dec = 1;
if (root.left != null) {
if (root.left.val == root.val + 1) { // 确认是否能和孩子形成升序/降序列
inc = left[0] + 1;
}
if (root.left.val == root.val - 1) {
dec = left[1] + 1;
}
}
if (root.right != null) {
if (root.right.val == root.val + 1) {
inc = Math.max(inc, right[0] + 1); // 取左右中最大的
}
if (root.right.val == root.val - 1) {
dec = Math.max(dec, right[1] + 1);
}
}
max = Math.max(inc + dec - 1, max);
return new int[] {inc, dec}; // 返回以当前节点为起点的上升、下降序列长度
}
}

2018.6 Onsite

  • 实现min stack,需要多一个popMin方法. 思路是在push的时候如果min发生了变化就把当前的min push进去,再更新min。这样在pop的时候就可以通过对比min和当前pop的元素知道是否需要更新min,若min已经被pop了,那么前一个再pop出来就是当前的min。
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
55
56
57
58
59
60
class MinStack {
private Stack<Integer> stack;
private int min;
/** initialize your data structure here. */
public MinStack() {
stack = new Stack<Integer>();
min = Integer.MAX_VALUE;
}

public void push(int x) {
if (stack.isEmpty()) {
min = x;
} else {

if (x <= min) {
stack.push(min);
min = x;
}
}
stack.push(x);
}

public void pop() {
if (stack.isEmpty()) {
return;
}
int temp = stack.pop();
if (temp == min && !stack.isEmpty()) { // means the min has gone
min = stack.pop(); // need to update
}
// return temp;
}

public int top() {
if (stack.isEmpty()) {
return 0;
}
return stack.peek();
}

public int getMin() {
if (stack.isEmpty()) {
return 0;
}
return min;
}
public int popMin() {
int temp = 0;
while (!stack.isEmpty()) {
temp = stack.pop();
if (temp == min) {
if (!isEmpty()) {
min = stack.pop();
}
break;
}
}
return temp;
}
}

2018.2 电面

  • 给一个grid,里面有空地、墙和守卫,求离最近守卫距离最远的点的坐标集合。
  • 从所有守卫开始BFS,最后一个level到达的点就是所求了。类似lc 286.

2018.3 Onsite

31. next-permutation
  • 给一个int数组,将它改造成按字典序的下一个排列,若不存在则直接按从小到大的顺序调整。要求In-place对原数组操作,不能申请额外空间。
  • 从右往左找相邻的两个数使得nums[i - 1] < nums[i]找到第一个升序对,然后再从右往左找首次出现的比nums[i]大的数nums[j],二者对调,然后将nums[i + 1]及其之后的内容(一定是降序的)reverse一下即可。
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
public void nextPermutation(int[] nums) {
if (nums == null || nums.length < 2) {
return;
}
int i = nums.length - 1;
for (; i >= 1; i--) {
if (nums[i - 1] < nums[i]) { // 第一个升序对;后续一定是完美降序
int j = nums.length - 1;
while (j >= i) { // 找第一个恰好大于nums[i - 1]的数
if (nums[j] > nums[i - 1]) {
swap(nums, i - 1, j);
break;
}
j--;
}
break;
}
}
reverse(nums, i);
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
private void reverse(int[] nums, int start) {
for (int i = start, j = nums.length - 1; i < j; i++, j--) {
swap(nums, i, j);
}
}

2018.7 Onsite

133. clone-graph
  • 给一个无向有环全联通图的其中一个节点,要求拷贝一个完整的无向有环图,并返回拷贝而成的对应的该节点。而且两个节点之间可以有多条通路。
  • 直接用map + DFS搞,对于每一个节点直接new一个相同label的节点出来,然后遍历所有的邻居递归调用clone即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private Map<Integer, UndirectedGraphNode> map = new HashMap<>();
public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) {
return clone(node);
}
public UndirectedGraphNode clone(UndirectedGraphNode node) {
if (node == null) return null;
if (map.containsKey(node.label)) {
return map.get(node.label);
}
UndirectedGraphNode cloned = new UndirectedGraphNode(node.label);
map.put(clone.label, cloned);
for (UndirectedGraphNode neighbor : node.neighbors) {
cloned.neighbors.add(clone(neighbor));
}
return cloned;
}
20. valid-parentheses
  • 给一个字符串,判断其中三种括号(), [], {}是否匹配。用stack搞即可。
  • follow-up: 求最少删几个字符可以使原字符串合法。例如(([{))就是把中间两个删除即可。
  • 思路应该类似301. remove-invalid-parentheses,也是一个图论BFS。从当前字符串开始出发,所有可达点就是「删除一个字符串所得的新字符串」,注意需要用Set防止重复。每次对相同level的字符串判断是否valid,一旦出现了valid,则当前level所有的valid的字符串就是「最少删除之后的valid字符串」。
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
public List<String> removeInvalidParentheses(String s) {
List<String> res = new ArrayList<>();
if (s == null) return res;

Set<String> visited = new HashSet<>();
Queue<String> queue = new LinkedList<>();

queue.add(s);
visited.add(s);
boolean found = false;

while (!queue.isEmpty()) {
curr = queue.poll();
if (isValid(curr)) {
res.add(curr);
found = true;
}

if (found) continue;

// generate all possible states
for (int i = 0; i < curr.length(); i++) {
String neighbor = curr.substring(0, i) + curr.substring(i + 1);
if (!visited.contains(neighbor)) {
queue.add(neighbor);
visited.add(neighbor);
}
}
}
return res;
}
public boolean isValid(String s) {
Stack<Character> stack = new Stack<Character>();
for (char c : s.toCharArray()) {
if (c == '(')
stack.push(')');
else if (c == '{')
stack.push('}');
else if (c == '[')
stack.push(']');
else if (stack.isEmpty() || stack.pop() != c)
return false;
}
return stack.isEmpty();
}

2018.5 Onsite

29. divide-two-integers
  • 给两个数字,不使用乘、除、模运算前者除以后者之商,若结果越界则返回MAX_INT。
  • 除了傻傻地一路减法减下去,还可以用位运算提速。提取出正负号之后统一按照正long数来处理。用sum来累计当前已经用了多少除数(dividend),将next初始化为除数,在循环中找到恰好让sum + next > longDividendnext,然后把next往回移一位就是当前这一波累计的除数总和了,加到sum即可。同时用一个count计数记录用了多少个除数,这就是所求的商了,也是累计即可。
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
public int divide(int dividend, int divisor) {
if ((dividend == Integer.MIN_VALUE && divisor == -1)
|| divisor == 0) {
return Integer.MAX_VALUE;
}
if (dividend == 0) {
return 0;
}

// using XOR to adjust sign
int sign = (dividend < 0) ^ (divisor < 0) ? -1 : 1;
int ans = 0;
long longDividend = Math.abs((long)dividend);
long longDivisor = Math.abs((long)divisor);
long sum = 0;
while (sum < longDividend) { // warning use long abs value!
int count = -1;
long next = longDivisor;
while (sum + next <= longDividend) {
count++;
next <<= 1;
}
if (next == longDivisor) { // 没有变化
break;
}
sum += (next >> 1); // how much has accumulated
ans += (1 << count); // how many divisor used
}
return sign * ans;
}

2018.5.21 电面

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
public int[] searchRange(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return new int [] {-1, -1};
}
int first = searchFirst(nums, target);
if (first == -1) {
return new int [] {-1, -1};
}
int last = searchLast(nums, target);
return new int[] {first, last};
}
private int searchFirst(int[] nums, int target) {
// hope to biase to the front
// invariant relation: A[start] < target <= A[end]
int start = -1, end = nums.length;
while (end - start > 1) {
int mid = start + (end - start) / 2;
if (nums[mid] >= target) { // end need to be more aggresive
end = mid; // shrink down
} else {
start = mid; // step up
}
}
return end == nums.length || nums[end] != target? -1: end; // end will proceed at front
}
private int searchLast(int[] nums, int target) {
// biase to the back
// invariant relation: A[start] <= target < A[end]
int start = -1, end = nums.length;
while (end - start > 1) {
int mid = start + (end - start) / 2;
if (nums[mid] > target) {
end = mid; // shrink down
} else { // start need to be more aggressive
start = mid; // step up
}
}
return start;
}

2018.7.1 电面 x2

  • lc 72 edit distance
  • 给两个字符串word1, word2,求使用addition, replacement, removement三种操作的情况下最少多少步可以从word1转换成word2。
  • dp[i][j]表示当前长度i的子字符串1转换成长度j的子字符串2最少需要的操作数。初始条件显然是dp[0][x] = dp[x][0] = x。对于任意一个dp[i][j],需要由上、左上、左三个方格的计算结果决定,往上dp[i-1][j]表示一个deletion,往左dp[i][j-1]表示一个addition,左上dp[i-1][j-1]表示一个replacement。每次选取三者中最小的,加上1就是当前的最小操作数了。
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
public int minDistance(String word1, String word2) {
if (word1 == null || word2 == null) {
return 0;
}
int len1 = word1.length(), len2 = word2.length();
int[][] dp = new int [len1 + 1][len2 + 1];
for (int i = 0; i <= len1; i++) {
for (int j = 0; j <= len2; j++) {
if (i == 0) {
dp[i][j] = j;
} else if (j == 0) {
dp[i][j] = i;
} else {
// s1取i-1、s2取j-1个字符的情况,然后s1和s2都取,不需要增加操作数
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
// left: s1取i、s2取j-1个字符的操作数,现在相当于需要往s1中加s2对应位置的字符才能匹配上
// up: s1取i-1、s2取j个字符的操作数,相当于要从s1中删除字符才能匹配上
// left-up: s1取i-1、s2取j-1个字符的操作数,现在相当于要进行替换才能匹配上
int left = dp[i][j - 1], up = dp[i - 1][j], upLeft = dp[i - 1][j - 1];
dp[i][j] = Math.min(Math.min(left, up), upLeft) + 1;
}
}
}
}
return dp[len1][len2];
}

2018.2.13 电面 x3

  • 给一堆time range,可能有重合。比如[1,3], [2,4], [5,6]。总共的uniq的时间,即merge之后的总时间长度。类似lc 56,但不需要维护所有的interval了,只需要保存prev,当后续interval没有overlap的时候就累加time range即可。
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
public int getTimeRange(List<Interval> intervals) {
if (intervals == null) {
return new ArrayList<Interval>();
}
Collections.sort(intervals, (a, b) -> {
if (a.start != b.start) {
return a.start - b.start;
} else {
return a.end - b.end;
}
});
Interval prev = null;
int range = 0;
for (Interval itv : intervals) {
if (prev == null) {
prev = itv;
} else {
if (itv.start > prev.end) {
range += (prev.end - prev.start);
prev = itv;
} else {
prev.end = Math.max(prev.end, itv.end);
}
}
}
range += (prev.end - prev.start);
return range;
}
  • follow-up: 最长的连续range(能合并的合并,最长的range).直接在上面加个取长度即可。
  • follow-up2: 求出现次数最多的range。思路也是先排序,利用一个currCount统计当前的time range数,出现了重叠就将prev设置为overlap的部分并累加,当overlap结束就更新maxCount即可。
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
public static Interval getMaxInterval(List<Interval> intervals) {
if (intervals == null || intervals.size() == 0) {
return null;
}
Collections.sort(intervals, (a, b) -> {
if (a.start != b.start) {
return a.start - b.start;
} else {
return a.end - b.end;
}
});
int maxCount = 1, currCount = 1;
Interval prev = null, ret = null;
for (Interval itv : intervals) {
if (prev == null) {
prev = itv;
currCount = 1;
} else {
if (itv.start >= prev.end) {
prev = itv;
currCount = 1;
} else {
prev = new Interval(itv.start, Math.min(prev.end, itv.end));
currCount += 1;
if (currCount > maxCount) {
ret = prev;
}
}
}
}
return ret;
}

2017.11 电面

  • 类似于lc 158,给一个API read4(char[] buf),每次可以从某个file中读4个字符到buf,并返回实际读取的字符数。
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
public class Solution extends Reader4 {
/**
* @param buf Destination buffer
* @param n Maximum number of characters to read
* @return The number of characters read
*/
private int N = 4, tempIndex = 0, lastLimit = 0;
char[] temp = new char[N];
public int read(char[] buf, int n) {
if (n < 1) {
return n;
}
int index = 0;
if (tempIndex == lastLimit) {
lastLimit = read4(temp);
tempIndex = 0;
}
while (lastLimit > 0) {
while (n > 0 && tempIndex < lastLimit && index < buf.length) {
buf[index++] = temp[tempIndex++];
n--;
}
// 如果n读够了,直接返回
if (n == 0) {
return index;
}
// 如果tempIndex到头了,可以继续读
lastLimit = read4(temp);
tempIndex = 0;
}
return index;
}
}

2015.10 电面

785. is-graph-bipartite x3
  • 给一个数组,每个index对应着该node的所有邻接点。问这个graph能否只用两个颜色给node上色使得相邻两个点的颜色都不一样。
  • 方法一:DFS。对于每一个点都一波直接深度搜索上色,用一个数组存储上色状态,0表示未访问过,-1和1分别表示两个颜色,在dfs时对于当前的点若已经访问过就判断是否符合当前给定的颜色,若未访问则直接上色并DFS到它所有邻接点。需要注意可能有若干独立的cluster,因此不能一次DFS搜索就结束了,而是需要一个循环保证对所有未访问过的点都进行一次DFS。
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
class Solution {
public boolean isBipartite(int[][] graph) {
if (graph == null || graph.length == 0) {
return true;
}
int[] colors = new int[graph.length];
for (int i = 0; i < graph.length; i++) {
if (colors[i] == 0 && !checkColor(graph, colors, -1, i)) {
return false;
}
}
return true;
}
private boolean checkColor(int[][] graph, int[] colors, int currColor, int currNode) {
if (colors[currNode] != 0) {
return colors[currNode] == currColor;
}
colors[currNode] = currColor;
for (int neighbor : graph[currNode]) {
if (!checkColor(graph, colors, -currColor, neighbor)) {
return false;
}
}
return true;
}
}
  • 方法二:BFS,还是用queue存放所有邻接点然后逐一上色。
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
class Solution {
public boolean isBipartite(int[][] graph) {
if (graph == null || graph.length == 0) {
return true;
}
int[] colors = new int[graph.length];
for (int i = 0; i < graph.length; i++) { // 只check没有访问过的点,即各个独立的cluster
if (colors[i] == 0 && !checkColor(graph, colors, -1, i)) {
return false;
}
}
return true;
}
private boolean checkColor(int[][] graph, int[] colors, int currColor, int currNode) {
if (colors[currNode] != 0) {
return colors[currNode] == currColor;
}
Queue<Integer> q = new LinkedList<>();
int color = currColor;
q.offer(currNode);
while (!q.isEmpty()) {
int size = q.size();
while (size-- > 0) {
int node = q.poll();
if (colors[node] != 0) {
if (colors[node] != color) return false;
} else {
colors[node] = color;
}
for (int neighbor : graph[node]) {
if (colors[neighbor] == 0) q.offer(neighbor); // 避免回头路
}
}
color = -color;
}
return true;
}
}

2017.10 电面

720. longest-word-in-dictionary
  • 给一个string数组,只含有小写字母,这些word可能形成链式如a, ap, app, appl, apple,求能形成链式的最长的单词,若有多个则取lexicographical最小的。
  • 用sort + Set的方式比较trivial,排序后就可以逐个取word判断prefix是否在Set中。
  • 想考的方法其实是Trie。根据所给的word构建Trie,每个节点直接存储word而不是boolean isWord,因为最后要求的是最长的String,直接存String方便返回。构建完Trie之后就从root开始找最长的连续的到达leaf的路径,要求路径上每一个word都长度更长(increasing)。
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
class Solution {
class TrieNode {
TrieNode[] nexts = new TrieNode[26];
String word = "";
}
class Trie {
TrieNode root;
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode curr = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
int index = c - 'a';
if (curr.nexts[index] == null) {
curr.nexts[index] = new TrieNode();
}
curr = curr.nexts[index];
}
curr.word = word;
}
}
private String dfs(TrieNode node) {
String ret = node.word;
for (TrieNode next : node.nexts) {
if (next != null && next.word.length() > 0) {
String childWord = dfs(next);
if (childWord.length() > ret.length()) {
ret = childWord;
}
}
}
return ret;
}
public String longestWord(String[] words) {
if (words == null || words.length == 0) {
return "";
}
Trie trie = new Trie();
for (String word : words) {
trie.insert(word);
}
return dfs(trie.root);
}
}

2018.3 电面

  • 将二叉树转换成双向链表。应该是类似lc 426的。
426. convert-binary-search-tree-to-sorted-doubly-linked-list
  • 给一个BST,将它转换成排好序的循环双向链表(头尾相接),返回头部。可以把左右孩子就看作是当前节点的前后链表节点,不需要额外搞ListNode类。
  • BST要维持顺序,那就是用中序遍历。利用全局变量prev来记录每一个子树的结尾节点,先build左子树,然后把当前节点拼到prev的后面,再去继续build右子树即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private Node prev = null;
public Node treeToDoublyList(Node root) {
if (root == null) {
return null;
}
Node dummy = new Node(); // 伪头部的next就是head
prev = dummy;
buildDoublyList(root);
prev.right = dummy.right;
dummy.right.left = prev;
dummy.left = dummy.right = null; // 清理dummy
return prev.right;
}
// 执行buildDoublyList后会将node下面的部分都形成双向链表
public void buildDoublyList(Node node) {
if (node == null) {
return;
}
buildDoublyList(node.left); // 先对左子树build一下,prev会指向最后一个节点
prev.right = node; // 将node拼进去
node.left = prev;
prev = node; // 左半部分+当前节点的结尾就是node
buildDoublyList(node.right);// 继续build右子树
}
  • 方法二:分治法。先把左右子树的循环双向链表build好,再把当前节点塞到中间,同时把新的前后循环连接一下。注意在分别build的时候,需要把root本身设一个自循环,这样就可以重复使用connect方法了。
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
public Node treeToDoublyList(Node root) {
if (root == null) {
return null;
}
Node leftHead = treeToDoublyList(root.left);
Node rightHead = treeToDoublyList(root.right);
root.left = root;
root.right = root;
return connect(connect(leftHead, root), rightHead);
}
public Node connect(Node leftHead, Node rightHead) {
if (leftHead == null) {
return rightHead;
}
if (rightHead == null) {
return leftHead;
}
Node leftTail = leftHead.left;
Node rightTail = rightHead.left;
leftTail.right = rightHead;
rightHead.left = leftTail;
leftHead.left = rightTail;
rightTail.right = leftHead;
return leftHead;
}

2017.12 电面

127. word-ladder
  • 给一个List的String,然后一个初始String一个目标String,要求每次变动一个字母且变动后的词语来自该List的情况下,最短需要变几次得到目标String。
  • 可以看作BFS问题,每个word就是一个node,neighbor就是换掉一个字母后且在wordList中存在的词。这样BFS最快到达dest的就是最短了。
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
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
if (beginWord == null || endWord == null || beginWord.length() == 0 || endWord.length() == 0
|| beginWord.length() != endWord.length() || wordList == null || wordList.size() == 0) {
return 0;
}
Set<String> wordSet = new HashSet<>(wordList);
Queue<String> toVisit = new LinkedList<>();
bfs(beginWord, wordSet, toVisit);
int dist = 2;
while (!toVisit.isEmpty()) {
int size = toVisit.size();
for (int i = 0; i < size; i++) {
String curr = toVisit.poll();
if (curr.equals(endWord)) {
return dist;
} else {
bfs(curr, wordSet, toVisit);
}
}
dist++;
}
return 0;
}
private void bfs(String beginWord, Set<String> wordSet, Queue<String> toVisit) {
wordSet.remove(beginWord);
StringBuilder sb = new StringBuilder(beginWord);
for (int i = 0; i < sb.length(); i++) {
char origin = sb.charAt(i);
for (int k = 0; k < 26; k++) {
char c = (char)('a' + k);
sb.setCharAt(i, c);
String curr = sb.toString();
if (wordSet.contains(curr)) {
toVisit.add(curr);
wordSet.remove(curr);
}
}
sb.setCharAt(i, origin);
}
}
  • follow-up:如何记录最短路径?其实就是126。还是用BFS找最短路径,维护一个distMap记录每个节点到达时所经过的步数,到达终点后再从头开始DFS,每次只去步数增长1的邻居点。
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
List<List<String>> ans = new ArrayList<>();
if (beginWord == null || endWord == null || beginWord.length() == 0 || endWord.length() == 0
|| beginWord.length() != endWord.length() || wordList == null || wordList.size() == 0) {
return ans;
}
Map<String, Integer> distMap = new HashMap<>();
for (String word : wordList) {
distMap.put(word, Integer.MAX_VALUE);
}
if (!distMap.containsKey(endWord)) {
return ans;
}
distMap.put(beginWord, 0);

Map<String, Set<String>> neighborMap = new HashMap<>();
Queue<String> toVisit = new LinkedList<>();
toVisit.offer(beginWord);
boolean found = false;
while (!toVisit.isEmpty() && !found) {
int size = toVisit.size();
for (int i = 0; i < size; i++) {
String currWord = toVisit.poll();
if (currWord.equals(endWord)) {
break;
} else {
bfs(currWord, distMap, neighborMap, toVisit);
}
}
}
dfs(beginWord, endWord, distMap, neighborMap, ans, new ArrayList<>());
return ans;
}
public void bfs(String fromWord, Map<String, Integer> distMap,
Map<String, Set<String>> neighborMap, Queue<String> q) {
StringBuilder sb = new StringBuilder(fromWord);
for (int i = 0; i < sb.length(); i++) {
char origin = sb.charAt(i);
for (int k = 0; k < 26; k++) {
char c = (char)('a' + k);
sb.setCharAt(i, c);
String curr = sb.toString();
if (distMap.containsKey(curr) && distMap.get(fromWord) + 1 <= distMap.get(curr)) {
q.add(curr);
distMap.put(curr, distMap.get(fromWord) + 1);
neighborMap.putIfAbsent(fromWord, new HashSet<>());
neighborMap.get(fromWord).add(curr);
}
}
sb.setCharAt(i, origin);
}
}
public void dfs(String fromWord, String toWord, Map<String, Integer> distMap,
Map<String, Set<String>> neighborMap, List<List<String>> ans, List<String> path) {
path.add(fromWord);
if (fromWord.equals(toWord)) {
ans.add(new ArrayList<>(path));
path.remove(path.size() - 1);
return;
}
Set<String> neighbors = neighborMap.get(fromWord);
if (neighbors != null) {
for (String neighbor : neighbors) {
if (distMap.get(neighbor) == distMap.get(fromWord) + 1) {
dfs(neighbor, toWord, distMap, neighborMap, ans, path);
}
}
}
path.remove(path.size() - 1);
}

2017.10 电面 x4

  • pinterest页面例子,每个图片都等宽但不等高,给一堆pin的id、高度,求放置pin的策略,让每个col的pin都比较好看,即按顺序拼pin,每次选最短的column append到底部。例如输入pins = [{'id':1, 'height': 100}, {'id':2, 'height':300}, {'id':3, 'height':150}.....],以及col = 3。返回List of List[[1,.....],[2,...],[3,...]],每一个List表示该column存放的图片的id。
  • 如何保证每次都取最短的col去append?联想到了PriorityQueue,可以logK插入,每次都直接取到最短的进行append。时间复杂度O(NlogK),其中K是col数量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static List<List<Integer>> getColumnPinIds(Map<Integer, Integer> heightMap, int colTotal) {
int[] colHeights = new int[colTotal];
PriorityQueue<Integer> pq = new PriorityQueue<>((i, j) -> colHeights[i] != colHeights[j] ?
colHeights[i] - colHeights[j] : i - j);
for (int i = 0; i < colTotal; i++) {
pq.offer(i);
}
List<List<Integer>> ans = new ArrayList<>();
for (int i = 0; i < colTotal; i++) {
ans.add(new ArrayList<>());
}
for (int pinId : heightMap.keySet()) {
int colIndex = pq.poll();
colHeights[colIndex] += heightMap.get(pinId);
ans.get(colIndex).add(pinId);
pq.offer(colIndex);
}
return ans;
}
  • 如果不让用heap?那就直接O(NK),每次都遍历k个col看看谁最小即可。
  • follow-up: resize一下窗口,如何快速rearrange这些pin?

2017.10 电面

  • behavior tree. 给一对用户的action如
    1
    2
    3
    4
    5
    6
    7
    8
    user_id, timestamp, action
    100, 1000, A
    200, 1003, A
    300, 1009, B
    100, 1026, B
    100, 1030, C
    200, 1109, B
    200, 1503, A
    如何从上面的log file构造成一个system log的graph如
    1
    2
    3
    4
    5
    |---A (2)
    | |---B (2)
    | | |---C (1)
    | | |---A (1)
    |---B (1)
  • 这个和Trie很类似。可以考虑对于序列中每一个动作,维护一个TrieNode,其中包含action, count, 和后续操作节点nexts.
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
55
56
57
58
59
60
61
62
63
64
class LogParser {
static class TrieNode {
String action;
int count;
Map<String, TrieNode> nexts;
public TrieNode() {
count = 0;
nexts = new HashMap<>();
}
}

public static void insertActions(TrieNode root, List<String> actions) {
TrieNode curr = root;
for (String action : actions) {
curr.nexts.putIfAbsent(action, new TrieNode());
curr.nexts.get(action).count++;
curr = curr.nexts.get(action);
}
}
static class Record {
int userId;
int timestamp;
String action;
public Record(int userId, int timestamp, String action) {
this.userId = userId;
this.timestamp = timestamp;
this.action = action;
}
}
public static TrieNode getGraph(String[] logs) {
Record[] records = new Record[logs.length];
for (int i = 0; i < logs.length; i++) {
String[] splitted = logs[i].split(",");
int userId = Integer.parseInt(splitted[0].trim());
int timestamp = Integer.parseInt(splitted[1].trim());
String action = splitted[2].trim();
records[i] = new Record(userId, timestamp, action);
}
Arrays.sort(records, (a, b) -> a.timestamp - b.timestamp);
Map<Integer, List<String>> userActionMap = new HashMap<>();
for (Record record : records) {
userActionMap.putIfAbsent(record.userId, new ArrayList<>());
userActionMap.get(record.userId).add(record.action);
}
TrieNode root = new TrieNode();
for (int userId : userActionMap.keySet()) {
insertActions(root, userActionMap.get(userId));
}
return root;
}
public final static String PADDING_STRING = "| ";
public final static String LEADING_STRING = "|---";
public static void printGraph(TrieNode node, StringBuilder sb) {
for (String nextAction : node.nexts.keySet()) {
System.out.print(sb);
System.out.print(LEADING_STRING);
System.out.print(nextAction);
System.out.println("(" + node.nexts.get(nextAction).count + ")");
sb.append(PADDING_STRING);
printGraph(node.nexts.get(nextAction), sb);
sb.delete(sb.length() - PADDING_STRING.length(), sb.length());
}
}
}

2018.3 Onsite

LC 146 LRU
  • 说是设计Gmail界面,说是类似实现LRU。实现Cache就是用Map,但是least recent特性就需要额外的数据结构来提升效率:双向链表。Map中存放key - Node映射,所有的cache都放在一个双向链表上,每次get的时候就将get到的node挪到第一位。put时若capacity超了就将链表最后一个节点删除即可。
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class LRUCache {
class Node {
int key;
int val;
Node prev;
Node next;
public Node(int key, int val) {
this.key = key;
this.val = val;
prev = null;
next = null;
}
}
Map<Integer, Node> map;
Node fakeHead, fakeTail;
int capacity;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>();
fakeHead = new Node(-1, -1);
fakeTail = new Node(-1, -1);
fakeHead.next = fakeTail;
fakeTail.prev = fakeHead;
}

public int get(int key) {
if (map.containsKey(key)) {
Node ret = map.get(key);
moveToFirst(ret);
return ret.val;
} else {
return -1;
}
}

public void put(int key, int value) {
if (capacity == 0) {
return;
}
if (map.containsKey(key)) {
Node oldNode = map.get(key);
oldNode.val = value;
moveToFirst(oldNode);
return;
}
if (map.size() == capacity) {
map.remove(fakeTail.prev.key);
remove(fakeTail.prev);
}
Node newNode = new Node(key, value);
insertToFirst(newNode);
map.put(key, newNode);
}

private void moveToFirst(Node node) {
remove(node);
insertToFirst(node);
}
private void insertToFirst(Node node) {
node.next = fakeHead.next;
fakeHead.next = node;
node.prev = fakeHead;
node.next.prev = node;
}
private void remove(Node node) {
if (node.prev == null) {
return;
}
node.prev.next = node.next;
node.next.prev = node.prev;
}
}

2018.1 Onsite

437. path-sum-iii
  • 给一个二叉树,给一个目标值sum,求有几条从上往下累加的路径之和等于sum。
  • 除了O(N^2)的笨方法,考虑使用cache保存已有结果,这里用到的是prefixSum的思路。在深入下层节点的时候直接累计currSum,同时在cache中找是否存在若干段path是currSum - target,这样就可以把这些prefix从path中给去掉就可以得到target了。在深入更下层节点之前,需要更新currSum的计数,这样在更深层的时候就知道最新的currSum作为prefixSum的计数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public int pathSum(TreeNode root, int sum) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
dfs(root, 0, sum, map);
return count;
}
int count;
private void dfs(TreeNode root, int currSum, int target, Map<Integer, Integer> map) {
if (root == null) {
return;
}
currSum += root.val;
count += map.getOrDefault(currSum - target, 0);
map.put(currSum, map.getOrDefault(currSum, 0) + 1);

dfs(root.left, currSum, target, map);
dfs(root.right, currSum, target, map);

map.put(currSum, map.get(currSum) - 1);
}
362. design-hit-counter
  • 实现一个hitCount类,通过hit(timestamp)表示在什么时候出现了hit(可能同一时刻有多次hit),然后通过getHits得到最近300s内hit了多少次。
  • 利用循环数组记录hits即可,容量可以直接设为300,这样最多就可以同时记录300s中每一秒的hit数量,在getHis的时候直接遍历一边如何保证每个bucket都是valid的count呢,就需要记录每一个bucket对应的hit的时刻是几时,因此需要另一个time数组来记录。
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
class HitCounter {
private int[] hits;
private int[] time;
final private int TIMEWINDOW = 300;
/** Initialize your data structure here. */
public HitCounter() {
hits = new int [TIMEWINDOW];
time = new int [TIMEWINDOW];
}

/** Record a hit.
@param timestamp - The current timestamp (in seconds granularity). */
public void hit(int timestamp) {
int index = timestamp % TIMEWINDOW;
if (time[index] != timestamp) {
time[index] = timestamp;
hits[index] = 1;
} else {
hits[index]++;
}
}

/** Return the number of hits in the past 5 minutes.
@param timestamp - The current timestamp (in seconds granularity). */
public int getHits(int timestamp) {
int count = 0;
for (int i = 0; i < TIMEWINDOW; i++) {
if (timestamp - time[i] < TIMEWINDOW) {
count += hits[i];
}
}
return count;
}
}

2017.12 Onsite

392. is-subsequence
  • 给两个字符串,判断其中一个字符串s是否是另一个字符串t的子序列串,这个字串不要求字符连续出现,只要求出现顺序一致且全部出现即可。
  • trivail的方法是直接两个指针扫,只有当s的字符和t匹配了才挪动s的指针。
  • 如果t很大,而s是很多个小字符串需要连续调用这个函数呢?这就可以考虑使用记录索引+二分查找了。首先对t中每个字符记录出现的索引并存入List,这个是不会变的。然后对于输入s,每个字符都对该字符的List用二分查找找索引的「第一次出现/插入位置」,找到后就作为后一次搜索的索引。如果插入位置是在最后,说明t无法满足s的对应位置;
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
public boolean isSubsequence(String s, String t) {
if (s == null || s.length() == 0) {
return true;
}
List<Integer>[] indexCache = new List [26];
char[] tChar = t.toCharArray();

for (int i = 0; i < tChar.length; i++) {
int index = tChar[i] - 'a';
if (indexCache[index] == null) {
indexCache[index] = new ArrayList<Integer>();
}
indexCache[index].add(i);
}

char[] sChar = s.toCharArray();
int pos = 0; // 在t中出现的最小索引
for (int i = 0; i < sChar.length; i++) {
int index = sChar[i] - 'a';
if (indexCache[index] == null) {
return false;
}

int insertPos = Collections.binarySearch(indexCache[index], pos);
if (insertPos < 0) {
insertPos = -insertPos - 1;
}
if (insertPos == indexCache[index].size()) {
return false;
}
pos = indexCache[index].get(insertPos) + 1; // 下一步所需最小索引更新为已有索引的下一位
}
return true;
}

2018.5 电面

67. add-binary
  • 给两个String表示的二进制数,返回二者相加后的二进制字符串。
  • 模拟加法,可以从两个数的最后一位开始加,最后reverse一下即可,注意最后需要确认首位是否需要加上carry。
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
public String addBinary(String a, String b) {
if (a == null || a.length() == 0) {
return b;
} else {
if (b == null || b.length() == 0) {
return a;
}
}
StringBuilder sb = new StringBuilder();
char[] aChar = a.toCharArray(), bChar = b.toCharArray();
int i = aChar.length - 1, j = bChar.length - 1, carry = 0;
while (i >= 0 || j >= 0) {
int sum = carry; // from prev result
if (i >= 0) {
sum += aChar[i--] - '0';
}
if (j >= 0) {
sum += bChar[j--] - '0';
}
sb.append(sum % 2); // what's left after taking out carry
carry = sum / 2; // 3,2 -> 1; 1,0 -> 0
}
if (carry != 0) {
sb.append(carry); // warning! the most significant position
}
return sb.reverse().toString();
}

2017.11 Onsite

14. longest-common-prefix
  • 给一串字符串数组,求这些字符串开头的公共部分。
  • 直接按照字典序排序,找首位和末尾的字符串比较看看有多少公共部分即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
Arrays.sort(strs);
String s1 = strs[0];
String s2 = strs[strs.length - 1];
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s1.length(); i++) {
if (s1.charAt(i) != s2.charAt(i)) {
break;
}
sb.append(s1.charAt(i));
}
return sb.toString();
}

2018.8 Onsite

560. subarray-sum-equals-k
  • 给一个int数组和一个k,问有多少连续的subarray之和等于k。这些int都在[-1000, 1000],数组长度最多20000。
  • 一开始想用双指针,但有正有负,更新条件不好搞。正解是使用Map,在计算sum的时候顺便看看之前是否出现过sum - k。这其实和path sum III 很像,都是利用prefix sum.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public int subarraySum(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return 0;
}
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
int count = 0, sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
if (map.containsKey(sum - k)) { // 看之前是否已经出现了这一段prefix
count += map.get(sum - k); // 这样sum - prefix就等于k了
}
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
return count;
}

2018.5 Onsite

698. partition-to-k-equal-sum-subsets
  • 给一个只含有(0, 10000)的int数组和一个k,判断是否可以将该数组划分为k个相等sum的partition。
  • 似乎是个NP-hard的问题。只能用暴力办法,DFS+标记数组,每次累加过后进入下一层看看是否达到了targetSum,达到了就清空继续往后找新的一组subset.最后如果只剩下一组了,直接返回true,因为此时其他k - 1个组都已经达到targetSum了,当前的sum = k * targetSum - (k - 1) * targetSum = targetSum.
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
class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
if (nums == null || nums.length == 0 || k > nums.length || k < 1) {
return false;
}
if (k == 1) {
return true;
}
int sum = IntStream.of(nums).sum(); // Java8的stream!
if (sum % k != 0) {
return false;
}
int targetSum = sum / k;
boolean[] visited = new boolean[nums.length];
return checkPartition(nums, visited, 0, targetSum, 0, k);
}
public boolean checkPartition(int[] nums, boolean[] visited, int startIndex, int targetSum, int currSum, int k) {
if (k == 1) { // 提前break
return true;
}
if (currSum == targetSum) {
return checkPartition(nums, visited, 0, targetSum, 0, k - 1);
}
for (int i = startIndex; i < nums.length; i++) {
if (!visited[i]) {
visited[i] = true;
if (checkPartition(nums, visited, i + 1, targetSum, currSum + nums[i], k)) {
return true;
}
visited[i] = false;
}
}
return false;
}
}
  • 方法二:更快的做法是先对数组排序,然后存k个bucket,每个bucket从后往前取元素不断累加.
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
class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
if (nums == null || nums.length == 0 || k > nums.length || k < 1) {
return false;
}
if (k == 1) {
return true;
}
int sum = IntStream.of(nums).sum(); // Java8的stream!
if (sum % k != 0) {
return false;
}
int targetSum = sum / k;
Arrays.sort(nums);
return checkPartition(nums, targetSum, new int[k], nums.length - 1);
}
public boolean checkPartition(int[] nums, int targetSum, int[] buckets, int numsIndex) {
if (numsIndex < 0) {
for (int bucket : buckets) {
if (bucket != targetSum) {
return false;
}
}
return true;
}
for (int i = 0; i < buckets.length; i++) {
if (buckets[i] + nums[numsIndex] <= targetSum) {
buckets[i] += nums[numsIndex];
if (checkPartition(nums, targetSum, buckets, numsIndex - 1)) {
return true;
}
buckets[i] -= nums[numsIndex];
}
}
return false;
}
}
  • follow-up: 如果去掉正数的限制,允许出现负数和0?
  • 上面的方法只考虑了直接累积叠加,无法解决负数问题。因此需要引入一个elementCount来统计当前这一波存入了多少element,当达到targetSum的时候需要判断当前这一波是否真的存入了元素。
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
class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
if (nums == null || nums.length == 0 || k > nums.length || k < 1) {
return false;
}
if (k == 1) {
return true;
}
int sum = IntStream.of(nums).sum();
if (sum % k != 0) {
return false;
}
int targetSum = sum / k;
boolean[] visited = new boolean[nums.length];
return checkPartition(nums, visited, 0, targetSum, 0, k, 0);
}
public boolean checkPartition(int[] nums, boolean[] visited, int startIndex, int targetSum, int currSum, int k, int elementCount) {
if (k == 0) { // 必须算完才行
return true;
}
if (currSum == targetSum && elementCount > 0) {
return checkPartition(nums, visited, 0, targetSum, 0, k - 1, 0);
}
for (int i = startIndex; i < nums.length; i++) {
if (!visited[i]) {
visited[i] = true;
if (checkPartition(nums, visited, i + 1, targetSum, currSum + nums[i], k, elementCount + 1)) {
return true;
}
visited[i] = false;
}
}
return false;
}
}
  • follow-up: 如何记录路径?也就是在dfs的时候多用一个List记录存入的数字即可。
数组与query x2
  • 给一个长度为N的数组,其中每一个数字都是8bit的正整数(0~255)。给一个二维数组M,类似于[[1,100],[5,1000],etc],求所给range的mean。
  • naive方法是对于每一个query,都求一个sum / size,复杂度O(M*N)。可以用lc 303 rangeSum的思路,先求一波和,然后求sum的时候直接用两个位置的相减即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public int[] getMeans(int[] nums, int[][] queries) {
if (nums == null || nums.length == 0
|| queries == null || queries.length == 0) {
return null;
}
int N = nums.length, M = queries.length;
int[] sums = new int[N];
sums[0] = nums[0];
for (int i = 1; i < N; i++) {
sums[i] = sums[i - 1] + nums[i];
}

double[] means = new double[M];
for (int i = 0; i < M; i++) {
int start = quries[i][0];
int end = quries[i][1];
means[i] = (sum[end] - (start > 0 ? sums[start - 1] : 0))
/ (double)(end - start + 1);
}
}
  • follow-up: 还是这些input,但query内容变为「求每个range的median」。median自然想到排序,而这个0~255的限制范围就容易想到木桶排序,然后根据个数就可以找到median了。在预处理的时候直接给每一个元素维护一个长度为256的bucket,
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
static public double[] getMedians(int[] nums, int[][] queries) {
if (nums == null || nums.length == 0
|| queries == null || queries.length == 0) {
return null;
}
int N = nums.length, M = queries.length;
int[][] buckets = new int[N][256];
buckets[0][nums[0]]++;
for (int i = 1; i < N; i++) {
buckets[i] = Arrays.copyOf(buckets[i - 1], 256);
buckets[i][nums[i]]++;
}

double[] medians = new double[M];
for (int i = 0; i < M; i++) {
int start = queries[i][0];
int end = queries[i][1];
int[] tempBucket = Arrays.copyOf(buckets[end], 256);
if (start > 0) {
for (int j = 0; j < 256; j++) {
tempBucket[j] -= buckets[start - 1][j];
}
}
medians[i] = getMedian(tempBucket, (end - start) / 2, ((end - start + 1) & 1) == 0);
}
return medians;
}
static private double getMedian(int[] bucket, int pos, boolean isEven) {
int i = 0, index = -1;
while (i < 256) {
index += bucket[i++];
if (index >= pos) {
break;
}
}
if (!isEven || index > pos) {
return i - 1;
}
int first = i - 1;
while (i < 256) {
index += bucket[i++];
if (index > pos) {
return (first + i - 1) / 2.0;
}
}
return first;
}
  • 再follow-up: 当N特别大的时候,无法全部放入内存,如何在预处理的时候进行压缩。(给定一个压缩指标K, K<<N, 问怎么在预处理的时候进行压缩,同时效率还是O(M+N). 答案是每次隔K个进行存储,这样在查找的时候每次需要K次,但是K是常数,所以效率还是O(M+N).????)

2017.11 Onsite

139. word-break
  • 给一个List表示词典,给一个String,求能否把该String按照某种方式以空格分割,使得每个单词都来自于字典。
  • 利用DP状态转换,若当前子字符串在dict中且子字符串之前的部分也符合要求,则当前这一段都true。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public boolean wordBreak(String s, List<String> wordDict) {
if (s == null || s.length() == 0 || wordDict == null || wordDict.size() == 0) {
return false;
}
Set<String> set = new HashSet<>();
int maxLen = 0;
for (String word : wordDict) {
maxLen = Math.max(word.length(), maxLen);
set.add(word);
}
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int end = 1; end <= s.length(); end++) {
for (int start = end - 1; end - start <= maxLen && start >= 0; start--) {
if (set.contains(s.substring(start, end)) && dp[start]) {
dp[end] = true;
break;
}
}
}
return dp[s.length()];
}
  • follow-up: 如果wordDict很大怎么存?用Trie。用Trie也存不下呢???
  • theyearofdragon可以分成the year of dragon或者they ear of dragon,显然第一种分法好些,问怎么去判断分成不同语句哪种最好。???
column放pin
  • Pinterest主页上有N个column,给一个set of pins,pins有score和length,每次把score最高的pin贴到最短的column上,return List<List<Pin>>表示每个column里的pins。
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
static class Pin implements Comparable<Pin> {
double score;
double height;
public Pin(double score, double height) {
this.score = score;
this.height = height;
}

@Override
public int compareTo(Pin that) {
return this.score == that.score ?
(int)(this.height - that.height)
: (int)(that.score - this.score);
}
}
public static List<List<Pin>> getPinPos(List<Pin> pinList, int col) {
Collections.sort(pinList);
List<List<Pin>> ret = new ArrayList<>();
double[] heights = new double[col];
PriorityQueue<Integer> pq = new PriorityQueue<>((i, j) -> heights[i] != heights[j] ? (int)(heights[i] - heights[j]) : i - j);

for (int i = 0; i < col; i++) {
ret.add(new ArrayList<>());
pq.offer(i);
}
for (Pin pin : pinList) {
int index = pq.poll();
heights[index] += pin.height;
ret.get(index).add(pin);
pq.offer(index);
}
return ret;
}
  • follow up: 用户的手机屏幕只有M长,如果屏幕的顶点距离主页顶点距离为K(???),求出能显示出的pins。
Merge interval
  • 给List of Interval,含有start, end, weight属性。如果两个interval overlap了,overlap部分的weight相加变成新的interval,返回merge之后的interval list。但是如果有多个overlap怎么办?比如[0,1], [0,4], [0,5], [3,6]这种,merge多次吗?

2018.7 Onsite

相似Set
  • 输入一个data stream,比如:a -> [b]; b -> [a, d, f]; c -> [b, j]; ...,表示这些元素是相似的,且相似具有传递性,最后输出所有相似的set。
  • 可以考虑用并查集,每一个起始点作为root,其余点就是它的children。
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
// TODO
public static void printGroup(Map<String, List<String>> map) {
if (map == null || map.size() == 0) {
return;
}
Map<String, String> parentMap = new HashMap<>();
Set<String> nodeSet = new HashSet<>();
for (String key : map.keySet()) {
map.putIfAbsent(key, key);
nodeSet.add(key);
String parent = findParent(key);
List<String> equivalents = map.get(key);
for (String equivalent : equivalent) {
nodeSet.add(equivalent);
String currParent = findParent(equivalent);
if (!currParent.equals(parent)) {
map.put(currParent, parent);
}
}
}
// ....
}
public static String findParent(String str, Map<String, String> parentMap) {
while (!parentMap.getOrDefault(str, str).equals(str)) {
String parent = parentMap.get(str);
parentMap.put(str, parentMap.getOrDefault(parent, parent));
str = parent;
}
return str;
}
  • 或者直接建graph之后,找connected component来搞,似乎实现起来更不容易出错?

[2016.5 Onsite]

124. binary-tree-maximum-path-sum
  • 给一个二叉树,求其中的任意一条路径使得经过所有节点的值最大,不一定要经过root。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
private int max; // store the value of taking current root as swag point "^"
public int maxPathSum(TreeNode root) {
max = Integer.MIN_VALUE;
return Math.max(dfs(root), max);
}
private int dfs(TreeNode root) {
if (root == null) {
return 0;
}
int left = Math.max(0, dfs(root.left));
int right = Math.max(0, dfs(root.right));
max = Math.max(max, left + right + root.val); // take both of the branches
return root.val + Math.max(left, right); // means only take one of the branches
}
}
meeting-rooms-ii变种
  • 给一堆task的开始和结束时间和每个task所需的cpu数量,求完成所有task的最小cpu数。
  • 先根据开始时间排序,然后往PriorityQueue中逐个加入task,若当前开始时间比队首的结束时间大,说明可以复用前面的cpu了,每次注意要更新cpu数量保证是最大值即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public int minCPU(Task[] tasks) {
if (tasks == null || tasks.length == 0) {
return 0;
}
Arrays.sort(tasks, (a, b) -> a.start - b.start);
PriorityQueue<Task> pq = new PriorityQueue<>(); // 同时执行的task
int max = 0, curr = 0;
for (int i = 0; i < tasks.length; i++) {
// 若当前task比最先结束的task晚开始,就可以用之前释放的cpu了
while (!pq.isEmpty() && tasks[i].start >= pq.peek().end) {
curr -= pq.poll().cpu; // 更新当前pq中所有任务所需cpu
}
curr += tasks[i].cpu;
max = Math.max(curr, max);
pq.offer(tasks[i]);
}
return max;
}
判断相同文件
  • 给定两个函数,get_files(dir), get_dir(dir) 返回所有内容相同的file. 问怎么定义内容相同,用文件名还是binary, 小哥说要内容相同,那我就用recursion遍历所有file, 然后用hashing把文件的内容hash成key放在dictionary,最后返回这个dictionary。
  • follow up说, 如果hashing非常expensive怎么办? hashing前先check file size,如果文件大小一样,再用hashing判断是不是一样。
  • 2nd follow up说get_files, get_dir如果expensive怎么办,答曰用parrallel programming (gpu or multi core) 可以优化。

2018.5 Onsite

read-n-characters-given-read4变种
  • 给一堆有序号的无限block,给你一个读给定index的单个block的API,每个block 64MB,然后给你一个开始的byte位置(例如:3MB,就是从index = 0 开始),和要读的byte长度(例如:67MB),返回读的结果。就分情况,慢慢写,先是第一个block(可能,只是读部分),然后中间连续读几个block(计算一下就好),最后一个block可能只读部分。 感觉整体就是玩index。
见上面的word-ladder
36. valid-sudoku
  • 给一个二维char数组,里面是一个未完成的数独9x9棋盘,要求每一行、每一列、每个3x3方块中数字1-9有且仅有出现一次。判断这个棋盘是否符合数独规则,返回布尔值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean isValidSudoku(char[][] board) {
if (board == null || board.length == 0 || board[0].length == 0) {
return false;
}
Set<String> set = new HashSet<>();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (board[i][j] != '.') {
if (!set.add(board[i][j] + " in row " + i)
|| !set.add(board[i][j] + " in col " + j)
|| !set.add(board[i][j] + " in block " + (i / 3) + '-' + (j / 3))) {
return false;
}
}
}
}
return true;
}
类似362. design-hit-counter详情母鸡
  • 定义 eventOccur()numEvent() 方法,有个sliding window size N。然后扩展,减少空间,Map,加一个功能,返回当前时间eventOccur() call次数最多的时间,要求 amortized O(1), 楼主一时想不到,最后只想到类似 LFU 的那种 2 map + DLL,1 个 map key 是 count,DLL 存 Cell(time, count),一个就是整个cell。这个能保证O(logN), 面试官表示满意,方法不错,但是他后来是说,既然之前用了 queue 存所有 time + count,直接maintain 一个 max variable,每次 max variable 超过了 sliding window,则遍历 剩下的queue里的 count 就好,对于无限流,这个就是 amortized O(1), 但是楼主想不通,就举了个,每次 时间下 call evenOccur() 次数是递减的,那么就是每次都是O(n); 面试官表示是的,这个特殊的例子是O(n), generally 还是每 N 次才更新。


以下是卡拉特面经

统计点击率

  • 给一堆网址和点击率,求不同部分域名的点击率。如mobile.sports.yahoo.com就需要统计四个部分。lc 811
  • split之后搞就行了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public List<String> subdomainVisits(String[] cpdomains) {
List<String> ans = new ArrayList<>();
if (cpdomains == null || cpdomains.length == 0) {
return ans;
}
Map<String, Integer> map = new HashMap<>();
for (String cpdomain : cpdomains) {
String[] data = cpdomain.split(" ");
int count = Integer.parseInt(data[0]);
int index = 0;
while (index >= 0) {
String key = data[1].substring(index == 0 ? index : index + 1);
map.put(key, map.getOrDefault(key, 0) + count);
index = data[1].indexOf(".", index + 1);
}
}
for (String key : map.keySet()) {
ans.add(map.get(key) + " " + key);
}
return ans;
}

求最长公共子数组

  • 实现一个函数取两个数组作为input,求这两个数组的最长公共元素。
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
public static String[] longestCommonSubarray(String[] log1, String[] log2) {
if (log1 == null || log2 == null || log1.length == 0 || log2.length == 0) {
return null;
}
int maxLen = 0, end = -1;
int[][] dp = new int[log1.length + 1][log2.length + 1];
for (int i = 0; i <= log1.length; i++) {
for (int j = 0; j <= log2.length; j++) {
if (i == 0 || j == 0) {
dp[i][j] = 0;
} else {
if (log1[i - 1].equals(log2[j - 1])) {
dp[i][j] = 1 + dp[i - 1][j - 1];
if (dp[i][j] > maxLen) {
maxLen = dp[i][j];
end = i;
}
}
}
}
}
if (maxLen == 0) {
return new String[] {"None"};
}
String[] ret = new String[maxLen];
for (int i = 0; i < ret.length; i++) {
ret[i] = log1[end - maxLen + i];
}
return ret;
}

矩阵中最长上升序列

  • 给一个矩阵,求最长上升序列的长度。lc 329.
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
public static int longestIncreasingPath(int[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int rowTotal = matrix.length, colTotal = matrix[0].length, len = 0;
int[][] cache = new int[rowTotal][colTotal];
for (int i = 0; i < rowTotal; i++) {
for (int j = 0; j < colTotal; j++) {
len = Math.max(len, getLen(matrix, cache, i, j));
}
}
return len;
}
private static int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
private static int getLen(int[][] matrix, int[][] cache, int i, int j) {
if (i < 0 || i > matrix.length || j < 0 || j > matrix[i].length) {
return 0;
}
if (cache[i][j] > 0) {
return cache[i][j];
}
int len = 1;
for (int[] dir : dirs) {
int rowNext = i + dir[0];
int colNext = j + dir[1];
if (rowNext < 0 || rowNext >= matrix.length
|| colNext < 0 || colNext >= matrix[rowNext].length
|| matrix[rowNext][colNext] <= matrix[i][j]) {
continue;
}
int nextLen = getLen(matrix, cache, rowNext, colNext);
len = Math.max(nextLen + 1, len);
}
cache[i][j] = len;
return len;
}

2018.9.20 电面

  • 给一个String,只含有数字和加、减。实现eval返回算式的结果。
  • 遇到符号就把前面的数字累加/减即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int calculate(String s) {
if (s == null || s.length() == 0) {
return 0;
}
int val = 0, num = 0, sign = 1;
for (char c : s.toCharArray()) {
if (Character.isDigit(c)) {
num = num * 10 + (int)(c - '0');
} else if (c == '+' || c == '-') {
val = val + sign * num;
num = 0;
sign = c == '+' ? 1 : -1;
}
}
val = val + sign * num;
return val;
}
  • follow-up: 除了上面这些,还含有括号。lc 224。利用Stack,遇到加减还是直接算个num出来,遇到左括号说明前面的计算告一段落,将前面的数字和符号都push进stack,遇到右括号时就说明当前括号以内的结果出来了,就从栈顶取数值和符号,合并出整体num之后再继续往后。
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
public int calculate(String s) {
if (s == null || s.length() == 0) {
return 0;
}
int val = 0, sign = 1;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
if (Character.isDigit(s.charAt(i))) {
int num = s.charAt(i) - '0';
while (i + 1 < s.length() && Character.isDigit(s.charAt(i + 1))) {
num = num * 10 + (int)(s.charAt(++i) - '0');
}
val += sign * num;
} else {
if (s.charAt(i) == '+') {
sign = 1;
} else if (s.charAt(i) == '-') {
sign = -1;
} else if (s.charAt(i) == '(') {
stack.push(val);
stack.push(sign);
val = 0;
sign = 1;
} else if (s.charAt(i) == ')') {
val = stack.pop() * val + stack.pop();
}
}
}
return val;
}
  • follow-up2: 如果再输入一个map包含一些exp: val对,但是算式中并不是所有的变量都能找到对应的val。可以考虑用replaceAll+正则表达式将输入字符串中的部分换成有效数字。然后还是用stack,只不过现在需要一个类来把数字、字符串、符号wrap起来。
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
55
56
57
58
public static String calculate(String s, Map<String, Integer> map) {
if (s == null || s.length() == 0) {
return null;
}
for (String key : map.keySet()) {
String regStr = String.format("(?<!\\w)%s(?!\\w)", key);
s = s.replaceAll(regStr, Integer.toString(map.get(key)));
}
System.out.println(s);
Stack<Part> stack = new Stack<>();
Part curr = new Part(0, null, 1);
for (int i = 0; i < s.length(); i++) {
if (Character.isDigit(s.charAt(i))) {
int num = s.charAt(i) - '0';
while (i + 1 < s.length() && Character.isDigit(s.charAt(i + 1))) {
num = num * 10 + (int)(s.charAt(++i) - '0');
}
curr.num += curr.sign * num;
} else {
if (s.charAt(i) == '+') {
curr.sign = 1;
} else if (s.charAt(i) == '-') {
curr.sign = -1;
} else if (s.charAt(i) == '(') {
stack.push(curr);
curr = new Part(0, null, 1);
} else if (s.charAt(i) == ')') {
Part prev = stack.pop();
curr.num = prev.sign * curr.num + prev.num;
if (curr.var != null && prev.sign < 0) {
curr.var.setCharAt(0, curr.var.charAt(0) == '+' ? '-' : '+');
if (prev.var != null) {
curr.var.insert(0, prev.var);
}
}
} else if (s.charAt(i) == ' ') {
continue;
} else {
StringBuilder sb = new StringBuilder().append(s.charAt(i));
while (i + 1 < s.length() && s.charAt(i + 1) != ' '
&& s.charAt(i + 1) != '+' && s.charAt(i + 1) != '-'
&& s.charAt(i + 1) != '(' && s.charAt(i + 1) != ')') {
sb.append(s.charAt(++i));
}
if (curr.sign > 0) {
sb.insert(0, '+');
} else {
sb.insert(0, '-');
}
if (curr.var != null) {
sb.insert(0, curr.var);
}
curr.var = sb;
}
}
}
return curr.toString();
}

2018.3.14电面

  • 给一个二维String数组,每一行存放每个点赞记录url, user, timestamp。1.求每个网站的最早点赞记录。2.给一个user,求与他点赞过的url重复最多的用户。
  • 第一问略。第二问,将数据存入user -> set(url),和url -> set(user),根据给定user找到他点赞过的url集合,然后对于里面的每个url找点赞过的用户集合,统计每个用户出现的次数即可。注意这个不是最省memory的方法,可以只维护url2User,同时把这个user访问过的url存入Set,这样就可以只遍历这个set了而不用额外维护每个user访问过那些网站。
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
public static String getCommonUser(String[][] logs, String user) {
if (logs == null || logs.length == 0 || user == null) {
return null;
}
Map<String, Set<String>> user2Url = new HashMap<>();
Map<String, Set<String>> url2User = new HashMap<>();
for (String[] log : logs) {
user2Url.putIfAbsent(log[0], new HashSet<String>());
user2Url.get(log[0]).add(log[1]);
url2User.putIfAbsent(log[1], new HashSet<String>());
url2User.get(log[1]).add(log[0]);
}
if (!user2Url.containsKey(user)) {
return null;
}
Set<String> urls = user2Url.get(user);
int max = 0;
String ret = null;
Map<String, Integer> overlapCount = new HashMap<>();
for (String url : urls) {
Set<String> users = url2User.get(url);
for (String curr : users) {
if (curr.equals(user)) {
continue;
}
overlapCount.put(curr, overlapCount.getOrDefault(curr, 0) + 1);
if (overlapCount.get(curr) > max) {
max = overlapCount.get(curr);
ret = curr;
}
}
}
return ret;
}

2018.9.15 电面

  • 给一堆edges。1. 找nodes that have no parents, and nodes that have 1 parent; 2. 找graph中两个node有无common ancestor(直接相连的不算共同祖先);3. 找furthest ancestor from node。
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public static List<Integer> getIndependentNode(int[][] edges) {
if (edges == null) {
return null;
}
Map<Integer, Set<Integer>> parentMap = getParentMap(edges);
List<Integer> ans = new ArrayList<>();
for (int key : parentMap.keySet()) {
if (parentMap.get(key).size() <= 1) {
ans.add(key);
}
}
return ans;
}
public static boolean hasCommonAncestor(int[][] edges, int node1, int node2) {
Map<Integer, Set<Integer>> parentMap = getParentMap(edges);
Set<Integer> ancestorSet = new HashSet<>();
Queue<Integer> q = new LinkedList<>();
q.offer(node1);
while (!q.isEmpty()) {
Set<Integer> parentSet = parentMap.get(q.poll());
for (int parent : parentSet) {
q.offer(parent);
ancestorSet.add(parent);
}
}

q.offer(node2);
while (!q.isEmpty()) {
Set<Integer> parentSet = parentMap.get(q.poll());
for (int parent : parentSet) {
if (ancestorSet.contains(parent)) {
return true;
}
q.offer(parent);
}
}
return false;
}
public static int getFurthestAncestor(int[][] edges, int node) {
int ret = -1;
if (edges == null) {
return ret;
}
Map<Integer, Set<Integer>> parentMap = getParentMap(edges);
if (parentMap.get(node).isEmpty()) {
return ret;
}
Queue<Integer> q = new LinkedList<>();
q.offer(node);
while (!q.isEmpty()) {
ret = q.peek();
int size = q.size();
while (size-- > 0) {
Set<Integer> parentSet = parentMap.get(q.poll());
for (int parent : parentSet) {
q.offer(parent);
}
}
}
return ret;
}
private static Map<Integer, Set<Integer>> getParentMap(int[][] edges) {
if (edges == null) {
return null;
}
Map<Integer, Set<Integer>> parentMap = new HashMap<>();
for (int[] edge : edges) {
parentMap.putIfAbsent(edge[0], new HashSet<>());
parentMap.putIfAbsent(edge[1], new HashSet<>());
parentMap.get(edge[1]).add(edge[0]);
}
return parentMap;
}