拼扯惹斯特
迎难而上,祝我好运。面朝大海,春暖花开。
2018.8.9
269. alien-dictionary x3
- 给一个按照某种外星人字典序排好序的String数组,只包含小写字母,求这些出现过的字母的顺序。若有多种可能(平级)则任意顺序均可。
- topo排序问题,前后两个字符串逐个字符找到第一对不同的字符,确定先后顺序(前->后)构成一条边,两两字符串全部遍历完之后就形成了一个graph。维护一个inDegree,每次BFS时从入度为0的节点出发,将它可达的所有邻接点的入度都减1(删掉这些边),最后如果全部字符都遍历到了即可。如果出现了环,则一定会又入度不为0的点残留。
1 | class Solution { |
271. encode-and-decode-strings
- 实现encode和decode函数,将字符串List和单一条字符串互相转换。
- 字符含有哪些?(所有ASCII字符都可能出现)
- 由于每个符号都有可能出现,所以不能想着用某个特殊符号来拼接和拆分。因此需要额外的信息进行标记,这里用的是在
/
之前加上所跟字符串的长度的方式,decode时每次从特定位置开始找/
,然后取出它前面的长度信息直接截取后面的子字符串。
1 | public class Codec { |
2018.8.4 电面
- 给一个n,返回所有可能的factor组合。lc 254 x2
1 | public List<List<Integer>> getFactors(int n) { |
2018.9.14 Onsite
- 给一个连接关系{A - > B; B - > A,C; C - > B; D - > E; E -> D},group这些node并返回这些group:[(A,B,C), (D,E)].
1 | public static List<List<Node>> getGroups(Map<Node, List<Node>> graph) { |
425. word-squares x2
- 给一个wordList,将List中的String放入matrix中使得行、列的单词都来自于这个List。
- Trie + DFS,对于每个Trie节点,除了正常的nexts数组、isWord布尔值,额外维护一个List
保存以「到达当前TrieNode路径」为prefix的所有word。固定一个word之后,下一个词的prefix可以通过纵向append得到,具体规律见这个discussion.
1 | class Solution { |
2018.4 Onsite
index sum
- 一个array自己跟自己的element可以组成一个pair,这个pair有个sum,求the kth sum分别对应的index。应该类似于lc 215 k-th element吧,只不过需要多存一个index作为返回。
- 方法一:PriorityQueue维护top K元素即可。
1 | public int findKthLargest(int[] nums, int k) { |
- 方法二:quickSelect,用快排的partition来找到元素。
1 | public int findKthLargest(int[] nums, int k) { |
463. island-perimeter x2
- 给一个0/1二维矩阵,求其中为1的island的周长。
- 方法一:BFS,每个1都先算它有四条边,然后根据邻接情况减掉不是边的即可。
1 | class Solution { |
- 方法二:找规律。对于每一个1的cell,看它的下方和右方neighbor是不是1,记录neighbor数,最后周长就是islands * 4 - neighbours * 2。
1 | public int islandPerimeter(int[][] grid) { |
2018.9 Onsite
permutation系列
1 | public List<List<Integer>> permute(int[] nums) { |
1 | public List<List<Integer>> permuteUnique(int[] nums) { |
2016.8 onsite
Letter Combinations of a Phone Number with isWord() API
- 和lc 17 Letter Combinations of a Phone Number类似,但会多给一个isWord()的API,需要判断生成的word是否在dict中。
1 | final char[][] num2Char = new char [][] { |
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 | public boolean hasZero(int[] nums, int start) { |
merge K sorted list
- 可以直接把元素通通存入PriorityQueue,然后从头取元素即可。或者用分治法递归来搞,
1 | public ListNode mergeKLists(ListNode[] lists) { |
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 | /* The knows API is defined in the parent class Relation. |
- 更巧妙的办法,从头开始一个pass,判断如果i认识j则把candidate设为j,如果i不认识j则j不可能是candidate;完了之后再一个pass看这个candidate是不是不认识所有人、且所有人都认识他。
1 | public int findCelebrity(int n) { |
759. employee-free-time
- 给一个List of List,每一个子List存放每个employee的工作时间,求所有employee的共同空闲时间。没有则返回空List。
- 直接将List flatten,然后按照工作开始时间从小到大排序,再往后依次取工作时间,若当前开始时间比之前的结束时间长,说明出现了空闲时间;否则需要更新prev的end,保证prev的结束时间cover到所有结束时间,即取max。
1 | public List<Interval> employeeFreeTime(List<List<Interval>> schedule) { |
求出现次数多于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 | class Trie { |
2018.3 Onsite x2
Longest increasing path in the binary tree
1 | class Solution { // 从上到下的单向路径 |
2018.6 Onsite
- 实现min stack,需要多一个popMin方法. 思路是在push的时候如果min发生了变化就把当前的min push进去,再更新min。这样在pop的时候就可以通过对比min和当前pop的元素知道是否需要更新min,若min已经被pop了,那么前一个再pop出来就是当前的min。
1 | class MinStack { |
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 | public void nextPermutation(int[] nums) { |
2018.7 Onsite
133. clone-graph
- 给一个无向有环全联通图的其中一个节点,要求拷贝一个完整的无向有环图,并返回拷贝而成的对应的该节点。而且两个节点之间可以有多条通路。
- 直接用map + DFS搞,对于每一个节点直接new一个相同label的节点出来,然后遍历所有的邻居递归调用clone即可。
1 | private Map<Integer, UndirectedGraphNode> map = new HashMap<>(); |
20. valid-parentheses
- 给一个字符串,判断其中三种括号
(), [], {}
是否匹配。用stack搞即可。 - follow-up: 求最少删几个字符可以使原字符串合法。例如
(([{))
就是把中间两个删除即可。 - 思路应该类似301. remove-invalid-parentheses,也是一个图论BFS。从当前字符串开始出发,所有可达点就是「删除一个字符串所得的新字符串」,注意需要用Set防止重复。每次对相同level的字符串判断是否valid,一旦出现了valid,则当前level所有的valid的字符串就是「最少删除之后的valid字符串」。
1 | public List<String> removeInvalidParentheses(String s) { |
2018.5 Onsite
29. divide-two-integers
- 给两个数字,不使用乘、除、模运算前者除以后者之商,若结果越界则返回MAX_INT。
- 除了傻傻地一路减法减下去,还可以用位运算提速。提取出正负号之后统一按照正long数来处理。用sum来累计当前已经用了多少除数(dividend),将next初始化为除数,在循环中找到恰好让
sum + next > longDividend
的next
,然后把next往回移一位就是当前这一波累计的除数总和了,加到sum即可。同时用一个count计数记录用了多少个除数,这就是所求的商了,也是累计即可。
1 | public int divide(int dividend, int divisor) { |
2018.5.21 电面
1 | public int[] searchRange(int[] nums, int target) { |
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 | public int minDistance(String word1, String word2) { |
2018.2.13 电面 x3
- 给一堆time range,可能有重合。比如[1,3], [2,4], [5,6]。总共的uniq的时间,即merge之后的总时间长度。类似lc 56,但不需要维护所有的interval了,只需要保存prev,当后续interval没有overlap的时候就累加time range即可。
1 | public int getTimeRange(List<Interval> intervals) { |
- follow-up: 最长的连续range(能合并的合并,最长的range).直接在上面加个取长度即可。
- follow-up2: 求出现次数最多的range。思路也是先排序,利用一个currCount统计当前的time range数,出现了重叠就将prev设置为overlap的部分并累加,当overlap结束就更新maxCount即可。
1 | public static Interval getMaxInterval(List<Interval> intervals) { |
2017.11 电面
- 类似于lc 158,给一个API
read4(char[] buf)
,每次可以从某个file中读4个字符到buf,并返回实际读取的字符数。
1 | public class Solution extends Reader4 { |
2015.10 电面
785. is-graph-bipartite x3
- 给一个数组,每个index对应着该node的所有邻接点。问这个graph能否只用两个颜色给node上色使得相邻两个点的颜色都不一样。
- 方法一:DFS。对于每一个点都一波直接深度搜索上色,用一个数组存储上色状态,0表示未访问过,-1和1分别表示两个颜色,在dfs时对于当前的点若已经访问过就判断是否符合当前给定的颜色,若未访问则直接上色并DFS到它所有邻接点。需要注意可能有若干独立的cluster,因此不能一次DFS搜索就结束了,而是需要一个循环保证对所有未访问过的点都进行一次DFS。
1 | class Solution { |
- 方法二:BFS,还是用queue存放所有邻接点然后逐一上色。
1 | class Solution { |
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 | class Solution { |
2018.3 电面
- 将二叉树转换成双向链表。应该是类似lc 426的。
426. convert-binary-search-tree-to-sorted-doubly-linked-list
- 给一个BST,将它转换成排好序的循环双向链表(头尾相接),返回头部。可以把左右孩子就看作是当前节点的前后链表节点,不需要额外搞ListNode类。
- BST要维持顺序,那就是用中序遍历。利用全局变量prev来记录每一个子树的结尾节点,先build左子树,然后把当前节点拼到prev的后面,再去继续build右子树即可。
1 | private Node prev = null; |
- 方法二:分治法。先把左右子树的循环双向链表build好,再把当前节点塞到中间,同时把新的前后循环连接一下。注意在分别build的时候,需要把root本身设一个自循环,这样就可以重复使用connect方法了。
1 | public Node treeToDoublyList(Node root) { |
2017.12 电面
127. word-ladder
- 给一个List的String,然后一个初始String一个目标String,要求每次变动一个字母且变动后的词语来自该List的情况下,最短需要变几次得到目标String。
- 可以看作BFS问题,每个word就是一个node,neighbor就是换掉一个字母后且在wordList中存在的词。这样BFS最快到达dest的就是最短了。
1 | public int ladderLength(String beginWord, String endWord, List<String> wordList) { |
- follow-up:如何记录最短路径?其实就是126。还是用BFS找最短路径,维护一个distMap记录每个节点到达时所经过的步数,到达终点后再从头开始DFS,每次只去步数增长1的邻居点。
1 | public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) { |
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 | public static List<List<Integer>> getColumnPinIds(Map<Integer, Integer> heightMap, int colTotal) { |
- 如果不让用heap?那就直接O(NK),每次都遍历k个col看看谁最小即可。
- follow-up: resize一下窗口,如何快速rearrange这些pin?
2017.10 电面
- behavior tree. 给一对用户的action如 如何从上面的log file构造成一个system log的graph如
1
2
3
4
5
6
7
8user_id, timestamp, action
100, 1000, A
200, 1003, A
300, 1009, B
100, 1026, B
100, 1030, C
200, 1109, B
200, 1503, A1
2
3
4
5|---A (2)
| |---B (2)
| | |---C (1)
| | |---A (1)
|---B (1) - 这个和Trie很类似。可以考虑对于序列中每一个动作,维护一个TrieNode,其中包含action, count, 和后续操作节点nexts.
1 | class LogParser { |
2018.3 Onsite
LC 146 LRU
- 说是设计Gmail界面,说是类似实现LRU。实现Cache就是用Map,但是least recent特性就需要额外的数据结构来提升效率:双向链表。Map中存放key - Node映射,所有的cache都放在一个双向链表上,每次get的时候就将get到的node挪到第一位。put时若capacity超了就将链表最后一个节点删除即可。
1 | class LRUCache { |
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 | public int pathSum(TreeNode root, int sum) { |
362. design-hit-counter
- 实现一个hitCount类,通过hit(timestamp)表示在什么时候出现了hit(可能同一时刻有多次hit),然后通过getHits得到最近300s内hit了多少次。
- 利用循环数组记录hits即可,容量可以直接设为300,这样最多就可以同时记录300s中每一秒的hit数量,在getHis的时候直接遍历一边如何保证每个bucket都是valid的count呢,就需要记录每一个bucket对应的hit的时刻是几时,因此需要另一个time数组来记录。
1 | class HitCounter { |
2017.12 Onsite
392. is-subsequence
- 给两个字符串,判断其中一个字符串s是否是另一个字符串t的子序列串,这个字串不要求字符连续出现,只要求出现顺序一致且全部出现即可。
- trivail的方法是直接两个指针扫,只有当s的字符和t匹配了才挪动s的指针。
- 如果t很大,而s是很多个小字符串需要连续调用这个函数呢?这就可以考虑使用记录索引+二分查找了。首先对t中每个字符记录出现的索引并存入List,这个是不会变的。然后对于输入s,每个字符都对该字符的List用二分查找找索引的「第一次出现/插入位置」,找到后就作为后一次搜索的索引。如果插入位置是在最后,说明t无法满足s的对应位置;
1 | public boolean isSubsequence(String s, String t) { |
2018.5 电面
67. add-binary
- 给两个String表示的二进制数,返回二者相加后的二进制字符串。
- 模拟加法,可以从两个数的最后一位开始加,最后reverse一下即可,注意最后需要确认首位是否需要加上carry。
1 | public String addBinary(String a, String b) { |
2017.11 Onsite
14. longest-common-prefix
- 给一串字符串数组,求这些字符串开头的公共部分。
- 直接按照字典序排序,找首位和末尾的字符串比较看看有多少公共部分即可。
1 | public String longestCommonPrefix(String[] strs) { |
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 | public int subarraySum(int[] nums, int k) { |
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 | class Solution { |
- 方法二:更快的做法是先对数组排序,然后存k个bucket,每个bucket从后往前取元素不断累加.
1 | class Solution { |
- follow-up: 如果去掉正数的限制,允许出现负数和0?
- 上面的方法只考虑了直接累积叠加,无法解决负数问题。因此需要引入一个elementCount来统计当前这一波存入了多少element,当达到targetSum的时候需要判断当前这一波是否真的存入了元素。
1 | class Solution { |
- 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 | public int[] getMeans(int[] nums, int[][] queries) { |
- follow-up: 还是这些input,但query内容变为「求每个range的median」。median自然想到排序,而这个0~255的限制范围就容易想到木桶排序,然后根据个数就可以找到median了。在预处理的时候直接给每一个元素维护一个长度为256的bucket,
1 | static public double[] getMedians(int[] nums, int[][] queries) { |
- 再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 | public boolean wordBreak(String s, List<String> wordDict) { |
- 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 | static class Pin implements Comparable<Pin> { |
- 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 | // TODO |
- 或者直接建graph之后,找connected component来搞,似乎实现起来更不容易出错?
[2016.5 Onsite]
124. binary-tree-maximum-path-sum
- 给一个二叉树,求其中的任意一条路径使得经过所有节点的值最大,不一定要经过root。
1 | class Solution { |
meeting-rooms-ii变种
- 给一堆task的开始和结束时间和每个task所需的cpu数量,求完成所有task的最小cpu数。
- 先根据开始时间排序,然后往PriorityQueue中逐个加入task,若当前开始时间比队首的结束时间大,说明可以复用前面的cpu了,每次注意要更新cpu数量保证是最大值即可。
1 | public int minCPU(Task[] tasks) { |
判断相同文件
- 给定两个函数,
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 | public boolean isValidSudoku(char[][] board) { |
类似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 | public List<String> subdomainVisits(String[] cpdomains) { |
求最长公共子数组
- 实现一个函数取两个数组作为input,求这两个数组的最长公共元素。
1 | public static String[] longestCommonSubarray(String[] log1, String[] log2) { |
矩阵中最长上升序列
- 给一个矩阵,求最长上升序列的长度。lc 329.
1 | public static int longestIncreasingPath(int[][] matrix) { |
2018.9.20 电面
- 给一个String,只含有数字和加、减。实现eval返回算式的结果。
- 遇到符号就把前面的数字累加/减即可。
1 | public int calculate(String s) { |
- follow-up: 除了上面这些,还含有括号。lc 224。利用Stack,遇到加减还是直接算个num出来,遇到左括号说明前面的计算告一段落,将前面的数字和符号都push进stack,遇到右括号时就说明当前括号以内的结果出来了,就从栈顶取数值和符号,合并出整体num之后再继续往后。
1 | public int calculate(String s) { |
- follow-up2: 如果再输入一个map包含一些
exp: val
对,但是算式中并不是所有的变量都能找到对应的val。可以考虑用replaceAll+正则表达式将输入字符串中的部分换成有效数字。然后还是用stack,只不过现在需要一个类来把数字、字符串、符号wrap起来。
1 | public static String calculate(String s, Map<String, Integer> map) { |
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 | public static String getCommonUser(String[][] logs, String user) { |
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 | public static List<Integer> getIndependentNode(int[][] edges) { |