LeetCode 401-500
LeetCode Problems 401-500.
401. Binary Watch
https://leetcode.com/problems/binary-watch/
直接调用Integer.bitCount()函数,如果时和分中的1相加等于turnedOn,就添加到结果中。
class Solution { public List<String> readBinaryWatch(int turnedOn) { List<String> result = new ArrayList<>();
for (int hour = 0; hour < 12; hour++) { for (int minute = 0; minute < 60; minute++) { if (Integer.bitCount(hour) + Integer.bitCount(minute) == turnedOn) { result.add(hour + (minute < 10 ? ":0" : ":") + minute); } } }
return result; }}402. Remove K Digits
https://leetcode.com/problems/remove-k-digits/
遍历字符串,找到递减序列,定位到第一个需要被替换的“大”数字,从之后找到更小的数字覆盖它。如果循环结束后,k的次数仍有剩余,根据end和k的大小关系,判断返回值。
class Solution { public String removeKdigits(String num, int k) { int end = 0; char[] array = num.toCharArray();
for (int i = 0; i < num.length(); i++) { // encounter a decreasing sequence, locate the digit to be overwritten while (end > 0 && k > 0 && array[end - 1] > array[i]) { k--; end--; }
// skip leading '0' and overwrite one digit with a digit after if (end > 0 || array[i] != '0') { array[end] = array[i]; end++; } }
// if there are still k left, return 0 or truncate the last k digits return end <= k ? "0" : String.valueOf(array, 0, end - k); }}403. Frog Jump
https://leetcode.com/problems/frog-jump/
DFS思想,每一跳都尝试三种长度可能。同时可以用一个map存储每个下标与对应的k值,如果我们此前已经用k值到达过这个下标,就记录下来,避免之后重复DFS。
class Solution { // <index, set of all possible k> Map<Integer, Set<Integer>> checked = new HashMap<>();
public boolean canCross(int[] stones) { for (int i = 0; i < stones.length; i++) { checked.put(stones[i], new HashSet<>()); } return helper(0, 1, stones[stones.length - 1]); }
private boolean helper(int current, int k, int last) { if (current == last) { return true; }
// skip if we have checked this k at the current stone if (k <= 0 || checked.get(current).contains(k)) { return false; }
int next = current + k; if (checked.containsKey(next)) { if (helper(next, k - 1, last) || helper(next, k, last) || helper(next, k + 1, last)) { return true; } } checked.get(current).add(k);
return false; }}404. Sum of Left Leaves
https://leetcode.com/problems/sum-of-left-leaves/
从根节点开始:
- 如果
root.left不空,则检查左子树- 如果
root.left.left已经是叶节点,直接加上它的值 - 否则,递归加上左子树中所有左叶节点的和
- 如果
- 如果
root.right不空,检查右子树中所有左叶节点的和
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public int sumOfLeftLeaves(TreeNode root) { int result = 0;
if (root == null) { return result; }
if (root.left != null) { if (root.left.left == null && root.left.right == null) { result += root.left.val; } else { result += sumOfLeftLeaves(root.left); } }
if (root.right != null) { result += sumOfLeftLeaves(root.right); }
return result; }}405. Convert a Number to Hexadecimal
https://leetcode.com/problems/convert-a-number-to-hexadecimal/
使用循环,每次提取num最后4位转成16进制,再将最后的结果倒转。
class Solution { public String toHex(int num) { if (num == 0) { return "0"; }
char[] map = new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; StringBuilder sb = new StringBuilder(); while (num != 0) { // transform last 4 bits to hex sb.append(map[num & 15]);
// get rid of the last 4 bits num >>>= 4; }
return sb.reverse().toString(); }}406. Queue Reconstruction by Height
https://leetcode.com/problems/queue-reconstruction-by-height/
本质而言,队列的恢复是一个排序并且重新插入元素的过程。
- 对输入数组进行排序。排序规则是:默认按照
h(身高)进行排序,身高越高排在越前面;身高一样时,按照k(面前身高大于等于h的人数)进行排序,人数越少越靠前 - 把排序好的队列插入到List中,注意插入时需要参考
k值,k值是多少就插入到哪个位置。因为上一步中已经排序,而此时又是按照排序后的顺序进行遍历,因此k值相同时,身高矮的人比身高高的人后插入,起到了调整顺序的作用
最后,把List转为array即可。
class Solution { public int[][] reconstructQueue(int[][] people) { Arrays.sort(people, (a, b) -> (a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]));
List<int[]> result = new LinkedList<>(); for (int[] p : people) { result.add(p[1], p); }
return result.toArray(new int[result.size()][]); }}407. Trapping Rain Water II
https://leetcode.com/problems/trapping-rain-water-ii/
核心思路是:把每个block定义成Comparable的object,这样能够利用Priority Queue根据每个Block的高度进行排序。之后,从map的四条边缘开始,把四条边上的所有Block加入到Priority Queue中,开始进行BFS遍历。
BFS遍历的流程是:从边缘Block开始,向四个方向的邻近Block遍历,筛除掉超过map边界和已访问Block的情况。之后按照高度进行判断:如果邻近的Block比现有的这个更高,就把它加入到Priority Queue中,用于后续进一步的判断,并且中止BFS;而如果它更矮,说明可以trap一些水,水的体积就是两个Block的高度差,并且继续进行BFS。
因为使用了Priority Queue,因此能够确保遍历Block的时候一定是按照从低到高的顺序开始,因此能保证这种方式trap的水量是最大的。
class Solution { private int water = 0; private boolean[][] visited; private int visitedCnt = 0;
private static class Block implements Comparable<Block> { int row; int col; int value;
Block(int row, int col, int value) { this.row = row; this.col = col; this.value = value; }
@Override public int compareTo(Block block) { return value - block.value; } }
public int trapRainWater(int[][] heightMap) { if (heightMap == null || heightMap.length < 3 || heightMap[0].length < 3) { return 0; }
int rows = heightMap.length; int cols = heightMap[0].length; PriorityQueue<Block> pq = new PriorityQueue<Block>(); visited = new boolean[rows][cols];
// add row edge blocks to priority queue for (int i = 0; i < cols; i++) { Block block1 = new Block(0, i, heightMap[0][i]); Block block2 = new Block(rows - 1, i, heightMap[rows - 1][i]); pq.offer(block1); pq.offer(block2); visited[0][i] = true; visited[rows - 1][i] = true; visitedCnt += 2; }
// add column edge blocks to priority queue for (int i = 1; i < rows - 1; i++) { Block block1 = new Block(i, 0, heightMap[i][0]); Block block2 = new Block(i, cols - 1, heightMap[i][cols - 1]); pq.offer(block1); pq.offer(block2); visited[i][0] = true; visited[i][cols - 1] = true; visitedCnt += 2; }
// BFS while (visitedCnt < rows * cols) { Block block = pq.poll(); fill(block.row - 1, block.col, block.value, pq, heightMap); fill(block.row, block.col - 1, block.value, pq, heightMap); fill(block.row + 1, block.col, block.value, pq, heightMap); fill(block.row, block.col + 1, block.value, pq, heightMap); }
return water; }
public void fill(int row, int col, int min, PriorityQueue<Block> pq, int[][] heightMap) { if (row < 0 || col < 0 || row >= heightMap.length || col >= heightMap[0].length || visited[row][col]) { // out of bound, or visited block return; } else if (heightMap[row][col] >= min) { // visit a higher block, add to queue and mark it as visited Block newBlock = new Block(row, col, heightMap[row][col]); pq.offer(newBlock); visited[row][col] = true; visitedCnt++; } else { // visit a lower block, add water to be trapped water += min - heightMap[row][col]; visited[row][col] = true; visitedCnt++; fill(row - 1, col, min, pq, heightMap); fill(row, col - 1, min, pq, heightMap); fill(row + 1, col, min, pq, heightMap); fill(row, col + 1, min, pq, heightMap); } }}408. Valid Word Abbreviation
https://leetcode.com/problems/valid-word-abbreviation/
使用两个指针分别遍历word和abbr,如果abbr遇到了数字,判断这个数字number是多少,直到数字结束;否则,让word的指针跳过number的下标,看此时word和abbr下标对应的字母仍然相等。重复这个循环,最后判断两个指针是否能分别完成遍历。
class Solution { public boolean validWordAbbreviation(String word, String abbr) { int number = 0; int i = 0, j = 0;
while (i < word.length() && j < abbr.length()) { if (abbr.charAt(j) >= '0' && abbr.charAt(j) <= '9') { number = number * 10 + abbr.charAt(j) - '0'; if (number == 0) { return false; } j++; } else { i += number; if (i >= word.length() || word.charAt(i) != abbr.charAt(j)) { return false; } number = 0; i++; j++; } }
// abbr ends with number i += number; return i == word.length() && j == abbr.length(); }}409. Longest Palindrome
https://leetcode.com/problems/longest-palindrome/
统计每个字符出现的次数,用一个变量odd记录出现次数为奇数的字符数量。如果odd最后小于2,说明可以直接用s构造最长的回文串;反之,需要剔除掉odd - 1个这种奇数字符(只能选一个字符全部加入,其他的奇数字符每个要减掉一个)。
class Solution { public int longestPalindrome(String s) { int[] freq = new int[128]; for (int i = 0; i < s.length(); i++) { freq[s.charAt(i)]++; }
int odd = 0; for (int i = 0; i < freq.length; i++) { if (freq[i] % 2 != 0) { odd++; } }
return odd < 2 ? s.length() : s.length() - odd + 1; }}410. Split Array Largest Sum
https://leetcode.com/problems/split-array-largest-sum/
如果m和nums的个数相等,那么每个数组都是一个子数组,所以返回nums中最大的数字即可;如果m为1,那么整个nums数组就是一个子数组,返回nums所有数字之和,所以对于其他有效的m值,返回的值必定在上面两个值之间,可以用二分搜索法来做。
判断和是否满足条件的办法:遍历找到数组的最大值作为初始minVal,数组的和为初始maxVal,选择mid作为二者的平均值。从数组头开始遍历,计算累加和大于等于mid的子数组总量。如果子数组的数量大于m,说明子数组太多了,也就是说区间的和定义的太小了,应该增大minVal的值,因此把minVal设置为mid + 1;相反,则要降低maxVal的值。
class Solution { public int splitArray(int[] nums, int m) { int minVal = Integer.MIN_VALUE, maxVal = 0; for (int num : nums) { minVal = Math.max(minVal, num); maxVal += num; }
while (minVal < maxVal) { int mid = minVal + (maxVal - minVal) / 2;
if (canSplit(mid, nums, m)) { maxVal = mid; } else { minVal = mid + 1; } }
return minVal; }
private boolean canSplit(int upperBoundSubarraySum, int[] nums, int m) { int curSubarraySum = 0, subarrayCount = 1;
for (int num : nums) { curSubarraySum += num;
if (curSubarraySum > upperBoundSubarraySum) { subarrayCount++; curSubarraySum = num; if (subarrayCount > m) { return false; } } }
return true; }}411. Minimum Unique Word Abbreviation
https://leetcode.com/problems/minimum-unique-word-abbreviation/
尝试利用位掩码来快速表示不同字符串之间的差别。具体而言,用一个整数diff表示字符串word和target之间的差别,假设要比较它们第i位的字符是否相等,如果不相等,diff就加上1 << i。解题流程如下:
- 对于给定的
target,在dic中寻找和target长度相同的word,记录下表示word和target之间的差异的diff,用一个Set存起来 - 如果经过第一步后Set为空,说明不存在缩写冲突的可能,当前字符串直接使用字符串长度作为最短缩写
- 寻找最大的冲突表示整数
bits。具体而言,需要穷举0 ~1 << targetLen之间的数与diff进行比较,跳过完全与diff完全不相等的情况,并在剩下的情况中用位运算寻找bits - 根据最大冲突位数生成最终的缩略字符串。将
bits和1 << i逐位进行对比,如果这当前位相等,说明此时出现冲突,不能用数字缩略,必须将之前的缩略长度添加到缩略字符串后清空,再加上当前位置的字符
class Solution { public String minAbbreviation(String target, String[] dic) { Set<Integer> diffs = new HashSet<>(); int targetLen = target.length();
for (String word : dic) { if (word.length() == targetLen) { // mark each bit's difference between target and word int diff = 0; for (int i = 0; i < targetLen; i++) { if (target.charAt(i) != word.charAt(i)) { diff += 1 << i; } }
diffs.add(diff); } }
// all words have different length, then return the shortest abbr if (diffs.isEmpty()) { return Integer.toString(targetLen); }
int bits = Integer.MIN_VALUE; int max = Integer.MIN_VALUE; for (int i = 0; i < (1 << targetLen); i++) { boolean flag = true;
// skip cases that are totally different from dic for (int diff : diffs) { if ((i & diff) == 0) { flag = false; break; } }
// get maximum conflicting bits if (flag) { int sum = 0; for (int j = 0; j < targetLen - 1; j++) { if (((i >> j) & 3) == 0) { sum++; } }
if (sum > max) { max = sum; bits = i; } } }
StringBuilder result = new StringBuilder(); int count = 0; for (int i = 0; i < targetLen; i++) {
// current pos cannot be replaced with an abbr digit, must use alphabet instead if ((bits & (1 << i)) != 0) { if (count != 0) { result.append(count); count = 0; } result.append(target.charAt(i)); } else { count++; } }
if (count != 0) { result.append(count); } return result.toString(); }}412. Fizz Buzz
https://leetcode.com/problems/fizz-buzz/
直接使用常规循环判断法解题即可。
class Solution { public List<String> fizzBuzz(int n) { List<String> result = new ArrayList<>(); for (int i = 1; i <= n; i++) { if (i % 3 == 0 && i % 5 == 0) { result.add("FizzBuzz"); } else if (i % 3 == 0) { result.add("Fizz"); } else if (i % 5 == 0) { result.add("Buzz"); } else { result.add(String.valueOf(i)); } }
return result; }}如果想让代码更加优雅,并且在未来具有良好的拓展性,可以设计一个TreeMap,存放所有的编码规则,在循环遍历时直接去Map中找当前数字所有匹配的规则。
class Solution { public List<String> fizzBuzz(int n) { // define the order of how you want to concat the words here using lambda comparator TreeMap<Integer, String> map = new TreeMap<>((a, b) -> a - b); map.put(3, "Fizz"); map.put(5, "Buzz"); // map.put(7, "Yuzz"); // map.put(9, "Mozz"); // add more encoding options here ...
List<String> result = new LinkedList(); for (int i = 1; i <= n; i++) { StringBuilder encoded = new StringBuilder(); for (int num : map.keySet()) { if (i % num == 0) { encoded.append(map.get(num)); } }
result.add((encoded.length() == 0) ? String.valueOf(i) : encoded.toString()); }
return result; }}413. Arithmetic Slices
https://leetcode.com/problems/arithmetic-slices/
循环遍历数组,只需要跟踪当前下标数与之前的两个数,判断这三个数能不能成为一组新的slice。如果可以,当前newSlice数量加1,并且注意实时更新结果数量时,要添加的是目前的newSlice数量而不是1(因为当前newSlice每加一,就代表前面已有的slice都可以再拓展一位当前数,组成一个新的slice)。而一旦三个数中断组成slice,newSlice全要被清零。
class Solution { public int numberOfArithmeticSlices(int[] A) { int newSlice = 0, result = 0;
for (int i = 2; i < A.length; i++) { if (A[i-1] - A[i-2] == A[i] - A[i-1]) { newSlice++; result += newSlice; } else { newSlice = 0; } }
return result; }}414. Third Maximum Number
https://leetcode.com/problems/third-maximum-number/
使用三变量法进行统计,注意变量初始化时声明为null更加严谨。
class Solution { public int thirdMax(int[] nums) { Integer max1 = null; Integer max2 = null; Integer max3 = null;
for (Integer n : nums) { if (n.equals(max1) || n.equals(max2) || n.equals(max3)) { continue; }
if (max1 == null || n > max1) { max3 = max2; max2 = max1; max1 = n; } else if (max2 == null || n > max2) { max3 = max2; max2 = n; } else if (max3 == null || n > max3) { max3 = n; } }
return max3 == null ? max1 : max3; }}如果要拓展成更加一般性的第k大数字,可以使用优先队列+集合。
class Solution { public int thirdMax(int[] nums) { PriorityQueue<Integer> pq = new PriorityQueue<>(); Set<Integer> set = new HashSet<>();
for (int n : nums) { if (set.add(n)) { pq.offer(n);
if (pq.size() > 3) { pq.poll(); } } }
if (pq.size() == 2) { pq.poll(); } return pq.peek(); }}415. Add Strings
https://leetcode.com/problems/add-strings/
仍然是模拟加法,从字符串尾部开始,取数 -> 做加法 -> 得到进位,循环直到两个数都完整被遍历过一次。
class Solution { public String addStrings(String num1, String num2) { StringBuilder result = new StringBuilder(); int carry = 0; int len1 = num1.length() - 1; int len2 = num2.length() - 1;
while (len1 >= 0 || len2 >= 0) { int x = len1 >= 0 ? num1.charAt(len1) - '0' : 0; int y = len2 >= 0 ? num2.charAt(len2) - '0' : 0; int sum = (x + y + carry) % 10; carry = (x + y + carry) / 10; result.append(sum); len1--; len2--; }
if (carry != 0) { result.append(carry); } return result.reverse().toString(); }}416. Partition Equal Subset Sum
https://leetcode.com/problems/partition-equal-subset-sum/
使用DFS,把问题转化成选若干个数,使得总和恰好为sum的一半。注意考虑两种情况:
- 选择当前数字,并修改下一次递归的
target - 不选择当前数字,而是找到下一个不同的数字并选择它进行递归
class Solution { public boolean canPartition(int[] nums) { int sum = 0; for (int val: nums) sum += val; if (sum % 2 == 1) { return false; } sum /= 2;
Boolean[] dp = new Boolean[sum + 1]; dp[0] = true; boolean result = canPartition(sum, 0, nums, dp); return result; }
private boolean canPartition(int sum, int pos, int[] nums, Boolean[] dp) { if (sum == 0) { return true; } if (sum < 0 || pos == nums.length) { return false; } if (dp[sum] != null) { return dp[sum]; }
dp[sum] = canPartition(sum - nums[pos], pos + 1, nums, dp) || canPartition(sum, pos + 1, nums, dp); return dp[sum]; }}417. Pacific Atlantic Water Flow
https://leetcode.com/problems/pacific-atlantic-water-flow/
使用DFS逆向推导,因为边缘的格子一定能够进入海洋。逆向推导的过程中,用两个二维数组分别表示当前的坐标是否能进入太平洋/大西洋。注意,因为两个海洋对应四条边,因此需要以四条边的每个点做为起点进行DFS。
class Solution { public List<List<Integer>> pacificAtlantic(int[][] matrix) { List<List<Integer>> result = new ArrayList<>(); if (matrix.length == 0 || matrix[0].length == 0) { return result; }
int m = matrix.length; int n = matrix[0].length; boolean[][] pacific = new boolean[m][n]; boolean[][] atlantic = new boolean[m][n];
// top and bottom for (int col = 0; col < n; col++) { dfs(matrix, 0, col, matrix[0][col], pacific); dfs(matrix, m-1, col, matrix[m-1][col], atlantic); }
// left and right for (int row = 0; row < m; row++) { dfs(matrix, row, 0, matrix[row][0], pacific); dfs(matrix, row, n-1, matrix[row][n-1], atlantic); }
// find all grids that meet requirements for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (pacific[i][j] && atlantic[i][j]) { List<Integer> temp = new ArrayList(); temp.add(i); temp.add(j); result.add(temp); } } }
return result; }
private void dfs(int[][] matrix, int r, int c, int preHeight, boolean[][] ocean) { // skip out-of-bound & higher flow & visited cases if (r < 0 || c < 0 || r > matrix.length-1 || c > matrix[0].length-1 || matrix[r][c] < preHeight || ocean[r][c]) { return; }
ocean[r][c] = true; dfs(matrix, r - 1, c, matrix[r][c], ocean); // up dfs(matrix, r + 1, c, matrix[r][c], ocean); // down dfs(matrix, r, c - 1, matrix[r][c], ocean); // left dfs(matrix, r, c + 1, matrix[r][c], ocean); // right }}418. Sentence Screen Fitting
https://leetcode.com/problems/sentence-screen-fitting/
使用数组times记录每一行完成遍历的次数;使用数组nextIndex记录下一行的起始单词对应下标。
中间的循环,本质是遍历单词填满每一行长度的过程,如果填满了就保存下一行的起始下标与这一行的次数,之后跳下一行;如果这一行完成了遍历,还要更新遍历次数。最后,根据rows的长度,顺序累加遍历次数即可,检查完一行需要根据下一行起始下标进行跳转。
class Solution { public int wordsTyping(String[] sentence, int rows, int cols) { // a new line which is starting with certain index in sentence, what is the starting index of next line int[] nextIndex = new int[sentence.length];
// how many times the pointer in current line passes over the last index int[] times = new int[sentence.length];
for (int i = 0; i < sentence.length; i++) { int curLen = 0; int cur = i; int time = 0;
while (curLen + sentence[cur].length() <= cols) { // word + space curLen += sentence[cur].length() + 1; cur++;
// the whole sentence is added for a time if (cur == sentence.length) { cur = 0; time++; } }
// stop at the first word of next line and times of the previous line nextIndex[i] = cur; times[i] = time; }
int result = 0; int cur = 0; // start from the first word in the sentence for (int i = 0; i < rows; i++) { result += times[cur]; cur = nextIndex[cur]; // update start of next row }
return result; }}419. Battleships in a Board
https://leetcode.com/problems/battleships-in-a-board/
因为战舰的构造一定是1*N或者N*1,因此不需要搜索,直接两重循环遍历,检查当前方格是否处在一行或一列’X’中,具体而言只需要循环和左、上邻近方格比较即可。
class Solution { public int countBattleships(char[][] board) { if (board.length == 0 || board[0].length == 0) { return 0; }
int result = 0; for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { if (board[i][j] == '.') { continue; }
// 1 x N or N x 1, thus we can always check left or up grid to determine the same battleship if ((i > 0 && board[i-1][j] == 'X') || (j > 0 && board[i][j-1] == 'X')) { continue; } result++; } }
return result; }}420. Strong Password Checker
https://leetcode.com/problems/strong-password-checker/
记录下缺失的字符类型数量(小写、大写、数字),再用一个数组记录字符串截止到每一位时的重复字符数量。
- 如果字符串太短,检查是否需要补缺失的字符,再看剩下的长度是否达到至少6位
- 删减重复子字符串,根据每位重复字符数量,把大于2的情况删减修改成2
- 如果字符串很长,就先做删减重复子字符串,看删完之后剩下的长度是否还超过20位,并视需要补缺失字符
class Solution { public int strongPasswordChecker(String s) { int result = 0;
// number of missing lowercase letters, uppercase letters and digits int a = 1, A = 1, d = 1;
// number of repeating characters starting at the corresponding position in the string char[] carr = s.toCharArray(); int[] arr = new int[carr.length];
for (int i = 0; i < arr.length;) { char currentChar = carr[i]; if (Character.isLowerCase(currentChar)) a = 0; if (Character.isUpperCase(currentChar)) A = 0; if (Character.isDigit(currentChar)) d = 0;
int j = i; while (i < carr.length && carr[i] == carr[j]) { i++; } arr[j] = i - j; }
int totalMissing = (a + A + d);
// pad length to at least 6 if (arr.length < 6) { result += totalMissing + Math.max(0, 6 - (arr.length + totalMissing)); } else { int overLenCharNum = Math.max(arr.length - 20, 0), leftOverNum = 0; result += overLenCharNum;
for (int k = 1; k < 3; k++) { for (int i = 0; i < arr.length && overLenCharNum > 0; i++) { if (arr[i] < 3 || arr[i] % 3 != (k - 1)) { continue; }
// turn all numbers in the arr array greater than 2 into the form of (3m + 2) arr[i] -= Math.min(overLenCharNum, k); overLenCharNum -= k; } }
for (int i = 0; i < arr.length; i++) { if (arr[i] >= 3 && overLenCharNum > 0) { // minimum number of changes needed to fix the repeating character problem by deletion only int need = arr[i] - 2; arr[i] -= overLenCharNum; overLenCharNum -= need; }
if (arr[i] >= 3) { leftOverNum += arr[i] / 3; } }
result += Math.max(totalMissing, leftOverNum); }
return result; }}421. Maximum XOR of Two Numbers in an Array
https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/
使用循环,设定前i位为可能的最大异或值,之后从set中寻找能匹配的记录。
class Solution { public int findMaximumXOR(int[] nums) { int max = 0, mask = 0;
// find max number to get max length int maxNum = nums[0]; for (int i = 1; i < nums.length; i++) { maxNum = Math.max(maxNum, nums[i]); }
int len = Integer.toBinaryString(maxNum).length();
for (int i = len - 1; i >= 0; i--) { // The mask will grow like 100..000 , 110..000, 111..000, then 1111...111 mask = mask | (1 << i);
HashSet<Integer> set = new HashSet<>();
// only care about the left parts for (int num : nums) { set.add(num & mask); }
// try to find a candidate which can give the greediest try; int tmp = max | (1 << i); for (int prefix : set) { // if a ^ b = c, then a ^ c = b // to get b, we need a ^ c, if a ^ c exisited in our set, then we're good to go if (set.contains(tmp ^ prefix)) { max = tmp; break; } } }
return max; }}422. Valid Word Square
https://leetcode.com/problems/valid-word-square/
把words中每个word提取出来,拷贝到二维数组matrix中,之后直接在matrix根据对角线镜像对称判断是否合法。
class Solution { public boolean validWordSquare(List<String> words) { if (words == null || words.isEmpty()) { return false; } int m = words.size(); char[][] matrix = new char[m][m];
for (int i = 0; i < m; i++) { String word = words.get(i); if (word.length() > m) { return false; } System.arraycopy(word.toCharArray(), 0, matrix[i], 0, word.length()); }
for (int row = 0; row < m; row++) { for (int col = row; col < m; col++) { if (matrix[row][col] != matrix[col][row]) { return false; } } } return true; }}423. Reconstruct Original Digits from English
https://leetcode.com/problems/reconstruct-original-digits-from-english/
所有偶数英文单词都有一个独有的字母;而奇数的单词中虽然没有独有字母,但可以通过排除掉偶数之后得到唯一的情况。
class Solution { public String originalDigits(String s) { // hashmap, map letter into its frequency char[] count = new char[26 + (int)'a']; for (char letter: s.toCharArray()) { count[letter]++; }
// map array, map digit into its frequency int[] out = new int[10];
// letter "z" only in "zero" out[0] = count['z'];
// letter "w" only in "two" out[2] = count['w'];
// letter "u" only in "four" out[4] = count['u'];
// letter "x" only in "six" out[6] = count['x'];
// letter "g" only in "eight" out[8] = count['g'];
// letter "h" only in "three" and "eight" out[3] = count['h'] - out[8];
// letter "f" only in "five" and "four" out[5] = count['f'] - out[4];
// letter "s" only in "seven" and "six" out[7] = count['s'] - out[6];
// letter "i" is in "nine", "five", "six", and "eight" out[9] = count['i'] - out[5] - out[6] - out[8];
// letter "n" is in "one", "nine", and "seven" out[1] = count['n'] - out[7] - 2 * out[9];
StringBuilder result = new StringBuilder(); for (int i = 0; i < 10; i++) { for (int j = 0; j < out[i]; j++) { result.append(i); } } return result.toString(); }}424. Longest Repeating Character Replacement
https://leetcode.com/problems/longest-repeating-character-replacement/
使用滑动窗口,用一个变量maxCount记录当前窗口中出现次数最多的单个字符数量。因为最多只能进行k次替换,因此必须保证当前窗口内的其他字符不能超过k个,否则就必须从窗口头部删去字符。每完成一次窗口的更新,就更新当前窗口的最大长度。
class Solution { public int characterReplacement(String s, int k) { int maxCount = 0; // largest count of a single, unique character in the current window int maxLength = 0; int[] freq = new int[26];
char[] chars = s.toCharArray(); for (int start = 0, end = 0; end < s.length(); end++) { maxCount = Math.max(maxCount, ++freq[chars[end] - 'A']);
// at most k replacements in the window, and there are more characters in the window than we can replace if (end - start + 1 - maxCount > k) { freq[chars[start] - 'A']--; start++; }
maxLength = Math.max(maxLength, end - start + 1); }
return maxLength; }}425. Word Squares
https://leetcode.com/problems/word-squares/
构造Trie,把输入的词都添加到TrieNode中,再构建一个len*len大小的char map用于DFS检查。注意DFS开始时传入的参数是一个TrieNode的数组tns,表示一共组建了n个Trie,tns[i]在DFS中表示对于word[i],下一个可用的字母。每一层DFS中都新开一个数组,拷贝符合的字母树路径。遍历当前的candidate字符串时,根据对称性修改二维char map的值进行匹配比较。如果纵向每个字母都能匹配就开始下一层递归,直到len层长度都匹配完成。
class Solution { class TrieNode { List<String> list; // all words that have this node as prefix TrieNode[] children; TrieNode() { list = new ArrayList<>(); children = new TrieNode[26]; } }
// O(n^len) * len public List<List<String>> wordSquares(String[] words) { TrieNode root = new TrieNode(); for (String w : words) { root.list.add(w); TrieNode tn = root;
for (char ch : w.toCharArray()) { if (tn.children[ch - 'a'] == null) { tn.children[ch - 'a'] = new TrieNode(); } tn = tn.children[ch - 'a'];
// add word to each node in the tree tn.list.add(w); } }
// length of square int len = words[0].length();
// form n tries starting from root, one trie for each word // tns[i] tells us in the DFS, for word[i], which letter is available next TrieNode[] tns = new TrieNode[len]; Arrays.fill(tns, root);
List<List<String>> result = new ArrayList<>(); dfs(0, new char[len][len], result, tns); return result; }
private void dfs(int idx, char[][] m, List<List<String>> result, TrieNode[] tns) { // one word square, add all the strings to result if (idx == m.length) { List<String> wordSquare = new ArrayList<>(idx); for (char[] ca : m) { wordSquare.add(new String(ca)); } result.add(wordSquare); return; }
TrieNode[] tns2 = new TrieNode[tns.length];
//for each word in prefix list of this idx node, recursively build the word square starting from cand for (String cand : tns[idx].list) { int i = idx;
// set m[idx][i] to be cand[idx] the first time in this for loop // for every after iteration i, set m[idx][i] and m[i][idx] to be cand[idx+i] // check if one of the tns2 possible roots contain that ch, otherwise backtrack from dfs to try another candidate word for (; i < m.length; ++i) { char ch = cand.charAt(i); m[idx][i] = ch; if (i > idx) { if (tns[i].children[ch - 'a'] == null) { break; } m[i][idx] = ch;
//tns2 serves as the trie node for each position i that we are trying to fill tns2[i] = tns[i].children[ch - 'a']; } }
// found a match for every letter in this cand word, then dfs at next level if (i == m.length) { dfs(idx + 1, m, result, tns2); } } }}426. Convert Binary Search Tree to Sorted Doubly Linked List
https://leetcode.com/problems/convert-binary-search-tree-to-sorted-doubly-linked-list
解题流程分为2步:
- 用中序遍历,把每一个BST节点之间用双向指针连起来
- 把头尾节点用双向指针连起来
class Solution { Node curr = null;
public Node treeToDoublyList(Node root) { if (root == null) { return root; }
Node dummyHead = new Node(0); curr = dummyHead;
// inorder tranversal by recursion to connect the original BST inOrderConnect(root);
// connect head and tail, notice that current ptr is tail curr.right = dummyHead.right; dummyHead.right.left = curr;
return dummyHead.right; }
private void inOrderConnect(Node node) { if (node == null) { return; }
inOrderConnect(node.left);
// construct doubly link curr.right = node; node.left = curr; curr = node;
inOrderConnect(node.right); }}427. Construct Quad Tree
https://leetcode.com/problems/construct-quad-tree/
深度优先搜索,在四个子区域内不断减半长度,得到的四个新的区域节点,如果这四个区域节点的isLeaf()都返回true,并且节点的val都一致,说明当前节点本身已经是一个子节点了。否则,当前节点就是这四个子节点的父节点。
/*// Definition for a QuadTree node.class Node { public boolean val; public boolean isLeaf; public Node topLeft; public Node topRight; public Node bottomLeft; public Node bottomRight;
public Node() { this.val = false; this.isLeaf = false; this.topLeft = null; this.topRight = null; this.bottomLeft = null; this.bottomRight = null; }
public Node(boolean val, boolean isLeaf) { this.val = val; this.isLeaf = isLeaf; this.topLeft = null; this.topRight = null; this.bottomLeft = null; this.bottomRight = null; }
public Node(boolean val, boolean isLeaf, Node topLeft, Node topRight, Node bottomLeft, Node bottomRight) { this.val = val; this.isLeaf = isLeaf; this.topLeft = topLeft; this.topRight = topRight; this.bottomLeft = bottomLeft; this.bottomRight = bottomRight; }};*/
class Solution { public Node construct(int[][] grid) { return helper(grid, 0, 0, grid.length); }
private Node helper(int[][] grid, int x, int y, int len) { // the minimum grid if (len == 1) { return new Node(grid[x][y] != 0, true); }
Node result = new Node(); Node topLeft = helper(grid, x, y, len / 2); Node topRight = helper(grid, x, y + len / 2, len / 2); Node bottomLeft = helper(grid, x + len / 2, y, len / 2); Node bottomRight = helper(grid, x + len / 2, y + len / 2, len / 2);
if (topLeft.isLeaf && topRight.isLeaf && bottomLeft.isLeaf && bottomRight.isLeaf && topLeft.val == topRight.val && topRight.val == bottomLeft.val && bottomLeft.val == bottomRight.val) { // current grid has the same value result.isLeaf = true; result.val = topLeft.val; } else { // up to parent node result.topLeft = topLeft; result.topRight = topRight; result.bottomLeft = bottomLeft; result.bottomRight = bottomRight; }
return result; }}428. Serialize and Deserialize N-ary Tree
https://leetcode.com/problems/serialize-and-deserialize-n-ary-tree/
核心是使用前序遍历法。序列化时,按照前序遍历顺序,每一个节点提取出val和子节点的个数进行转化,通过 + '0'的操作保证转化后这两个值能用1个字符表示(这里的前提是val和N值都不会很大,例如都是200以内的数)。反序列化时,同样按照前序遍历,把字符转化为具体的值。可以使用一个变量index[1]记录当前处理序列化字符串的下标。
/*// Definition for a Node.class Node { public int val; public List<Node> children;
public Node() {}
public Node(int _val) { val = _val; }
public Node(int _val, List<Node> _children) { val = _val; children = _children; }};*/
class Codec { // Encodes a tree to a single string. public String serialize(Node root) { StringBuilder sb = new StringBuilder(); serialize(root, sb); return sb.toString(); }
private void serialize(Node node, StringBuilder sb) { if (node == null) { return; }
// append value char sb.append((char) (node.val + '0'));
// append children size char sb.append((char) (node.children.size() + '0'));
for (Node n : node.children) { serialize(n, sb); } }
// Decodes your encoded data to tree. public Node deserialize(String data) { if (data == null || data.length() == 0) { return null; } return deserialize(data, new int[1]); }
Node deserialize(String data, int[] index) { int val = data.charAt(index[0]) - '0'; int childrenSize = data.charAt(++index[0]) - '0';
Node node = new Node(val, new ArrayList<Node>()); for (int i = 0; i < childrenSize; i++) { index[0]++; node.children.add(deserialize(data, index)); }
return node; }}429. N-ary Tree Level Order Traversal
https://leetcode.com/problems/n-ary-tree-level-order-traversal/
与二叉树层次遍历类似,仍然是使用队列+BFS的思路,队列弹出当前节点时,遍历把所有子节点添加到队列中即可。
/*// Definition for a Node.class Node { public int val; public List<Node> children;
public Node() {}
public Node(int _val) { val = _val; }
public Node(int _val, List<Node> _children) { val = _val; children = _children; }};*/
class Solution { public List<List<Integer>> levelOrder(Node root) { List< List<Integer> > result = new ArrayList<>(); if (root == null) { return result; }
Queue<Node> queue = new LinkedList<>(); queue.offer(root);
while (!queue.isEmpty()) { List<Integer> curLevel = new ArrayList<>();
// these are all nodes in current level int currentLevelSize = queue.size(); for (int i = 0; i < currentLevelSize; i++) { Node cur = queue.poll(); curLevel.add(cur.val);
// add current node's children to queue for (Node child : cur.children) { queue.offer(child); } }
// finish a level, add current level's value to result result.add(curLevel); }
return result; }}430. Flatten a Multilevel Doubly Linked List
https://leetcode.com/problems/flatten-a-multilevel-doubly-linked-list/
建立一个栈,如果遇到某个节点存在child指针,就把这个节点的next指针存到栈里,以待日后重新返回这一层时不迷路。之后,把这个节点和child指针指向的节点双向连起来,再把child指针删掉。
/*// Definition for a Node.class Node { public int val; public Node prev; public Node next; public Node child;};*/
class Solution { public Node flatten(Node head) { if (head == null) { return null; }
Node dummyHead = new Node(0, null, head, null); Node cur, prev = dummyHead;
// set a stack and push head into it Stack<Node> stack = new Stack(); stack.push(head);
while (!stack.isEmpty()) { cur = stack.pop();
// link previous and current node prev.next = cur; cur.prev = prev;
// push next node or child node if (cur.next != null) stack.push(cur.next); if (cur.child != null) { stack.push(cur.child);
// unlink current node with child cur.child = null; }
// move pointers forward prev = cur; }
// link first node with dummyHead to make a valid doubly linked list dummyHead.next.prev = null;
return dummyHead.next; }}431. Encode N-ary Tree to Binary Tree
https://leetcode.com/problems/encode-n-ary-tree-to-binary-tree/
把N叉树转化成二叉树时,总是将当前节点的第一个子节点转化成左儿子,剩下的儿子递归组成左儿子的右子树。
/*// Definition for a Node.class Node { public int val; public List<Node> children;
public Node() {}
public Node(int _val) { val = _val; }
public Node(int _val, List<Node> _children) { val = _val; children = _children; }};*/
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */
class Codec { // Encodes an n-ary tree to a binary tree. public TreeNode encode(Node root) { if (root == null) { return null; } TreeNode result = new TreeNode(root.val);
// next level -> left if (root.children.size() > 0) { result.left = encode(root.children.get(0)); }
// same level -> right TreeNode cur = result.left; for (int i = 1; i < root.children.size(); i++) { cur.right = encode(root.children.get(i)); cur = cur.right; } return result; }
// Decodes your binary tree to an n-ary tree. public Node decode(TreeNode root) { if (root == null) { return null; } Node result = new Node(root.val, new ArrayList<>());
// first child is root.left, all other children are on the right subtree TreeNode cur = root.left; while (cur != null) { result.children.add(decode(cur)); cur = cur.right; }
return result; }}432. All O`one Data Structure
https://leetcode.com/problems/all-oone-data-structure/
对于插入和删除操作,使用HashMap可以轻松达到O(1)复杂度。而在此基础上,要想实现取最值也是O(1)复杂度,还需要借助双向链表LinkedHashSet。人工维护一个按照value大小排序的链表,每个链表节点中包含一个LinkedHashSet,里面存储了所有value为当前链表节点值的key。每一次插入删除节点时,更新链表以及节点的LinkedHashSet。具体而言,需要时刻跟踪链表的head和tail两个头尾节点:
- 插入
key时,检查这个节点是不是新的节点- 如果是,把
head和tail都指向这个节点,更新节点中的Set以及全局Map - 如果不是,这个
key对应的节点value自增。由于发生了自增,需要把key从之前的节点Set中踢掉,然后用新value创建一个新节点,把新节点插到原来节点的后面,再把key插到新节点的key中去。最后更新全局Map
- 如果是,把
- 删除
key时,需要确认这个key是否存在,之后- 如果是头节点,直接更新
head和tail - 否则,由于发生了自减,把把
key从原来节点Set中踢掉,然后用新value创建一个新节点,把新节点插到原来节点的前面,再把key插到新节点的key中去。最后更新全局Map
- 如果是头节点,直接更新
- 获取最大最小值,直接从链表
head和tail节点中的Set中提取,调用迭代器方法即可
class AllOne {
Map<String, Node> map; Node head, tail;
class Node { int val; LinkedHashSet<String> keys; Node next, pre; public Node(int val) { this.val = val; keys = new LinkedHashSet(); next = pre = null; } }
/** Initialize your data structure here. */ public AllOne() { map = new HashMap(); head = tail = null; }
/** Inserts a new key <Key> with value 1. Or increments an existing key by 1. */ public void inc(String key) { if (map.containsKey(key)) { // get old node Node node = map.get(key); int val = node.val;
// remove old key in the old node node.keys.remove(key);
// add new node with new val if (node.next == null || node.next.val != val + 1) { Node newNode = new Node(val + 1); newNode.pre = node; newNode.next = node.next; node.next = newNode; if (newNode.next != null) { newNode.next.pre = newNode; } }
// add new key to the next node node.next.keys.add(key);
// update node map.put(key, node.next);
// update head if (node.keys.isEmpty()) { if (node == head) { head = node.next; } node.next.pre = node.pre; if (node.pre != null) { node.pre.next = node.next; } }
// update tail if (tail == node) { tail = node.next; } } else { if (head == null || head.val > 1) { Node node = new Node(1); node.next = head; if (head != null) { head.pre = node; } head = node; if (tail == null) { tail = node; } } head.keys.add(key); map.put(key, head); } }
/** Decrements an existing key by 1. If Key's value is 1, remove it from the data structure. */ public void dec(String key) { if (!map.containsKey(key)) { return; }
// get old node Node node = map.get(key); int val = node.val;
// remove old val from old node node.keys.remove(key);
// remove old key from map map.remove(key);
// if it's head, update head and tail if (val == 1) { if (node.keys.isEmpty()) { if (head == node) { head = node.next; } if (head != null) { head.pre = null; } if (tail == node) { tail = null; } } } else { // add new node at pre position if (node.pre == null || node.pre.val != val - 1) { Node newNode = new Node(val - 1); newNode.next = node; newNode.pre = node.pre; node.pre = newNode; if (newNode.pre != null) { newNode.pre.next = newNode; } }
// add new key to the newNode or pre node node.pre.keys.add(key);
// update node map.put(key, node.pre);
// update tail if (node.keys.isEmpty()) { if (tail == node) { tail = node.pre; } node.pre.next = node.next; if (node.next != null) { node.next.pre = node.pre; } }
// update head if (head == node) { head = node.pre; } } }
/** Returns one of the keys with maximal value. */ public String getMaxKey() { return tail == null ? "" : tail.keys.iterator().next(); }
/** Returns one of the keys with Minimal value. */ public String getMinKey() { return head == null ? "" : head.keys.iterator().next(); }}
/** * Your AllOne object will be instantiated and called as such: * AllOne obj = new AllOne(); * obj.inc(key); * obj.dec(key); * String param_3 = obj.getMaxKey(); * String param_4 = obj.getMinKey(); */433. Minimum Genetic Mutation
https://leetcode.com/problems/minimum-genetic-mutation/
使用回溯算法,遍历bank,每一次mutate一个字母,看是否能得到end;如果不能,退回尝试mutate其他字母。
class Solution { private int count = Integer.MAX_VALUE;
public int minMutation(String start, String end, String[] bank) { recurse(start, end, bank, 0, new HashSet<String>()); return count == Integer.MAX_VALUE ? -1 : count; }
private void recurse(String start, String end, String[] bank, int mutateNum, Set<String> visited) { if (start.equals(end)) { count = Math.min(count, mutateNum); }
for (String e : bank) { int diff = 0;
// only 1 diff is allowed to mutate to the next state for (int i = 0; i < e.length(); i++) { if (start.charAt(i) != e.charAt(i)) { diff++; if (diff > 1) { break; } } }
// backtrack, try to mutate to next state then come back if (diff == 1 && !visited.contains(e)) { visited.add(e); recurse(e, end, bank, mutateNum + 1, visited); visited.remove(e); } } }}434. Number of Segments in a String
https://leetcode.com/problems/number-of-segments-in-a-string/
去掉空白部分,再分割字符串。
class Solution { public int countSegments(String s) { s = s.trim(); if (s == null || s.length() == 0) { return 0; }
String[] result = s.split("\\s+"); return result.length; }}435. Non-overlapping Intervals
https://leetcode.com/problems/non-overlapping-intervals/
按照每个区间的尾元素,从小到大排序。之后遍历所有区间,如果相邻两个区间不重叠,就把不重叠的区间个数加1。最后将所有区间个数减去不重叠的区间个数,得到需要移除的区间个数。
class Solution { public int eraseOverlapIntervals(int[][] intervals) { if (intervals == null || intervals.length == 0 || intervals[0].length == 0) { return 0; }
// sort by end element Arrays.sort(intervals, (int[] a, int[] b) -> { return a[1] - b[1]; });
int end = intervals[0][1]; int nonOverlap = 1;
for (int i = 1; i < intervals.length; i++) { if (intervals[i][0] >= end) { end = intervals[i][1]; nonOverlap++; } }
return intervals.length - nonOverlap; }}436. Find Right Interval
https://leetcode.com/problems/find-right-interval/
使用桶,找出最小的start和最大的end,确定桶的大小。因为每个区间的start是唯一的,因此能够根据每个区间的start值,判断应该丢在哪个桶内。处理之后,每个桶里存放的就是下一个区间的起始值。注意部分桶可能为空,因此需要用相邻非空桶里的值覆盖。
class Solution { public int[] findRightInterval(int[][] intervals) { if (intervals == null || intervals.length == 0 || intervals[0].length == 0) { return new int[] {}; }
int[] result = new int[intervals.length];
// find min start and max end int min = intervals[0][0], max = intervals[0][1]; for (int i = 1 ; i < intervals.length; i++) { min = Math.min(min, intervals[i][0]); max = Math.max(max, intervals[i][1]); }
int[] buckets = new int[max - min + 1]; Arrays.fill(buckets, -1); for (int i = 0; i < intervals.length; i++) { buckets[intervals[i][0] - min] = i; }
for (int i = buckets.length - 2; i >= 0; i--) { if (buckets[i] == -1) { buckets[i] = buckets[i+1]; } }
for (int i = 0; i < intervals.length; i++) { result[i] = buckets[intervals[i][1] - min]; }
return result; }}437. Path Sum III
https://leetcode.com/problems/path-sum-iii/
使用一个Hashmap,统计每一条path sum以及对应出现的次数。遍历每一个节点的时候,可能出现的path sum由三部分组成:
- 以某个上层节点起始,当前节点结束的路径
- 左子树中的路径
- 右子树中的路径
在每完成一次递归调用返回上层时,需要注意把map中当前path sum对应的出现次数减少1。(可以理解为,通过左子树累加出来的次数,并不能对右子树产生效果,反之同理)
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public int pathSum(TreeNode root, int sum) { if (root == null) { return 0; }
// <root to current node's sum, frequency> Map<Long, Integer> map = new HashMap<>(); map.put(0L, 1); return findPathSum(root, 0, sum, map); }
private int findPathSum(TreeNode node, long sum, int target, Map<Long, Integer> map) { if (node == null) { return 0; }
// update the prefix sum by adding the current node's val sum += node.val;
// get the number of valid path, ended by the current node int numPathToCurr = map.getOrDefault(sum - target, 0);
// update the map with the current sum map.put(sum, map.getOrDefault(sum, 0) + 1);
// the total number of valid paths in the subtree rooted at the current node's left child // the total number of valid paths in the subtree rooted at the current node's right child // the number of valid paths ended by the current node int result = numPathToCurr + findPathSum(node.left, sum, target, map) + findPathSum(node.right, sum, target, map);
// restore the map, as the recursion goes from the bottom to the top map.put(sum, map.get(sum) - 1);
return result; }}438. Find All Anagrams in a String
https://leetcode.com/problems/find-all-anagrams-in-a-string/
使用双指针滑动窗口,用一个变量count统计频率为正的字母个数。移动滑动窗口时,如果count为0,说明窗口中能组成一个anagram。
class Solution { public List<Integer> findAnagrams(String s, String p) { List<Integer> result = new ArrayList<>(); if (s == null || p == null || s.length() == 0 || p.length() == 0) { return result; }
int[] arr = new int[26]; for (int i = 0; i < p.length(); i++) { arr[p.charAt(i) - 'a']++; }
int left = 0, right = 0; int count = p.length(); while (right < s.length()) { // character is existing in the map, update count and map if (arr[s.charAt(right) - 'a'] >= 1) { count--; }
arr[s.charAt(right) - 'a']--; right++;
// sum of all frequency >= 0, and count is the sum of all positive frequency // so, every frequency is 0 if and only if count is 0 if (count == 0) { result.add(left); }
// window size is p's length, shrink the window size from the begin if (right - left == p.length()) { // restore a freq, and only update if in the map if (arr[s.charAt(left) - 'a'] >= 0) { count++; } arr[s.charAt(left) - 'a']++; left++; } }
return result; }}439. Ternary Expression Parser
https://leetcode.com/problems/ternary-expression-parser/
统计?和:的配对情况,遍历时如果遇到的?和:数量相同,就检查?之前是T还是F,并根据相应true或false情况,截取对应的子字符串进行递归检查。
class Solution { public String parseTernary(String expression) { for (int i = 0; i < expression.length(); i++) { if (expression.charAt(i) == '?') { int flag = 1; for (int j = i + 1; j < expression.length(); j++) { if (expression.charAt(j) == '?') { flag++; } if (expression.charAt(j) == ':') { flag--; }
// ? and : are equal now if (flag == 0) { if (expression.charAt(i-1) == 'T') { return parseTernary(expression.substring(i+1, j)); } else { return parseTernary(expression.substring(j+1, expression.length())); } } } } }
return expression; }}440. K-th Smallest in Lexicographical Order
https://leetcode.com/problems/k-th-smallest-in-lexicographical-order/
可以把起始的1~9看成9棵树的根节点。对于根节点1,它有10个儿子,分别是10, 11, 12, … 19,而这10个子节点分别又可以有10个子节点,例如节点10有儿子100, 101, 102…以此类推,只要没有超过范围n,每个节点都可以有10个子节点,因此问题也就转化为了十叉树的先后遍历问题。
要确定每棵树的大小,其实就是确定以当前节点为根,子树中节点中的个数。观察发现,对于节点i,子节点中最小的一个节点一定是10*i。通过逐层*10,能够确定每一层的节点大小范围,进而确定每一层的节点个数,由此得到树的大小。之后,将树的大小与k进行判断,如果树的大小小于k,说明目标值并不在当前的树中,直接跳过;否则,才继续递归查找子树。
class Solution { public int findKthNumber(int n, int k) { long cur = 1;
while (k > 1) { long count = findSubTreeCount(cur, n);
if (count <= k - 1) { // travel to next tree k -= count; cur = cur + 1; } else { // travel to leftmost child cur = cur * 10; k -= 1; } }
return (int)cur; }
private long findSubTreeCount(long cur, int n) { long count = 0; long upper = cur + 1;
while (cur <= n) { // number of nodes in this level count += Math.min(n + 1, upper) - cur; cur = cur * 10; upper = upper * 10; }
return count; }}441. Arranging Coins
https://leetcode.com/problems/arranging-coins/
解方程即可,注意考虑溢出问题。
class Solution { public int arrangeCoins(int n) { return (int)((-1 + Math.sqrt(1 + 8 * (long)n)) / 2); }}442. Find All Duplicates in an Array
https://leetcode.com/problems/find-all-duplicates-in-an-array/
由于数组中的数范围都在1到n之间,因此可以遍历数组做一个映射,遇到数字num的时候,把nums[abs(num)] - 1对应的数翻转成负数。在翻转前进行判断,如果已经是负数了,说明之前遇到过num,这就是一个重复数。
class Solution { public List<Integer> findDuplicates(int[] nums) { List<Integer> result = new ArrayList<>();
for (int i = 0; i < nums.length; i++) { int index = Math.abs(nums[i]) - 1; if (nums[index] < 0) { result.add(index + 1); } nums[index] = -nums[index]; }
return result; }}443. String Compression
https://leetcode.com/problems/string-compression/
由于限定只能用O(1)的空间,因此只能用双指针遍历输入字符数组,右边的处理每个字符,左边的负责原地修改数组内容。解题流程分为3步:
- 遇到相同的字符就用循环跳过,并统计出现的相同字符个数,直到遇上下一个不同的字符
- 把当前处理的字符拷贝到数组左侧
- 如果当前的字符被压缩,把重复的个数转化成字符串,添加到字符后面
class Solution { public int compress(char[] chars) { int result = 0, index = 0;
while (index < chars.length) { char currentChar = chars[index]; int count = 0;
// compress same character while (index < chars.length && chars[index] == currentChar) { index++; count++; }
// add current char to array chars[result] = currentChar; result++;
// convert count to string format then add if (count != 1) { for (char c : Integer.toString(count).toCharArray()) { chars[result] = c; result++; } } }
return result; }}444. Sequence Reconstruction
https://leetcode.com/problems/sequence-reconstruction/
使用一个数组index记录下nums中每个元素的下标。之后遍历seqs中的每个list,进行如下检查:
- list中的每个数必须在
[1, n]的范围内 - 任意list中,前一个数在
nums中的下标必须小于后一个数在nums中的下标,保证拓扑序的一致性 - 如果前一个数在
nums中的下标 + 1为后一个数在nums中的下标,便能够保证这两个数的拓扑序的唯一性。必须保证所有相邻两数之间都能满足这一拓扑序的唯一性,才能最终覆盖序列的所有数
class Solution { public boolean sequenceReconstruction(int[] nums, List<List<Integer>> seqs) { if (seqs == null || seqs.size() == 0) { return false; } int len = nums.length; boolean[] covered = new boolean[len + 1];
// for each element in nums, record its index int[] index = new int[len + 1]; for (int i = 0; i < len; i++) { index[nums[i]] = i; }
for (List<Integer> list : seqs) { for (int i = 0; i < list.size(); i++) { int cur = list.get(i);
// each number should be in the range of [1, n] if (cur < 1 || cur > nums.length) { return false; }
// previous element must appear before current element in nums if (i == 0) { continue; } int prev = list.get(i - 1); if (index[prev] >= index[cur]) { return false; }
// ensure one-by-one order in index if (!covered[prev] && index[prev] + 1 == index[cur]) { covered[prev] = true; } } }
// must cover all numbers for (int i = 0; i < nums.length - 1; i++) { if (covered[nums[i]] == false) { return false; } }
return true; }}445. Add Two Numbers II
https://leetcode.com/problems/add-two-numbers-ii/
由于限制不能反转链表,因此只能从高位开始按顺序递归累加。先提前获取两个链表的长度,确保能先从长的链表开始遍历,每完成一次遍历,更新两个链表的长度差,判断此时长链表是否与短链表的最高位齐平。遍历的时候有两种情况:
- 单独遍历长链表节点时,节点的值由长链表节点的值 + 进位组成。
- 同时遍历双链表节点时,节点的值由两个链表节点的值 + 进位组成
当前位调用递归的时候,能够获取后面所有位数的结果post,并同时携带了进位信息。注意,进位信息保存在post的最前一个节点,并不需要额外用一个变量储存。同理,全部递归完成后,处理第一个节点的进位也是用相同的方法。
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { int size1 = getListLength(l1); int size2 = getListLength(l2); ListNode head = new ListNode(1);
// make sure l1.length >= l2.length head.next = size1 < size2 ? helper(l2, l1, size2 - size1) : helper(l1, l2, size1 - size2);
// handle the first digit if (head.next.val > 9) { head.next.val = head.next.val % 10; return head; }
return head.next; }
public int getListLength(ListNode l) { int len = 0; while (l != null) { l = l.next; len++; } return len; }
public ListNode helper(ListNode l1, ListNode l2, int lenDiff) { if (l1 == null) { return null; }
ListNode current = lenDiff == 0 ? new ListNode(l1.val + l2.val) : new ListNode(l1.val); ListNode post = lenDiff == 0 ? helper(l1.next, l2.next, 0) : helper(l1.next, l2, lenDiff - 1);
// handle carry if (post != null && post.val > 9) { current.val += 1; post.val = post.val % 10; }
current.next = post; return current; }}446. Arithmetic Slices II - Subsequence
https://leetcode.com/problems/arithmetic-slices-ii-subsequence/
使用二维dp,其中dp[i][j]表示包含dp[i]和dp[j]的arithmetic序列个数。遍历数组,记录每一个数出现的下标,并把所有下标归类到一个链表中,再把<下标, 链表>映射以HashMap形式存储。之后双重循环,锁定i和j,利用A[k] - A[i] = A[i] - A[j] (k < i < j)这个性质,从HashMap中查找是否存在合适的k值。如果存在,dp[i][j]值的就在dp[k][i]的基础上 + 1,表示新增了一个arithmetic序列。每次完成循环后,当前dp[i][j]新加的值就统计到总的结果中。
class Solution { public int numberOfArithmeticSlices(int[] nums) { int n = nums.length; int[][] dp = new int[n][n];
Map<Long, List<Integer>> map = new HashMap(); for (int i = 0; i < n; i++) { map.putIfAbsent((long)nums[i], new ArrayList()); map.get((long)nums[i]).add(i); }
int result = 0; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { long target = 2 * (long) nums[i] - nums[j];
if (map.containsKey(target)) { for (int k : map.get(target)) { if (k < i) { dp[i][j] += (dp[k][i] + 1); } } }
result += dp[i][j]; } }
return result; }}447. Number of Boomerangs
https://leetcode.com/problems/number-of-boomerangs/
双重循环,两两判断点之间点的距离,固定i并得到当前points[i]和points[j]之间的距离,通过hashmap.merge()方法得到这一轮i循环中这个距离出现的次数,如果已经出现了distCount次,组合数增加就要增加distCount - 1(相当于在加入当前这段距离之前,原来的重复距离每一个都要跟新加入的距离组成配对)。由于每个组合内后两个点顺序可以互换,因此最后总的结果还要乘2。
注意,这里的hashmap.merge()方法作用是依照BiFunction,替换更新key对应的value。在本题中,其时间复杂度是O(1),因为BiFunction是两数求和,而其余的合并底层操作,比如get和remove等也都是O(1)。
class Solution { public int numberOfBoomerangs(int[][] points) { int result = 0; Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < points.length; i++) { for (int j = 0; j < points.length; j++) { if (i == j) { continue; }
// count of current distance between two points int distCount = map.merge(getDist(points[i], points[j]), 1, Integer::sum);
// shake hand rules result += distCount - 1; }
map.clear(); }
// double order, double result return result * 2; }
public int getDist(int a[], int b[]) { return (a[0] - b[0])*(a[0] - b[0]) + (a[1] - b[1])*(a[1] - b[1]); }}448. Find All Numbers Disappeared in an Array
https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/
两次循环,第一次循环访问每个数对应的下标,把下标位置上对应的数加上n。第二次循环直接顺序检查每个下标,如果当前下标对应的值小于n,说明之前并没有被访问修改过,因此说明数组中并不存在对应的数字来跳转访问这个下标。
class Solution { public List<Integer> findDisappearedNumbers(int[] nums) { List<Integer> result = new ArrayList<>(); int n = nums.length;
// modify original number and add n for (int i = 0; i < n; i++) { nums[(nums[i]-1) % n] += n; }
// numbers still less than n are disappeared for (int i = 0; i < n; i++) { if (nums[i] <= n) { result.add(i+1); } }
return result; }}449. Serialize and Deserialize BST
https://leetcode.com/problems/serialize-and-deserialize-bst/
依然是前序遍历编码解码的思想。由于要求编码的结果尽可能紧凑,可以将null的值压缩成n。
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */public class Codec {
// Encodes a tree to a single string. public String serialize(TreeNode root) { StringBuilder sb = new StringBuilder(); serializeHelper(root, sb); return sb.toString(); }
private void serializeHelper(TreeNode node, StringBuilder sb) { if (node == null) { sb.append("n,"); return; } sb.append(node.val).append(","); serializeHelper(node.left, sb); serializeHelper(node.right, sb); }
// Decodes your encoded data to tree. public TreeNode deserialize(String data) { String[] nodes = data.split(","); int[] index = new int[1]; return deserializeHelper(nodes, index); } private TreeNode deserializeHelper(String[] nodes, int[] index) { if (nodes[index[0]].equals("n")) { index[0]++; return null; } TreeNode cur = new TreeNode(Integer.valueOf(nodes[index[0]])); index[0]++; cur.left = deserializeHelper(nodes, index); cur.right = deserializeHelper(nodes, index); return cur; }}
// Your Codec object will be instantiated and called as such:// Codec ser = new Codec();// Codec deser = new Codec();// String tree = ser.serialize(root);// TreeNode ans = deser.deserialize(tree);// return ans;450. Delete Node in a BST
https://leetcode.com/problems/delete-node-in-a-bst/
找到目标节点,从右子树中找到最小的节点进行替换。
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public TreeNode deleteNode(TreeNode root, int key) { if (root == null) { return root; }
// Search in subtrees if (root.val < key) { root.right = deleteNode(root.right, key); return root; } else if (root.val > key) { root.left = deleteNode(root.left, key); return root; }
// No child or only one child if (root.left == null && root.right == null) { return null; } else if (root.left == null) { return root.right; } else if (root.right == null) { return root.left; }
// Two children if (root.right.left == null) { root.right.left = root.left; return root.right; } else { TreeNode smallest = deleteSmallest(root.right); smallest.left = root.left; smallest.right = root.right; return smallest; } }
// Find the smallest node in right subtree and delete it private TreeNode deleteSmallest(TreeNode root) { TreeNode cur = root.left; TreeNode pre = root; while (cur.left != null) { pre = cur; cur = cur.left; } pre.left = cur.right; return cur; }}451. Sort Characters By Frequency
https://leetcode.com/problems/sort-characters-by-frequency/
存储<character, frequency>map序列并按照frequency降序排序,之后按照顺序输出每个字符即可。
class Solution { public String frequencySort(String s) { int[] map = new int[128];
for (char c : s.toCharArray()) { map[c]++; }
// insert <character, frequency> into a priority queue, with descending frequency order PriorityQueue<int[]> q = new PriorityQueue<>((a, b) -> b[1] - a[1]);
// insert only if the char is present in input string for (int i = 0; i < 128; i++) { if (map[i] > 0) { q.offer(new int[] {i, map[i]}); } }
char[] result = new char[s.length()]; int index = 0;
// output characters from queue while (!q.isEmpty()) { int[] entry = q.poll(); char letter = (char) entry[0]; int frequency = entry[1];
for (int i = 0; i < frequency; i++) { result[index] = letter; index++; } }
return String.valueOf(result); }}452. Minimum Number of Arrows to Burst Balloons
https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/
本质上是区间合并问题的变种,先按照xend对每个区间进行排序,之后遍历,如果相邻两个区间没有重叠,就说明这两个区间一定需要2箭分别穿过,总的箭数量+1;反之说明两个区间可以合并,用1箭穿过。
class Solution { public int findMinArrowShots(int[][] points) { if (points == null || points.length == 0) { return 0; }
// sort by x-end coordinate Arrays.sort(points, (a, b) -> { return a[1] == b[1] ? 0 : a[1] > b[1] ? 1 : -1; });
int result = 1; int curEnd = points[0][1]; for (int i = 1; i < points.length;) { // need another arrow to shoot if (points[i][0] > curEnd) { curEnd = points[i][1]; result++; } else { // two balloons can be shot with one arrow, merge them i++; } }
return result; }}453. Minimum Moves to Equal Array Elements
https://leetcode.com/problems/minimum-moves-to-equal-array-elements/
n - 1个数增大1,等同于1个数减少1。因此只要找出数组最小值,再计算其他每个数跟最小值的差值总和是多少即可。
class Solution { public int minMoves(int[] nums) { int result = 0;
// find minimum value of the array int min = Integer.MAX_VALUE; for (int n : nums) { min = Math.min(min, n); }
// n - 1 number increment by 1 = 1 number decrement by 1, and all numbers should match with minimum value for (int i = 0; i < nums.length; i++) { result += nums[i] - min; }
return result; }}454. 4Sum II
https://leetcode.com/problems/4sum-ii/
对4sum进行拆分,先算出nums1、nums2中两两组合能得到的所有sum,并统计每个sum出现的个数,把结果存到map中。再遍历nums3、nums4中所有的两两组合,如果发现map中有对应的key(负值),说明当前4个数字能组成4sum为0的组合。
class Solution { public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) { if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0 || nums3 == null || nums3.length == 0 || nums4 == null || nums4.length == 0) { return 0; }
int result = 0; int len = nums1.length;
// <sum of two numbers, count> Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < len; i++) { for (int j = 0; j < len; j++) { map.compute(nums1[i] + nums2[j], (k, v) -> v == null ? 1 : v + 1); } }
for (int i = 0; i < len; i++) { for (int j = 0; j < len; j++) { if (map.containsKey(-(nums3[i] + nums4[j]))) { result += map.get(-(nums3[i] + nums4[j])); } } }
return result; }}455. Assign Cookies
https://leetcode.com/problems/assign-cookies/
先对两个数组进行排序。遍历s,总是尝试用当前最小size的饼干去满足当前的小孩,如果能满足的话,小孩个数+1。无论是否成功满足,都必须遍历下一块饼干。
class Solution { public int findContentChildren(int[] g, int[] s) { Arrays.sort(g); Arrays.sort(s);
int result = 0; for (int i = 0; result < g.length && i < s.length; i++) { // always try to use the minimum size to satisfy current child if (g[result] <= s[i]) { result++; } }
return result; }}456. 132 Pattern
https://leetcode.com/problems/132-pattern/
单调栈,从数组右侧开始遍历,如果当前数nums[i]比栈顶的元素更大,就把栈顶所有小于等于nums[i]的数弹出,弹出的数作为s3 candidate,而栈顶作为s2 candidate。这一步的目的是确保s2 > s3。在这个前提下,如果又能在遍历时发现nums[i] < s3,则说明nums[i]是合适的s1 candidate。
class Solution { public boolean find132pattern(int[] nums) { int s3 = Integer.MIN_VALUE; Stack<Integer> stack = new Stack<Integer>();
for (int i = nums.length - 1; i >= 0; i--) { if (nums[i] < s3) { // num[i] is s1 candidate return true; } else { // pop out all stack top numbers that are smaller than nums[i]. Left numbers are s2 candidates while (!stack.isEmpty() && nums[i] > stack.peek()) { s3 = stack.pop(); } stack.push(nums[i]); } }
return false; }}457. Circular Array Loop
https://leetcode.com/problems/circular-array-loop/
用DFS,维护每个元素的访问状态,转化为DFS检测环问题。注意需排除长度为1,以及包含前后movement的情况。
class Solution { private int NOT_VISITED = 0; private int VISITING = 1; private int VISITED = 2;
public boolean circularArrayLoop(int[] nums) { int N = nums.length; int[] visited = new int[N]; for (int i = 0; i < N; i++) { if (visited[i] == NOT_VISITED && dfs(i, visited, nums)) { return true; } }
return false; }
// return true if there is a cycle private boolean dfs(int cur, int[] visited, int[] nums) { int N = nums.length; if (visited[cur] == VISITING) { return true; } if (visited[cur] == VISITED) { return false; }
visited[cur] = VISITING;
int next = cur + nums[cur]; next %= N; if (next < 0) { next += N; }
// invalid cycle if the length is 1, or it consists of forward and backward movement if (next == cur || nums[next] * nums[cur] < 0) { visited[cur] = VISITED; return false; }
if (dfs(next, visited, nums)) { return true; } visited[cur] = VISITED;
return false; }}458. Poor Pigs
https://leetcode.com/problems/poor-pigs/
需要至少minutesToTest / minutesToDie + 1轮测试,每一轮测试中的每只猪都要能覆盖到一些bucket,必须要保证通过全部的测试,所有的bucket都被覆盖到。
class Solution { public int poorPigs(int buckets, int minutesToDie, int minutesToTest) { int result = 0;
while (Math.pow(minutesToTest / minutesToDie + 1, result) < buckets) { result += 1; }
return result; }}459. Repeated Substring Pattern
https://leetcode.com/problems/repeated-substring-pattern/
从输入字符串的中间开始,向前遍历到能整除字符串长度的下标,因为这个下标可能是重复pattern的结尾。之后双指针向前移动,确保双指针经过的内容完全一致,直到右指针到达字符串终点。
class Solution { public boolean repeatedSubstringPattern(String s) { char [] chars = s.toCharArray(); int len = chars.length;
for (int i = len / 2; i > 0; i--) { // potential pattern start if (len % i == 0) { int left = 0; int right = i;
// should always match while (right < len && chars[left] == chars[right]) { left++; right++; }
if (right == len) { return true; } } }
return false; }}460. LFU Cache
https://leetcode.com/problems/lfu-cache/
维护3个HashMap,第一个keyToVal用于存储每个输入的(key, value)对;第二个keyToCount用于存储每个key被使用了几次;第三个countToLRUKeys用于存储对于每个使用次数count,分别有哪些key被使用了这么多次,相同使用次数的key用一个HashSet存在一起。
put新的(key, value)时,首先确保capacity有效。之后讨论情况:
- 如果当前
key已经存在,则覆盖原来keyToVal保存的值,直接返回 - 如果这个
key不存在,并且已经没有空余位置,需要从当前使用次数最少的key中踢掉一个,注意踢的时候既要从countToLRUKeys,也要从keyToVal踢,踢完之后,更新keyToVal,并且注意此时的最小使用count一定是1(要么是新key,要么之前的最小值就是1),在这个基础上更新keyToCount和countToLRUKeys。
get(key)时,先确保key在keyToVal中存在。之后从countToLRUKeys中移除掉自己的记录(因为自己的count + 1了),再看情况决定是否需要更新min。最后更新keyToCount,返回keyToVal的取值。
class LFUCache { private int min; private int capacity; private HashMap<Integer, Integer> keyToVal; private HashMap<Integer, Integer> keyToCount; private HashMap<Integer, LinkedHashSet<Integer>> countToLRUKeys;
public LFUCache(int capacity) { this.min = -1; this.capacity = capacity; this.keyToVal = new HashMap<>(); this.keyToCount = new HashMap<>(); this.countToLRUKeys = new HashMap<>(); }
public int get(int key) { if (!keyToVal.containsKey(key)) { return -1; }
int count = keyToCount.get(key);
// remove key from current count (since we will increase count) countToLRUKeys.get(count).remove(key);
// no element is of min count if (count == min && countToLRUKeys.get(count).size() == 0) { min++; }
updateCount(key, count + 1); return keyToVal.get(key); }
public void put(int key, int value) { if (capacity <= 0) { return; }
if (keyToVal.containsKey(key)) { // update key's value keyToVal.put(key, value);
// update key's count get(key); return; }
// new element and no enough space, evict LRU from this min count bucket if (keyToVal.size() >= capacity) { int evictKey = countToLRUKeys.get(min).iterator().next(); countToLRUKeys.get(min).remove(evictKey); keyToVal.remove(evictKey); }
// adding new key and value keyToVal.put(key, value);
// min count must be 1 now min = 1;
// adding new key and min count updateCount(key, min); }
private void updateCount(int key, int count) { keyToCount.put(key, count); if (!countToLRUKeys.containsKey(count)) { countToLRUKeys.put(count, new LinkedHashSet<>()); } countToLRUKeys.get(count).add(key); }}
/** * Your LFUCache object will be instantiated and called as such: * LFUCache obj = new LFUCache(capacity); * int param_1 = obj.get(key); * obj.put(key,value); */461. Hamming Distance
https://leetcode.com/problems/hamming-distance/
要求出不同的位数,其实就是看x和y异或结果中1的个数,可以使用循环右移法,累加出1的总数。
class Solution { public int hammingDistance(int x, int y) { int xor = x ^ y;
int result = 0; for (int i = 0; i < 32; i++) { result += (xor >> i) & 1; }
return result; }}462. Minimum Moves to Equal Array Elements II
https://leetcode.com/problems/minimum-moves-to-equal-array-elements-ii/
将数组排序,让最大和最小的数组成一对,次大和次小的数组成一对,以此类推,累加每组数对的差值。
class Solution { public int minMoves2(int[] nums) { Arrays.sort(nums); int count = 0;
int i = 0, j = nums.length - 1; while (i < j) { count += nums[j] - nums[i]; i++; j--; }
return count; }}463. Island Perimeter
https://leetcode.com/problems/island-perimeter/
检查每个方格,如果是岛,先加上4的边长;之后检查这个岛的左、上是否有相邻岛,如果有相邻就减2边长,因为相邻的两个岛都不需要交界边了。为了避免重复,检查相邻岛时只需要检查左、上即可(右、下也可以)。
class Solution { public int islandPerimeter(int[][] grid) { if (grid == null || grid.length == 0 || grid[0].length == 0) { return 0; }
int result = 0; for (int i = 0; i < grid.length; i++) { for (int j = 0; j < grid[0].length; j++) { if (grid[i][j] == 1) { result += 4;
if (i > 0 && grid[i-1][j] == 1) { result -= 2; } if (j > 0 && grid[i][j-1] == 1) { result -= 2; } } } }
return result; }}464. Can I Win
https://leetcode.com/problems/can-i-win/
排除掉一些特殊情况,比如desiredTotal太小或者太大,或者恰好二者相差为1,其余情况使用DFS,用二进制状态码表示当前已经选择的数字,并且再推导出选择下一个数字后的新状态,逐步深入判断先手玩家能否取胜,再修改对应的DP数组。
class Solution { public boolean canIWin(int maxChoosableInteger, int desiredTotal) { // first player directly win if (maxChoosableInteger >= desiredTotal) { return true; } else if (desiredTotal <= maxChoosableInteger * 2 - 1) { // if max + 1 = desired, first player choose x, second always choose max - x and win return maxChoosableInteger + 1 != desiredTotal; } else if (maxChoosableInteger * (maxChoosableInteger + 1) / 2 < desiredTotal) { // desiredTotal is too large to reach return false; }
return dfs(maxChoosableInteger, 0, desiredTotal, new int[(1 << (maxChoosableInteger))]); }
private boolean dfs(int maxChoosableInteger, int chosen, int remaining, int[] memo) { if (memo[chosen] != 0) { // win case return memo[chosen] == 1; }
for (int i = maxChoosableInteger; i >= 1; i--) { // current state is chosen and we want to try number i, next state is chosen ^ (1 << i - 1). int mask = 1 << (i-1); if ((chosen & mask) != 0) { continue; } if (i >= remaining) { // reach or exceed desired total return true; }
// first player win if (!dfs(maxChoosableInteger, chosen | mask, remaining - i, memo)) { memo[chosen] = 1; return true; } }
// second player win memo[chosen] = -1; return false; }}465. Optimal Account Balancing
https://leetcode.com/problems/optimal-account-balancing/
首先使用两个HashMap统计出每个人的净收入/支出情况。之后使用DFS进行回溯,找到第一个债务不是0的人固定为start,然后在此人后面找到另一个债务也不是0,并且极性与之相反的人,试图让这两个人完成交易互相清算一次债务。以此类推,之后的人按照这个流程,然后根据回溯判断出哪一种方案需要的交易次数最少。
如果要进一步加快运行速度,可以对一些特殊情况进行优化,在开始DFS之前先检查一遍,找出所有双人组,每个双人组中的一个人恰好需要偿还给另一个人所应得的债务。这种交易方式一定是最优的,因为它只需要最少的交易次数,就能让两个人的最终债务情况都变成0。
class Solution { public int minTransfers(int[][] transactions) { // track each person's net balance HashMap<Integer, Integer> map = new HashMap();
for (int[] transaction : transactions) { // Subtract from source map.put(transaction[0], map.getOrDefault(transaction[0], 0) - transaction[2]);
// Add to target map.put(transaction[1], map.getOrDefault(transaction[1], 0) + transaction[2]); }
int[] balances = new int[map.size()]; int idx = 0; for (int key : map.keySet()) { if (map.get(key) != 0) { balances[idx] = map.get(key); idx++; } }
return dfs(0, balances); }
public int dfs(int start, int[] balances) { // find first non-zero. while (start < balances.length && balances[start] == 0) { start++; }
// all debts are cleared if (start == balances.length) { return 0; }
int min = Integer.MAX_VALUE;
// Avoid previous duplicate back-to-back balances. Default is 0, since we check non-zero balances anyway int prev = 0;
// FIRST check all pairs of opposite balances that cancel each other out, before diving deep into DFS. for (int i = start + 1; i < balances.length; i++) { if (balances[start] + balances[i] == 0) { balances[i] += balances[start]; min = Math.min(min, 1 + dfs(start + 1, balances)); balances[i] -= balances[start]; prev = balances[i];
// early termination, since this will be the best settling of debts return min; } }
for (int i = start + 1; i < balances.length; i++) { if (balances[i] != prev && balances[start] * balances[i] < 0) { balances[i] += balances[start]; min = Math.min(min, 1 + dfs(start + 1, balances)); balances[i] -= balances[start]; prev = balances[i]; } }
// If the min is still Integer.MAX_VALUE, we've settled all balances to 0, so the fewest transactions is now 0. return min == Integer.MAX_VALUE ? 0 : min; }}466. Count The Repetitions
https://leetcode.com/problems/count-the-repetitions/
双指针法,移动idx1使得idx1与idx2指向相同字符,分类讨论两种情况,要么是idx2走完了一个s2长度;要么是发现idx1走了一个完整的循环,并用这个idx2长度除以s2长度除以s2重复次数。
class Solution { class Index { int idx1; int idx2; public Index(int i1, int i2) { idx1 = i1; idx2 = i2; } }
public int getMaxRepetitions(String s1, int n1, String s2, int n2) { HashMap<Integer, Index> idxMap = new HashMap();
int len1 = s1.length(), len2 = s2.length(); int idx1 = 0, idx2 = 0; int totalCharsOf1 = len1 * n1; while (idx1 < totalCharsOf1) { int modIdx1 = idx1 % len1; int modIdx2 = idx2 % len2;
if (s1.charAt(modIdx1) == s2.charAt(modIdx2)) { int key = modIdx1 * 1000 + modIdx2;
if (idxMap.containsKey(key)) { Index prev = idxMap.get(key); int d1 = idx1 - prev.idx1; int d2 = idx2 - prev.idx2;
int k = (totalCharsOf1 - idx1) / d1; idx1 += k * d1; idx2 += k * d2; if (idx1 >= totalCharsOf1) { break; } } else { idxMap.put(key, new Index(idx1, idx2)); }
idx2++; }
idx1++; }
return idx2 / len2 / n2; }}467. Unique Substrings in Wraparound String
https://leetcode.com/problems/unique-substrings-in-wraparound-string/
使用一个map,用于统计以每个字母为开头的子字符串的个数。如果给定的原字符串是连续的,那么很显然每遍历一个字符,连续字符串的size就增加1,遍历n个字符之后以起始字符开头的子字符串就是n个,第二个字符开头的子字符串就是n-1个,以此类推。
完成遍历后,对于每个字母做一下比较,比较这个字母是作为一个子字符串头,能生成的数量多,还是作为一个已有连续子字符串的中间元素,能生成的数量多。注意因为是循环数组,需要循环两次,保证map中的每个元素都能被更新。
class Solution { public int findSubstringInWraproundString(String p) { if (p.length() == 0) { return 0; } int[] map = new int[26];
char[] chs = p.toCharArray(); char head = chs[0]; int size = 0; // current consecutive substring length for (int i = 0; i < chs.length; i++) { if (i == 0 || isAdjacent(chs[i - 1], chs[i])) { size++; } else { map[head - 'a'] = Math.max(map[head - 'a'], size); size = 1; head = chs[i]; } }
// last consecutive substring map[head - 'a'] = Math.max(map[head - 'a'], size);
// repeat loop to ensure to update all elements for (int i = 0; i < 26; i++) { map[i % 26] = Math.max(map[i % 26], map[(i + 25) % 26] - 1); }
for (int i = 0; i < 26; i++) { map[i % 26] = Math.max(map[i % 26], map[(i + 25) % 26] - 1); }
int result = 0; for (int num: map) { result += num; } return result; }
private boolean isAdjacent(char a, char b) { return a + 1 == b || a == 'z' && b == 'a'; }}468. Validate IP Address
https://leetcode.com/problems/validate-ip-address/
使用split()函数分割输入IP,之后进行检查:
- 对于IPv4而言,每个部分需要是整数,不能大于255,如果是0长度必须为1;总共只能有4个整数
- 对于IPv6而言,每个部分需要是整数,不能超过65535,长度不能超过4;总共只能有8个整数
注意输入的queryIP可能在结尾包含空字符串,需要在split()中把limit参数设为-1,这样不会忽略空字符串。
class Solution { public String validIPAddress(String queryIP) { if (isIPv4(queryIP)) { return "IPv4"; } else if (isIPv6(queryIP)) { return "IPv6"; }
return "Neither"; }
private boolean isIPv4(String ip) { // trailing empty strings will not be trimmed String[] arr = ip.split("\\.", -1);
for (String a: arr) { try { if (Integer.parseInt(a) > 255 || (a.charAt(0) == '0' && a.length() != 1)) { return false; } } catch (NumberFormatException e) { return false; } }
return arr.length == 4; }
private boolean isIPv6(String ip) { // trailing empty strings will not be trimmed String[] arr = ip.split(":", -1);
for (String a: arr) { try { if (Integer.parseInt(a, 16) > 65535 || a.length() > 4) { return false; } } catch (NumberFormatException e) { return false; } }
return arr.length == 8; }}469. Convex Polygon
https://leetcode.com/problems/convex-polygon/
凸多边形任何一个内角不能超过180度,所以相邻三个点组成的两条相邻边,交点不能在多边形内部。
class Solution { public boolean isConvex(List<List<Integer>> points) { int flag = 0; for (int i = 0; i < points.size(); i++) { int angle = getAngle(points, i);
// collinear if (angle == 0) { continue; }
if (flag == 0) { flag = angle > 0 ? 1 : -1; } else if (flag > 0 != angle > 0) { // angles cannot exceed 180 degree return false; } }
return true; }
private int getAngle(List<List<Integer>> points, int i) { // add modular to inspect last two elements List<Integer> c = points.get(i % points.size()); List<Integer> b = points.get((i + 1) % points.size()); List<Integer> a = points.get((i + 2) % points.size());
return (a.get(1) - b.get(1)) * (b.get(0) - c.get(0)) - (b.get(1) - c.get(1)) * (a.get(0) - b.get(0)); }}470. Implement Rand10() Using Rand7()
https://leetcode.com/problems/implement-rand10-using-rand7/
rand7()是1~7,所以rand7() - 1是0~6,用(rand7() - 1) * 7 + rand7() - 1凑出0~48,丢弃40~48便能得到0~39,对这个结果%10 + 1就是0~10。
一般情况下,如果要用randN()实现randM(),M > N,则应该先生成一个中间的X = N^(b + 1),且randX() = N^b * (randN() - 1) + N^(b - 1) * (randN() - 1) + N^(b - 2) * (randN() - 1) + ... + N^0 * (randN() - 1)。有了randX()之后再舍弃一部分值使得randX()的范围是randM()的倍数,这样就能按比例缩小范围生成randM()。
/** * The rand7() API is already defined in the parent class SolBase. * public int rand7(); * @return a random integer in the range 1 to 7 */class Solution extends SolBase { public int rand10() { int rand40 = 40;
while (rand40 >= 40) { rand40 = (rand7() - 1) * 7 + rand7() - 1; }
return rand40 % 10 + 1; }}471. Encode String with Shortest Length
https://leetcode.com/problems/encode-string-with-shortest-length/
使用HashMap存储每个子字符串的encode结果,加快中间过程推导。循环遍历每个可能子字符串长度,并尝试对于每个子字符串,是子字符串本身更长,还是压缩编码后更长,不断累加起来。
class Solution { private Map<String, String> map = new HashMap<>();
public String encode(String s) { if (map.containsKey(s)) { return map.get(s); } String result = s;
for (int l = 1; l <= result.length(); l++) { int count = countRepeat(s, l);
// minimize temp string's length String temp = s.substring(0, l); if (count > 1) { temp = encode(temp); } int tempLength = temp.length();
// determine whether encoding process does not make the string shorter for (int r = 1; r <= count; r++) { StringBuilder builder = new StringBuilder();
// digit + [ + ], or raw temp string if (tempLength + 3 < tempLength * r) { builder.append(r).append("[").append(temp).append("]"); } else { builder.append(s, 0, r * l); }
builder.append(encode(s.substring(r * l))); if (builder.length() < result.length()) { result = builder.toString(); } } }
map.put(s, result); return result; }
private int countRepeat(String input, int max) { int i = 1; while ((i + 1) * max <= input.length()) { for (int j = 0; j < max; j++) { if (input.charAt(j) != input.charAt(max * i + j)) { return i; } } i++; }
return i; }}472. Concatenated Words
https://leetcode.com/problems/concatenated-words/
使用Trie,先把所有words按长短排序,把短词优先插入Trie中。之后的长词进来可以尝试进行匹配,长词匹配完到一个短词之后,检查是否之后的部分还能匹配上其他短词,直到整个词都匹配完毕。
class Solution { public List<String> findAllConcatenatedWordsInADict(String[] words) { Arrays.sort(words, Comparator.comparingInt(String::length));
Trie trie = new Trie(); List<String> result = new ArrayList<String>(); for (String word : words) { if (word.length() == 0) { continue; } if (trie.search(word)) { result.add(word); } else { trie.insert(word); } }
return result; }}
class Trie { private static class Node { Node[] children; boolean isEnd;
Node() { children = new Node[26]; isEnd = false; } }
private Node root = new Node();
public void insert(String key) { Node curr = root; for (int i = 0; i < key.length(); i++) { int index = key.charAt(i) - 'a'; if (curr.children[index] == null) { curr.children[index] = new Node(); } curr = curr.children[index]; } curr.isEnd = true; }
public boolean search(String key) { return search(key, 0); }
private boolean search(String key, int pos) { if (pos == key.length()) { return true; }
Node curr = root; for (int i = pos; i < key.length(); i++) { int index = key.charAt(i) - 'a'; if (curr.children[index] == null) { return false; } curr = curr.children[index]; if (curr.isEnd && search(key, i + 1)) { return true; } }
return false; }}473. Matchsticks to Square
https://leetcode.com/problems/matchsticks-to-square/
使用回溯算法,首先可以把所有火柴的长度加起来,只有总长是4的倍数才能组成正方形,并且如果能组成正方形其边长一定是总和的1/4。有了这个目标就能回溯进行尝试,把所有火柴按长度排序之后,从最长边开始尝试把四边都填满,如果已经当前边已有长度 + 当前火柴太长,或者是发现已有的两条边一样长,就跳过尝试填下一条边。
class Solution { public boolean makesquare(int[] nums) { if (nums == null || nums.length == 0) { return false; }
int sum = 0; for (int num : nums) { sum += num; }
// must be 4's multiples to form a square if (sum % 4 != 0) { return false; }
Arrays.sort(nums);
// start from the longest matchstick return backtrack(nums, new int[4], nums.length - 1, sum / 4); }
boolean backtrack(int[] nums, int[] sums, int index, int target) { if (index == -1) { return true; }
for (int i = 0; i < 4; i++) { // current sum + edge is too big, or there are already two same edges if ((sums[i] + nums[index] > target) || (i > 0 && sums[i] == sums[i-1])) { continue; }
// try to fill current sums[i] sums[i] += nums[index]; if (backtrack(nums, sums, index - 1, target)) { return true; } sums[i] -= nums[index]; }
return false; }}474. Ones and Zeroes
https://leetcode.com/problems/ones-and-zeroes/
动态规划,dp[i][j]表示能用i个0,j个1构成的最大子集大小。遍历每个字符串,每个字符串必然有zero个0和one个1。如果用了当前这个字符串,那么就剩下i - zero个0,j - one个字符串,最大子集就变成了dp[i - zero][j - one] + 1,此处的1指的是当前这个字符串本身也在最大子集内;而如果不用当前这个字符串,最大子集仍然是由dp[i][j]推出。二者的最大值就是目标解。
class Solution { public int findMaxForm(String[] strs, int m, int n) { int[][] dp = new int[m + 1][n + 1];
for (String str : strs) { int one = 0; int zero = 0;
for (char c : str.toCharArray()) { if (c == '1') { one++; } else { zero++; } }
for (int i = m; i >= zero; i--) { for (int j = n; j >= one; j--) { if (zero <= i && one <= j) { // dp[i][j] is the max number of str we can pick with i "0"s and j "1"s dp[i][j] = Math.max(dp[i][j], dp[i - zero][j - one] + 1); } } } }
return dp[m][n]; }}475. Heaters
https://leetcode.com/problems/heaters/
先把house和heater排序,之后双指针法,对于每个house找到第一个比它大的heater,有三种情况:
heater是全部heaters里最小的,那么这台heater必须要覆盖到当前house,这段距离要被更新到result里heater是全部heaters里最大的,那么这台heater也必须要覆盖到当前house,这段距离要被更新到result里heater位于house[i-1]和house[i]之间,那么比较heater和这两个房子之间的距离,短的那段就是最小需覆盖距离
class Solution { public int findRadius(int[] houses, int[] heaters) { Arrays.sort(houses); Arrays.sort(heaters);
int result = 0; for (int i = 0, j = 0; i < houses.length; i++) { while (j < heaters.length && heaters[j] <= houses[i]) { j++; }
if (j == 0) { // the leftmost house must be covered by the first heaters result = Math.max(result, heaters[0] - houses[i]); } else if (j == heaters.length) { // the rightmost house must be covered by the last heaters result = Math.max(result, houses[i] - heaters[j-1]); } else { // heaters[j-1] < houses[i] < heaters[j], choose the min distance between them result = Math.max(result, Math.min(houses[i] - heaters[j-1], heaters[j] - houses[i])); } }
return result; }}476. Number Complement
https://leetcode.com/problems/number-complement/
按照补码公式推导。
class Solution { public int findComplement(int num) { int n = 0; while (n < num) { n = 2*n + 1; } return n - num; }}477. Total Hamming Distance
https://leetcode.com/problems/total-hamming-distance/
对于n个数,因为每个数都是32位,遍历每一位,如果n个数中有k个数这一位是1,n-k个数这一位是0,那么这一位就会让总的hamming distance增加k * (n - k)。
class Solution { public int totalHammingDistance(int[] nums) { int result = 0, n = nums.length;
for (int j = 0; j < 32; j++) { int bitCount = 0;
// if there are n integers, k of them have a particular bit set and (n-k) do not, // then that bit contributes k*(n-k) hamming distance to the total for (int i = 0; i < n; i++) { bitCount += (nums[i] >> j) & 1; } result += bitCount*(n - bitCount); }
return result; }}478. Generate Random Point in a Circle
https://leetcode.com/problems/generate-random-point-in-a-circle/
用极坐标形式去理解,因为圆的半径是radius,与x轴形成夹角最大是2 * pi,所以这二者分别乘以一个[0, 1]之间的随机数,就能得到一个圆内的点。
class Solution { double radius, x_center, y_center;
public Solution(double radius, double x_center, double y_center) { this.radius = radius; this.x_center = x_center; this.y_center = y_center; }
public double[] randPoint() { double len = Math.sqrt(Math.random()) * radius; double deg = Math.random() * 2*Math.PI; double x = x_center + len * Math.cos(deg); double y = y_center + len * Math.sin(deg); return new double[]{x, y}; }}
/** * Your Solution object will be instantiated and called as such: * Solution obj = new Solution(radius, x_center, y_center); * double[] param_1 = obj.randPoint(); */479. Largest Palindrome Product
https://leetcode.com/problems/largest-palindrome-product/
对于每个n,确定上下界,从大到小穷举回文数pal,再检查sqrt(pal)与上界之间的数是否存在可以被pal整除的i。
class Solution { public int largestPalindrome(int n) { if (n == 1) { return 9; } long upper = (long)(Math.pow(10, n) - 1); long lower = upper/10 + 1; long half = upper * upper / (long)Math.pow(10, n);
while (half > 0) { // For each palindrome number pal, it checks if the number can be divided by a number i with n digits long pal = Long.parseLong(half + new StringBuilder().append(half--).reverse().toString());
// As it's in descending order, the first number that meets this check is the largest palindrome number we want for (long i = upper; i * i >= pal && i > lower; i--) { if (pal % i == 0) { return (int) (pal % 1337); } } half--; }
return 0; }}480. Sliding Window Median
https://leetcode.com/problems/sliding-window-median/
使用两个优先队列,一个small用于降序存窗口中较小一半的数,一个large用于升序存窗口中较大一半的数,这样可以保证无论窗口长度是奇数还是偶数,中位数都可以通过优先队列的头元素快速求出。之后向前移动窗口时,需要把即将进入窗口的数和当前small中的最大值进行比较,进而判断这个数应该加到small还是large中。因为新加入的数可能会导致两个优先队列长度失衡,因此维护一个balance变量用于动态平衡两个队列的长度。
class Solution { public double[] medianSlidingWindow(int[] nums, int k) { if (k > nums.length || k == 0) { return new double[0]; }
// priority queue will store [array index, array element] Queue<int[]> small = new PriorityQueue<>((n1, n2) -> n1[1] == n2[1] ? 0 : n2[1] > n1[1] ? 1 : -1); // descending Queue<int[]> large = new PriorityQueue<>((n1, n2) -> n1[1] == n2[1] ? 0 : n1[1] > n2[1] ? 1 : -1); // ascending
double[] result = new double[nums.length - k + 1]; for (int i = 0; i < k; i++) { large.offer(new int[]{i, nums[i]}); } for (int i = 0; i < (k + 1) / 2; i++) { small.offer(large.poll()); }
int balance = 0; for (int i = 0; i < result.length; i++) { // get current median result[i] = small.peek()[1]; if (k % 2 == 0) { result[i] = (result[i] + large.peek()[1]) / 2; }
if (i == result.length - 1) { break; }
// decide the incoming number should go to small or large balance += nums[i] <= small.peek()[1] ? -1 : 1; if (!small.isEmpty() && nums[i + k] < small.peek()[1]) { small.add(new int[]{i + k, nums[i + k]}); balance++; } else { large.add(new int[]{i + k, nums[i + k]}); balance--; }
// how many numbers should one queue offers to another queue if (balance > 0) { large.offer(small.poll()); balance -= 2; } if (balance < 0) { small.offer(large.poll()); balance += 2; }
// move forward the sliding window while (!small.isEmpty() && small.peek()[0] <= i) { small.poll(); } while (!large.isEmpty() && large.peek()[0] <= i) { large.poll(); } }
return result; }}481. Magical String
https://leetcode.com/problems/magical-string/
双指针 + 数组,具体使用参考注释。
class Solution { public int magicalString(int n) { if (n <= 0) { return 0; } if (n <= 3) { return 1; }
// +1 to avoid overflow because the last round head might points to a number 2 int[] a = new int[n + 1]; a[0] = 1; a[1] = 2; a[2] = 2;
// head points to the number which will be used to generate new numbers // tail points to the next empty position to put the new number int head = 2, tail = 3, num = 1, result = 1;
while (tail < n) { for (int i = 0; i < a[head]; i++) { a[tail] = num; if (num == 1 && tail < n) { result++; } tail++; }
// flip number back and forth between 1 and 2 num = num ^ 3; head++; }
return result; }}482. License Key Formatting
https://leetcode.com/problems/license-key-formatting/
把原始字符串中的'-'去掉,之后计算长度,把mod K的余数部分放在第一部分,其他每个部分都是K的长度,中间用'-'间隔。
class Solution { public String licenseKeyFormatting(String S, int K) { String s = S.replace("-", ""); int len = s.length(); if (S.length() == 0 || len == 0) { return ""; }
StringBuilder sb = new StringBuilder();
// the first group could be shorter than K int firstLen = len % K; if (firstLen >= 1) { for (int i = 0; i < firstLen; i++) { char ch = s.charAt(i); sb.append(Character.toUpperCase(ch)); } sb.append("-"); }
for (int i = firstLen; i < len; i = i + K) { // after K characters, add a hyphen for (int j = 0; j < K; j++) { char ch = s.charAt(i + j); sb.append(Character.toUpperCase(ch)); } sb.append("-"); }
// remove last hyphen sb.setLength(sb.length() - 1); return sb.toString(); }}483. Smallest Good Base
https://leetcode.com/problems/smallest-good-base/
算出最大的可能次幂,然后向下循环缩小范围。
class Solution { public String smallestGoodBase(String n) { long num = Long.parseLong(n);
// smallest base is 2 so our m must be between 2 and log2n int maxPower = (int)(Math.log((double)num) / Math.log(2));
for (int i = maxPower; i >= 2; i--) { // ⌊i-th root of num⌋ is the only candidate that needs to be tested int root = (int)Math.pow(num, 1.0 / i); long sum = 1; long term = 1; for (int j = 1; j <= i; j++) { term *= root; sum += term; }
// (i^(j+1) - 1) / (i - 1) = num if (sum == num) { return Integer.toString(root); } }
// else result is num - 1 return Long.toString(num - 1); }}484. Find Permutation
https://leetcode.com/problems/find-permutation/
先初始化一个有序的序列,之后把所有含'D'的序列区间进行颠倒,颠倒时注意是头尾双指针两两配对交换,交换完后移动指针。
class Solution { public int[] findPermutation(String s) { if (s == null || s.isEmpty()) { return new int[0]; }
int n = s.length(); int[] sorted = new int[n + 1]; for (int i = 0; i <= n; i++) { sorted[i] = i + 1; }
int right = 0; while (right < n) { if (s.charAt(right) == 'I') { right++; continue; }
// find first 'D' and set as left int left = right; while (right < n && s.charAt(right) == 'D') { right++; }
// swap intervals with all 'D's reverse(sorted, left, right); }
return sorted; }
// swap all pairs of number in [left, right] private void reverse(int[] arr, int left, int right) { while (left < right) { int tmp = arr[left]; arr[left] = arr[right]; arr[right] = tmp;
left++; right--; } }}485. Max Consecutive Ones
https://leetcode.com/problems/max-consecutive-ones/
遇到1就计数增加,反之检查是否需要更新最大值。注意循环结束后仍然要检查一次。
class Solution { public int findMaxConsecutiveOnes(int[] nums) { int max = 0, count = 0;
for (int i: nums) { if (i == 1) { count++; } else { max = Math.max(max, count); count = 0; } }
return Math.max(max, count); }}486. Predict the Winner
https://leetcode.com/problems/predict-the-winner/
使用动态规划,dp[i][j]表示如果第一个玩家先手,能够从nums[i, j]之间比第二个玩家多拿多少数字。如果先手玩家拿了nums[i],剩下的nums[i+1, j]里面就会生成dp[i+1][j],则第一个玩家比第二个玩家多的部分就应该是nums[i] + dp[i+1][j]。
然而,注意到一旦第一个玩家拿了nums[i],nums[i+1, j]中就变成了第二个玩家先手,因此此时我们看到的dp[i+1][j]实际上是第二个玩家在nums[i+1, j]中比第一个玩家多拿了多少数字(因为两个人都是用最优策略在玩游戏,因此如果把两个人视为一个整体,取数字的顺序一定是固定的,只是谁先手的差异)。
因此,如果先手玩家拿nums[i],dp[i][j]是nums[i] - dp[i+1][j],即抵消了子区间内对方多拿的数字。同理,如果先手玩家拿nums[j],dp[i][j]是nums[j] - dp[i][j-1],这两种取法选择较大的一种作为最终策略。
class Solution { public boolean PredictTheWinner(int[] nums) { int n = nums.length;
// dp[i][j]: how much more scores that the first player will get from i to j than the second player int[][] dp = new int[n][n]; for (int i = 0; i < n; i++) { dp[i][i] = nums[i]; }
for (int len = 1; len < n; len++) { for (int i = 0; i < n - len; i++) { int j = i + len;
// first player takes num[i], and second player starts first to result dp[i + 1, j] // or similarily, first player takes num[j], and second player starts first to result dp[i, j - 1] dp[i][j] = Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]); } }
return dp[0][n - 1] >= 0; }}487. Max Consecutive Ones II
https://leetcode.com/problems/max-consecutive-ones-ii/
使用两个变量zeroLeft和zeroRight,zeroRight表示当前遍历时连续遇到的1的个数,遇到0就清零;而zeroLeft则在遇到0时才更新,把此前zeroRight赋值给zeroLeft,这个值包含了此前连续的1的个数 + 1(当前的0可以翻转成1)。最大值由zeroLeft + zeroRight得到。
class Solution { public int findMaxConsecutiveOnes(int[] nums) { int result = 0, zeroLeft = 0, zeroRight = 0;
for (int i = 0; i < nums.length; i++) { zeroRight++; if (nums[i] == 0) { zeroLeft = zeroRight; zeroRight = 0; } result = Math.max(result, zeroLeft + zeroRight); }
return result; }}对于follow-up,需要使用一个queue存储所有0的下标,然后用双指针low和high计算最大的连续1区间长度。只要还在flip的上限范围内,就只需要更新high,不需要更新low;而一旦超出上限,就必须把最早的0的下标弹出,更新low。
public int findMaxConsecutiveOnes(int[] nums) { // flip at most k zero int result = 0, k = 1;
Queue<Integer> zeroIndex = new LinkedList<>(); for (int low = 0, high = 0; high < nums.length; high++) { if (nums[high] == 0) { zeroIndex.offer(high); }
// eject the earliest 0's index if (zeroIndex.size() > k) { low = zeroIndex.poll() + 1; }
result = Math.max(result, high - low + 1); }
return result;}488. Zuma Game
https://leetcode.com/problems/zuma-game/
分类讨论,单球+2/双球+1/双球之间插1异色球的情况。
class Solution { private int result = Integer.MAX_VALUE; private int[] map = new int[26]; private char[] colors = {'R', 'Y', 'B', 'G', 'W'};
public int findMinStep(String board, String hand) { for (int i = 0; i < hand.length(); i++) { map[hand.charAt(i) - 'A']++; } dfs(new StringBuilder(board), 0); return result == Integer.MAX_VALUE ? -1 : result; }
private void dfs(StringBuilder board, int step) { // skip rest of the dfs if step is already too big, or update the min step if (step >= result) { return; }
if (board.length() == 0) { result = Math.min(step, result); return; }
for (int i = 0; i < board.length(); i++) { char c = board.charAt(i); int j = i; while (j + 1 < board.length() && board.charAt(j + 1) == c) { j++; }
if (j == i && map[c - 'A'] >= 2) { // single ball, insert two same color balls StringBuilder tmp = new StringBuilder(board); tmp.insert(i, c + "" + c); map[c - 'A'] -= 2; dfs(eliminate(tmp), step + 2); map[c - 'A'] += 2; } else if (j == i + 1) { // two adjacent balls with same color // insert a same color ball if (map[c - 'A'] >= 1) { StringBuilder tmp = new StringBuilder(board); tmp.insert(i, c); map[c - 'A']--; dfs(eliminate(tmp), step + 1); map[c - 'A']++; }
// insert a different color ball between these two adjacent balls for (char color : colors) { if (color == c) { continue; } if (map[color - 'A'] >= 1) { StringBuilder tmp = new StringBuilder(board); tmp.insert(i + 1, color); map[color - 'A']--; dfs(eliminate(tmp), step + 1); map[color - 'A']++; } } } } }
private StringBuilder eliminate(StringBuilder sb) { boolean flag = true; while (flag) { flag = false; for (int i = 0; i < sb.length(); i++) { int j = i + 1; while (j < sb.length() && sb.charAt(j) == sb.charAt(i)) { j++; }
// remove consecutive balls if (j - i >= 3) { sb.delete(i, j); flag = true; } } }
return sb; }}489. Robot Room Cleaner
https://leetcode.com/problems/robot-room-cleaner/
回溯算法 + BFS,然后使用一个HashSet存储所有已经走过的方格。
/** * // This is the robot's control interface. * // You should not implement it, or speculate about its implementation * interface Robot { * // Returns true if the cell in front is open and robot moves into the cell. * // Returns false if the cell in front is blocked and robot stays in the current cell. * public boolean move(); * * // Robot will stay in the same cell after calling turnLeft/turnRight. * // Each turn will be 90 degrees. * public void turnLeft(); * public void turnRight(); * * // Clean the current cell. * public void clean(); * } */
class Solution { private static int[][] DIR = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} }; // UP, RIGHT, DOWN, LEFT Set<Pair<Integer, Integer>> visited = new HashSet(); Robot robot;
public void cleanRoom(Robot robot) { this.robot = robot; backtrack(0, 0, 0); }
// dir: 0: up, 1: right, 2: down, 3: left public void backtrack(int x, int y, int dir) { visited.add(new Pair(x, y)); robot.clean();
for (int i = 0; i < 4; i++) { int newDir = (dir + i) % 4; int[] move = DIR[newDir];
int newX = x + move[0]; int newY = y + move[1];
if (!visited.contains(new Pair(newX, newY)) && robot.move()) { backtrack(newX, newY, newDir);
// go back robot.turnRight(); robot.turnRight(); robot.move(); robot.turnRight(); robot.turnRight(); }
// turn the robot following chosen direction : clockwise robot.turnRight(); } }}490. The Maze
https://leetcode.com/problems/the-maze/
使用DFS + BFS,先用BFS尝试一个方向,然后在这个方向上一条路走到黑直到撞墙(即判断是否还在迷宫范围内),之后再使用DFS从新的位置开始下一次尝试,直到小球抵达终点。
class Solution { private static final int[][] DIRS = { {1,0},{-1,0},{0,1},{0,-1} };
public boolean hasPath(int[][] maze, int[] start, int[] destination) { boolean[][] visited = new boolean[maze.length][maze[0].length]; return dfs(maze, start, destination, visited); }
private boolean dfs(int[][] maze, int[] start, int[] end, boolean[][] visited) { // start and end points are the same if (start[0] == end[0] && start[1] == end[1]) { return true; }
// already visited grid if (visited[start[0]][start[1]]) { return false; }
visited[start[0]][start[1]] = true; for (int[] dir : DIRS) { int x = dir[0] + start[0]; int y = dir[1] + start[1];
// ball always runs until it hit a wall while (isValid(maze, x, y) && maze[x][y] == 0) { x += dir[0]; y += dir[1]; } x -= dir[0]; y -= dir[1];
// current grid is unvisited, try subsequent dfs if (!visited[x][y]) { if (dfs(maze, new int[]{x, y}, end, visited)) { return true; } } }
return false; }
// still in maze's range private boolean isValid(int[][] maze, int x, int y) { return x >= 0 && y >= 0 && x < maze.length && y < maze[0].length; }}491. Increasing Subsequences
https://leetcode.com/problems/increasing-subsequences/
回溯算法,分类讨论。如果当前数字比此前的数字更大,可以把它添加到当前list中,然后dfs尝试之后的数组区间,直到访问到数组尾。而另一种情况下,即便当前数字比此前数字更大,我们也不一定用这个数字,而是向后继续搜索找到另一个递增数字。所以综合起来,共有2种触发dfs的方式。
class Solution { public List<List<Integer>> findSubsequences(int[] nums) { List<List<Integer>> result = new ArrayList<>(); dfs(result, nums, 0, Integer.MIN_VALUE, new ArrayList<>()); return result; }
private void dfs(List<List<Integer>> result, int[] nums, int index, int pre, List<Integer> cur) { // reach the end of nums, add current list to result if (index == nums.length) { if (cur.size() > 1) { result.add(new ArrayList(cur)); } return; }
// current number is larger than previous number, add to current list if (nums[index] >= pre) { cur.add(nums[index]); dfs(result, nums, index + 1, nums[index], cur); cur.remove(cur.size() - 1); }
// may or may not use current number, so try next number if (nums[index] != pre) { dfs(result, nums, index + 1, pre, cur); } }}492. Construct the Rectangle
https://leetcode.com/problems/construct-the-rectangle/
通过开方可以快速定位到离sqrt(area)最近的质因数。
class Solution { public int[] constructRectangle(int area) { int width = (int)Math.sqrt(area); while (area % width != 0) { width--; } return new int[]{area / width, width}; }}493. Reverse Pairs
https://leetcode.com/problems/reverse-pairs/
归并排序,在归并的同时搜索reverse pairs。
class Solution { public int reversePairs(int[] nums) { return nums == null ? 0 : mergeSort(nums, 0, nums.length - 1); }
public int mergeSort(int[] nums, int start, int end) { if (start >= end) { return 0; }
int mid = start + ((end - start) >> 1); int result = mergeSort(nums, start, mid) + mergeSort(nums, mid + 1, end); int[] cache = new int[end - start + 1];
int k = mid + 1, c = 0; for (int i = start, j = mid + 1; i <= mid; i++, c++) { // find pairs while (j <= end && nums[i] > 2 * (long)nums[j]) { j++; }
// find numbers smaller than nums[i] and copy to cache while (k <= end && nums[k] < nums[i]) { cache[c] = nums[k]; c++; k++; } cache[c] = nums[i];
// update result result += j - (mid + 1); }
while (k <= end) { cache[c] = nums[k]; c++; k++; }
System.arraycopy(cache, 0, nums, start, end - start + 1); return result; }}494. Target Sum
https://leetcode.com/problems/target-sum/
nums可以拆成两部分来看,一部分用于添加加号,另一部分用于添加负号。如果把这两部分分别用pos和negative两个集合表示,很显然有sum(pos) - sum(negative) = s,化简得到2*sum(pos) = s + sum。根据这个推论,我们可以把问题转化为典型01背包问题,即从nums中找出一个子集(组成pos),使得子集里所有元素的和为(s + sum) / 2。
寻找子集可以使用动态规划,其中dp[i][j]表示使用前i个数得到目标数j共有几种方式。我们遍历nums中的每个数i,对0到target之间的状态j进行穷举,并分类讨论:
- 当前的目标
j太小,第i个数太大,肯定没有办法选择它,因此dp[i][j]的值仍然与dp[i-1][j]相同 - 否则,第
i个数可选或者可不选。如果不选,与此前的情况一样;如果选了,就相当于之前的i-1个数字总和必须为j - 第i个数的值
最后,二维dp可以进一步优化成一维dp,详见注释。
class Solution { public int findTargetSumWays(int[] nums, int s) { int sum = 0; for (int num : nums) { sum += num; }
// too big, too small, or mismatched parity if (Math.abs(s) > sum || (s+sum) % 2 != 0) { return 0; }
// sum(pos) - sum(negative) = s // 2 * sum(pos) = s + sum(negative) + sum(pos) = s + sum return subsetSum(nums, (s+sum) / 2); }
public int subsetSum(int[] nums, int target) {// int[] dp = new int[target + 1];// dp[0] = 1;
// for (int num : nums) {// for (int i = target; i >= num; i--) {// dp[i] += dp[i - num];// }// }
// return dp[target];
int[][] dp = new int[nums.length + 1][target + 1]; dp[0][0] = 1;
for (int i = 1; i <= nums.length; i++) { for (int j = 0; j <= target; j++) { if (j < nums[i - 1]) { // current j is too small, so only possible to repeating all the ways to get sum j by using first i-1 elements dp[i][j] = dp[i - 1][j]; } else { // otherwise, could also add nums[i] on top of each way to get sum j-nums[i-1] using first i elements dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]]; } } }
return dp[nums.length][target]; }}495. Teemo Attacking
https://leetcode.com/problems/teemo-attacking/
除了最后一个数,前面所有数两两之间的间隔都要和duration进行比较,取较小值累加;最后一个数直接累加duration即可。
class Solution { public int findPoisonedDuration(int[] timeSeries, int duration) { if (timeSeries == null || timeSeries.length == 0) { return 0; }
int result = 0; for (int i = 0; i < timeSeries.length - 1; i++) { if (timeSeries[i + 1] - timeSeries[i] > duration) { result += duration; } else { result += timeSeries[i + 1] - timeSeries[i]; } } result += duration; return result; }}496. Next Greater Element I
https://leetcode.com/problems/next-greater-element-i/
使用一个HashMap,先储存nums2中所有数字的下标,之后遍历nums1,获取到每个数在nums2中的下标,然后向后搜索看能不能找到比他大的数。
class Solution { public int[] nextGreaterElement(int[] nums1, int[] nums2) { // <number, number index> Map<Integer, Integer> indexMap = new HashMap<>(); for (int i = 0; i < nums2.length; i++) { indexMap.put(nums2[i], i); }
int[] result = new int[nums1.length]; for (int i = 0; i < result.length; i++) { result[i] = getNext(nums2, indexMap.get(nums1[i])); }
return result; }
// start from current number's index in nums2, and search afterwards private int getNext(int[] nums, int index) { int curr = nums[index];
for (int i = index + 1; i < nums.length; i++) { if (nums[i] > curr) { return nums[i]; } }
return -1; }}497. Random Point in Non-overlapping Rectangles
https://leetcode.com/problems/random-point-in-non-overlapping-rectangles/
计算每个矩形的面积,注意因为边界上的点也需要用到,因此边长需要+1。得到每个矩形的面积后用数组存储,然后以总的面积和为上限,得到一个范围,从这个范围内生成一个随机数,利用二分查找找到这个随机数对应的是哪个矩形(即,找到数组内的最小下标left,使得这个随机数落在arr[left - 1]和arr[left]的区间之内)。找到矩形后,再提取这个矩形的坐标,生成范围内的随机坐标点。
class Solution { private Random rand; private int[][] re; private int N; private int sum; private int[] arr;
public Solution(int[][] rects) { rand = new Random(); re = rects; N = rects.length; arr = new int[N]; for (int i = 0; i < N; i++) { arr[i] = area(rects[i]); }
// each i in the arr represents a range of (arr[i-1], arr[i]] sum = arr[0]; for (int i = 1; i < N; i++) { sum += arr[i]; arr[i] = arr[i-1] + arr[i]; } }
// boundaries are used, so + 1 public int area(int[] rect) { return (rect[2] - rect[0] + 1) * (rect[3] - rect[1] + 1); }
public int[] pick() { int num = rand.nextInt(sum) + 1;
// find the index l, which is at the left most idx that makes arr[l] >= num (arr[l-1] < num <= arr[l]) int left = 0; int right = arr.length - 1; while (left < right) { int mid = left + (right - left) / 2;
if (arr[mid] >= num) { right = mid; } else { left = mid + 1; } }
int x1 = re[left][0]; int y1 = re[left][1]; int x2 = re[left][2]; int y2 = re[left][3];
int x = rand.nextInt(x2 - x1 + 1) + x1; int y = rand.nextInt(y2 - y1 + 1) + y1; return new int[]{x, y}; }}
/** * Your Solution object will be instantiated and called as such: * Solution obj = new Solution(rects); * int[] param_1 = obj.pick(); */498. Diagonal Traverse
https://leetcode.com/problems/diagonal-traverse/
观察奇偶性,发现向上移动时,行与列下标相加总是偶数;向下移动时,总是奇数。运用此规律分类讨论,并注意单独分析边界掉头转向的情况。
class Solution { public int[] findDiagonalOrder(int[][] matrix) { if (matrix.length == 0) { return new int[0]; } int r = 0, c = 0, m = matrix.length, n = matrix[0].length, result[] = new int[m * n];
for (int i = 0; i < result.length; i++) { result[i] = matrix[r][c];
if ((r + c) % 2 == 0) { if (c == n - 1) { // move to next row, go downward r++; } else if (r == 0) { // move to next column, go downward c++; } else { r--; c++; } } else { if (r == m - 1) { // move to next column, go upward c++; } else if (c == 0) { // move to next row, go upward r++; } else { r++; c--; } } }
return result; }}499. The Maze III
https://leetcode.com/problems/the-maze-iii/
从起始点开始,遍历4个可能的方向,对任何一个方向,尝试在这个方向上一条路走到黑,如果撞墙了就尝试左右转向。不断递归尝试,看最后是否有情况下能到达hole。维护两个变量minS和min,分别表示已走过的最短成功路径字符串和最短成功路径的长度,再对应维护两个变量表示当前路径的详情和长度,每走一步就更新当前变量,如果到达hole看是否能更新最小值。
class Solution { private int min; // min distance to hole private String minS; // min distance's path string private int[] hole; private int[][] maze; private int[][] map; // shortest distant traveling from ball to this point private int[][] dirs = { {0, 1}, {-1, 0}, {1, 0}, {0, -1} }; // right, up, down, left
public String findShortestWay(int[][] maze, int[] ball, int[] hole) { this.min = Integer.MAX_VALUE; this.minS = null; this.hole = hole; this.maze = maze; this.map = new int[maze.length][maze[0].length]; for (int i = 0; i < map.length; i++) { Arrays.fill(map[i], Integer.MAX_VALUE); }
move(ball[0], ball[1], 0, "", -1); return minS == null ? "impossible" : minS; }
private void move(int r, int c, int cnt, String path, int dir) { // current path's length is not shortest if (cnt > min || cnt > map[r][c]) { return; }
// exclude start point if (dir != -1) { // add current diretions to path if (dir == 0) { path += 'r'; } else if (dir == 1) { path += 'u'; } else if (dir == 2) { path += 'd'; } else { path += 'l'; }
while (r >= 0 && r < maze.length && c >= 0 && c < maze[0].length && maze[r][c] == 0) { map[r][c] = Math.min(map[r][c], cnt);
// reach the hole if (r == hole[0] && c == hole[1]) { // lexicographically smallest way if (cnt == min && path.compareTo(minS) < 0) { minS = path; } else if (cnt < min) { min = cnt; minS = path; } return; }
// roll along direction r += dirs[dir][0]; c += dirs[dir][1]; cnt++; }
// [r, c] is wall, need to walk back 1 step r -= dirs[dir][0]; c -= dirs[dir][1]; cnt--; }
// hit wall (or start) -> try to turn for (int i = 0; i < dirs.length; i++) { // don't keep going or go back if (dir == i || dir == 3 - i) { continue; }
int newR = r + dirs[i][0]; int newC = c + dirs[i][1];
// new space is still valid if (newR >= 0 && newR < maze.length && newC >= 0 && newC < maze[0].length && maze[newR][newC] == 0) { move(r, c, cnt, path, i); } } }}500. Keyboard Row
https://leetcode.com/problems/keyboard-row/
统计每个单词中的字母分别在每行出现了几次,如果只在一行出现,就把它添加到结果中。
class Solution { public String[] findWords(String[] words) { String[] strs = {"qwertyuiop","asdfghjkl","zxcvbnm"}; List<String> results = new ArrayList();
for (String word : words) { int count1 = 0, count2 = 0, count3 = 0;
for (char c : word.toCharArray()) { if (strs[0].indexOf(c) != -1) { count1++; } if (strs[1].indexOf(c) != -1) { count2++; } if (strs[2].indexOf(c) != -1) { count3++; } }
// only comes from one row if ((count1 == 0 && count2 == 0) || (count1 == 0 && count3 == 0) || (count2 == 0 && count3 == 0)) { results.add(word); } }
return results.toArray(new String[results.size()]); }}