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()]);
}
}