2021版本
受欢迎的元素
- 给一个数组,求其中受欢迎的元素(出现频数超过一半)。Map统计即可。
- 若数组排好序了,求出现频数超过n/4的。既然排好序就可以考虑直接通过前后比较来计数了,不需要额外空间。
- follow-up: 优化时间复杂度?排好序就应该想下有没有贪心/挪指针/二分查找了。12345678910111213141516171819202122232425262728293031323334353637383940414243444546public int getPopularElement(int[] sorted) {if (sorted == null || sorted.length == 0) {// throw new InputValidationExcpetion();return -1;}int n = sorted.length, threshold = n / 4;if (countElement(sorted, sorted[n / 4]) > threshold) {return sorted[n / 4];} else if (countElement(sorted, sorted[n / 2]) > threshold) {return sorted[n / 2];} else if (countElement(sorted, sorted[(n * 3) / 4]) > threshold) {return sorted[(n * 3) / 4];} else {// throw new NotFoundException();return -1;}}private int countElement(int[] arr, int target) {int firstIndex = binarySearchFirst(arr, target);int lastIndex = binarySearchLast(arr, target);return lastIndex - firstIndex + 1;}private int binarySearchFirst(int[] arr, int target) {int left = 0, right = arr.length;while (left < right) { // [left, right)int mid = left + (right - left) / 2;if (arr[mid] >= target) { // [left, mid)right = mid;} else {left = mid + 1;}}return left;}private int binarySearchLast(int[] arr, int target) {int left = 0, right = arr.length;while (left < right) {int mid = left + (right - left) / 2;if (arr[mid] <= target) { // [mid + 1, right)left = mid + 1;} else {right = mid;}}return left - 1;}
flip一次求最多1的数量
- 给一个硬币面朝上朝下的0/1一维数组,求通过最多一次局部翻转(翻转起止位置自己定)能够得到的最大1的个数。
本质上就是求0比1数量多最多的subarray段,类似用贪心做法求max subarray sum。
12345678910111213141516171819202122232425262728293031public int maxSumAfterFlip(int[] coins) {if (coins == null || coins.length == 0) {return 0;}int currSum = 0, maxSum = 0, ones = 0;int left = 0, right = 0, len = coins.length;while (left < len) {if (coins[left] == 0) {right = left;currSum = 0;while (right < len) {if (coin[right] == 0) {currSum++;} else {ones++;currSum--;}if (currSum <= 0) {break; // 这一段的0和1持平,直接忽略}// 表示这一段0比1多多少,也就是翻转后会多出多少1maxSum = Math.max(currSum, maxSum);left++;right++;}}left++;}return maxSum + ones;}follow-up:如果要记录起止位置?那么就在内层while循环中maxSum需要更新时对start, end赋值即可。
- follow-up:如果将一维数组换成二维matrix,求其中submatrix flip之后最多能得到的1的数量?
2018版本
10. regular-expression-matching
- 输入两个字符串,写个支持
.
和*
的正则表达式判断的method。例如s = "ab"
,p = "a*"
, 为True。 - 目标字符串s和正则字符串p之间相互比较,就需要维护一个boolean的二维数组
dp[i][j]
,表示s[0~i-1]
与p[0~j-1]
是否匹配。1234567891011121314151617181920212223242526272829303132class Solution {public boolean isMatch(String s, String p) {if (s == null || p == null || s.equals(p)) {return true;}char[] sChar = s.toCharArray(), pChar = p.toCharArray();int m = sChar.length, n = pChar.length;boolean[][] dp = new boolean [m + 1][n + 1];// initial the dp statesdp[0][0] = true;for (int j = 2; j <= n; j++) {dp[0][j] = pChar[j - 1] == '*' && dp[0][j - 2];}for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (pChar[j - 1] == '*') {// check if ignoring curr pattern// OR (the char is matched AND ignoring curr pattern)dp[i][j] = (dp[i][j - 2]) // 不取pattern|| ((sChar[i - 1] == pChar[j - 2] || pChar[j - 2] == '.') && dp[i - 1][j]);} else {// check if char is matched for curr patterndp[i][j] = dp[i - 1][j - 1]&& (sChar[i - 1] == pChar[j - 1] || pChar[j - 1] == '.');}}}return dp[m][n];}}
36. valid-sudoku
- 给一个二维char数组,里面是一个未完成的数独9x9棋盘,要求每一行、每一列、每个3x3方块中数字1-9有且仅有出现一次。判断这个棋盘是否符合数独规则,返回布尔值。
方法一:直接双重循环遍历,注意可以利用index的变换同时判断行、列和box的情况
123456789101112131415161718192021222324public boolean isValidSudoku(char[][] board) {if (board == null || board.length == 0 || board[0].length == 0) {return false;}for (int i = 0; i < board.length; i++) {Set<Character> rowSet = new HashSet<>();Set<Character> colSet = new HashSet<>();Set<Character> boxSet = new HashSet<>();int rowIndex = 3 * (i / 3), colIndex = 3 * (i % 3);for (int j = 0; j < board[0].length; j++) {if (board[i][j] != '.' && !rowSet.add(board[i][j])) {return false;}if (board[j][i] != '.' && !colSet.add(board[j][i])) {return false;}if (board[rowIndex + j / 3][colIndex + j % 3] != '.'&& !boxSet.add(board[rowIndex + j / 3][colIndex + j % 3])) {return false;}}}return true;}方法二:利用String encoding,直接将每个数字出现的行、列、box情况encode成String存入set。
123456789101112131415161718public 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;}
37. sudoku-solver
- 给一个二位char数组,里面是一个未完成的数独9x9棋盘,要求每一行、每一列、每个3x3方块中数字1-9有且仅有出现一次。解出数独,解有且仅有一种,直接在原二维数组中将
.
改为数字的char。 - 暴力dfs,确定一个位置后就进入下一层DFS去判断。时间复杂度为O(9^m),m为可填的空位数。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950final String inRow = " in row ";final String inCol = " in col ";final String inBlk = " in blk ";public void solveSudoku(char[][] board) {if (board == null || board.length != 9 || board[0].length != 9) {return;}Set<String> set = new HashSet<>();for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {if (board[i][j] != '.') {set.add(board[i][j] + inRow + i);set.add(board[i][j] + inCol + j);set.add(board[i][j] + inBlk + i/3 + "-" + j/3);}}}dfs(board, set);}private boolean dfs(char[][] board, Set<String> set) {for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {if (board[i][j] == '.') {for (int c = 0; c < 9; c++) {char temp = (char)(c + '1');String row = temp + inRow + i;String col = temp + inCol + j;String blk = temp + inBlk + i/3 + "-" + j/3;if (!set.contains(row) && !set.contains(col) && !set.contains(blk)) {set.add(row);set.add(col);set.add(blk);board[i][j] = temp;if (dfs(board, set)) {return true;} else {set.remove(row);set.remove(col);set.remove(blk);board[i][j] = '.';}}}return false;}}}return true;}
41. first-missing-positive
- 给一个乱序整数数组,要求返回所缺正整数中的最小值。要求constant space,O(n).例如
[3,4,-1,1]
,返回2. - 尝试将在合理范围内的正数[1, nums.length]放入对应的位置,即与它的值对应的索引进行swap,注意执行swap之前需要判断该对应处的元素是否已经处在正确的位置,已经是正确位置就不能swap。最后从头遍历一波,第一个放错位置的即为所求。123456789101112131415161718192021222324252627public int firstMissingPositive(int[] nums) {if (nums == null || nums.length == 0) {return 1;}int i = 0;while (i < nums.length) {if (nums[i] == i + 1 || nums[i] <= 0 || nums[i] > nums.length) {i++;} else if (nums[nums[i] - 1] != nums[i]) { // warning: not nums[i] != i + 1// critical: avoid re-overwriting that correct spotswap(nums, i, nums[i] - 1); // i is curr index, nums[i] - 1 is where it should be} else {i++;}}i = 0;while (i < nums.length && nums[i] == i + 1) {i++;}return i + 1;}private void swap(int[] nums, int i, int j) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}
42. trapping-rain-water
- 给一个int数组,其中包含每个索引处墙的高度。求装满水时的横截面的积水的面积。
- 利用双指针,left向后更新maxLeft,right往前更新maxRight。每次在left处和right处的高度中取较小值与maxLeft和maxRight进行比较,如果是凹的就直接累加高度差即可。12345678910111213141516171819202122232425public int trap(int[] height) {if (height == null || height.length == 0) {return 0;}int area = 0, maxLeft = 0, maxRight = 0;int left = 0, right = height.length - 1;while (left <= right) {if (height[left] <= height[right]) { // curr value less than some right valueif (maxLeft <= height[left]) {maxLeft = height[left];} else { // curr value is also less than left valuearea += maxLeft - height[left]; // means it is concave}left++;} else {if (maxRight <= height[right]) {maxRight = height[right];} else {area += maxRight - height[right];}right--;}}return area;}
43. multiply-strings
- 给两个字符串形式的int,模拟乘法求他们的积,返回字符串。
- 观察index的关系,num1的第i个数字乘以num2的第j个数字得到的两位数将出现在结果数的第
i + j
和第i + j + 1
位。因此就从最低位开始,双重循环相乘,不断更新结果数组即可。注意数组中保存的是一位数,有进位需要存到前一个位置。12345678910111213141516171819202122232425262728293031public String multiply(String num1, String num2) {if (num1 == null || num2 == null|| num1.length() == 0 || num2.length() == 0) {return "";}int m = num1.length(), n = num2.length();int[] result = new int [m + n];char[] cnum1 = num1.toCharArray(), cnum2 = num2.toCharArray();// from least significant bitfor (int i = m - 1; i >= 0; i--) {for (int j = n - 1; j >= 0; j--) {int mult = (cnum1[i] - '0') * (cnum2[j] - '0');int first = i + j, second = i + j + 1;int sum = mult + result[second]; // add carry from prev stepsresult[first] += sum / 10; // accumulateresult[second] = sum % 10; // overwrite}}StringBuilder sb = new StringBuilder();int i = 0;// find the first non-zero itemwhile (i < result.length && sb.length() == 0 && result[i] == 0) {i++;}while (i < result.length) {sb.append(result[i++]);}return sb.length() == 0? "0": sb.toString();}
44. wildcard-matching
- 和前面的regular-expression-matching很像,但这里用
?
代表任意一个字符、用*
代表任意长度的任意字符而不依赖它前面的字符(而且不只能匹配单一个字符,直接匹配任意长度的任意字符组合)。总的来说比上一题简单,要讨论的情况少了。 - 维护一个二维boolean数组,
dp[i][j]
表示s[0~i-1]
和p[0~j-1]
是否匹配。初始化方面,对于空的p,dp[i][0]
仍是除dp[0][0]
外全部false,不可能用空的p去匹配非空的s;对于空的s,dp[0][j]
就要看当前是否是'*'
且考虑dp[0][j-1]
。接着双重循环更新dp
-> 若当前字符p[j-1]是'*'
,则考虑取p的*
但不匹配s当前字符时,s[0~i-1]和p[0~j-2]
的匹配情况,即dp[i][j-1]
;或将'*'
假设为s[i-1]
那个字符,看看s[0~i-2]
与p[0~i-1]
的匹配情况,即dp[i-1][j]
;或者不取*
也不匹配s当前字符,s[0~i-2]和p[0~j-2]
的匹配情况,即dp[i-1][j-1]
.
-> 若当前字符p[j-1]不是'*'
,就直接看s[i-1]和p[j-1]的匹配情况再结合dp[i-1][j-1]
了。
除了DP,这个题目似乎还可以用贪心给两个字符串分别用一个指针一直向后挪。1234567891011121314151617181920212223242526272829303132public boolean isMatch(String s, String p) {if (s == null || p == null || s.equals(p)) {return true;}int m = s.length(), n = p.length();char[] sChar = s.toCharArray(), pChar = p.toCharArray();boolean[][] dp = new boolean [m + 1][n + 1];dp[0][0] = true; // "" fits ""for (int j = 1; j <= n; j++) { // only "x*" can fit ""if (pChar[j - 1] == '*') {dp[0][j] = dp[0][j - 1];} else {dp[0][j] = false;}}for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (pChar[j - 1] == '*') { // don't take * or view * as one char or just// get rid of the char in sChardp[i][j] = dp[i][j - 1] || dp[i - 1][j] || dp[i - 1][j - 1];} else {if (pChar[j - 1] == sChar[i - 1] || pChar[j - 1] == '?') {dp[i][j] = dp[i - 1][j - 1];} else {dp[i][j] = false;}}}}return dp[m][n];}
56. merge-intervals
- 给一个Interval类的list,将重叠部分进行合并,返回合并之后的list。
方法一:使用自定义排序,按照start再按照end排序,然后入栈,每次与栈顶比较。
123456789101112131415161718192021222324public List<Interval> merge(List<Interval> intervals) {if (intervals == null) {return new ArrayList<Interval>();}Collections.sort(intervals,(a, b) -> a.start != b.start ?a.start - b.start : a.end - b.end;);Stack<Interval> stack = new Stack<>();for (Interval itv: intervals) {if (stack.isEmpty()) {stack.push(itv);} else {Interval curr = stack.peek();if (itv.start > curr.end) {stack.push(itv);} else {stack.peek().end = Math.max(itv.end, curr.end);}}}// directly get List from stack with constructorreturn new ArrayList<Interval>(stack);}方法二:将左、右boundary分别排序,然后快慢指针遍历。当
left[fast + 1] > right[fast]
,说明slow~fast可以组成一个独立的interval,存入list后将慢指针放到fast + 1即可。1234567891011121314151617181920212223242526public List<Interval> merge(List<Interval> intervals) {List<Interval> ans = new ArrayList<Interval>();if (intervals == null) {return ans;}int len = intervals.size();int[] left = new int [len]; // all left boundaryint[] right = new int [len]; // all right boundaryfor (int i = 0; i < len; i++) {left[i] = intervals.get(i).start;right[i] = intervals.get(i).end;}Arrays.sort(left);Arrays.sort(right);// fast will select right bound and fast+1 is the candidate for next left bound// when candidate is valid, store curr interval (slow, fast) and move slow to candidate// for next left boundfor (int fast = 0, slow = 0; fast < len; fast++) {if (fast == len - 1 || left[fast + 1] > right[fast]) {ans.add(new Interval(left[slow], right[fast]));slow = fast + 1;}}return ans;}
59. spiral-matrix-ii
- 给一个整数n,生成一个n*n的二维数组方阵,使得螺旋式遍历的结果为1,2,3,…,n^2。
- 直接用循环搞。123456789101112131415161718192021222324252627282930313233343536373839404142public int[][] generateMatrix(int n) {// Declarationint[][] matrix = new int[n][n];// Edge Caseif (n == 0) {return matrix;}// Normal Caseint rowStart = 0;int rowEnd = n - 1;int colStart = 0;int colEnd = n - 1;int num = 1; //changewhile (rowStart <= rowEnd && colStart <= colEnd) {for (int i = colStart; i <= colEnd; i++) {matrix[rowStart][i] = num++; //change}rowStart++;for (int i = rowStart; i <= rowEnd; i++) {matrix[i][colEnd] = num++; //change}colEnd--;for (int i = colEnd; i >= colStart; i--) {if (rowStart <= rowEnd)matrix[rowEnd][i] = num++; //change}rowEnd--;for (int i = rowEnd; i >= rowStart; i--) {if (colStart <= colEnd)matrix[i][colStart] = num++; //change}colStart++;}return matrix;}
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就是当前的最小操作数了。12345678910111213141516171819202122232425262728public 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 + s1];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];}
91. decode-ways
- 给一个纯数字的字符串,看有多少中方式解析成对应的字母A-Z。如
12
可解析为AB
或L
两种,返回2。 - 联想到“跳楼梯的方式”那题,每次可以选择1位数或者2位数,dp[i]表示取i个字符总共有多少decode的方式,那么当前的方式数就取决于前一位或者前两位的方式数。注意如果取两位digit,需要判断是否在10~26范围之内。1234567891011121314151617181920public int numDecodings(String s) {if (s == null || s.length() == 0) {return 0;}int n = s.length();int[] dp = new int[ n+ 1];dp[0] = 1;dp[1] = s.charAt(0) != '0' ? 1 : 0;for (int i = 2; i <= n; i++) {int first = Integer.valueOf(s.substring(i - 1, i));int second = Integer.valueOf(s.substring(i - 2, i));if (first >= 1 && first <= 9) {dp[i] += dp[i - 1];}if (second >= 10 && second <= 26) {dp[i] += dp[i - 2];}}return dp[n];}
95. unique-binary-search-trees-ii
- 只给一个整数n表示二分查找树的节点数,返回所有结构不同的树。而上一问(96)则只是输出不同树的个数,只是个小DP。
方法一:DP。dp[i]表示节点数为i的时候后所有可能的unique BST根节点。
123456789101112131415161718192021222324252627282930313233343536public List<TreeNode> generateTrees(int n) {List<TreeNode>[] dp = new List[n + 1];dp[0] = new ArrayList<TreeNode>();if (n < 1) {return dp[0];}dp[0].add(null); // 空BST// BST: left children all smaller, right all larger.// each time fix the root, take prev as left subtree, and prev with offset as right subtree.// 固定跟节点,左子树节点数从0到nfor (int nodeNum = 1; nodeNum <= n; nodeNum++) {dp[nodeNum] = new ArrayList<TreeNode>();for (int leftNum = 0; leftNum < nodeNum; leftNum++) {for (TreeNode leftNode: dp[leftNum]) {for (TreeNode rightNode: dp[nodeNum - leftNum - 1]) {TreeNode curr = new TreeNode(leftNum + 1);curr.left = leftNode;curr.right = addOffset(rightNode, leftNum + 1);dp[nodeNum].add(curr);}}}}return dp[n];}// its kinda tricky to come up with this offsetprivate TreeNode addOffset(TreeNode node, int offset) {if (node == null) {return null;}TreeNode newNode = new TreeNode(node.val + offset);newNode.left = addOffset(node.left, offset);newNode.right = addOffset(node.right, offset);return newNode;}方法二:分治法,分别build左右两边的子树。
123456789101112131415161718192021222324252627public List<TreeNode> generateTrees(int n) {if (n < 1) {return new ArrayList<TreeNode>();}return generate(1, n);}private List<TreeNode> generate(int start, int end) {List<TreeNode> result = new ArrayList<>();if (start > end) { // recursion end conditionresult.add(null);return result;}// take i as root valfor (int i = start; i <= end; i++) {List<TreeNode> left = generate(start, i - 1); // generate all possible left subtreeList<TreeNode> right = generate(i + 1, end); // gennerate all possible right subtreefor (TreeNode l: left) {for (TreeNode r: right) {TreeNode node = new TreeNode(i); // set root valnode.left = l;node.right = r;result.add(node);}}}return result;}
OA: Email地址处理
- 给String数组包含email地址,如name@domain,其中name需要将(1)dots(‘.’) between some characters去除 (2)如果有’+’,’+’和后面的全去除。将处理完的email地址归类,统计每个email出现个数,返回里面>1个email兄弟的个数。12345678910111213141516171819202122232425public int getEmailCount(String[] emails) {if (emails == null || emails.length == 0) {return 0;}Map<String, Set<String>> map = new HashMap<>();int count = 0;for (String email : emails) {int atIndex = email.indexOf("@");if (atIndex == -1) {continue;}String simplifiedEmail = simplify(email.substring(0, atIndex)) + email.substring(atIndex);map.putIfAbsent(simplifiedEmail, new HashSet<String>());map.get(simplifiedEmail).add(email);}for (Map.Entry<String, Set<String>> e : map.entrySet()) {if (e.getValue().size() > 1) {count++;}}return count;}private String simplify(String local) {return local.replaceAll("(?<=[a-zA-Z]+)\\.+(?=[a-zA-Z]+)|(\\+.*)", "");}
OA: 两个篮子取水果
- lc 159变种。只有两个篮子可以放水果,每个篮子只能放一样水果,但可以放无数个相同的水果。从任意一个位置开始往右取,求最多取多少水果。
- 滑动窗口。
OA: 上一个最接近时间
- 类似于lc 681. 给一个时间string如
hh:mm
,求前一个最近的时间,可以重复使用已有的数字。 - 将所有出现过的数字存入TreeSet,从最低位开始尝试将当前数字替换成比他小的最大的数,若存在这样的数就是previou closest time了,若没有这样的数则需要替换成最大值,注意需要validate。1234567891011121314151617181920212223242526272829303132333435363738public static String prevClosestTime(String time) {if (time == null || time.length() != 5) {return null;}TreeSet<Integer> digitSet = new TreeSet<>();char maxChar = '0';for (char c : time.toCharArray()) {if (c != ':') {digitSet.add(c - '0');maxChar = (char) Math.max(maxChar, c);}}char[] ans = time.toCharArray();for (int i = 4; i >= 0; i--) {if (i != 2) {int currDigit = ans[i] - '0';Integer temp = digitSet.lower(currDigit);if (temp != null) {ans[i] = (char) (temp + '0');return String.valueOf(ans);} else {updateDigitToMax(ans, i, maxChar, digitSet);}}}return String.valueOf(ans);}private static void updateDigitToMax(char[] ans, int i, char maxChar, TreeSet<Integer> digitSet) {if (i == 3 && maxChar > '5') {ans[i] = (char) (digitSet.floor(5) + '0');} else if (i == 1 && ans[i - 1] == '2' && maxChar > '3') {ans[i] = (char) (digitSet.floor(3) + '0');} else if (i == 0 && maxChar > '2') {ans[i] = (char) (digitSet.floor(2) + '0');} else {ans[i] = maxChar;}}
OA: 开花时间
- 给一个数组,index表示开花时间,element表示开花的位置,第i天arr[i]位置的花会开,为了维持M个group,问最晚的天数k。
- 考虑逆向处理,这样就可以保证先找到是最晚天数。在最后一天所有的花都开了,使用TreeSet存放没有开的花的index,往前找最大的天数、往后找最小的天数,若前后都间隔了>= k,说明前后都满足连续>= k朵花开放的条件,这样clusterCount++。但是若当前没有开导致前后的小于k,就需要减少clusterCount。当clusterCount == m时就是最晚的、有m个至少k朵花开放的天数。1234567891011121314151617181920212223242526272829public static int getDay(int[] flowers, int k, int m) {if (flowers == null || flowers.length == 0) {return 0;}TreeSet<Integer> posSet = new TreeSet<>();int count = 1;for (int day = flowers.length - 1; day >= 0; day--) {Integer prevPos = posSet.lower(flowers[day]);if (prevPos == null) {prevPos = 0;}Integer nextPos = posSet.higher(flowers[day]);if (nextPos == null) {nextPos = flowers.length + 1;}int prevBloomLen = flowers[day] - prevPos - 1;int nextBloomLen = nextPos - flowers[day] - 1;if (prevBloomLen >= k && nextBloomLen >= k) {count++;} else if (prevBloomLen < k && nextBloomLen < k) {count--;}if (count >= m) {return day;}posSet.add(flowers[day]);}return -1;}
以下是实习面经
求有序数组的平方数
- 给一个有序数组,求其中各项平方后的结果并排好序。
- 方法一:双指针,一个从0开始往后,一个从最后一个元素往前,比较大小然后从后往前地存入结果数组。
O(N)
。 - 方法二:从前往后,找到第一个非负数开始,往前后两个方向merge,搜索部分复杂度
O(N)
,merge部分复杂度O(N)
。 方法三:既然是有序,那么查找的时候就用二分查找,找『0插入的地方』,然后往两边merge。搜索部分复杂度
O(logN)
,merge部分复杂度O(N)
。再来复习一遍二分查找找第一个出现/插入的位置:12345678910111213private int binarySearch(int[] nums, int target) {int start = -1, end = nums.length;while (end - start > 1) {int mid = start + (end - start) / 2;// invariant relation: nums[start] < target <= nums[end]if (nums[mid] >= target) {end = mid;} else {start = mid;}}return end;}follow-up:这次是求
x ^ 2 + a * x
而不单单是平方了。- 思路也是类似,找到函数的最小值,往两边merge,这里对称轴出现在
-a/2
,所以找到这个值之后往两边merge即可。
String字符交换
- 给两个字符,问能否交换str1的两个字符得到str2.
- 需要考虑两个字符串本身是否相等?长度小于2?
方法一:从前往后遍历两个数组,把不同的位置拼到List里,然后判断不同的是否恰好两位且可以互换。
1234567891011121314151617181920private boolean convert(String s1, String s2) {if (s1.length() != s2.length()) {return false;}List<Integer> arr = new ArrayList<>();char[] sChar1 = s1.toCharArray();char[] sChar2 = s2.toCharArray();for (int i = 0; i < sChar1.length; i++) {if (sChar1[i] != sChar2[i]) {arr.add(i);}}if (arr.size() != 2) {return false;}if (sChar1[arr.get(0)] == sChar2[arr.get(1)] && sChar1[arr.get(1)] == sChar2[arr.get(0)]) {return true;}return false;}方法二:优化空间,不用额外的List,而是直接用一个变量记录第一个不同的索引,之后再出现不同直接判断就可以了。
123456789101112131415161718192021222324private boolean convert(String s1, String s2) {if (s1.length() != s2.length()) {return false;}char[] sChar1 = s1.toCharArray();char[] sChar2 = s2.toCharArray();int index = -1, count = 0;for (int i = 0; i < sChar1.length; i++) {if (sChar1[i] != sChar2[i]) {count++;if (count > 2) {return false;}if (index == -1) {index = i;} else {if (sChar1[index] != sChar2[i] || sChar2[index] != sChar1[i]) {return false;}}}}return true;}
同义词 734 737
- 给一堆同义词[(“restaurant”, “cafe”), (“ratings”, “reviews”), …],再给一些queries[(“restaurant ratings”, “cafe reviews”), …],要求返回每个query里的对应词是否都是synonym。
如果不需要考虑传递性,就直接把字符作为key、对应的所有同义词的set作为value存入map,正反都放一次,比如
map.get("restaurant").add("cafe")), map.get("restaurant").add("cafe")
,这样在query的时候就可以直接调用了。123456789101112131415161718192021222324252627282930// 维护一个总的map,每个单词作为key,对等的单词塞入它维护的Set中// 有对称性所以需要正反都加public boolean areSentencesSimilar(String[] words1, String[] words2, String[][] pairs) {if (words1 == null || words2 == null || pairs == null|| words1.length != words2.length) {return false;}Map<String, Set<String>> map = new HashMap<>();for (int i = 0; i < pairs.length; i++) {if (!map.containsKey(pairs[i][0])) {map.put(pairs[i][0], new HashSet<>());}if (!map.containsKey(pairs[i][1])) {map.put(pairs[i][1], new HashSet<>());}map.get(pairs[i][0]).add(pairs[i][1]); // 构建a->bmap.get(pairs[i][1]).add(pairs[i][0]); // 构建b->a}for (int i = 0; i < words1.length; i++) {if (words1[i].equals(words2[i])) {continue;}if (map.get(words1[i]) == null || !map.get(words1[i]).contains(words2[i])) {return false;}}return true;}需要考虑是否可以传递,比如
a=b, b=c -> a=c?
,可能就需要有个并查集找共同老大。或者直接通过DFS遍历所有可达点。12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849// 和前一个版本的区别是这个可以无限传递a=b=c=d...// 还是map维护每个单词等价的单词,但匹配不上的话还得看它的set里所有单词对应的单词是否能匹配到public boolean areSentencesSimilarTwo(String[] words1, String[] words2, String[][] pairs) {if (words1 == null || words2 == null || pairs == null|| words1.length != words2.length) {return false;}// 表示每个单词直接相连的同义词Map<String, Set<String>> map = new HashMap<>();for (int i = 0; i < pairs.length; i++) {if (!map.containsKey(pairs[i][0])) {map.put(pairs[i][0], new HashSet<>());}if (!map.containsKey(pairs[i][1])) {map.put(pairs[i][1], new HashSet<>());}map.get(pairs[i][0]).add(pairs[i][1]); // 构建a->bmap.get(pairs[i][1]).add(pairs[i][0]); // 构建b->a}for (int i = 0; i < words1.length; i++) {if (words1[i].equals(words2[i])) {continue;}if (!map.containsKey(words1[i])) {return false;}if (!dfs(words1[i], words2[i], map, new HashSet<String>())) {return false;}}return true;}private boolean dfs(String start, String end, Map<String, Set<String>> map, Set<String> visited) {if (map.get(start).contains(end)) { // 终止条件:start连接着endreturn true;}visited.add(start);Set<String> neighbors = map.get(start);if (neighbors == null) {return false;}for (String neighbor : neighbors) {if (!visited.contains(neighbor) && dfs(neighbor, end, map, visited)) {return true;}}return false;}或者通过并查集,初始化时每个单词都是自己的root;然后根据同义词关系将前者的老大设为后者。判断句子是否同义时就找两个单词的老大是否相等即可
123456789101112131415161718192021222324252627282930313233343536373839// 并查集。初始化时每个单词都是自己的root;然后根据同义词关系将前者的老大设为后者。// 判断句子是否同义时就找两个单词的老大是否相等即可public boolean areSentencesSimilarTwo(String[] words1, String[] words2, String[][] pairs) {if (words1.length != words2.length) {return false;}Map<String,String> map = new HashMap<>();for (String[] pair : pairs) { // 开始时每个老大都是自己map.put(pair[0], pair[0]);map.put(pair[1], pair[1]);}for (String[] pair : pairs) {String par1 = findParent(pair[0], map);String par2 = findParent(pair[1], map);if (!par1.equals(par2)) {map.put(par1, par2); // par1的老大设为par2}}for (int i = 0; i < words1.length; i++) {if (words1[i].equals(words2[i])) {continue;}if (!map.containsKey(words1[i]) || !map.containsKey(words2[i])) {return false;}String par1 = findParent(words1[i], map);String par2 = findParent(words2[i], map);if (!par1.equals(par2)) {return false;}}return true;}public String findParent(String str, Map<String,String> map){while (!str.equals(map.get(str))) { // 追溯str的老大str = map.get(str);}return str;}
flip game 293 294
- 给一个只含有
+
和-
的字符串,两个人每次选择一个++
flip成--
。 - 第一问:求下一步所有可能的情况。直接找连续出现的
++
然后取前面部分的substring拼上--
再拼上后续部分即可。 - 第二问:问先开始的玩家能否保证赢(操作后再也没有
++
让对方没法再flip)。暴力的方法是从头开始循环找到每个startsWith("++")
,然后替换成--
再递归判断对方能否稳赢。一旦对方没法稳赢,就直接return了。但是复杂度略高。可以用Set或者Map缓存中间结果,避免重复递归。1234567891011121314151617181920212223242526class Solution {// O(n!!) 复杂度。递归找,找到++的时候就替换成--然后递归判断对方能否保证赢,不能就直接返回true了。public boolean canWin(String s) {if (s == null || s.length() < 2) {return false;}Map<String, Boolean> map = new HashMap<>(); // 存放已经访问过的的Stringreturn canWin(s, map);}private boolean canWin(String s, Map<String, Boolean> map) {if (map.containsKey(s)) {return map.get(s);}for (int i = 0; i < s.length(); i++) {if (s.startsWith("++", i)) {String t = s.substring(0, i) + "--" + s.substring(i + 2);if (!canWin(t, map)) { // 轮到对方操作,判断map.put(s, true);return true;}}}map.put(s, false); // 缓存起来return false;}}
首个prefix不匹配字符串
- 给出一个按字典序排好序的字符串数组dictionary,找出其中第一个不是以指定字符串prefix作为前缀的字符串。如
[aa, aaa, ax, b, c], aa -> ax
- 看到“排好序”,“找”,就想到二分查找了。12345678910111213141516class Google {// O(logN)二分查找第一个不符合要求的XDpublic String firstUnmatch(String[] words, String prefix) {int lo = 0, hi = words.length - 1;while (lo < hi) {int mid = lo + (hi - lo) / 2;if (words[mid].startsWith(prefix)) {lo = mid + 1;} else {hi = mid;}}return lo == words.length || words[lo].startsWith(prefix)?"none": words[lo];}}
flatten iterator 251
- 给一个二维List,要求模拟一维iterator,用hasNext和next从头到尾遍历。
方法一: 第一版的两个iterator遍历的方法,其实不必要存下整个vec2d。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657public class Vector2D implements Iterator<Integer> {private List<List<Integer>> vec2d;private Iterator<Integer> itCol = null;private Iterator<List<Integer>> itRow = null;public Vector2D(List<List<Integer>> vec2d) {this.vec2d = vec2d;if (vec2d == null || vec2d.size() == 0) {itRow = null;} else {itRow = vec2d.iterator();itCol = itRow.next().iterator();}}public Integer next() {if (!itCol.hasNext()) {return null;}Integer ret = itCol.next();return ret;}public boolean hasNext() {if (itCol == null) {return false;}if (itCol.hasNext()) {return true;}while (itCol != null && !itCol.hasNext()) {if (itRow != null) {if (!itRow.hasNext()) {return false;} else {while (itRow.hasNext()) {List<Integer> newRow = itRow.next();itCol = newRow.iterator();if (itCol.hasNext()) {return true;}}}} else {return false;}}return false;}}/*** Your Vector2D object will be instantiated and called as such:* Vector2D i = new Vector2D(vec2d);* while (i.hasNext()) v[f()] = i.next();*/经过简洁:省略了很多条件语句。
1234567891011121314151617181920212223242526public class Vector2D implements Iterator<Integer> {// 只用到两个iterator,一个存第几行,一个存当前行的第几列Iterator<List<Integer>> itRow;Iterator<Integer> itCol;public Vector2D(List<List<Integer>> vec2d) {itRow = vec2d.iterator();}public Integer next() {if (itCol.hasNext()) {return itCol.next();} else {return null;}}public boolean hasNext() {// 当itCol为空或当前行没有next 并且还有后续行的时候while ((itCol == null || !itCol.hasNext()) && itRow.hasNext()) {itCol = itRow.next().iterator(); // 持续挪}return itCol != null && itCol.hasNext(); // 空或新行行首}}
判断二叉树是否对称 101
recursive:对称就是在递归判断左子树和右子树是否对称
123456789101112131415161718192021222324252627282930/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val = x; }* }*/class Solution {public boolean isSymmetric(TreeNode root) {if (root == null) {return true;}return helper(root.left, root.right);}private boolean helper(TreeNode left, TreeNode right) {if (left == null && right == null) {return true;}if (left == null || right == null) {return false;}if (left.val == right.val) { // 首先保证节点值相等// 然后判断左-左和右-右 以及 左-右和右-左return helper(left.left, right.right) && helper(left.right, right.left);}return false;}}iterative:用Stack吞吐,左先入栈再到右,弹出判断左右是否相等。然后先入左-左再入右-右,然后左-右和右-左(保证对称)
123456789101112131415161718192021222324252627282930class Solution {public boolean isSymmetric(TreeNode root) {if (root == null) {return true;}Stack<TreeNode> stack = new Stack<>();stack.push(root.left);stack.push(root.right);while (!stack.isEmpty()) {TreeNode left = stack.pop(); // take two nodes from stack topif (stack.isEmpty()) {return false; // should always have 2 at}TreeNode right = stack.pop();if (left == null && right == null) {continue;}if (left == null || right == null|| left.val != right.val) { // ensure equalreturn false;}stack.push(left.left); // push 2 symmetric positions into stackstack.push(right.right);stack.push(left.right);stack.push(right.left);}return true;}}follow-up:判断多叉树是否对称。直觉就是把left、right换成一个
List<TreeNode>
,判断对称的时候一个从前往后取子节点、另一个从后往前取子节点。
outbound spiral
- 給一個String 用outbound spiral方式輸出,例如
abcd -> cdba
,abcde -> cdbae
。(其实没太看懂这题啥意思。。。) - 双指针,从中间点开始往两边取。123456789class Google {public spiralString(String s) {StringBuilder sb = new StringBuilder();char[] sChar = s.toCharArray();int right = sChar.length / 2, left = right - 1;return sb.toString();}}
Longest substirng with k distinct characters 340
- 给一个字符串和k,求最长子字符串的长度使得其中只含有k个不同的字符。
- substring问题->双指针。O(N)。123456789101112131415161718192021222324252627282930313233class Google {// 双指针,right向前直到多过允许的k个,然后挪left直到恢复到k个public int longestSubstringK(String s, int k) {if (s == null || s.length() == 0) {return 0;}Map<Character, Integer> map = new HashMap<>();int maxLen = 0, currCharNum = 0, left = 0;char[] sChar = s.toCharArray();for (int right = 0; right < sChar.length; right++) {if (!map.containsKey(sChar[right])) {currCharNum++;map.put(sChar[right], 1);} else {map.put(sChar[right], map.get(sChar[right]) + 1);}if (currCharNum > k) {while (currCharNum > k && left < sChar.length) {Integer temp = map.get(sChar[left]);if (temp == 1) {map.remove(sChar[left]);currCharNum--;} else {map.put(sChar[left], temp - 1);}left++;}}maxLen = Math.max(maxLen, right - left + 1);}return maxLen;}}
正则 10
- 输入两个字符串,写个支持
.
和*
的正则表达式判断的method。 - 目标字符串s和正则字符串p之间相互比较,就需要维护一个boolean的二维数组dp[i][j],表示
s[0~i-1]
与p[0~j-1]
是否匹配。1234567891011121314151617181920212223242526272829303132class Solution {public boolean isMatch(String s, String p) {if (s == null || p == null || s.equals(p)) {return true;}char[] sChar = s.toCharArray(), pChar = p.toCharArray();int m = sChar.length, n = pChar.length;boolean[][] dp = new boolean [m + 1][n + 1];// initial the dp statesdp[0][0] = true;for (int j = 2; j <= n; j++) {dp[0][j] = pChar[j - 1] == '*' && dp[0][j - 2];}for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (pChar[j - 1] == '*') {// check if ignoring curr pattern// OR (the char is matched AND ignoring curr pattern)dp[i][j] = (dp[i][j - 2])|| ((sChar[i - 1] == pChar[j - 2] || pChar[j - 2] == '.') && dp[i - 1][j]);} else {// check if char is matched for curr patterndp[i][j] = dp[i - 1][j - 1]&& (sChar[i - 1] == pChar[j - 1] || pChar[j - 1] == '.');}}}return dp[m][n];}}
X删除字眼
- 给一个字符串如
aabbaac
,给一个forbidden字眼a
,返回删除后的字符串bbc
,若不存在就返回本身。 - O(N)直接用StringBuilder拼接。12345678910111213class Google {// 直接拼接public void deleteChar(String s, char c) {StringBuilder sb = new StringBuilder();char[] sChar = s.toCharArray();for (char temp: sChar) {if (temp != c) {sb.append(temp);}}return sb.toString();}}
字母矩阵移动组成单词
- 给一个列数生成由大写字母组成的矩阵,再给一个单词,从左上角开始挪动,求挪动的路径(用&连接).
- 求路径,用BFS可以保证最短。在point中加入一个path段记录到达该处的路径,一旦找到目标字符就直接把path拼进去。再以该处为起点继续找后续字符(需要清空path)。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374class Google {class Point {int row, col;String path;public Point(int row, int col, String path) {this.row = row;this.col = col;this.path = path;}}final private int CHAR_NUM = 26;public String moveWord(int width, String target) {if (target == null || target.length() == 0) {return new ArrayList<String>();}char[][] table = buildTable(width);int rows = table.length, cols = table[0].length;char[] targetChar = target.toCharArray();Point curr = new Point(0, 0);StringBuilder sb = new StringBuilder();boolean first = true;for (int i = 0; i < targetChar.length; i++) {curr = bfs(table, curr, targetChar[i], new boolean[][] visited);if (!first) {sb.append(" & ");}sb.append(curr.path);curr.path = ""; // 重置当前点为起点first = false;}return sb.toString();}private Point bfs(char[][] table, Point p, char c, boolean[][] visited, String path, StringBuilder sb) {if (visited[p.row][p.col]) {return null;}Queue<Point> q = new LinkedList<>();q.add(p);while (!q.isEmpty()) {Point curr = q.poll();if (table[curr.row][curr.col] == c) { // 找到了return curr;}visited[curr.row][curr.col] = true; // 标记当前坐标点已访问if (curr.col < table[0].length - 1 && !visited[curr.row][curr.col + 1]) {q.add(new Point(curr.row, curr.col + 1, curr.path + "R"));}if (curr.row < table.length - 1 && !visited[curr.row + 1][curr.col]) {q.add(new Point(curr.row + 1, curr.col, curr.path + "D"));}if (curr.col > 0 && !visited[curr.row][curr.col - 1]) {q.add(new Point(curr.row, curr.col - 1, curr.path + "L"));}if (curr.row > 0 && !visited[curr.row - 1][curr.col]) {q.add(new Point(curr.row - 1, curr.col, curr.path + "U"));}}return null;}private char[][] buildTable(int width) {int height = CHAR_NUM / width;if (CHAR_NUM % width > 0) {height++;}char[][] table = new char[height][width];char c = 'A';for (int i = 0; i < height; i++) {for (int j = 0; j < width && c < 'Z'; j++) {table[i][j] = c++;}}return table;}}
43. multiply-strings
解析在此
格子中最长连续 562
- 给一个只有0和1的int二维数组,求横或竖或两个对角方向上最长连续出现1的个数。
方法一:O(N^2)从每个点出发往四个方向分别遍历,求最长长度。
123456789101112131415161718192021222324252627282930313233343536373839class Solution {// O(N^3):矩阵遍历每个点是N^2,对每个点在扫四个方向是4Npublic int longestLine(int[][] M) {if (M == null || M.length == 0 || M[0].length == 0) {return 0;}int longest = 0;for (int i = 0; i < M.length; i++) {for (int j = 0; j < M[0].length; j++) {if (M[i][j] == 1) {longest = Math.max(longest, getLongest(M, i, j));}}}return longest;}// 右、下、右下、左下final private int[][] directions = new int[][] {{0, 1}, {1, 0}, {1, 1}, {1, -1}};private int getLongest(int[][] M, int row, int col) {int maxLen = 1;for (int[] direction: directions) {int len = 1;int newRow = row + direction[0];int newCol = col + direction[1];// 持续在一个方向上继续走while (isValidPosition(M, newRow, newCol) && M[newRow][newCol] == 1) {len++;newRow += direction[0];newCol += direction[1];}maxLen = Math.max(maxLen, len);}return maxLen;}private boolean isValidPosition(int[][] M, int row, int col) {return row >= 0 && col >= 0 &&row < M.length && col < M[0].length;}}方法二:类似于DP,记录下四个方向各自的最大长度。参考这个被lz
和N皇后问题很像。1234567891011121314151617181920212223242526272829303132333435class Solution {// O(N^2)时间,O(M+N)空间public int longestLine(int[][] M) {// validationif (M == null || M.length == 0 || M[0].length == 0) {return 0;}int rows = M.length, cols = M[0].length;int longest = 0;int[] bucketCol = new int [cols];int[] bucketDiag1 = new int [rows + cols];int[] bucketDiag2 = new int [rows + cols];for (int i = 0; i < rows; i++) {int row = 0; // 新行初始化为0for (int j = 0; j < cols; j++) {if (M[i][j] == 1) { // 当前为1,对应更新bucketrow++;bucketCol[j]++;bucketDiag1[j + i]++;bucketDiag2[j - i + M.length]++;longest = Math.max(longest, row);longest = Math.max(longest, bucketCol[j]);longest = Math.max(longest, bucketDiag1[j + i]);longest = Math.max(longest, bucketDiag2[j - i + M.length]);} else {row = 0;bucketCol[j] = 0;bucketDiag1[j + i] = 0;bucketDiag2[j - i + M.length] = 0;}}}return longest;}}
给出一系列字母求相除结果 399
- 给一系列字符串的倍数关系,给出后续query的结果。
- 图论。可以抽象成一个双向图,每个字符串都作为节点,然后权重就是两个节点的商。我的做法分三步:遍历一遍形成str到index的映射,然后建立邻接矩阵并把权重填充进去(正反都放,不存在就是0),最后就query的时候就用DFS,遍历当前节点所有能走到的节点看看能否到达终点。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172class Solution {public double[] calcEquation(String[][] equations, double[] values, String[][] queries) {if (equations == null || values == null || queries == null ||equations.length == 0 || values.length == 0 || queries.length == 0|| equations.length != values.length) {return new double[0];}double[] ans = new double [queries.length];// create string to index mapping and get the total # of strint index = 0;Map<String, Integer> str2Index = new HashMap<>();for (String[] equation: equations) {if (!str2Index.containsKey(equation[0])) {str2Index.put(equation[0], index++);}if (!str2Index.containsKey(equation[1])) {str2Index.put(equation[1], index++);}}// update the values into matrixdouble[][] matrix = new double[index][index];for (int i = 0; i < values.length; i++) {int from = str2Index.get(equations[i][0]);int to = str2Index.get(equations[i][1]);matrix[from][to] = values[i];matrix[to][from] = 1.0 / values[i];}// query with dfsfor (int i = 0; i < queries.length; i++) {if (!str2Index.containsKey(queries[i][0]) || !str2Index.containsKey(queries[i][1])) {ans[i] = -1.0;} else {boolean[] visited = new boolean[index];int from = str2Index.get(queries[i][0]);int to = str2Index.get(queries[i][1]);if (from == to) {ans[i] = 1.0;} else {//System.out.println("from: " + from + " to " + to);double temp = dfs(matrix, visited, str2Index, from, to);ans[i] = isZero(temp)? -1.0: temp;}}}return ans;}private double dfs(double[][] matrix, boolean[] visited, Map<String, Integer> str2Index, int from, int to) {if (matrix[from][to] > 0) {return matrix[from][to];} else {visited[from] = true;for (int i = 0; i < matrix.length; i++) {//System.out.println(" from: " + from + " to " + to);if (matrix[from][i] > 0 && !visited[i]) {double temp = dfs(matrix, visited, str2Index, i, to);if (temp > 0) {return matrix[from][i] * temp;}}}visited[from] = false;return 0.0;}}private boolean isZero(double val) {return Math.abs(val) < 0.00000001;}}
十进制转七进制 可参考405
- 十进制转二进制就是除+膜。不过需要注意负数除法的时候不能带上最前面都符号位。
- 推广到div进制。1234567891011121314final private char[] map = {'0', '1', '2', '3','4','5','6','7','8','9','a','b','c','d','e','f'};public String trans(int num, int div) {if (num == 0) {return "0";}long longNum = num & 0x00000000ffffffffL; // 不能直接强制转换,不然会保留符号String hexStr = "";while (longNum != 0) {// System.out.println();hexStr = map[(int)(longNum % div)] + hexStr;longNum /= div;}return hexStr;}
九宫格 面巾
- 给一个只含有数字【1到9】的矩阵,从矩阵中寻找九宫格(3x3的正方形,横着加,竖着加,对角线加都是15),返回矩阵中这样的九宫格的个数。
- 遍历所有九宫格的中心点,求对角线x2、行x3、列x3。有个hint是,只有中心点是5的九宫格才有可能符合要求,因此只需要在5处跑一遍3x3的判断即可。
字符串重组
- 版本一:pattern和str,判断是否match。比如:输入“acg”和“abcdefg”,输出true。但“agc”和“abcdefg”就不对了,因为顺序不同。双指针。
版本二:莉蔻567。不同之处在于不是subsequence而是严格的substring,且顺序不需要关注,因为是后者包含前者的permutation。
123456789101112131415161718192021222324252627282930313233343536373839class Solution {public boolean checkInclusion(String s1, String s2) {if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0 ) {return false;}char[] s1Char = s1.toCharArray();Map<Character, Integer> map = new HashMap<>();int len1 = s1Char.length;for (int i = 0; i < len1; i++) { // 统计s1中每个字母出现个数,作为producermap.put(s1Char[i], map.getOrDefault(s1Char[i], 0) + 1);}char[] s2Char = s2.toCharArray();int count = map.size();int left = 0, right = 0;while (right < s2Char.length) { // 遍历s2作为consumer消耗字符,直到map中所有字符消耗完if (map.containsKey(s2Char[right])) {map.put(s2Char[right], map.get(s2Char[right]) - 1);if (map.get(s2Char[right]) == 0) {count--;}}while (count == 0) { // 左指针补回来,直到map中出现available的字符if (right - left + 1 == len1) {return true;}if (map.containsKey(s2Char[left])) {map.put(s2Char[left], map.get(s2Char[left]) + 1);if (map.get(s2Char[left]) > 0) {count++;}}left++;}right++;}return false;}}与之类似的莉蔻
1234567891011121314151617181920212223242526272829303132333435363738394041class Solution {// O(N)双指针。先一波流统计p中各个字符出现的频数,然后consume掉map中的字符直到没有available的// 此时判断左右指针之间长度是否等于目标的长度,然后挪动左指针重新往map中加回去,直到出现可选字符public List<Integer> findAnagrams(String s, String p) {List<Integer> ans = new ArrayList<Integer>();if (s == null || p == null || s.length() == 0 || p.length() == 0) {return ans;}Map<Character, Integer> map = new HashMap<>();char[] pChar = p.toCharArray();int pLen = pChar.length;for (char c: pChar) {map.put(c, map.getOrDefault(c, 0) + 1);}char[] sChar = s.toCharArray();int count = map.size(); // 还有count个不同的字符可选int left = 0, right = 0;while (right < sChar.length) {if (map.containsKey(sChar[right])) {map.put(sChar[right], map.get(sChar[right]) - 1);if (map.get(sChar[right]) == 0) {count--; // 可选字符少了一个}}while (count == 0) {if (right - left + 1 == pLen) {ans.add(left);}if (map.containsKey(sChar[left])) {map.put(sChar[left], map.get(sChar[left]) + 1);if (map.get(sChar[left]) > 0) {count++; // 恢复可选字符}}left++;}right++;}return ans;}}最长无重复字符的子串长度、 最长至多两个不同字符的字串长度、 最长至多k个不同字符的字串长度.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970public int lengthOfLongestSubstring(String s) {if (s == null || s.length() == 0) {return 0;}Map<Character, Integer> map = new HashMap<>();char[] sChar = s.toCharArray();int left = 0, right = 0, ans = 1;while (right < sChar.length) {if (!map.containsKey(sChar[right])) { // 首次出现map.put(sChar[right], right); // 存出现的索引} else {ans = Math.max(ans, right - left); // 出现重复字符c,更新长度left = Math.max(map.get(sChar[right]) + 1, left); // 跳到c上次出现索引的下一位或不动map.put(sChar[right], right); // 更新c的"最后一次出现索引"}right++;}return Math.max(ans, right - left); // 可能需要看最后一段}// 双指针,右指针负责加入新的字符,一旦和当前范围内除了最后出现字符的另一个字符不同,说明超过了2,则需要更新到最后出现字符首次出现处。public int lengthOfLongestSubstringTwoDistinct(String s) {if (s == null || s.length() == 0) {return 0;}int ans = 1;int firstIndex = 0, lastIndex = -1;char[] sChar = s.toCharArray();for (int i = 1; i < sChar.length; i++) {if (sChar[i] == sChar[i - 1]) { // 连续相同的,直接往后挪continue;}if (lastIndex > -1 && sChar[i] != sChar[lastIndex]) {ans = Math.max(ans, i - firstIndex);firstIndex = lastIndex + 1;}lastIndex = i - 1; // sChar[i]与前一个字符不同,则前一个字符的最后一个出现位置是i - 1}return Math.max(sChar.length - firstIndex, ans);}// 双指针,right向前直到多过允许的k个,然后挪left直到恢复到k个public int lengthOfLongestSubstringKDistinct(String s, int k) {if (s == null || s.length() == 0) {return 0;}Map<Character, Integer> map = new HashMap<>();int maxLen = 0, currCharNum = 0, left = 0;char[] sChar = s.toCharArray();for (int right = 0; right < sChar.length; right++) {if (!map.containsKey(sChar[right])) {currCharNum++;map.put(sChar[right], 1);} else {map.put(sChar[right], map.get(sChar[right]) + 1);}if (currCharNum > k) {while (currCharNum > k && left < sChar.length) {Integer temp = map.get(sChar[left]);if (temp == 1) {map.remove(sChar[left]);currCharNum--;} else {map.put(sChar[left], temp - 1);}left++;}}maxLen = Math.max(maxLen, right - left + 1);}return maxLen;}莉蔻还有类似的双指针破双字符串的题,如 76最小长度的substring ,即给一个t,求s中最短的字符串使得t中出现的字母都包含在该子串中。
1234567891011121314151617181920212223242526272829303132333435363738394041class Solution {public String minWindow(String s, String t) {if (s == null || t == null) {return "";}Map<Character, Integer> map = new HashMap<>();char[] tChar = t.toCharArray();for (char c: tChar) { // 统计每个字母出现的频数map.put(c, map.getOrDefault(c, 0) + 1);}int minLen = Integer.MAX_VALUE, start = 0;char[] sChar = s.toCharArray();int count = map.size(); // count表示可用的不同字符数int left = 0, right = 0;while (right < sChar.length) {if (map.containsKey(sChar[right])) {map.put(sChar[right], map.get(sChar[right]) - 1);if (map.get(sChar[right]) == 0) {count--;}}while (count == 0) { // 没有avaliable的字符,需要左指针补回来while (!map.containsKey(sChar[left])) {left++;}int currLen = right - left + 1;if (minLen > currLen) {minLen = currLen;start = left;}map.put(sChar[left], map.get(sChar[left]) + 1);if (map.get(sChar[left]) > 0) {count++;}left++;}right++;}return minLen == Integer.MAX_VALUE? "": s.substring(start, start + minLen);}}上面这个需要和 727最小长度的subsequence 区分,这里除了出现的字符一样,出现分的先后顺序也要一致。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public String minWindow(String S, String T) {// 动态规划, 行为tLen + 1, 列为sLen + 1, dp[][]表示从dp[i][j]到j到这部分字符串是所求,// 即T[0, j)是S[0, i)的subsequence with substring S[dp[i][j], j).// 初始状态为// 状态转换为,若当前字符不匹配,则根据左侧(即S前一个字符)情况决定起始位置(保证最短)// 若匹配,则依赖于左上方的结果,即T前一个字符的起始位置。// 可进一步压缩空间到O(Slen)if (S == null || T == null || S.length() == 0 || T.length() == 0) {return "";}char[] sChar = S.toCharArray();char[] tChar = T.toCharArray();int sLen = sChar.length, tLen = tChar.length;int[] startFrom = new int [sLen + 1];Arrays.fill(startFrom, -1);int firstOccur = -1;for (int j = 1; j <= sLen; j++) {if (sChar[j - 1] == tChar[0]) {if (firstOccur == -1) {firstOccur = j - 1;}startFrom[j] = j - 1;} else {startFrom[j] = startFrom[j - 1];}}for (int i = 2; i <= tLen; i++) {int newFirstOccur = -1;for (int j = firstOccur + 1; j <= sLen; j++) {if (sChar[j - 1] == tChar[i - 1]) {if (newFirstOccur == -1) {newFirstOccur = j - 1;}startFrom[j] = startFrom[j]; // don't change} else {startFrom[j] = startFrom[j - 1];}}firstOccur = newFirstOccur;}int start = 0, end = sLen, minLen = Integer.MAX_VALUE;for (int j = sLen; j > 0 && startFrom[j] != -1; j--) {int currLen = j - startFrom[j];if (currLen < minLen) {start = startFrom[j];end = j;minLen = currLen;}}return minLen == Integer.MAX_VALUE? "" : S.substring(start, end);}
stream observer
- 已知一组字符串,每次观察一个输入的字符,若输入的字符顺序和字符串组里的match,就返回match的list,否则返回空。问题的形式让我有点懵,涉及到了interface,然后让我自己定义一个class,自定member variable, hierarchy等等
- 我是用sliding window加上trie做的。先找出list中最长单词的长度k。然后维护一个长度为k的window。建trie的时候注意是反向建,每个单词从最后一个字母开始往前建。同时查的时候也是从window的最右边往前查。但如果有多个匹配(例如abc, abcd都在List中),则需要返回List。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113public static class StreamObserver {StringBuilder stream;int maxLen;TrieNode root;List<String> list = new ArrayList<>();public StreamObserver(String[] targets) {root = new TrieNode();maxLen = 0;for (int i = 0; i < targets.length; i++) {maxLen = Math.max(targets[i].length(), maxLen);insert(targets[i]);}stream = new StringBuilder();}public List<String> putChar(char c) {stream.append(c);int len = stream.length();String window = stream.substring(Math.max(len - maxLen, 0), len);System.out.println("stream: " + stream);if (stream.length() > maxLen) {stream.deleteCharAt(0);}return search(window);}class TrieNode {private boolean isWord;private TrieNode[] next;public TrieNode() {isWord = false;next = new TrieNode[26];}public TrieNode getNext(int index) {return next[index];}public void setNext(int index, TrieNode newNode) {next[index] = newNode;}public boolean getIsWord() {return isWord;}public void setIsWord(boolean val) {isWord = val;}}/** Inserts a word into the trie in reversed order. */public void insert(String word) {if (word == null || word.length() == 0) {return;}char[] wordChar = word.toCharArray();TrieNode curr = root;for (int i = wordChar.length - 1; i >= 0; i--) {int index = wordChar[i] - 'a';if (curr.getNext(index) == null) {curr.setNext(index, new TrieNode());}curr = curr.getNext(index);}curr.setIsWord(true);}public List<String> search(String word) {char[] wordChar = word.toCharArray();TrieNode curr = root;for (int i = wordChar.length - 1; i >= 0; i--) {int index = wordChar[i] - 'a';if (curr.getNext(index) == null) {break;}curr = curr.getNext(index);if (curr.getIsWord()) {list.add(word.substring(i, wordChar.length));}}return list;}/*// Returns if the word is in the trie.public boolean search(String word) {if (word == null || word.length() == 0) {return false;}char[] wordChar = word.toCharArray();TrieNode curr = root;for (int i = wordChar.length - 1; i >= 0; i--) {int index = wordChar[i] - 'a';if (curr.getNext(index) == null) {return false;}curr = curr.getNext(index);}return curr.getIsWord();}// Returns if there is any word in the trie that starts with the given prefix.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.getNext(index) == null) {return false;}curr = curr.getNext(index);}return true;}*/}
有向无环图最长路径
- 给出公司并购的关系列表,比如
[["baidu", "ofo"], ["mobike", "alibaba"],...]
,表示baidu并购了ofo,摩拜并购了阿里巴巴。。。求最长的一个并购链。保证无环。 - 类似简化版拓扑,完整版拓扑可参考这个submission,不过这里其实只需要找到入度为0的节点,然后更新可达点的同时更新所走的步数。
带时间戳的Map
带时间戳的map:
put(k, v, timestamp) get(k , timestamp)
EX:12345put(1, haha, 3)put(1, ha, 5)get(1, 0) -> nullget(1, 4) -> hahaget(1, 6) -> ha维护
Map<Integer, Map<Integer, String>>
,新插入的key就新建一个TreeMap,然后时间戳为key,String为value存入。读取的时候就取floorKey(timestamp)
,也就是最接近的不大于timestamp的值,有的话即为所求了。- Follow-up是问用ArrayList, LinkedList, BST, Balanced BST实现的get和put的时间复杂度分别是多少。
树的遍历
- preorder, inorder, post-order。要求掌握iterative和recursive的方法。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960public List<Integer> preorderTraversal(TreeNode root) {List<Integer> ans = new LinkedList<>();if (root == null) {return ans;}Stack<TreeNode> stack = new Stack<>();stack.push(root);while (!stack.isEmpty()) {TreeNode curr = stack.pop();// 栈顶的先输出ans.add(curr.val);if (curr.right != null) { // 栈优先push右子(让它晚点出来)stack.push(curr.right);}if (curr.left != null) {stack.push(curr.left);}}return ans;}public List<Integer> inorderTraversal(TreeNode root) {List<Integer> ans = new ArrayList<>();if (root == null) {return ans;}Stack<TreeNode> s = new Stack<>();TreeNode curr = root;while (curr != null || !s.isEmpty()) {while (curr != null) {s.push(curr);curr = curr.left; // 栈总是优先push左子}curr = s.pop(); // 直到无左子ans.add(curr.val);curr = curr.right;}return ans;}public List<Integer> postorderTraversal(TreeNode root) {List<Integer> ans = new LinkedList<>();if (root == null) {return ans;}Stack<TreeNode> stack = new Stack<>();Stack<Integer> val = new Stack<>();stack.push(root);while (!stack.isEmpty()) {TreeNode curr = stack.pop();if (curr.left != null) {stack.push(curr.left);}if (curr.right != null) {stack.push(curr.right);}val.push(curr.val); // 栈顶的晚输出}while (!val.isEmpty()) {ans.add(val.pop());}return ans;}
Frequencey Iterator (类似于利口604)
- 给一个int数组,奇数位是count偶数位是number,实现next和hasNext函数。如
[2,4,0,9,5,3]
,那输出就是[4,4,3,3,3,3,3]
。 - 自定义类保存数字和对应的频数,然后入Queue开始输出就好了。12345678910111213141516171819202122232425262728293031323334class FreqNumber {int freq;int num;public FreqNumber(int freq, int num) {this.freq = freq;this.num = num;}}public class MyIterator {Queue<FreqNumber> queue;public MyIterator(int[] array) {int i = 0, n = array.length; // 需要保证是偶数个元素queue = new LinkedList<>();while (i + 1 < n) {if (array[i] > 0) {queue.add(new FreqNumber(array[i], array[i + 1]));}i += 2;}}public int next() throws Exception {if (!hasNext()) {throw new Exception();}FreqNumber top = queue.peek();if (--top.freq == 0) {queue.poll();}return top.num;}public boolean hasNext() {return !queue.isEmpty();}}
toeplitz matrix
- 判断一matrix是不是toeplitz matrix, 就是所有左上到右下对角线的数字都相等。
对角线方向元素其实就是相邻两行元素的前后判断。
12345678910111213141516public class Google {public boolean isToeplitzMatrix(int[][] matrix) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}int rows = matrix.length, cols = matrix[0].length;for (int i = 1; i < rows; i++) {for (int j = 1; j < cols; j++) {if (matrix[i][j] != matrix[i - 1][j - 1]) {return false;}}}return true;}}follow-up: 如果matrix特别大,每次只能存两行,只用matrix提供的
matrix.next
,matrix.has_next
, 和matrix.size
实现。其实上面的方法已经只存两行了。
3sum
- 给一个数组,求其中所有的a, b, c使得
a + b + c == 0
123456789101112131415161718192021222324252627282930313233343536373839public List<List<Integer>> threeSum(int[] nums) {List<List<Integer>> ans = new ArrayList<>();if (nums == null || nums.length == 0) {return ans;}Arrays.sort(nums);for (int i = 0; i < nums.length; i++) {if (i > 0 && nums[i] == nums[i - 1]) {continue;}// 固定nums[i]用双指针找后面两个数b, c,使b + c == 0 - nums[i]int left = i + 1, right = nums.length - 1, target = 0 - nums[i];while (left < right) {int sum = nums[left] + nums[right];if (sum == target) {List<Integer> currList = new ArrayList<>();currList.add(nums[i]);currList.add(nums[left]);currList.add(nums[right]);ans.add(currList);left++;right++;while (left < right && nums[left] == nums[left - 1]) { // 避免重复left++;}while (right > left && nums[right] == nums[right + 1]) { // 避免重复right++;}} else if (sum < target) {left++;} else {right--;}}}return ans;}
validate 2D array
- 2-D array,里面有R,G,B,Y四种类型的element,如果横竖没有三个element是同一类型的话,这个2-Darray就是valid的。先让写个function判断一个2-D array是否valid。
感觉和那个OOXX(348 Design tic tac toe)的游戏很像,不过就不能通过简单的加一减一来搞了。这里就是暴力了,判断当前字符的前一行、下一行,以及前一列、下一列。
123456789101112131415161718192021222324252627public static boolean isValidMatrix(char[][] matrix) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}int rows = matrix.length, cols = matrix[0].length;for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {if (i > 0 && matrix[i - 1][j] == matrix[i][j]) { // 等于上一行if (i + 1 < rows && matrix[i + 1][j] == matrix[i][j]) {return false; // 判断下一行}}colBucket[j] = matrix[i][j];if (j > 0 && matrix[i][j] == matrix[i][j - 1]) {if (j + 1 < cols && matrix[i][j + 1] == matrix[i][j]) {return false;} else {j++;if (j < cols) {colBucket[j] = matrix[i][j];}}}}}return true;}follow-up:能否并行化计算?各种方式有什么优劣?可以replicate原array,每个thread只处理一个颜色。也可以划分成小块,但是边界线情况讨论起来特别复杂,而且会有很多个thread。
2D array search & sum
240 Search a 2D Matrix II,可以直接从第一行的最大列开始找,因为右下方的一定是比当前元素大的,所以只需要查找左下和左前即可。
123456789101112131415161718// O(M + N)public boolean searchMatrix(int[][] matrix, int target) {if (matrix == null || matrix.length < 1 || matrix[0].length <1) {return false;}int col = matrix[0].length - 1;int row = 0;while (col >= 0 && row <= matrix.length-1) {if (target == matrix[row][col]) {return true;} else if (target < matrix[row][col]) {col--;} else if (target > matrix[row][col]) {row++;}}return false;}304. Range Sum Query 2D - Immutable 也是缓存中间结果的思想,利用额外的O(MN)的空间存下来,需要取sum的时候就减左侧、上侧再加回左上侧结果即可。
1234567891011121314151617private int[][] sumRegion;public NumMatrix(int[][] matrix) {if (matrix.length != 0) sumRegion = new int[matrix.length + 1][matrix[0].length + 1];for (int i = 0; i < matrix.length; i++) {int sum = 0;for (int j = 0; j < matrix[0].length; j++) {sum += matrix[i][j];sumRegion[i + 1][j + 1] = sum + sumRegion[i][j + 1];}}}public int sumRegion(int row1, int col1, int row2, int col2) {return sumRegion[row2 + 1][col2 + 1] - sumRegion[row1][col2 + 1] - sumRegion[row2 + 1][col1] + sumRegion[row1][col1];}follow-up是如果要对矩阵时不时进行更新怎么办。如果还是用上面这个的思路,返回sumRegion的时间就不再是constant了,同时update也不是constant。为了更新,还需要把matrix存下来(必须的)。
123456789101112131415161718192021222324252627282930313233343536373839private int[][] sumRegion;private int[][] matrix;public NumMatrix(int[][] matrix) {if( matrix == null|| matrix.length == 0|| matrix[0].length == 0 ){return;}this.matrix = matrix;int m = matrix.length;int n = matrix[0].length;sumRegion = new int[m + 1][n];for(int i = 1; i <= m; i++){for(int j = 0; j < n; j++){sumRegion[i][j] = sumRegion[i - 1][j] + matrix[i - 1][j];}}}//time complexity for the worst case scenario: O(m)public void update(int row, int col, int val) {for(int i = row + 1; i < sumRegion.length; i++){sumRegion[i][col] = sumRegion[i][col] - matrix[row][col] + val;}matrix[row][col] = val;}//time complexity for the worst case scenario: O(n)public int sumRegion(int row1, int col1, int row2, int col2) {int ret = 0;for(int j = col1; j <= col2; j++){ret += sumRegion[row2 + 1][j] - sumRegion[row1][j];}return ret;}另一个高效办法是Binary Index Tree。我们先看看range sum 1D的情况,2D的暂时放一放。。。
12345678910111213141516171819202122232425262728293031323334353637383940414243int[] nums;int[] BIT;int n;public RangeSum(int[] nums) {this.nums = nums;this.n = nums.length;this.BIT = new int[n + 1]; // n + 1initiateBIT();}public int lowestBit(int x) { // 获取最低位的1return x & -x;}public void initiateBIT(){for(int i = 1; i <= n; i++){this.BIT[i] = 0;for(int j = i; j > i - lowestBit(i); j--){this.BIT[i] = this.BIT[i] + nums[j - 1];}}}//每次都只有后继节点会根据当前节点的变化而变化, 所以步长是i + lowestBit(i)public void update(int index, int val){for(int i = index + 1; i <= this.n; i = i + lowestBit(i)){this.BIT[i] = this.BIT[i] - this.nums[index] + val;}this.nums[index] = val;}//和为所有前驱节点相加, 所以步长是i - lowestBit(i)public int getSum(int k){int sum = 0;for(int i = k + 1; i > 0; i = i - lowestBit(i)){sum = sum + this.BIT[i];}return sum;}public int getRangeSum(int i, int j){return getSum(j) - getSum(i - 1);}
voting with timestamp
- 给一个
voteList=[(a, 100), (b, 150), (a, 200)] #(name, timestamp)
和时间T,找T时间之前, 得票数最高的人(或任一得票数最高的人)。 - 直接扫一波vote list,只保留在时间T之前的票存到Map中,顺便记下一个最大值和对应的人名。
- follow-up:给voteList、时间T,求top k候选人的list。也是先O(N)扫一波统计,然后再一波loop存入Heap,如果Heap规模达到k就需要比较堆顶和当前票数,若当前票数更大就需要把出堆再push。
- follow-up:给voteList、top k候选人的list,求时间T。只想到最暴力的办法,先把所有出现过的时间t存入从小到大的PriorityQueue,然后从小到大暴力搜“给定voteList和T”,看看求得的list和所给的list是否一致。不过这里每次不是从头开始做,而是累加,即前一个t不行,就取优先队列中的下一个t继续往后加,不用从头来过。
feed tree
- 一棵树,所有节点的value都是正整数,问只能增加某些节点值的情况下,如何调整使得从root到所有leaf的path上经过的节点值之和相等,返回增加的值的和,使这个和最小.
- 类似贪心,在后续遍历的过程中顺便求叶子到当前节点的和并返回,再用全局变量根据左右两边孩子的差值确定需要补多少,更新的条件是该节点一定有两个孩子来比较,这样就能保证和最小。123456789101112131415161718192021222324252627282930313233343536/*1/ \2 3/ \ / \3 2 2 4\11/ \4 3/ \ / \3 3 3 4\1*/class Solution {private int ans;public int minAddToTree(TreeNode root){// supposing the tree is binary treeans = 0;feedTree(root);return ans;}private int feedTree(TreeNode root){// this funciton 'feed' the tree to make all root->leaf path sums equal// return: path sum of the tree.if (root == null)return 0;int leftPath = feedTree(root.left);int rightPath = feedTree(root.right);if (leftPath != 0 && rightPath != 0) // check if one child is null, if so no need to feedans += Math.abs(leftPath - rightPath);return Math.max(leftPath, rightPath) + root.val;}}
remove node
- Given a binary tree, write a function to erase a list of nodes from the tree and return the forest created from this operation.自己决定输入输出形式,自己定义treenode结构.
一个思路是,可以在TreeNode中增加parent节点,这样在删除的时候就方便很多,直接设置就可以了。
1234567class TreeNode {int val;TreeNode left, right, parent;public TreeNode(int val) {this.val = val;}}如果不加parent,就需要通过postOrder遍历,先递归到当前节点的左和右孩子,再看看孩子是否要删掉,是就直接设为null。
1234567891011121314151617public List<TreeNode> delete(TreeNode root, List<TreeNode> toRemove) {List<TreeNode> res = new ArrayList<TreeNode>();TreeNode root1 = postorder(root, toRemove, res);if(root1 != null) res.add(root);return res;}public TreeNode postorder(TreeNode root, List<TreeNode> toRemove, List<TreeNode> res) {if(root == null) return null;root.left = postorder(root.left, toRemove, res);root.right = postorder(root.right, toRemove, res);if (toRemove.contains(root)) { // 当前节点需要删除if (root.left != null) res.add(root.left);if (root.right != null) res.add(root.right);return null;}return root;}
361. Bomb Enemy
- 给一个只含有
0, W, E
的char棋盘,炸弹只能放在0处,炸弹可以向上下左右扩散,求一颗炸弹最多炸死多少E。 对于每一行统计敌人数,一旦遇到W就重置为0;对于每一列则需要维护额外的一个数组,也是遇到W就重置。
1234567891011121314151617181920212223242526272829303132333435363738class Solution {// O(MN)统计每行、每列可杀E的数量,一旦前面(左或上)碰到W就要重新算一遍public int maxKilledEnemies(char[][] grid) {if (grid == null || grid.length == 0 || grid[0].length == 0) {return 0;}int rows = grid.length, cols = grid[0].length;int ans = 0, rowCount = 0;int[] colCount = new int [cols];for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {if (j == 0 || grid[i][j - 1] == 'W') { // 当上一列是W,就重新往右算当前列可以杀多少ErowCount = 0;for (int k = j; k < cols && grid[i][k] != 'W'; k++) { // 不会提升复杂度因为只有前一个是W才会开始、到W就结束if (grid[i][k] == 'E') {rowCount++;}}}if (i == 0 || grid[i - 1][j] == 'W') { // 当上一行是W,就重新往下算当前列可以杀多少EcolCount[j] = 0;for (int k = i; k < rows && grid[k][j] != 'W'; k++) {if (grid[k][j] == 'E') {colCount[j]++;}}}if (grid[i][j] == '0') {ans = Math.max(ans, rowCount + colCount[j]);}// else if (grid[i][j] == 'E') {// ans = Math.max(ans, rowCount + colCount[j] - 1);// }}}return ans;}}follow-up:如果在E上也可以放炸弹,要如何修改?那么就加多一个判断是否等于
E
的步骤,见上面注释部分。
以上是地里大概找的一部分面经。
以下是利口上的孤狗tag题。
4. Median of 2 sorted arrays
- 给两个有序int数组,返回二者合并后的中位数。
O(M + N)
:归并排序依次合并,合并到一半长度知道中位数了。O(log(M + N))
:中位数是用来将数组分割成相等长度的两部分的,因此使用二分查找找出刚好能分成等长的两部分并且前部分的最大值<=后部分的最小值。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {if (nums1 == null || nums2 == null) {return 0;}int m = nums1.length, n = nums2.length;if (nums1.length > nums2.length) { // ensure the len of 1 <= 2return findMedianSortedArrays(nums2, nums1);}// to ensure equality of the two parts after merged, i + j = m - i + n - jint iLo = 0, iHi = m, allMid = (n + m + 1) / 2; // merge odd / even case// i stands for "how many num taken from nums1 as front part" 0 ~ i-1 | i ~ m-1// j stands for "how many num taken from nums2 as front part" 0 ~ j-1 | j ~ n-1while (iLo <= iHi) {int i = (iLo + iHi) / 2, j = allMid - i;// nums1[i-1], nums2[j-1] are the largest element of front part of nums1, nums2// nums1[i], nums2[j] are the smallest of lag part of nums1, nums2if (i < m && nums2[j - 1] > nums1[i]) { // i not big enoughiLo = i + 1;} else if (i > 0 && nums1[i - 1] > nums2[j]) {iHi = i - 1;} else {int maxLeft = 0, minRight = 0;if (i == 0) {maxLeft = nums2[j - 1];} else if (j == 0) {maxLeft = nums1[i - 1];} else {maxLeft = Math.max(nums1[i - 1], nums2[j - 1]);}if ((m + n) % 2 == 1) { // I think thats why to make (allMid = (n + m + 1)/2)return maxLeft; // -- to make left part always at least no fewer than right}if (i == m) {minRight = nums2[j];} else if (j == n) {minRight = nums1[i];} else {minRight = Math.min(nums1[i], nums2[j]);}return (maxLeft + minRight) / 2.0;}}return 0;}}
17. letter-combinations
- 给一串数字的字符串,求这些数字可能输出的所有字母字符串,对应关系为手机的按键。
- DFS:12345678910111213141516171819202122232425262728293031323334353637class Solution {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;}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'}};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) {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);}}}
20. valid-parentheses
- 给一个字符串,判断其中三种括号
(), [], {}
是否匹配。 - Stack: 左括号本身并不入栈,而是它对应的右括号入栈,这样当右括号出现的时候只需判断二者是否相等或者是否栈已经空了即可。1234567891011121314public 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();}
22. generate-parentheses
- 给一个int表示括号的对儿数,输出所有符合括号匹配规则的字符串,存入List中返回。
- DFS:左括号、右括号分别1234567891011121314151617181920212223// O(2^2n)?class Solution {public List<String> generateParenthesis(int n) {List<String> ans = new ArrayList<>();if (n < 1) {return ans;}dfs(n, 0, 0, "", ans);return ans;}private void dfs(int n, int left, int right, String s, List<String> ans) {if (left == n && right == n) {ans.add(s);return;}if (left < n)dfs(n, left + 1, right, s + "(", ans);if (right < left)dfs(n, left, right + 1, s + ")", ans);}}
23. merge-k-sorted-lists
- 给一个ListNode节点数组,分别是排好序的若干链表头,要求输出合并后的新链表头.
直接用PriorityQueue:
1234567891011121314151617181920public ListNode mergeKLists(ListNode[] lists) {if (lists == null || lists.length == 0) {return null;}PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> {return a.val - b.val;});for (ListNode l: lists) {if (l != null)pq.add(l);}ListNode fakeHead = new ListNode(0), curr = fakeHead;while (!pq.isEmpty()) {curr.next = pq.poll();curr = curr.next;if (curr.next != null)pq.add(curr.next);}return fakeHead.next;}分治法。
1234567891011121314151617181920212223242526272829303132333435// divide and conquer, O(nlogk)public ListNode mergeKLists(ListNode[] lists) {if (lists == null || lists.length == 0) {return null;}// last two params are used to divide into sub problemsreturn mergeKLists(lists, 0, lists.length);}private ListNode mergeKLists(ListNode[] lists, int start, int end) {if (end - start == 1) { // only one listreturn lists[start];} else if (end - start == 2) { // merge two listsreturn mergeTwoLists(lists[start], lists[start + 1]);} else {int mid = start + (end - start) / 2;// cut into first and second halvesreturn 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;}}
31. next-permutation
- 给一个int数组,将它改造成按字典序的下一个排列,若不存在则直接按从小到大的顺序调整。要求In-place对原数组操作,不能申请额外空间。
- 这个想法从右往左找相邻的两个数使得
first < second
,然后再从右往左找首次出现的比first大的数,二者对调,然后将second及其之后的内容reverse一下即可。123456789101112131415161718192021222324252627282930313233// 1324 -> 1342// 6,3,4,9,8,7,1 -> 6,3,7,9,8,4,1 -> 6,3,7,1,4,8,9class Solution {public void nextPermutation(int[] nums) {if (nums == null || nums.length < 2) {return;}for (int i = nums.length - 2; i >= 0; i--) {if (nums[i] < nums[i + 1]) { // 找相邻的两个数int j = nums.length - 1;while (j > i) { // 从右往左找第一个大于first的数if (nums[j] > nums[i]) {swap(nums, i, j); //reverse(nums, i + 1);return;}j--;}}}reverse(nums, 0);}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);}}}
42. trapping-rain-water
- 给一个int数组,其中包含每个索引处墙的高度。求装满水时的横截面的积水的面积。
用两个辅助数组分别表示两侧最高高度,然后再一波遍历看能否装水。
1234567891011121314151617181920212223242526class Solution {public int trap(int[] height) {if (height == null || height.length == 0) {return 0;}int[] hiLeft = new int [height.length]; // leftward highestint[] hiRight = new int [height.length]; // rightward highestint left = height[0], right = height[height.length - 1];for (int i = 1, j = height.length - 2; i < height.length; i++, j--) {hiLeft[i] = left;left = Math.max(left, height[i]);hiRight[j] = right;right = Math.max(right, height[j]);}int area = 0;for (int i = 0; i < height.length; i++) {int h = Math.min(hiLeft[i], hiRight[i]);if (height[i] < h) {area += (h - height[i]);}}return area;}}follow-up:不能用额外空间呢?
1234567891011121314151617181920212223242526class Solution {public int trap(int[] height) {if (height == null || height.length == 0) {return 0;}int[] hiLeft = new int [height.length]; // leftward highestint[] hiRight = new int [height.length]; // rightward highestint left = height[0], right = height[height.length - 1];for (int i = 1, j = height.length - 2; i < height.length; i++, j--) {hiLeft[i] = left;left = Math.max(left, height[i]);hiRight[j] = right;right = Math.max(right, height[j]);}int area = 0;for (int i = 0; i < height.length; i++) {int h = Math.min(hiLeft[i], hiRight[i]);if (height[i] < h) {area += (h - height[i]);}}return area;}}
676. implement-magic-dictionary
- 给一个字典需要维护某种结构使得查询比较高效,查询是通过一个单词输入后判断字典中是否存在一个单词只替换输入串中的一个字符就能得到。
- 有点暴力的方法,在输入一个字符串数组的时候就维护一个从String映射到Character的Map,对于每个单词的每一个字符都换成
*
作为key,然后被替换的字符作为value存入。之后check的时候也是把每个字符都换成*
之后再确认map中有没有对应的key。12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455class MagicDictionary {// 将每个单词的每个字符都替换成*之后,插入map,key是替换后的字符串,value是被替换掉的那个字符// 如果出现替换过后的key一样,则该位可以放任意字符,因为例如hello和hallo只有在第二位不同,如果替换后是h*llo可以任选一个,一定是true。Map<String, Character> map = null;/** Initialize your data structure here. */public MagicDictionary() {map = new HashMap<String, Character>();}/** Build a dictionary through a list of words */public void buildDict(String[] dict) {if (dict == null || dict.length == 0) {return;}for (String word : dict) {StringBuilder sb = new StringBuilder(word);int len = word.length();for (int i = 0; i < len; i++) {sb.setCharAt(i, '*');Character c = map.get(sb.toString());if (c == null) {map.put(sb.toString(), word.charAt(i));} else {map.put(sb.toString(), '*');}sb.setCharAt(i, word.charAt(i));}}}/** Returns if there is any word in the trie that equals to the given word after modifying exactly one character */public boolean search(String word) {if (word == null || word.length() == 0) {return false;}int len = word.length();StringBuilder sb = new StringBuilder(word);for (int i = 0; i < len; i++) {sb.setCharAt(i, '*');Character c = map.get(sb.toString());if (c != null && (c == '*' || c != word.charAt(i))) {return true;}sb.setCharAt(i, word.charAt(i));}return false;}}/*** Your MagicDictionary object will be instantiated and called as such:* MagicDictionary obj = new MagicDictionary();* obj.buildDict(dict);* boolean param_2 = obj.search(word);*/