易贝
迎难而上,祝我好运。面朝大海,春暖花开。
k way merge
- LC23。给K个排好序的链表头节点数组,将他们merge到一个list中。
- 方法一:利用PriorityQueue存储,先一波流将所有链表头加入pq,然后每次poll掉最小的同时将它后续的节点也放进pq中。pq的offer, poll, remove复杂度都是log(N),因此在这里维护规模为K的就需要log(K),而总共有KN个节点,时间复杂度是O(KNlogK)。
1 | public ListNode mergeKLists(ListNode[] lists) { |
- 方法二:递归+分治法,归结为merge两个List的问题。时间复杂度是O(KNlogK),因为一开始K个数组,需要遍历每个元素合并之后形成K/2个数组,以此类推,每次需要遍历需要O(KN),总共有logK次,因此是O(KNlogK)。
1 | // divide and conquer |
longest common substring
- 给两个String,求最长的连续的共同的子串。例如s1 = 12358和s2 = 1242358,最长的子序列就是2358.
- DP.用二维int数组维护
dp[i][j]
表示s1以i结尾处、s2以j结尾处的substring情况。当s1.charAt(i) == s2.charAt(j)
时,就更新为dp[i - 1][j - 1] + 1
。
1 | public static String maxCommonSubstring(String s1, String s2) { |
reverse word in string
- 给一个字符串和一个分隔符,将每个单词翻转,同时大小写翻转。注意这个Zillow的那个翻转字符串不一样。skip.
decimal to hex
- 将一个十进制数转换成十六进制。
1 | final private char[] map = {'0', '1', '2', '3','4','5','6','7','8','9','a','b','c','d','e','f'}; |
最大正方形
- 类似LC 221,给一个0/1 grid,求其中最大的正方形面积。
- DP。当前位置为1,则需要看看左、左上、上三个方向的最小值+1作为边长,最后返回最大的即可。
1 | public int maximalSquare(char[][] matrix) { |
第N个BLAH数
- 如果一个数字可以被5,7,11整除 那么这个数称之为BLAH数 (5,7,10,11,14,15…). 求第n个BLAH数。
- 用三个变量存下一个5、7、11整除的blah数,从1到n每次取最小的,并更新到下一个。注意出现重复的时候就需要跳到下一个。
移动0到数组末尾
- 给一个数组,将所有的0移动到数组末尾,其余数组需要维持相对次序。
- 先走一波循环讲非0的项往前覆盖,走完之后剩余的数直接覆盖为0即可。
1 | public void moveZeroes(int[] nums) { |
反转链表
- 给一个链表头,反转之。LC 206
- 方法一:Iterative就直接前后两个节点直接把next指向对调一下就好了。
- 方法二:Recursive就先将当前节点后面的节点都reverse一波,当前节点的next原本指的是后续第一个节点、现在就变成了后续最末尾的节点,那么调整一下next指向即可。
1 | public ListNode reverseList(ListNode head) { |
找第一次、最后一次出现位置
- LC 34给一个有序数组,给一个target,找第一个和最后一个出现的位置。复习一波二分查找了。
1 | class Solution { |
二维矩阵二分查找
- LC 240。矩阵中每一行、每一列都是有序的,给一个target,判断是否存在。
- 方法一:仍然是二分查找的思想,取行和列的中间元素,若target小了则需要到左侧细长竖矩形和上方的肥扁矩形查找,否则是到右侧的细长竖矩形和下方的肥扁矩形中查找。
1 | class Solution { |
- 方法二:直接扫,首先从右上角开始,如果小了说明当前这一行都不可能了,如果大了说明当前这列都不可能了,因此每次都会排除掉一整行/一整列,时间复杂度是O(M + N),相当于线性查找。
1 | public boolean searchMatrix(int[][] matrix, int target) { |
实现LRU
- LC 146. 实现Cache就是用Map,但是least recent特性就需要额外的数据结构来提升效率:双向链表。Map中存放key - Node映射,所有的cache都放在一个双向链表上,每次get的时候就将get到的node挪到第一位。put时若capacity超了就将链表最后一个节点删除即可。
1 | class LRUCache { |
2018.3.23 Onsite
15. 3Sum
- 给一个int数组,列出三个数使得它们的和为0.
- 先排序,然后固定当前数,设置target为
0 - curr
,然后用双指针在后方找,当left + right == target
就是一组数了。
1 | class Solution { |
216. combination sum
- 在1~9中选k个数之和等于n,求所有可能的组合。DFS/backtracking搞定。
1 | class Solution { |
535. tinyURL
- 实现长URL和短URL的互相转换。
- 在生成短链接时,先利用随机数生成index逐个字符地拼接成短链接,然后判断是否存在。
1 | public class Codec { |
4. Median of 2 sorted arrays
- 给两个int数组,返回二者合并后的中位数,要求在O(log(m+n))的时间复杂度以内。
- 上来可以先讲讲mergesort的那种线性扫到中位数的O(m + n)做法。二分查找找的是能将两个数组恰好分成两个部分的index,而且确定了第一个数组的index之后,第二个数组的index也就确定了(因为每个部分取多少数是已知的)。
1 | class Solution { |
572. Subtree of Another Tree
- 给两个二叉树,判断其中一棵是否说另一个的子树(存在val、结构完全相同的子树)。
- naive的方法是无脑recursive,但是这样每个节点可能会被重复访问。另一种做法是用preorder将两个树encode成字符串,然后用contains判断是否是substring即可。但注意这个contains本身并不是O(N)的,可以提一句KMP算法。
1 | public class Solution { |
130. surrounded-regions
- 给一个只含有
XXOO
的char二维数组,要求把所有被X
完美包围的O
替换成X
,而绵延到边界的O
就不替换。 - 先将四个边边遍历一波,将所有的O以及连绵的O替换成特殊字符,然后再从头遍历,将特殊字符换回O、将中间的O换成X。
1 | public class Solution { |
2016.10.5 Onsite
698. partition-to-k-equal-sum-subsets
- 给一个数组,判断是否能划分成K个sum相等的子集。
- 累加当前元素后到达下一层,判断是否达到了targetSum并且确实有元素(防止targetSum == 0且有负数的case)的情况下,开始往后遍历下一个group。
1 | class Solution { |
252. meeting-rooms
- 给一个Interval的数组,每个Interval含有meeting的开始和结束时间戳,问是否能参加所有会议。
- 贪心算法,根据start排序,然后取前后两个看有没有overlap。
1 | class Solution { |
2018.5.1 电面
97. interleaving-string
- 给三个字符串s1, s2, s3,判断s3是否由s1和s2拼接而成,这里的拼接可以是交错的,但必须维持字符原本的出现顺序。
- 方法一:DP。
dp[i][j]
表示从s1中取i个字符、从s2中取j个字符能否交错组成s3[i + j - 1]
。判断时若从s1中取i个字符,即s1[i] == s3[i + j - 1]
且dp[i - 1][j]
能交错形成,或者从s2中取j个字符,即s2[j] == s3[i + j - 1]
且dp[i][j - 1]
能交错,则当前这个也可以,设为true即可。
1 | public boolean isInterleave(String s1, String s2, String s3) { |
- 方法二:DFS。也需要利用二维boolean数组标记是否能组成interleaving字符串,若之前已经判断是false了就不用继续DFS了。判断时也是分别尝试从s1中和s2中取字符,然后与s3比较。
1 | public boolean isInterleave(String s1, String s2, String s3) { |
- 方法三:BFS。和DFS类似,将可达的i/j组合存入queue,直到遍历完s3。
1 | public boolean isInterleave(String s1, String s2, String s3) { |
2018.4.6
279. perfect-squares
- 给一个正整数,求至少由多少个平方数相加能得到它。
- DP。dp[i]表示数字i至少需要多少个平方数相加,那么对于每个i,遍历
i - j * j
即可。
1 | public int numSquares(int n) { |
[???]
- 给一个BST,给一个区间
[min, max]
,将在区间之外的节点删除。
1 | public TreeNode deleteOutliers(TreeNode root, int min, int max) { |
2018.4.6 电面
53. maximum-subarray
- 给一个int数组,求最大的subarry sum。
- 方法一:直接O(N)搞定,维护一个当前的sum,要么是前面累加的结果、要么从当前开始加。
1 | class Solution { |
- 方法二:分治法,要么纯左边、要么纯右边、要么从交界处向左右取元素。
1 | class Solution { |
2018.4.4 Onsite
103. binary-tree-zigzag-level-order-traversal
- level层面的遍历二叉树,但是遍历顺序不是固定的从左到右,而是蛇形,这一层从左到右、下一行从右到左。
- 方法一:BFS+Queue,正一下反一下。
- 方法二:DFS时将level数也传入,对于偶数层则append到对应的List中,对于奇数层则insert到首位。
Longest common subsequence(注意和前面的max substring有异曲同工之妙)
- 给两个字符串,求最长的公共子序列的长度。
- DP。用两个指针i和j分别遍历s1和s2,若字符相同则取
dp[i - 1][j - 1] + 1
,否则取max{dp[i - 1][j], dp[i][j - 1]}
.
1 | public int maxCommonSubsequenceLen(String s1, String s2) { |
- Follow-up:如果需要返回任意一个这样的字符串呢?
- 根据DP table一路回溯,若当前两个字符串的所取字符相等则放入结果中。
1 | public static String maxCommonSubsequence(String s1, String s2) { |
2018.1.21 Onsite
348. design-tic-tac-toe
- XXOO游戏,两个玩家,每一行、每一列、两条对角线都是一样的话,对应玩家就赢了。要设计一个class,其中包含move函数,给坐标和玩家编号,在图里放,如果该玩家赢了就返回玩家编号。每行、每列需要统计两个玩家各自的个数,如果都是其中一个玩家都就赢了,两条对角线也是。
- 记录行、列、两条对较线。区分两个玩家的O和X就通过正负一来判断,这样如果有一个玩家赢了就意味着一路相加结果是n或者-n
1 | class TicTacToe { |
2017.9.19 Onsite
297. serialize-and-deserialize-binary-tree
- 实现二叉树的序列化与反序列化。
1 | public class Codec { |
- follow-up:如果是BST呢?那么在序列化的时候可以直接忽略掉null节点,因为在反序列化的时候可以直接通过val来判定root的值情况。
1 | public class Codec { |
24. swap-nodes-in-pairs
- 给一个链表头,要去反转所有相邻的节点,返回反转后的头。不可以修改头的val,这能修改next。
- Iterative:
1 | public ListNode swapPairs(ListNode head) { |
- Follow-up: 用Recursive来做呢?
1 | public ListNode swapPairs(ListNode head) { |
382. linked-list-random-node
- 面经里说的是“给一个stream of int”取随机取数字,要求每个数字被取到的概率相同。和这个LC原理相同,假设stream元素为
[1, 2, 3]
,每次只能读入一个数字。一开始读入1,只能保留它,概率为1;然后读入2,这是1和2之间保留谁的概率就是1/2;然后读入3,此时保留的数字与3只能保留一个,为了保证概率均等,需要在0~2中产生随机数,只有等于2的时候才选择索引为2的元素即3,概率为1/3,而上一步保留的那个数字留下来的概率则是1/2 * 2/3 = 1/3
,概率依然相等。这个算法还可以推广到取k个元素出来的情况,这样内存中始终就只有非常少量(k + 1)的数字需要读入了。
1 | public class Solution { |
2017.10.29 电面
98. validate-binary-search-tree
- 常见的错误是直接greedy,只判断当前节点和孩子的大小。但BST的定义是比「所有的左子树都大、比右都小」。
- 方法一:中序遍历时每次都判断前后两个元素的大小关系。又分为recursive和iterative的。
1 | // recursive: 全局的prev记录当前节点前一个是谁,比较大小 |
- 方法二:分治,为左右子树给定范围(min, max),在递归时更新左子树的上界、更新右子树的下界。
1 | private boolean validateBST3(TreeNode root) { |
2018.1.29 电面 + Onsite
235. lowest-common-ancestor-of-a-binary-search-tree
- 给一个BST和两个其中的节点,求他们的LCA。根据值来划分即可,若p和q的值都在root同侧则递归到那里,一旦发现不同侧就返回当前root。
1 | public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { |
236. lowest-common-ancestor-of-a-binary-tree
- 给一个任意的二叉树和两个其中的节点,求他们的LCA。无法用值来比较,只能直接查找节点了。分别往左边和右边找,若root为其中一个节点则返回。这样在上层拿到左边和右边的搜索结果后就可以判断出现在哪一侧,若两边都找到了则说明当前节点就是LCA.
1 | public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { |