LeetCode Problems 301-400.
301. Remove Invalid Parentheses
https://leetcode.com/problems/remove-invalid-parentheses/
从iStart起,统计openPar和closePar的个数,如果closePar更多,说明存在重复的闭括号需要删除。
确定了重复后,从jStart起,试图删除遇到的第一个重复closePar(或者字符串第一个字符就是closePar)。剩余的字符串继续递归检查。
如果从左到右检查后没有发现括号重复,再将原始字符串调转,对称检查另一半符号。
class Solution {
public List<String> removeInvalidParentheses(String s) {
List<String> result = new ArrayList<>();
helper(result, s, 0, 0, '(', ')');
return result;
}
private void helper(List<String> result, String s, int iStart, int jStart, char openPar, char closePar) {
int openNum = 0, closeNum = 0;
for (int i = iStart; i < s.length(); i++) {
if (s.charAt(i) == openPar) {
openNum++;
}
if (s.charAt(i) == closePar) {
closeNum++;
}
// more closePar, so they need to be deleted
if (closeNum > openNum) {
for (int j = jStart; j <= i; j++) {
// encounter first ) and delete, then use substring to recursively check two strings at each side
if (s.charAt(j) == closePar && (j == jStart || s.charAt(j-1) != closePar)) {
helper(result, s.substring(0, j) + s.substring(j+1), i, j, openPar, closePar);
}
}
return;
}
}
// reverse the string and recheck
String reversed = new StringBuilder(s).reverse().toString();
// must check both '(' and ')'
if (openPar == '(') {
helper(result, reversed, 0, 0, ')', '(');
} else {
// current sting is valid, add to result
result.add(reversed);
}
}
}
302. Smallest Rectangle Enclosing Black Pixels
https://leetcode.com/problems/smallest-rectangle-enclosing-black-pixels/
基于给定的点坐标,向当前列的上、下,当前行的上、下进行四组二分查找,如果发现区间里仍然存在1,就继续向外搜索,直到没有0为止。用最后的四个坐标锁定出一个矩形,进而求出面积。
class Solution {
public int minArea(char[][] image, int x, int y) {
if (image == null || image.length == 0 || image[0].length == 0) {
return 0;
}
int up = checkUp(image, x);
int down = checkDown(image, x);
int left = checkLeft(image, y);
int right = checkRight(image, y);
return (down - up + 1) * (right - left + 1);
}
private int checkUp(char[][] image, int x) {
int lo = 0, hi = x;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (rowContainsOne(image, mid)) {
hi = mid - 1;
} else {
lo = mid + 1;
}
}
return lo;
}
private int checkDown(char[][] image, int x) {
int lo = x, hi = image.length - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (rowContainsOne(image, mid)) {
lo = mid + 1;
} else {
hi = mid - 1;
}
}
return hi;
}
private int checkLeft(char[][] image, int y) {
int left = 0, right = y;
while (left <= right) {
int mid = left + (right - left) / 2;
if (colContainsOne(image, mid)) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
private int checkRight(char[][] image, int y) {
int left = y, right = image[0].length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (colContainsOne(image, mid)) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return right;
}
private boolean rowContainsOne(char[][] image, int r) {
for (int j = 0; j < image[0].length; j++) {
if (image[r][j] == '1') {
return true;
}
}
return false;
}
private boolean colContainsOne(char[][] image, int c) {
for (int i = 0; i < image.length; i++) {
if (image[i][c] == '1') {
return true;
}
}
return false;
}
}
303. Range Sum Query - Immutable
https://leetcode.com/problems/range-sum-query-immutable/
使用一个sums数组,sums[i]表示截止到下表i时,nums[0]~nums[i]的和。调用分段求和时,需要根据i的的大小进行讨论。
class NumArray {
private int[] sums;
public NumArray(int[] nums) {
sums = new int[nums.length];
if (nums.length > 0) {
sums[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
sums[i] = sums[i - 1] + nums[i];
}
}
}
public int sumRange(int i, int j) {
return i == 0 ? sums[j] : sums[j] - sums[i - 1];
}
}
304. Range Sum Query 2D - Immutable
https://leetcode.com/problems/range-sum-query-2d-immutable/
使用二维动态规划,其中dp[i+1][j+1]表示从matrix[0][0]到matrix[i][j]的数组元素和。
+-----+-+-------+ +--------+-----+ +-----+---------+ +-----+--------+
| | | | | | | | | | | | |
| | | | | | | | | | | | |
+-----+-+ | +--------+ | | | | +-----+ |
| | | | = | | + | | | - | |
+-----+-+ | | | +-----+ | | |
| | | | | | | |
| | | | | | | |
+---------------+ +--------------+ +---------------+ +--------------+
dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] +
matrix[i-1][j-1]
推广至任意的坐标起始点和终点:
+---------------+ +--------------+ +---------------+ +--------------+ +--------------+
| | | | | | | | | | | | | |
| (r1,c1) | | | | | | | | | | | | |
| +------+ | | | | | | | +---------+ | +---+ |
| | | | = | | | - | | | - | (r1,c2) | + | (r1,c1) |
| | | | | | | | | | | | | |
| +------+ | +---------+ | +---+ | | | | |
| (r2,c2)| | (r2,c2)| | (r2,c1) | | | | |
+---------------+ +--------------+ +---------------+ +--------------+ +--------------+
class NumMatrix {
private int[][] dp;
public NumMatrix(int[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return;
}
int row = matrix.length, col = matrix[0].length;
dp = new int[row+1][col+1];
for (int i = 1; i <= row; i++) {
for (int j = 1; j <= col; j++) {
dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + matrix[i-1][j-1];
}
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
return dp[row2+1][col2+1] - dp[row2+1][col1] - dp[row1][col2+1] + dp[row1][col1];
}
}
305. Number of Islands II
https://leetcode.com/problems/number-of-islands-ii/
运用union-find思想,使用树来表达所有岛。通过find()可以找到所有岛的祖先;而union()则判断两个节点是否属于同一个岛。
class Solution {
public List<Integer> numIslands2(int m, int n, int[][] positions) {
List<Integer> result = new ArrayList<Integer>();
int[] parent = new int[m*n + 1], rank = new int[m*n + 1];
int numIslands = 0;
for (int i = 0; i < positions.length; i++) {
int x = positions[i][0];
int y = positions[i][1];
int offset = x*n + y+1;
// duplicate position detected
// all positions have parent 0 initially, in case the parent is not 0 we must have encountered the position before)
if (parent[offset] != 0) {
result.add(numIslands);
continue;
}
parent[offset] = offset;
numIslands++;
if (x > 0 && parent[offset-n] != 0 && union(parent, rank, offset, offset-n)) {
// check the grid on top of current grid
numIslands--;
}
if (x < m-1 && parent[offset+n] != 0 && union(parent, rank, offset, offset+n)) {
// check the grid below current grid
numIslands--;
}
if (y > 0 && parent[offset-1] != 0 && union(parent, rank, offset, offset-1)) {
// check the grid to the left of the current grid
numIslands--;
}
if (y < n-1 && parent[offset+1] != 0 && union(parent, rank, offset, offset+1)) {
// check the grid to the right of the current grid
numIslands--;
}
result.add(numIslands);
}
return result;
}
// find root
private int find(int[] parent, int x) {
if (parent[x] == x) {
return x;
}
return find(parent, parent[x]);
}
private boolean union(int[] parent, int[] rank, int x, int y) {
int xparent = find(parent, x);
int yparent = find(parent, y);
// not belong to same island
if (xparent == yparent) {
return false;
}
if (rank[xparent] == rank[yparent]) {
// same rank, sacrifice x and increment y's rank
parent[xparent] = yparent;
rank[yparent]++;
} else if (rank[xparent] < rank[yparent]) {
// higher rank is parent
parent[xparent] = yparent;
} else {
parent[yparent] = xparent;
}
return true;
}
}
306. Additive Number
https://leetcode.com/problems/additive-number/
使用两个循环,分别表示用来试验的前两个数字,注意要排除掉两个数字以0开头的情况。之后的数字都可以从这两个数字推断得出,因此只要不断递推并和剩余字符串比较,如果第三个数符合的话,再向后寻找下一个符合的数,直到最后看是否完成了整个字符串的遍历。
class Solution {
public boolean isAdditiveNumber(String num) {
if (num == null || num.length() < 3) {
return false;
}
int n = num.length();
for (int i = 1; i <= n; i++) {
// get first number
String first = num.substring(0, i);
// first number cannot start with 0
if (first.startsWith("0") && first.length() > 1) {
break;
}
// get second number
for (int j = i + 1; j <= n; j++) {
String second = num.substring(i, j);
// second number cannot start with 0
if (second.startsWith("0") && second.length() > 1) {
break;
}
String remain = num.substring(j);
if (remain.length() == 0) {
break;
}
// skip impossible case, where the third number is less than previous one
int maxLen = Math.max(first.length(), second.length());
if (maxLen > remain.length()) {
break;
}
long firstNumber = Long.parseLong(first);
long secondNumber = Long.parseLong(second);
// deduct all following numbers and compare with string
while (remain.length() != 0) {
long thirdNumber = firstNumber + secondNumber;
String third = String.valueOf(thirdNumber);
if (remain.startsWith(third)) {
firstNumber = secondNumber;
secondNumber = thirdNumber;
remain = remain.substring(third.length());
} else {
break;
}
}
// there should be no character left when finish
if (remain.length() == 0) {
return true;
} else {
continue;
}
}
}
return false;
}
}
307. Range Sum Query - Mutable
https://leetcode.com/problems/range-sum-query-mutable/
使用线段树。
Notation is node_index: corresponding segment (left border included, right excluded). At the bottom row we have our array (0-indexed), the leaves of the tree.
For now suppose it’s length is a power of 2 (16 in the example), so we get perfect binary tree. When going up the tree we take pairs of nodes with indices (2 * i, 2 * i + 1) and combine their values in their parent with index i. This way when we’re asked to find a sum on interval [3, 11), we need to sum up only values in the nodes 19, 5, 12 and 26 (marked with bold), not all 8 values inside the interval.
class NumArray {
private int[] tree;
private int n;
public NumArray(int[] nums) {
n = nums.length;
tree = new int[2 * n];
for (int i = n; i < n << 1; i++) {
tree[i] = nums[i - n];
}
for (int i = n - 1; i > 0; i--) {
tree[i] = tree[i << 1] + tree[i << 1 | 1];
}
}
void update(int i, int val) {
for (tree[i += n] = val; i > 0; i >>= 1) {
tree[i >> 1] = tree[i] + tree[i ^ 1];
}
}
public int sumRange(int i, int j) {
int result = 0;
for (i += n, j += n; i <= j; i >>= 1, j >>= 1) {
if ((i & 1) == 1) {
result += tree[i];
i++;
}
if ((j & 1) == 0) {
result += tree[j];
j--;
}
}
return result;
}
}
308. Range Sum Query 2D - Mutable
https://leetcode.com/problems/range-sum-query-2d-mutable/
额外使用一个2D数组,存储当前行在此之前所有列的和。对于 函数,每当改变一个数组元素,同时也改变sum数组中改变位置之后所有列的值。对于sumRegion()函数,可以看作是2D版的Range Sum - Immutable,根据列坐标讨论进行相减即可求出目标范围内的和。
class NumMatrix {
private int[][] matrix;
private int[][] lSumMatrix; // the sum of all previous columns
public NumMatrix(int[][] matrix) {
this.matrix = matrix;
int len = matrix.length;
if (len > 0) {
lSumMatrix = new int[len][matrix[0].length];
for (int i = 0; i < lSumMatrix.length; i++) {
lSumMatrix[i][0] = matrix[i][0];
for (int j = 1; j < lSumMatrix[i].length; j++) {
lSumMatrix[i][j] = lSumMatrix[i][j-1] + matrix[i][j];
}
}
}
}
public void update(int row, int col, int val) {
int difference = val - matrix[row][col];
matrix[row][col] = val;
// update all following columns
for (int j = col; j < lSumMatrix[row].length; j++) {
lSumMatrix[row][j] += difference;
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
int sum = 0;
// 2D version range sum - immutable
for (int i = row1; i <= row2; i++) {
sum += (lSumMatrix[i][col2] - (col1 > 0 ? lSumMatrix[i][col1-1] : 0));
}
return sum;
}
}
309. Best Time to Buy and Sell Stock with Cooldown
https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/
使用buy和sell记录截止到今天为止,分别以买入/卖出操作结束所能获得到最大收益,再使用prevBuy和prevSell记录截止到前一天的收益。由于限制了卖出股票后必须休息一天,因此下一次买入的最大收益,只能从prevSell + price或者是prevBuy中获取到。
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length <= 1) {
return 0;
}
// buy: max profit end with 'buy' action up to current day
// sell: max profit end with 'sell' action up to current day
// prevBuy, prevSell: max profit up to previous day
int buy = Integer.MIN_VALUE, prevBuy = 0;
int sell = 0, prevSell = 0;
for (int price : prices) {
prevBuy = buy;
// use prevSell but not sell, since we cannot buy stock in 2 continuous days
buy = Math.max(prevSell - price, prevBuy);
prevSell = sell;
sell = Math.max(prevBuy + price, prevSell);
}
return sell;
}
}
310. Minimum Height Trees
https://leetcode.com/problems/minimum-height-trees/
从所有叶子节点开始进行BFS,每一回循环中从删去所有叶子节点,再判断之前与其连接的每个节点是否成为了新的叶子。循环结束后剩下的节点都是符合条件的跟节点。
class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
List<Integer> leaves = new ArrayList<>();
// 0 or 1 node tree
if (n == 0) {
return leaves;
} else if (n == 1) {
leaves.add(0);
return leaves;
}
List<Integer>[] lists = new ArrayList[n];
for (int i = 0; i < n; i++) {
lists[i] = new ArrayList<>();
}
for (int[] e : edges) {
int v1 = e[0], v2 = e[1];
lists[v1].add(v2);
lists[v2].add(v1);
}
// all nodes with only 1 edge must be a valid root
for (int i = 0; i < n; i++) {
if (lists[i].size() == 1) {
leaves.add(i);
}
}
int count = n;
while (count > 2) {
count -= leaves.size();
List<Integer> newLeaves = new ArrayList<>();
for (Integer leaf : leaves) {
for (Integer toRemove : lists[leaf]) {
lists[toRemove].remove(Integer.valueOf(leaf));
// check whether become new leaf
if (lists[toRemove].size() == 1) {
newLeaves.add(toRemove);
}
}
}
leaves = newLeaves;
}
return leaves;
}
}
311. Sparse Matrix Multiplication
https://leetcode.com/problems/sparse-matrix-multiplication/
常规矩阵相乘;由于是稀疏矩阵,可以在循环遍历时用A[i][j] != 0快速跳过元素值为0的情况。
class Solution {
public int[][] multiply(int[][] A, int[][] B) {
int newRow = A.length, ACol = A[0].length, newCol = B[0].length;
int[][] result = new int[newRow][newCol];
for (int i = 0; i < newRow; i++) {
for (int j = 0; j < ACol; j++) {
// sparse matrix
if (A[i][j] != 0) {
for (int k = 0; k < newCol; k++) {
result[i][k] += A[i][j] * B[j][k];
}
}
}
}
return result;
}
}
312. Burst Balloons
https://leetcode.com/problems/burst-balloons/
先把输入的iNums数组进行拷贝,在头尾两端各添加一个1。再使用二维DP,考虑dp[i][j]作为copy[i]至copy[i+l-1]段的最大收益。收益主要有3种可能来源:
- 打爆
copy[i]至copy[k-1]段的气球,则最大收益来自dp[i][k-1] - 打爆
copy[k+1]至copy[j]段的气球,则最大收益来自dp[k+1][j] - 只剩下
copy[k]一个气球可以打,则最大收益为copy[i-1] * copy[k] * copy[j+1]
class Solution {
public int maxCoins(int[] iNums) {
if (iNums == null || iNums.length == 0) {
return 0;
}
int n = iNums.length;
// a new array, with iNums[-1] and iNums[n]
int[] copy = new int[n+2];
copy[0] = copy[n+1] = 1;
for (int i = 0; i < n; i++) {
copy[i+1] = iNums[i];
}
// dp[i][j] means the max coins collected from iNums[i] to iNums[j]
int[][] dp = new int[n + 2][n + 2];
// define the subproblem size
for (int l = 1; l <= n; l++) {
// We solve iNums[i] to iNums[i + l - 1]
for (int i = 1; i + l - 1<= n; i++) {
int j = i + l - 1;
int best = 0;
// three parts of coins
// 1. burst ballons from index i to k - 1, i.e. copy[i: k - 1], max coins for this part is dp[i][k - 1]
// 2. burst ballons from index k + 1 to j, i.e. copy[i: k - 1], max coins for this part is dp[k + 1][j]
// 3. only copy[k] is left, the coins we can get is copy[i - 1] * copy[k] * copy[j + 1]
for (int k = i; k <= j; k++) {
best = Math.max(best, dp[i][k - 1] + copy[i - 1] * copy[k] * copy[j + 1] + dp[k + 1][j]);
}
dp[i][j] = best;
}
}
return dp[1][n];
}
}
313. Super Ugly Number
https://leetcode.com/problems/super-ugly-number/
仍然是使用迭代,尝试所有可能的idx * prime得到所有的ugly numbers candidates,每一轮循环后再选出最小的candidate作为最终的ugly number。
class Solution {
public int nthSuperUglyNumber(int n, int[] primes) {
int[] ugly = new int[n + 1];
ugly[0] = 1;
int[] pointer = new int[primes.length];
for (int i = 1; i < n; i++) {
int min = Integer.MAX_VALUE;
int minIndex = 0;
// try all primes as factor
for (int j = 0; j < primes.length; j++) {
if (ugly[pointer[j]] * primes[j] < min) {
min = ugly[pointer[j]] * primes[j];
minIndex = j;
} else if (ugly[pointer[j]] * primes[j] == min) {
pointer[j]++;
}
}
// choose the smallest one as next ugly number
ugly[i] = min;
pointer[minIndex]++;
}
return ugly[n-1];
}
}
314. Binary Tree Vertical Order Traversal
https://leetcode.com/problems/binary-tree-vertical-order-traversal/
使用一个map,存储每一层的所有节点。为了实现这一目标,需要维护两个链表:
- 一个遍历所有树节点的链表
queue - 一个存储每个节点对应深度的链表
levels
需要注意,这两个链表中的元素是一一对应的,即:从根节点开始,每遍历一个新节点时,同时向两个链表跟别添加这个节点的值和深度。使用BFS思想,如果遍历完一个节点,发现其还有左儿子或右儿子,就将其信息添加到这两个链表中,并且在每一轮循环中更新整个树的最小、最大深度。
/**
* 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 List<List<Integer>> verticalOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
if (root == null) {
return result;
}
// holding each level and values for that level
Map<Integer, List<Integer>> map = new HashMap<Integer, List<Integer>>();
// each node in queue corresponds with each integer in levels
ArrayDeque<TreeNode> queue = new ArrayDeque<TreeNode>();
LinkedList<Integer> levels = new LinkedList<Integer>();
queue.offer(root);
levels.offer(0);
// tracking min and max level for all nodes
int minLevel = 0, maxLevel = 0;
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
int l = levels.poll(); // level of current node
minLevel = Math.min(minLevel, l);
maxLevel = Math.max(maxLevel, l);
// for current level, add each node's value
if (!map.containsKey(l)) {
map.put(l, new ArrayList<>());
}
map.get(l).add(node.val);
// add left and right children
if (node.left != null) {
queue.offer(node.left);
levels.offer(l - 1);
}
if (node.right != null) {
queue.offer(node.right);
levels.offer(l + 1);
}
}
// migrate each level's data to result
for (int i = minLevel; i <= maxLevel; i++) {
result.add(map.get(i));
}
return result;
}
}
315. Count of Smaller Numbers After Self
https://leetcode.com/problems/count-of-smaller-numbers-after-self/
使用归并排序,在merge拷贝到新数组的同时,用一个count数组统计当前数右边有几个数比自身小。
class Item {
int val;
int index;
public Item(int val, int index) {
this.val = val;
this.index = index;
}
}
class Solution {
public List<Integer> countSmaller(int[] nums) {
// stores each num and its index
Item items[] = new Item[nums.length];
for (int i = 0; i < nums.length; i++) {
items[i] = new Item(nums[i], i);
}
// use mergesort to update count
int count[] = new int[nums.length];
mergesort(items, 0, items.length-1, count);
// convert int[] to List
List<Integer> result = new ArrayList<>();
for (int i = 0; i < count.length; i++) {
result.add(count[i]);
}
return result;
}
private void mergesort(Item items[], int low, int high, int count[]) {
if (low >= high) {
return;
}
int mid = low + (high - low) / 2;
mergesort(items, low, mid, count);
mergesort(items, mid+1, high, count);
merge(items, low, mid, mid+1, high, count);
}
private void merge(Item[] items, int lowStart, int lowEnd, int highStart, int highEnd, int count[]) {
int len = highEnd - lowStart + 1;
Item[] sortedArr = new Item[len];
int lowPtr = lowStart;
int highPtr = highStart;
int index = 0;
int rightCount = 0;
while (lowPtr <= lowEnd && highPtr <= highEnd) {
if (items[highPtr].val < items[lowPtr].val) {
rightCount++;
sortedArr[index] = items[highPtr];
index++;
highPtr++;
} else {
count[items[lowPtr].index] += rightCount;
sortedArr[index] = items[lowPtr];
index++;
lowPtr++;
}
}
// only one of the loop will be executed
while (lowPtr <= lowEnd) {
count[items[lowPtr].index] += rightCount;
sortedArr[index] = items[lowPtr];
index++;
lowPtr++;
}
while (highPtr <= highEnd) {
sortedArr[index] = items[highPtr];
index++;
highPtr++;
}
System.arraycopy(sortedArr, 0, items, lowStart, len);
}
}
316. Remove Duplicate Letters
https://leetcode.com/problems/remove-duplicate-letters/
第一次遍历字符串,统计每个字符使用的次数;第二次遍历,正常情况下直接向StringBuilder复制当前遍历的字符,并使当前字符使用次数-1。但如果遇到了一个字典序更小的新字符,并且确定原来StringBuilder的最后一个字符还没用完,就将原来的最后一个字符删去,留待之后再重新加入。
class Solution {
public String removeDuplicateLetters(String s) {
StringBuilder sb = new StringBuilder();
int[] count = new int[26];
boolean[] used = new boolean[26];
char[] chr = s.toCharArray();
// count how many times each character is used
for (char c : chr) {
count[c - 'a']++;
}
for (char c : chr) {
// update count and see how many using times are left
count[c - 'a']--;
// this character is used in the result, skip
if (used[c - 'a']) {
continue;
}
// a new smaller character & there is still last character left, delete it
// wait for the deleted character to be added later
while (sb.length() > 0 && sb.charAt(sb.length() - 1) > c && count[sb.charAt(sb.length() - 1) - 'a'] > 0) {
used[sb.charAt(sb.length() - 1) - 'a'] = false;
sb.deleteCharAt(sb.length() - 1);
}
sb.append(c);
used[c - 'a'] = true;
}
return sb.toString();
}
}
317. Shortest Distance from All Buildings
https://leetcode.com/problems/shortest-distance-from-all-buildings/
使用DFS,遍历数组,遇到第一块空地就开始BFS,同时不断递加距离和,再将空地的值减去总的距离和。完成之后,可能会发现有的楼不能到达;此外,也可能有的空地没有使用过,需要进行处理。之后,从所有距离和中选出一个最小的值,作为最终答案。
class Solution {
private int[][] dirs = new int[][]{ {0, 1}, {1, 0}, {0, -1}, {-1, 0} };
public int shortestDistance(int[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int row = grid.length, col = grid[0].length;
// a building which cannot be reached
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (grid[i][j] == 1 && !helper(grid, i, j, row, col)) {
return -1;
}
}
}
// select from all empty lands
int result = Integer.MAX_VALUE;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (grid[i][j] < 0) {
result = Math.min(result, -grid[i][j]);
}
}
}
return result == Integer.MAX_VALUE ? -1 : result;
}
private boolean helper(int[][] grid, int x, int y, int row, int col) {
boolean[][] visited = new boolean[row][col];
visited[x][y] = true;
Queue<int[]> queue = new LinkedList<int[]>();
queue.offer(new int[]{x, y, 1});
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
int[] p = queue.poll();
for (int[] dir : dirs) {
int nx = p[0] + dir[0];
int ny = p[1] + dir[1];
if (nx < 0 || nx >= row || ny < 0 || ny >= col || visited[nx][ny]) {
continue;
}
visited[nx][ny] = true;
// for all empty lands, use BFS to increment distance
if (grid[nx][ny] <= 0) {
grid[nx][ny] -= p[2];
queue.offer(new int[]{nx, ny, p[2] + 1});
}
}
}
}
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (!visited[i][j]) {
// a building which cannot be reached
if (grid[i][j] == 1) {
return false;
}
// unused empty land
if (grid[i][j] <= 0) {
grid[i][j] = 3;
}
}
}
}
return true;
}
}
318. Maximum Product of Word Lengths
https://leetcode.com/problems/maximum-product-of-word-lengths/
将每一个输入的单词转变为比特组合,此处可以自定义转化规则,例如每一位和1的左移进行异或。完成后,就可以根据位运算判断两个单词是否重复,并更新最大值。
class Solution {
public int maxProduct(String[] words) {
int[] arr = new int[words.length];
// transform word into bits combination
for (int i = 0; i < words.length; i++) {
String cur = words[i];
for (int j = 0; j < cur.length(); j++) {
arr[i] |= 1 << (cur.charAt(j) - 'a');
}
}
int maxLen = 0;
for (int i = 0; i < arr.length; i++) {
for (int j = i+1; j < arr.length; j++) {
// different words
if ((arr[i] & arr[j]) == 0) {
maxLen = Math.max(maxLen, words[i].length() * words[j].length());
}
}
}
return maxLen;
}
}
319. Bulb Switcher
https://leetcode.com/problems/bulb-switcher/
对于从1到n的所有数,所有完全平方数都有奇数个质因数(例如9 : 1, 3, 9),其他数都是偶数个质因数。而每一轮的开灯与关灯,实际上取决于这一轮的间隔切中了哪一个质因数。因此,最终的开关灯情况取决于每个数质因数的奇偶性。在经过了n轮开关之后,显然,最后能保持开状态的灯序号正好是1~n之间的完全平方数。这样的完全平方数共有sqrt(n)个。
class Solution {
public int bulbSwitch(int n) {
return (int)Math.sqrt(n);
}
}
320. Generalized Abbreviation
https://leetcode.com/problems/generalized-abbreviation/
回溯算法,逐位遍历生成一组缩略词,再向后递归调用新回溯。
class Solution {
public List<String> generateAbbreviations(String word) {
List<String> result = new ArrayList<>();
backtrack(result, word, new StringBuilder(), 0, 0);
return result;
}
// i for index, k for abbreviation
private void backtrack(List<String> result, String word, StringBuilder sb, int i, int k) {
int len = sb.length();
if (i == word.length()) {
if (k != 0) {
sb.append(k);
}
result.add(sb.toString());
} else {
backtrack(result, word, sb, i+1, k+1);
// generate an abbr
if (k != 0) {
sb.append(k);
}
sb.append(word.charAt(i));
// start a new abbr from next character
backtrack(result, word, sb, i+1, 0);
}
sb.setLength(len);
}
}
321. Create Maximum Number
https://leetcode.com/problems/create-maximum-number/
本质上是数组合并,并且需要我们根据双指针,人为实现一个数组的比较函数。使用一个循环遍历,将k一分为二,在两侧寻找“最大数组”,再将这两个“最大数组”合并起来,得到一个candidate数组。如果candidate大于已有的result,就更新result。
class Solution {
public int[] maxNumber(int[] nums1, int[] nums2, int k) {
int len1 = nums1.length, len2 = nums2.length;
int[] result = new int[k];
for (int i = Math.max(0, k - len2); i <= k && i <= len1; i++) {
int[] candidate = merge(maxArray(nums1, i), maxArray(nums2, k - i), k);
if (greater(candidate, 0, result, 0)) {
result = candidate;
}
}
return result;
}
// use mergesort to get a candidate array
private int[] merge(int[] nums1, int[] nums2, int k) {
int[] result = new int[k];
for (int i = 0, j = 0, r = 0; r < k; r++) {
result[r] = greater(nums1, i, nums2, j) ? nums1[i++] : nums2[j++];
}
return result;
}
// nums1 is 'greater than' nums2, if nums1 is longer or nums1[i] > nums2[j]
private boolean greater(int[] nums1, int i, int[] nums2, int j) {
while (i < nums1.length && j < nums2.length && nums1[i] == nums2[j]) {
i++;
j++;
}
return j == nums2.length || (i < nums1.length && nums1[i] > nums2[j]);
}
// get the max array within k length
private int[] maxArray(int[] nums, int k) {
int n = nums.length;
int[] result = new int[k];
for (int i = 0, j = 0; i < n; i++) {
while (n - i + j > k && j > 0 && result[j - 1] < nums[i]) {
j--;
}
if (j < k) {
result[j] = nums[i];
j++;
}
}
return result;
}
}
322. Coin Change
https://leetcode.com/problems/coin-change/
使用动态规划,dp[i]表示当总数为i时需要的硬币数量。遍历coins里的每个coin,假定这个面值就是我们当前拥有的最大面值,然后试着将coin到amount范围内所需的最少硬币数量更新。
class Solution {
public int coinChange(int[] coins, int amount) {
int dp[] = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int coin : coins) {
for (int i = coin; i <= amount; i++) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
return dp[amount] <= amount ? dp[amount] : -1;
}
}
323. Number of Connected Components in an Undirected Graph
https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/
使用一个数组root存储每个节点通往的根节点。遍历每一条边,如果发现两个边节点root1, root2通往的不是同一个根,就运用union思想将root1通往的根节点设为root2。这一过程相当于两个节点合成为了一个连通分量。由于初始的连通分量个数n实际上就是节点个数,因此需要让n递减1。查找根节点则运用find思想,逐步向上迭代,同时可以考虑进行路径压缩,加快后续查找效率。
class Solution {
public int countComponents(int n, int[][] edges) {
int[] roots = new int[n];
for (int i = 0; i < n; i++) {
roots[i] = i;
}
for (int[] e : edges) {
int root1 = find(roots, e[0]);
int root2 = find(roots, e[1]);
// two vertex of this edge is a connected component
if (root1 != root2) {
// union
roots[root1] = root2;
// numbers of cc decrement by 1
n--;
}
}
return n;
}
public int find(int[] roots, int id) {
while (roots[id] != id) {
// optional: path compression
roots[id] = roots[roots[id]];
id = roots[id];
}
return id;
}
}
324. Wiggle Sort II
https://leetcode.com/problems/wiggle-sort-ii/
使用桶排序,大桶优先排布到奇数下标上,之后再排布到偶数下标。
class Solution {
public void wiggleSort(int[] nums) {
int[] bucket = new int[5001];
for (int num : nums) {
bucket[num]++;
}
int index = 5000;
// fill odd index
for (int i = 1; i < nums.length; i += 2) {
while (bucket[index] == 0) {
index--;
}
nums[i] = index;
bucket[index]--;
}
// fill even index
for (int i = 0; i < nums.length; i += 2) {
while (bucket[index] == 0) {
index--;
}
nums[i] = index;
bucket[index]--;
}
}
}
325. Maximum Size Subarray Sum Equals k
https://leetcode.com/problems/maximum-size-subarray-sum-equals-k/
使用一个map遍历数组,记录nums累加到当前下标的和,同时检查nums[i] - k是否在map中,因为如果nums[i] - k在map中,说明下标map.get(nums[i] - k)和i之间的sum恰好是k,此时判断这个长度是否需要更新为最大值。
class Solution {
public int maxSubArrayLen(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>(); // <sum, index>
map.put(0, -1);
// every sum result
for (int i = 1; i < nums.length; i++) {
nums[i] += nums[i - 1];
}
int result = 0;
for (int i = 0; i < nums.length; i++) {
// a new sum, add to map
if (!map.containsKey(nums[i])) {
map.put(nums[i], i);
}
// find from previous sum key
if (map.containsKey(nums[i] - k)) {
result = Math.max(result, i - map.get(nums[i] - k));
}
}
return result;
}
}
326. Power of Three
https://leetcode.com/problems/power-of-three/
3的次幂肯定不为0;而在int范围内,最大的3的次幂是319,因此如果319能被这个数整除,说明这个数就是3的次幂。
class Solution {
public boolean isPowerOfThree(int n) {
return (n > 0 && 1162261467 % n == 0);
}
}
327. Count of Range Sum
https://leetcode.com/problems/count-of-range-sum/
先计算得到数组从头至当前的累加结果,再将累加数组一分为二,用mergesort分别计算两侧的结果后,合并数组。这样得到的复杂度是O(nlogn)。
class Solution {
private int count = 0;
private int lower;
private int upper;
public int countRangeSum(int[] nums, int lower, int upper) {
this.lower = lower;
this.upper = upper;
long[] sum = new long[nums.length + 1];
long[] temp = new long[nums.length + 1];
sum[0] = 0;
for (int i = 1; i <= nums.length; i++) {
sum[i] = sum[i - 1] + (long) nums[i - 1];
}
mergesort(sum, 0, sum.length - 1, temp);
return count;
}
private void mergesort(long[] sum, int start, int end, long[] temp) {
if (start >= end) {
return;
}
int mid = start + (end - start) / 2;
mergesort(sum, start, mid, temp);
mergesort(sum, mid + 1, end, temp);
merge(sum, start, mid, end, temp);
}
private void merge(long[] sum, int start, int mid, int end, long[] temp) {
int right = mid + 1;
int index = start;
int low = mid + 1, high = mid + 1;
for (int left = start; left <= mid; left++) {
// too small or too large
while (low <= end && sum[low] - sum[left] < lower) {
low++;
}
while (high <= end && sum[high] - sum[left] <= upper) {
high++;
}
// complete merge sort
while (right <= end && sum[right] < sum[left]) {
temp[index] = sum[right];
index++;
right++;
}
temp[index] = sum[left];
index++;
count += high - low;
}
while (right <= end) {
temp[index] = sum[right];
index++;
right++;
}
// use cache to override sum
for (int i = start; i <= end; i++) {
sum[i] = temp[i];
}
}
}
328. Odd Even Linked List
https://leetcode.com/problems/odd-even-linked-list/
使用两个指针,分别跟踪所有奇数节点和偶数节点,再额外使用一个变量存储第一个偶数节点(用于最后定位将所有偶数节点放置在奇数节点之后)。while循环中,提取下下个节点保证奇偶性作为下一个连接的元素。
/**
* 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 oddEvenList(ListNode head) {
if (head != null) {
ListNode odd = head, even = head.next, evenHead = even;
while (even != null && even.next != null) {
// link all odd elements
odd.next = odd.next.next;
odd = odd.next;
// link all even elements
even.next = even.next.next;
even = even.next;
}
// even elements are placed behind odd elements
odd.next = evenHead;
}
return head;
}
}
329. Longest Increasing Path in a Matrix
https://leetcode.com/problems/longest-increasing-path-in-a-matrix/
使用dfs,不断延伸尝试当前格四个方向能达到的最大值,并确保下一个值更大,再使用一个maxGrid数组记录下这个最大值。如果经过了已经访问过的方格,可以直接获取其最大值加快速度。
class Solution {
private int result = 0;
// store each grid's possible maximum number
private int[][] maxGrid;
public int longestIncreasingPath(int[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int m = matrix.length, n = matrix[0].length;
maxGrid = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
helper(matrix, i, j, Integer.MIN_VALUE);
}
}
return result;
}
public int helper(int[][] matrix, int i, int j, int last) {
if (i < 0 || i >= matrix.length || j < 0 || j >= matrix[0].length) {
return 0;
}
// skip non-increasing case
if (matrix[i][j] <= last) {
return 0;
}
// fetch result from previous calculation
if (maxGrid[i][j] != 0) {
return maxGrid[i][j];
}
// four direcions
int v1 = helper(matrix, i-1, j, matrix[i][j]);
int v2 = helper(matrix, i+1, j, matrix[i][j]);
int v3 = helper(matrix, i, j-1, matrix[i][j]);
int v4 = helper(matrix, i, j+1, matrix[i][j]);
// choose the maximum result and update the maxGrid array
int max = 1 + Math.max(v1, Math.max(v2, Math.max(v3, v4)));
result = Math.max(result, max);
maxGrid[i][j] = max;
return max;
}
}
330. Patching Array
https://leetcode.com/problems/patching-array/
先举一个例子:
[1,2,3,5,10,50,70], n = 100
- 看到第一个数1,我们知道[1,1]可以被覆盖
- 看到第二个数2,我们知道[1,3]可以被覆盖
- 3同理,[1,6]可以被覆盖
- 5同理,[1,11]可以被覆盖
- 10同理,[1,21]可以被覆盖
- 现在到50,发现不得不打补丁了,如果打补丁1,可以扩展为[1,22],如果打补丁2,可以扩展为[1,23]…可见,要得到最大的范围,应该打的补丁是22,这样能得到[1,43],为什么不能打补丁23呢?因为[1,2,3,5,10,23]得不到22
- [1,43]还是没有覆盖50,按照类似的逻辑,这次应该打的补丁是44,将范围扩充到[1,87]
- 最后到70,在[1,87]内,范围被扩充到[1,157]
- 157 > 100,结束
所以,题目的要点在于,如果当前的范围是[1, m],且当前的数字num > m,我们应该打补丁m+1,使得范围扩充到[1,2m+1]。另外,注意当输入n很大时,需要考虑溢出的情况。
class Solution {
public int minPatches(int[] nums, int n) {
long miss = 1;
int result = 0, i = 0;
while (miss <= n) {
if (i < nums.length && nums[i] <= miss) {
miss += nums[i];
i++;
} else {
miss += miss;
result++;
}
}
return result;
}
}
331. Verify Preorder Serialization of a Binary Tree
https://leetcode.com/problems/verify-preorder-serialization-of-a-binary-tree/
使用dfs,向前检查字符串,如果当前的下标超过了s的长度,或者遇到了#,就说明无路可走,停止于当前下标;否则,就访问当前节点(跳过所有非逗号的字符),再先后访问左、右子树。如果最终能完成整个字符串的遍历,就说明是合法的前序遍历序列。
class Solution {
public boolean isValidSerialization(String s) {
if (s == null || s.length() == 0) {
return true;
}
int result = dfs(s, 0);
// whether finish whole string's examination
return result == s.length() - 1;
}
private int dfs(String s, int idx) {
// cannot move forward, stop at current index
if (idx >= s.length() || s.charAt(idx) == '#') {
return idx;
}
// visit current node
while (idx < s.length() && s.charAt(idx) != ',') {
idx++;
}
// +1 to skip ','
idx++;
// visit left tree
int l = dfs(s, idx);
// right tree, finish
return dfs(s, l + 2);
}
}
332. Reconstruct Itinerary
https://leetcode.com/problems/reconstruct-itinerary/
为每一个出发地维护一个优先队列,按照字典序记录所有从该出发地到达的目的地。从JFK出发,访问第一个到达的目的地,将其从优先队列中移出,再递归使用dfs访问从这个地方能到的目的地。重复这个过程,直到某个出发地已经没有能到达的目的地了,这个地方就是旅行日程的最终点,将其添加到结果中。之后迭代返回时,不断将地名添加在list的首位,以实现倒序添加的目的。
class Solution {
private Map<String, PriorityQueue<String>> flights;
private LinkedList<String> path;
public List<String> findItinerary(List<List<String>> tickets) {
flights = new HashMap<>();
path = new LinkedList<>();
// for each departure place, maintain a priority queue of arrival places
// by default, the string priority queue will be sorted in lexical order
for (List<String> ticket : tickets) {
flights.putIfAbsent(ticket.get(0), new PriorityQueue<>());
flights.get(ticket.get(0)).add(ticket.get(1));
}
// always start from JFK
dfs("JFK");
return path;
}
public void dfs(String departure) {
PriorityQueue<String> arrivals = flights.get(departure);
// dfs to explore first arrival place
while (arrivals != null && !arrivals.isEmpty()) {
dfs(arrivals.poll());
}
// reversely add to the result
path.addFirst(departure);
}
}
333. Largest BST Subtree
https://leetcode.com/problems/largest-bst-subtree/
使用一个数组result传递当前记录的最大BST信息,其中result[0]和result[1]分别表示最小和最大值,而result[2]则记录了根节点。
从根节点开始访问,先找出当前节点左右子树中的最大BST,再判断加入当前节点是否会产生一个新的更大的BST,或者是返回这两棵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 int largestBSTSubtree(TreeNode root) {
int[] result = largestBST(root);
return result[2];
}
private int[] largestBST(TreeNode node) {
if (node == null) {
return new int[] {Integer.MAX_VALUE, Integer.MIN_VALUE, 0};
}
int[] left = largestBST(node.left);
int[] right = largestBST(node.right);
// adding current node contributes to a new largest BST
if (node.val > left[1] && node.val < right[0]) {
return new int[] { Math.min(node.val, left[0]), Math.max(node.val, right[1]), left[2] + right[2] + 1 };
}
// otherwise, the largest BST should be either left subtree or right subtree
return new int[] { Integer.MIN_VALUE, Integer.MAX_VALUE, Math.max(left[2], right[2]) };
}
}
334. Increasing Triplet Subsequence
https://leetcode.com/problems/increasing-triplet-subsequence/
从前向后遍历,维护当前已经遍历的最小值和次小值。如果发现当前的数比这两个数都要大,就说明之前已经遍历的最小值、次小值与当前数满足递增关系。
class Solution {
public boolean increasingTriplet(int[] nums) {
int firstSmall = Integer.MAX_VALUE;
int secondSmall = Integer.MAX_VALUE;
for (int i = 0; i < nums.length; i++) {
if (nums[i] <= firstSmall) {
firstSmall = nums[i];
} else if (nums[i] <= secondSmall) {
secondSmall = nums[i];
} else {
return true;
}
}
return false;
}
}
335. Self Crossing
https://leetcode.com/problems/self-crossing/
考虑到第四、五、六条线与第一条线相交的情况,分类讨论。
class Solution {
public boolean isSelfCrossing(int[] x) {
if (x == null || x.length < 4) {
return false;
}
for (int i = 3; i < x.length; i++) {
// Fourth line crosses first line and onward
if (x[i] >= x[i-2] && x[i-1] <= x[i-3]) {
return true;
}
// Fifth line meets first line and onward
if (i >= 4 && x[i-1] == x[i-3] && x[i] + x[i-4] >= x[i-2]) {
return true;
}
// Sixth line crosses first line and onward
if (i >= 5 && x[i-2] >=x[i-4] && x[i] >= x[i-2] - x[i-4] && x[i-1] >= x[i-3] - x[i-5] && x[i-1] <= x[i-3]) {
return true;
}
}
return false;
}
}
336. Palindrome Pairs
https://leetcode.com/problems/palindrome-pairs/
使用Trie,添加每个单词时,倒序添加;搜索时,正序搜索。总的时间复杂度是O(n * k^2)。
class Solution {
private static class TrieNode {
TrieNode[] next;
int index;
List<Integer> list;
TrieNode() {
next = new TrieNode[26];
index = -1;
list = new ArrayList<>();
}
}
public List<List<Integer>> palindromePairs(String[] words) {
List<List<Integer>> result = new ArrayList<>();
TrieNode root = new TrieNode();
for (int i = 0; i < words.length; i++) {
addWord(root, words[i], i);
}
for (int i = 0; i < words.length; i++) {
search(words, i, root, result);
}
return result;
}
// add each word reversely
private void addWord(TrieNode root, String word, int index) {
for (int i = word.length() - 1; i >= 0; i--) {
int j = word.charAt(i) - 'a';
// add current word as a new node
if (root.next[j] == null) {
root.next[j] = new TrieNode();
}
if (isPalindrome(word, 0, i)) {
root.list.add(index);
}
root = root.next[j];
}
root.list.add(index);
root.index = index;
}
private void search(String[] words, int i, TrieNode root, List<List<Integer>> result) {
for (int j = 0; j < words[i].length(); j++) {
// valid & non-equal index, all characters behind are palindrome
if (root.index >= 0 && root.index != i && isPalindrome(words[i], j, words[i].length() - 1)) {
result.add(Arrays.asList(i, root.index));
}
// move to next word
root = root.next[words[i].charAt(j) - 'a'];
if (root == null) {
return;
}
}
for (int j : root.list) {
if (i == j) {
continue;
}
result.add(Arrays.asList(i, j));
}
}
private boolean isPalindrome(String word, int i, int j) {
while (i < j) {
if (word.charAt(i++) != word.charAt(j--)) {
return false;
}
}
return true;
}
}
337. House Robber III
https://leetcode.com/problems/house-robber-iii/
考虑两种情况:
- 不偷当前根节点,那么最大利益的来源就是左、右子树的偷盗之和,并且左、右子树的偷盗必须都包含左右子树的根节点
- 偷当前根节点,那么最大利益的来源是当前节点与左、右子树的偷盗之和,并且左、右子树的偷盗都不能包含左右子树的根节点
考虑到每个节点都有这两种情况,使用一个长度为2的数组记下两种利益,并比较大小,得到以每个点为根子树的最大收益。
/**
* 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 rob(TreeNode root) {
return maxProfit(root)[1];
}
private int[] maxProfit(TreeNode root) {
if (root == null) {
return new int[2];
}
int[] result = new int[2];
int[] left = maxProfit(root.left);
int[] right = maxProfit(root.right);
// maxProfit[0] = max Money skipping root, maxProfit[1] = max Money robbing root
result[0] = left[1] + right[1];
result[1] = Math.max(root.val + left[0] + right[0], result[0]);
return result;
}
}
338. Counting Bits
https://leetcode.com/problems/counting-bits/
按0,1,2-3,4-7,8-15,…的区间划分,可以发现每一个数拥有1的个数都是在此之前offset个数拥有1的个数加上1。例如:
1(1) -> 3(11), offset = 2
3(11) -> 7(111), offset = 4
7(111) -> 15(1111), offset = 8
...
而观察发现,offset都是2的次幂,并且一定是不大于当前遍历数i的最大2的次幂。因此,使用数组存储之前的结果,在得到offset之后,就能够直接根据offset提取之前的结果递增即可。
class Solution {
public int[] countBits(int num) {
int[] result = new int[num + 1];
int offset = 1;
for (int i = 1; i < result.length; i++) {
if (offset * 2 == i) {
offset *= 2;
}
result[i] = 1 + result[i - offset];
}
return result;
}
}
339. Nested List Weight Sum
https://leetcode.com/problems/nested-list-weight-sum/
跟踪当前层数,注意到每一层的nestedList中可能有Integer或者List,分情况讨论,累加时乘上当前的层数即可。
/**
* // This is the interface that allows for creating nested lists.
* // You should not implement it, or speculate about its implementation
* public interface NestedInteger {
* // Constructor initializes an empty nested list.
* public NestedInteger();
*
* // Constructor initializes a single integer.
* public NestedInteger(int value);
*
* // @return true if this NestedInteger holds a single integer, rather than a nested list.
* public boolean isInteger();
*
* // @return the single integer that this NestedInteger holds, if it holds a single integer
* // Return null if this NestedInteger holds a nested list
* public Integer getInteger();
*
* // Set this NestedInteger to hold a single integer.
* public void setInteger(int value);
*
* // Set this NestedInteger to hold a nested list and adds a nested integer to it.
* public void add(NestedInteger ni);
*
* // @return the nested list that this NestedInteger holds, if it holds a nested list
* // Return empty list if this NestedInteger holds a single integer
* public List<NestedInteger> getList();
* }
*/
class Solution {
public int depthSum(List<NestedInteger> nestedList) {
return helper(nestedList, 1);
}
private int helper(List<NestedInteger> nestedList, int level) {
int result = 0;
// object in nestedList can either be an integer or a list
for (NestedInteger obj: nestedList) {
if (obj.isInteger()) {
result += level * obj.getInteger();
} else {
result += helper(obj.getList(), level + 1);
}
}
return result;
}
}
340. Longest Substring with At Most K Distinct Characters
https://leetcode.com/problems/longest-substring-with-at-most-k-distinct-characters/
维护一个sliding window,时刻关注window中不同字母的数量,如果超过k了就需要从头不断删字符,直到不同的字母数量降到k或k以下。每遍历一个字符就更新一次结果。
class Solution {
public int lengthOfLongestSubstringKDistinct(String s, int k) {
int[] count = new int[256];
int distinctNum = 0; // current distinct characters count
int start = 0; // start index of sliding window
int result = 0;
for (int end = 0; end < s.length(); end++) {
// append a new distinct character to the sliding window
if (count[s.charAt(end)]++ == 0) {
distinctNum++;
}
// more than k distinct characters, repeatedly delete starting characters of sliding window
if (distinctNum > k) {
while (--count[s.charAt(start++)] > 0) {
;
}
distinctNum--;
}
result = Math.max(result, end - start + 1);
}
return result;
}
}
341. Flatten Nested List Iterator
https://leetcode.com/problems/flatten-nested-list-iterator/
仍然是分情况讨论每一个nestedObj是integer还是list。如果当前的元素是list,就递归先将这个list里的所有元素全部添加到结果中,再返回处理后续的元素。遍历过程中,使用一个变量counter记录当前访问的下标,如果当前下标小于总的list长度,说明还没访问完。
/**
* // This is the interface that allows for creating nested lists.
* // You should not implement it, or speculate about its implementation
* public interface NestedInteger {
*
* // @return true if this NestedInteger holds a single integer, rather than a nested list.
* public boolean isInteger();
*
* // @return the single integer that this NestedInteger holds, if it holds a single integer
* // Return null if this NestedInteger holds a nested list
* public Integer getInteger();
*
* // @return the nested list that this NestedInteger holds, if it holds a nested list
* // Return empty list if this NestedInteger holds a single integer
* public List<NestedInteger> getList();
* }
*/
public class NestedIterator implements Iterator<Integer> {
private List<Integer> allValues = new ArrayList<>();
private int counter = 0;
public NestedIterator(List<NestedInteger> nestedList) {
// Flatten the list and then put the iterator to it.
for (NestedInteger val: nestedList) {
recursiveIterate(val, allValues);
}
}
@Override
public Integer next() {
Integer result = allValues.get(counter);
counter++;
return result;
}
@Override
public boolean hasNext() {
return counter < allValues.size();
}
private void recursiveIterate(NestedInteger nestedObj, List<Integer> allValues) {
// object in nestedList can either be an integer or a list
if (nestedObj.isInteger()) {
allValues.add(nestedObj.getInteger());
return;
}
for (NestedInteger val: nestedObj.getList()) {
recursiveIterate(val, allValues);
}
}
}
/**
* Your NestedIterator object will be instantiated and called as such:
* NestedIterator i = new NestedIterator(nestedList);
* while (i.hasNext()) v[f()] = i.next();
*/
342. Power of Four
https://leetcode.com/problems/power-of-four/
一个数是4的次幂,需要满足三个条件:
- 大于0
- 二进制表示下,只有一个’1’。因此,可以使用
num & (num - 1)去除掉这个1,检查是否变成0 - 这个’1’一定是在奇数位上。因此,使用0x55555555检查
num的1是否位置正确
class Solution {
public boolean isPowerOfFour(int num) {
return (num > 0 && (num & (num - 1)) == 0 && (num & 0x55555555) != 0);
}
}
343. Integer Break
https://leetcode.com/problems/integer-break/
对于一个数N,如果N是偶数,拆分成(N/2) * (N/2)能得到最大的积;如果N是奇数,拆分成((N-1)/2) * ((N+1)/2)能得到最大的积。
要完成这样的拆分,不难可以发现每一项拆出来的数必须小于4,否则我们总能进一步拆分这个数得到更大的乘积。排除1,合理的项只有2或者3了。进一步观察发现,尽可能多的使用3,比起使用2会得到更大的乘积(例如 6 = 3 + 3 = 2 + 2 + 2,但是3 * 3 > 2 * 2 * 2)。因此,使用循环,在可行范围内尽可能使用3。
class Solution {
public int integerBreak(int n) {
if (n == 2) {
return 1;
}
if (n == 3) {
return 2;
}
int product = 1;
while (n > 4) {
product *= 3;
n -= 3;
}
product *= n;
return product;
}
}
344. Reverse String
https://leetcode.com/problems/reverse-string/
头尾指针交换字符。
class Solution {
public void reverseString(char[] s) {
if (s == null || s.length == 0) {
return;
}
int begin = 0, end = s.length - 1;
while (begin < end) {
char c = s[begin];
s[begin] = s[end];
s[end] = c;
begin++;
end--;
}
return;
}
}
345. Reverse Vowels of a String
https://leetcode.com/problems/reverse-vowels-of-a-string/
仍然是头尾双指针,只有双指针都是元音字母时才进行交换。
class Solution {
public String reverseVowels(String s) {
char[] arr = s.toCharArray();
int i = 0, j = arr.length - 1;
while (i < j) {
boolean notVowel = false;
if (!isVowels(arr[i])) {
i++;
notVowel = true;
}
if (!isVowels(arr[j])) {
j--;
notVowel = true;
}
if (notVowel) {
continue;
}
char tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
i++;
j--;
}
return new String(arr);
}
private boolean isVowels(char c) {
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
}
}
346. Moving Average from Data Stream
https://leetcode.com/problems/moving-average-from-data-stream/
维护一个队列,如果队列中的元素数量超过window size,就将队列首个元素踢出。更新队列后,计算队列元素的平均值。
class MovingAverage {
private Queue<Integer> queue;
private int size = 0;
private long queueSum = 0;
/** Initialize your data structure here. */
public MovingAverage(int size) {
this.size = size;
queue = new ArrayDeque();
}
public double next(int val) {
queueSum += val;
queue.add(val);
if (queue.size() > size) {
int front = queue.poll();
queueSum -= front;
}
return (double)queueSum / queue.size();
}
}
347. Top K Frequent Elements
https://leetcode.com/problems/top-k-frequent-elements/
统计每个数出现的次数。再使用一个桶,对每一个出现频数,存储哪些数字出现了这么多次。最后再按照频数的大小,从大到小添加k个数字即可。
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
List<Integer>[] bucket = new List[nums.length + 1];
List<Integer> result = new ArrayList<>();
// frequency map
for (int num: nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
// for each frequency, add corresponding number to the bucket
for (int key: map.keySet()) {
int frequency = map.get(key);
if (bucket[frequency] == null) {
bucket[frequency] = new ArrayList<>();
}
bucket[frequency].add(key);
}
// get result from bucket
for (int pos = bucket.length - 1; pos >= 0; pos--) {
if (bucket[pos] != null) {
for (int i = 0; i < bucket[pos].size() && result.size() < k; i++) {
result.add(bucket[pos].get(i));
}
}
}
int[] arrayResult = new int[result.size()];
for (int i = 0; i < result.size(); i++) {
arrayResult[i] = result.get(i);
}
return arrayResult;
}
}
348. Design Tic-Tac-Toe
https://leetcode.com/problems/design-tic-tac-toe/
每下一子,会影响这一子所在的行、列以及双对角线(如果这一子恰好落在双对角线之一上),因此只需要跟踪这四条线的情况即可。可以使用两个数组检查所有row和col的情况,再使用两个变量跟踪双对角线。假设player1下一子,当前所在行/列/双对角线的值就+1,而player2下一子,当前所在行/列/双对角线的值就-1。每次下完一子后,检查这四条线的值,如果某一条绝对值等于棋盘大小size,说明这条线都是其中一名player的棋子,这个player胜利。
class TicTacToe {
private int[] rows;
private int[] cols;
private int diagonal;
private int antiDiagonal;
/** Initialize your data structure here. */
public TicTacToe(int n) {
rows = new int[n];
cols = new int[n];
}
/** Player {player} makes a move at ({row}, {col}).
@param row The row of the board.
@param col The column of the board.
@param player The player, can be either 1 or 2.
@return The current winning condition, can be either:
0: No one wins.
1: Player 1 wins.
2: Player 2 wins. */
public int move(int row, int col, int player) {
int toAdd = player == 1 ? 1 : -1;
// add current chess and update the status
rows[row] += toAdd;
cols[col] += toAdd;
if (row == col) {
diagonal += toAdd;
}
if (row + col == cols.length - 1) {
antiDiagonal += toAdd;
}
int size = rows.length;
// one of the players fills a row / col / cross diagonal
if (Math.abs(rows[row]) == size || Math.abs(cols[col]) == size || Math.abs(diagonal) == size || Math.abs(antiDiagonal) == size) {
return player;
}
return 0;
}
}
349. Intersection of Two Arrays
https://leetcode.com/problems/intersection-of-two-arrays/
使用一个Set,先遍历nums1将所有数存进Set,再遍历nums2的每个数,看Set中能不能删掉这个数。如果这个数能被删掉,说明这个数在nums1和nums2中都出现了,因此添加到答案中。
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> set = new HashSet<>();
for (int num : nums1) {
set.add(num);
}
int[] result = new int[set.size()];
int count = 0;
for (int num : nums2) {
// current num is in both num1 and num2
if (set.remove(num)) {
result[count] = num;
count++;
}
}
// copy all intersections
return Arrays.copyOfRange(result, 0, count);
}
}
350. Intersection of Two Arrays II
https://leetcode.com/problems/intersection-of-two-arrays-ii/
使用map,先统计nums1中每个数出现了多少次,之后遍历nums2,如果发现了一个数重复,就加到结果,同时把map里这个数的频数-1,以此保证能出现完全一样的次数。
Follow-ups:
- 如果两个数组已经排好序,直接使用双指针法同时遍历两个数组,类似于mergesort的流程
- 如果
nums1的长度比起nums2小了很多,并且仍然是排好序的情况下,可以考虑使用二分搜索;否则,应该使用小数组作为哈希计数表 - 如果
nums1较小,可以将nums1中的元素放入哈希表,每一次从中读一个chunk的大小(视内存而定),记录所有的intersections。而如果nums1也较大,要么使用MapReduce,要么使用streaming
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
// occurrence map
HashMap<Integer, Integer> map = new HashMap<>();
for (int i : nums1) {
int freq = map.getOrDefault(i, 0);
map.put(i, freq + 1);
}
int[] result = new int[nums1.length];
int count = 0;
ArrayList<Integer> list = new ArrayList<>();
for (int i : nums2) {
int currentNumFreq = map.getOrDefault(i, 0);
if (currentNumFreq > 0) {
result[count] = i;
count++;
// update occurrence to ensure as many times as appearance in both arrays
map.put(i, currentNumFreq - 1);
}
}
return Arrays.copyOfRange(result, 0, count);
}
}
351. Android Unlock Patterns
https://leetcode.com/problems/android-unlock-patterns/
使用一个缓存数组,对于所有的key数量,存储共有多少种图案。再使用dfs,每一次迭代传入当前的起始位置以及当前使用的key数量,再遍历其他所有可能的结束位置,如果起始 -> 结束的路径合法,就进入下一组迭代,模拟下一步。注意在模拟下一步之前,需要记录当前已经过数字的访问情况。
class Solution {
// for each key number, the total count of unlock patterns
private int[] cache;
boolean[] visited = new boolean [10];
public int numberOfPatterns(int m, int n) {
if (cache == null) {
cache = new int[10];
dfs(0, 0);
// cumulative result
for (int i = 1; i < 10; i++) {
cache[i] += cache[i-1];
}
}
return cache[n] - cache[m-1];
}
private void dfs(int x, int keyNum) {
if (keyNum > 0) {
cache[keyNum]++;
}
for (int y = 1; y < 10; y++) {
if (visited[y]) {
continue;
}
if (keyNum == 0 || validX2Y(x, y)) {
visited[y] = true;
dfs(y, keyNum + 1);
visited[y] = false;
}
// exploit the symmetries
if (keyNum == 0) {
if (y == 5) {
break;
}
if (y == 2) {
y += 2;
for (int i = 1; i < 10; i++) {
cache[i] *= 4;
}
}
}
}
}
// decide if route from number x -> number y is valid
private boolean validX2Y(int x, int y) {
// start / end is center, which must be valid
if (x == 5 || y == 5) {
return true;
}
// one number is 2/4/6/8, should not reach the other side without crossing center
if (x % 2 == 0 || y % 2 == 0) {
return x + y != 10 || visited[5];
}
// two numbers are both odd numbers, must cross the median number
return visited[(x + y) / 2];
}
}
352. Data Stream as Disjoint Intervals
https://leetcode.com/problems/data-stream-as-disjoint-intervals/
使用一个TreeMap,每个pair的key和value分别对应区间的两个端点。利用TreeMap特性,找到最接近的上下两个key,分类讨论当前插入的数是否需要跟邻近区间合并。如果不能合并,则当作一个新区间插入。
class SummaryRanges {
private TreeMap<Integer, Integer> tree;
/** Initialize your data structure here. */
public SummaryRanges() {
tree = new TreeMap<>();
}
public void addNum(int val) {
if (tree.containsKey(val)) {
return;
}
// the least key strictly greater than the given key, or null
Integer ceil = tree.higherKey(val);
// the greatest key strictly less than to given key, or null
Integer floor = tree.lowerKey(val);
// current number is a middle value between the other two, eg. insert 2 between 1, 3
if (ceil != null && floor != null && val == tree.get(floor) + 1 && val == ceil - 1) {
// modify floor pair and remove ceil pair to merge
tree.put(floor, tree.get(ceil));
tree.remove(ceil);
} else if (floor != null && val <= tree.get(floor) + 1) {
// merge with a smaller interval
tree.put(floor, Math.max(val, tree.get(floor)));
} else if (ceil != null && val >= ceil - 1) {
// merge with a greater internal, and remove duplicate interval if necessary
tree.put(val, tree.get(ceil));
if (ceil != val) {
tree.remove(ceil);
}
} else {
// a new number, insert as a self disjoint interval
tree.put(val, val);
}
}
public int[][] getIntervals() {
int[][] result = new int[tree.size()][2];
int i = 0;
for (int n : tree.keySet()) {
result[i][0] = n;
result[i][1] = tree.get(n);
i++;
}
return result;
}
}
353. Design Snake Game
https://leetcode.com/problems/design-snake-game/
定义一个Point类表示每个点的坐标,再用Point链表表示蛇,如果蛇吃到了食物,就在链表头增加一格,表示蛇在当前位置变长,并且游戏分数+1。只有确保当前的分数仍然小于总的食物个数时,才生成下一个食物。此外需要注意蛇移动时,不能出边界或咬到自己。
class SnakeGame {
class Position {
int x;
int y;
public Position(int x, int y) {
this.x = x;
this.y = y;
}
public boolean isEqual(Position p) {
return this.x == p.x && this.y == p.y ;
}
}
private int score;
private int rows, cols;
private int[][] food;
LinkedList<Position> snake;
/** Initialize your data structure here.
@param width - screen width
@param height - screen height
@param food - A list of food positions
E.g food = [[1,1], [1,0]] means the first food is positioned at [1,1], the second is at [1,0]. */
public SnakeGame(int width, int height, int[][] food) {
this.rows = height;
this.cols = width;
this.food = food;
// The snake is initially positioned at the top left corner (0,0)
snake = new LinkedList<Position>();
snake.add(new Position(0,0));
score = 0;
}
/** Moves the snake.
@param direction - 'U' = Up, 'L' = Left, 'R' = Right, 'D' = Down
@return The game's score after the move. Return -1 if game over.
Game over when snake crosses the screen boundary or bites its body. */
public int move(String direction) {
Position cur = new Position(snake.get(0).x, snake.get(0).y);
switch(direction) {
case "U":
cur.x--; break;
case "L":
cur.y--; break;
case "R":
cur.y++; break;
case "D":
cur.x++; break;
}
// out of bound
if (cur.x < 0 || cur.x >= rows || cur.y < 0 || cur.y >= cols) {
return -1;
}
// bite self
for (int i = 1; i < snake.size() - 1; i++) {
Position next = snake.get(i);
if (next.isEqual(cur)) {
return -1;
}
}
// add one size at current food's location
snake.addFirst(cur);
// guarantee that there should still be enough food
if (score < food.length) {
Position p = new Position(food[score][0], food[score][1]);
if (cur.isEqual(p)) {
score++;
}
}
while (snake.size() > score + 1) {
snake.removeLast();
}
return score;
}
}
/**
* Your SnakeGame object will be instantiated and called as such:
* SnakeGame obj = new SnakeGame(width, height, food);
* int param_1 = obj.move(direction);
*/
354. Russian Doll Envelopes
https://leetcode.com/problems/russian-doll-envelopes/
先按照宽度对所有信封进行升序排序,如果宽度一样再按照高度降序排序(否则会产生关于高度的递增序列)。之后,求出关于高度序列的最长递增序列。
class Solution {
public int maxEnvelopes(int[][] envelopes) {
if (envelopes == null || envelopes.length == 0 || envelopes[0].length != 2) {
return 0;
}
// sort by width in ascending order
// sort by height in descending order if width are equal
Arrays.sort(envelopes, new Comparator<int[]>() {
public int compare(int[] arr1, int[] arr2) {
return arr1[0] == arr2[0] ? arr2[1] - arr1[1] : arr1[0] - arr2[0];
}
});
// Find the longest increasing subsequence based on height.
int dp[] = new int[envelopes.length];
int result = 0;
for (int[] envelope : envelopes) {
int index = Arrays.binarySearch(dp, 0, result, envelope[1]);
if (index < 0) {
index = -(index + 1);
}
dp[index] = envelope[1];
if (index == result) {
result++;
}
}
return result;
}
}
355. Design Twitter
https://leetcode.com/problems/design-twitter/
设计一个User类,每一个用户存储用户ID,关注的所有用户的列表,以及发过的所有Tweet链表的头(如果没发过,就是null)。每一个Tweet类instance则相当于一个链表的节点,尤其注意其中存储了下一个Tweet节点的指针。这样设计的目的是在getNewsFeed()时,可以利用优先队列快速的对当前用户能看到的所有tweet按照时间戳进行排序。
此外,在用户关注/取消关注时,要注意到可能输入的无效测试样例(例如某个follwerId不存在),这时候需要先创建这个User,再记录到userMap中,之后才继续进行相关操作。
class Twitter {
private static int timeStamp = 0;
private Map<Integer, User> userMap; // user id -> User
private class Tweet {
public int id;
public int time;
public Tweet next;
public Tweet(int id) {
this.id = id;
time = timeStamp++;
next = null;
}
}
public class User {
public int id;
public Set<Integer> followed;
public Tweet tweet_head;
public User(int id) {
this.id = id;
followed = new HashSet<>();
follow(id); // first follow self
tweet_head = null;
}
public void follow(int id) {
followed.add(id);
}
public void unfollow(int id) {
followed.remove(id);
}
// everytime user post a new tweet, add it to the head of tweet list.
public void post(int id) {
Tweet t = new Tweet(id);
t.next = tweet_head;
tweet_head = t;
}
}
/** Initialize your data structure here. */
public Twitter() {
userMap = new HashMap<Integer, User>();
}
/** Compose a new tweet. */
public void postTweet(int userId, int tweetId) {
if (!userMap.containsKey(userId)) {
User u = new User(userId);
userMap.put(userId, u);
}
userMap.get(userId).post(tweetId);
}
public List<Integer> getNewsFeed(int userId) {
List<Integer> result = new LinkedList<>();
if (!userMap.containsKey(userId)) {
return result;
}
// get all tweets lists from one user including itself and all people it followed.
Set<Integer> users = userMap.get(userId).followed;
// add all heads into a max heap of timestamp. Every time we poll a tweet from the heap, add its next into the heap.
// sort by timestamp
PriorityQueue<Tweet> q = new PriorityQueue<Tweet>(users.size(), (a,b) -> (b.time - a.time));
for (int user: users) {
Tweet t = userMap.get(user).tweet_head;
if (t != null) {
// make sure current user has posted something
q.add(t);
}
}
// poll at most 10 tweets from the priority queue
int n = 0;
while (!q.isEmpty() && n < 10) {
Tweet t = q.poll();
result.add(t.id);
n++;
// make sure current tweet has next tweet
if (t.next != null) {
q.add(t.next);
}
}
return result;
}
/* Follower follows a followee. If the operation is invalid, it should be a no-op. */
public void follow(int followerId, int followeeId) {
if (!userMap.containsKey(followerId)) {
User u = new User(followerId);
userMap.put(followerId, u);
}
if (!userMap.containsKey(followeeId)) {
User u = new User(followeeId);
userMap.put(followeeId, u);
}
userMap.get(followerId).follow(followeeId);
}
/* Follower unfollows a followee. If the operation is invalid, it should be a no-op. */
public void unfollow(int followerId, int followeeId) {
if (!userMap.containsKey(followerId) || followerId == followeeId) {
return;
}
userMap.get(followerId).unfollow(followeeId);
}
}
/**
* Your Twitter object will be instantiated and called as such:
* Twitter obj = new Twitter();
* obj.postTweet(userId,tweetId);
* List<Integer> param_2 = obj.getNewsFeed(userId);
* obj.follow(followerId,followeeId);
* obj.unfollow(followerId,followeeId);
*/
356. Line Reflection
https://leetcode.com/problems/line-reflection/
重载一个Point类,用于比较两个点坐标是否相等。使用一个HashSet存储所有坐标。如果有一条直线能把所有点以自己为轴隔开,这些点必然两两轴对称,并且如果我们已知所有点横坐标的最小值和最大值,每一对对称点横坐标之和必然等于最小值和最大值的和。
class Solution {
public boolean isReflected(int[][] points) {
if (points.length == 0) {
return true;
}
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
HashSet<Point> set = new HashSet<>();
// find the minimum and maximum x-value of all points
for (int[] p : points) {
max = Math.max(max, p[0]);
min = Math.min(min, p[0]);
set.add(new Point(p[0], p[1]));
}
// this is actually twice the reflection axis coordinate
int sum = min + max;
for (int [] p : points) {
// y-value shoule be the same
Point ref = new Point(sum - p[0], p[1]);
if (!set.contains(ref)) {
return false;
}
}
return true;
}
private class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
Point p = (Point) o;
return this.x == p.x && this.y == p.y;
}
@Override
public int hashCode() {
return x * 31 + y * 17;
}
}
}
357. Count Numbers with Unique Digits
https://leetcode.com/problems/count-numbers-with-unique-digits/
本质上是排列组合题。进行数学递推:
- 如果
n为1,有10个unique numbers,即0-9 - 如果
n为2,有9*9个unique numbers,因为十位1-9,个位不能和十位重复,也只有9个选择 - 如果
n为3,有998个unique numbers - …
由于本题要求出0到10n范围的unique numbers数量,因此只需要将0~n的所有情况数量输出即可。
class Solution {
public int countNumbersWithUniqueDigits(int n) {
if (n == 0) {
return 1;
}
int result = 10;
int uniqueDigits = 9;
int availableNumber = 9;
while (n-- > 1 && availableNumber > 0) {
uniqueDigits *= availableNumber;
result += uniqueDigits;
availableNumber--;
}
return result;
}
}
358. Rearrange String k Distance Apart
https://leetcode.com/problems/rearrange-string-k-distance-apart/
先构造出全部字符的字符->频率表,得到出现次数最多的字母出现了几次,以及一共有几个这样的字母。有了这两个信息,可以判断出符合题意的字符串的最短长度是多少,因此能快速排除掉不符合题意的情况。之后,再构造频率->字符表,统计对于每个出现次数,分别有哪些字符。这样做的目的是方便输出构造字符串时,优先从出现次数多的字符开始构造。构造的过程默认按照k间隔填充字符串,注意如果下标超过原字符串长度时,要进行循环处理。
class Solution {
public String rearrangeString(String s, int k) {
if (k == 0) {
return s;
}
// build char->frequency map
int len = s.length();
// max character and how many characters with max frequency
int max = 0, maxCount = 0;
int[] cnt = new int[26];
for (char c : s.toCharArray()) {
cnt[c - 'a']++;
if (max == cnt[c - 'a']) {
maxCount++;
}
if (max < cnt[c - 'a']) {
max = cnt[c - 'a'];
maxCount = 1;
}
}
// to hold all max count characters, we need at least such length
int leastLen = (max - 1) * k + maxCount;
if (leastLen > len) {
return "";
}
// build frequency->char map
LinkedList<Integer>[] freq = new LinkedList[max + 1];
for (int i = 0; i < max + 1; i++) {
freq[i] = new LinkedList<Integer>();
}
for (int i = 0; i < 26; i++) {
freq[cnt[i]].add(i);
}
char[] result = new char[len];
int i = (len - 1) % k;
for (int f = max; f > 0; f--) {
while (!freq[f].isEmpty()) {
int cint = freq[f].remove();
char c = (char) (cint + 'a');
// dispatch current character with k as distance
for (int j = 0; j < f; j++) {
result[i] = c;
i += k;
if (i >= len) {
i = (i - 1) % k;
}
}
}
}
return new String(result);
}
}
359. Logger Rate Limiter
https://leetcode.com/problems/logger-rate-limiter/
使用一个HashMap,记录message和最新的有效timestamp。
class Logger {
private Map<String, Integer> map;
/** Initialize your data structure here. */
public Logger() {
map = new HashMap<>();
}
/** Returns true if the message should be printed in the given timestamp, otherwise returns false.
If this method returns false, the message will not be printed.
The timestamp is in seconds granularity. */
public boolean shouldPrintMessage(int timestamp, String message) {
if (!map.containsKey(message) || timestamp - map.get(message) >= 10) {
map.put(message, timestamp);
return true;
}
return false;
}
}
360. Sort Transformed Array
https://leetcode.com/problems/sort-transformed-array/
双指针法,一头一尾向中间逼近。分类讨论,如果a大于等于0,说明抛物线开口向上,此时两侧值较大,因此按照从大到小的顺序获取数组;如果a小于0,说明抛物线开口向下,此时两侧值较小,因此按照从小到大的顺序获取数组。
class Solution {
private int quad(int num, int a, int b, int c) {
return a * num * num + b * num + c;
}
public int[] sortTransformedArray(int[] nums, int a, int b, int c) {
if (nums == null || nums.length == 0) {
return new int[] {};
}
int len = nums.length;
int[] result = new int[len];
// index of result array
// a >= 0, then start from maximum value, otherwise start from minimum
int index = a >= 0 ? len - 1 : 0;
int i = 0, j = len - 1;
while (i <= j) {
if (a >= 0) {
result[index] = quad(nums[i], a, b, c) >= quad(nums[j], a, b, c) ? quad(nums[i++], a, b, c) : quad(nums[j--], a, b, c);
index--;
} else {
result[index] = quad(nums[i], a, b, c) >= quad(nums[j], a, b, c) ? quad(nums[j--], a, b, c) : quad(nums[i++], a, b, c);
index++;
}
}
return result;
}
}
361. Bomb Enemy
https://leetcode.com/problems/bomb-enemy/
常规思路,二重循环直接遍历记数,但注意到每一行杀死的敌人数只需要用一个变量存起来就行了,节省空间。
class Solution {
public int maxKilledEnemies(char[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int m = grid.length, n = grid[0].length;
int result = 0;
int rowhits = 0; // no need to store with array, since the result is updated after going through a row
int[] colhits = new int[n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// current row
if (j == 0 || grid[i][j - 1] == 'W') {
rowhits = 0;
for (int k = j; k < n && grid[i][k] != 'W'; k++) {
rowhits += grid[i][k] == 'E' ? 1 : 0;
}
}
// current column
if (i == 0 || grid[i - 1][j] == 'W') {
colhits[j] = 0;
for (int k = i; k < m && grid[k][j] != 'W'; k++) {
colhits[j] += grid[k][j] == 'E' ? 1 : 0;
}
}
if (grid[i][j] == '0') {
result = Math.max(result, rowhits + colhits[j]);
}
}
}
return result;
}
}
362. Design Hit Counter
https://leetcode.com/problems/design-hit-counter/
使用两个长度为300的数组,分别对应时间戳和点击量,每个时间戳和点击量都是根据数组下标一一对应的,每当有新点击时,将时间戳与300取模,这一步能方便的判断这个新时间是否需要将旧时间覆盖。如果覆盖,计数重置,反之计数增加1。获取点击量时,直接遍历数组,根据时间戳累加最近5分钟内的结果即可。
class HitCounter {
private int[] times;
private int[] hits;
/** Initialize your data structure here. */
public HitCounter() {
times = new int[300];
hits = new int[300];
}
/** Record a hit.
@param timestamp - The current timestamp (in seconds granularity). */
public void hit(int timestamp) {
int index = timestamp % 300;
// a new timestamp overrides an old timestamp more than 5 minutes ago
if (times[index] != timestamp) {
times[index] = timestamp;
hits[index] = 1;
} else {
hits[index]++;
}
}
/** Return the number of hits in the past 5 minutes.
@param timestamp - The current timestamp (in seconds granularity). */
public int getHits(int timestamp) {
int total = 0;
for (int i = 0; i < 300; i++) {
// only add timestamp in the past 5 minutes
if (timestamp - times[i] < 300) {
total += hits[i];
}
}
return total;
}
}
363. Max Sum of Rectangle No Larger Than K
https://leetcode.com/problems/max-sum-of-rectangle-no-larger-than-k/
先在水平方向遍历循环决定矩形的长度,把每一行的和整理到一个一维数组中,再从竖直方向遍历循环决定矩形的宽度,并根据这个宽度在一维数组上进行求和,得到最大的矩形。针对本题的test case,可以先不在竖直方向进行循环,直接尝试最大和,有助于提升运行效率。
class Solution {
public int maxSumSubmatrix(int[][] matrix, int k) {
int rows = matrix.length, cols = matrix[0].length;
int result = Integer.MIN_VALUE;
for (int j = 0; j < cols; j++) {
int[] sum = new int[rows];
// try every possible matrix length
for (int c = j; c < cols; c++) {
// get horizontal sum
for (int r = 0; r < rows; r++) {
sum[r] += matrix[r][c];
}
// for current length, get maximum vertical sum first, which greatly reduces runtime
int max = sum[0];
int maxSoFar = 0;
for (int s : sum) {
maxSoFar = Math.max(maxSoFar + s, s);
max = Math.max(max, maxSoFar);
if (max == k) {
return k;
}
}
if (max < k) {
result = Math.max(result, max);
} else {
// too large, should exclude some rows
for (int r = 0; r < rows; r++) {
int curSum = 0;
for (int i = r; i < rows; i++) {
curSum += sum[i];
if (curSum <= k) {
result = Math.max(result, curSum);
}
}
}
if (result == k) {
return k;
}
}
}
}
return result;
}
}
364. Nested List Weight Sum II
https://leetcode.com/problems/nested-list-weight-sum-ii/
逐层处理,对于当前一层的对象,如果是数字就进行累加,否则就丢到下一层的List中。每处理完一层,将累加的结果更新到结果中。注意每一层的累加不清空,因为高层的数需要被重复累加,才能得到正确的权值。
/**
* // This is the interface that allows for creating nested lists.
* // You should not implement it, or speculate about its implementation
* public interface NestedInteger {
* // Constructor initializes an empty nested list.
* public NestedInteger();
*
* // Constructor initializes a single integer.
* public NestedInteger(int value);
*
* // @return true if this NestedInteger holds a single integer, rather than a nested list.
* public boolean isInteger();
*
* // @return the single integer that this NestedInteger holds, if it holds a single integer
* // Return null if this NestedInteger holds a nested list
* public Integer getInteger();
*
* // Set this NestedInteger to hold a single integer.
* public void setInteger(int value);
*
* // Set this NestedInteger to hold a nested list and adds a nested integer to it.
* public void add(NestedInteger ni);
*
* // @return the nested list that this NestedInteger holds, if it holds a nested list
* // Return empty list if this NestedInteger holds a single integer
* public List<NestedInteger> getList();
* }
*/
class Solution {
public int depthSumInverse(List<NestedInteger> nestedList) {
int unweighted = 0, result = 0;
while (!nestedList.isEmpty()) {
List<NestedInteger> nextLevel = new ArrayList<>();
// simply add all integers in this level, or leave lists to next level
for (NestedInteger ni : nestedList) {
// former unweighted integers are not reset
if (ni.isInteger()) {
unweighted += ni.getInteger();
} else {
nextLevel.addAll(ni.getList());
}
}
// after dealing with each level, summarize to result
// depth is implemented with multiple adding in each level
result += unweighted;
nestedList = nextLevel;
}
return result;
}
}
365. Water and Jug Problem
https://leetcode.com/problems/water-and-jug-problem/
解题需要使用Bezout定理:
Let a and b be nonzero integers and let d be their greatest common divisor. Then there exist integers x and y such that ax + by = d.
In addition, the greatest common divisor of x and y, d, is the smallest positive integer that can be written as ax + by.
Every integer of the form ax + by is a multiple of the greatest common divisor d.
我们需要校验z是否是x, y公约数的倍数,因为如果是的话,就一定有符合的整数a, b使得ax + by = d。
在此题中,整数a, b代表着x, y倒水或者装水的次数。如果a或b是负数,代表从x或y中倒水;反之如果a或b是正数,代表给x或者y装满水。
class Solution {
public boolean canMeasureWater(int x, int y, int z) {
// cannot exceed two jugs' capacities
if (x + y < z) {
return false;
}
// judge if we can achieve by using one jug, or two jugs simple combination
if (x == z || y == z || x + y == z) {
return true;
}
// get GCD, then we can use the property of Bézout's identity
return z % GCD(x, y) == 0;
}
public int GCD(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
}
366. Find Leaves of Binary Tree
https://leetcode.com/problems/find-leaves-of-binary-tree/
计算每个节点到根的深度,将相同深度的节点归类到一个List中,遍历时直接按照深度添加到对应的List中即可。
/**
* 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 List<List<Integer>> findLeaves(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
height(root, result);
return result;
}
private int height(TreeNode node, List<List<Integer>> result) {
if (node == null) {
return -1;
}
// get height from current node to deepest leaf
int level = 1 + Math.max(height(node.left, result), height(node.right, result));
if (result.size() == level) {
result.add(new ArrayList<>());
}
result.get(level).add(node.val);
return level;
}
}
367. Valid Perfect Square
https://leetcode.com/problems/valid-perfect-square/
引用牛顿求平方根的公式。
class Solution {
public boolean isPerfectSquare(int num) {
long x = num;
while (x * x > num) {
x = (x + num / x) / 2;
}
return x * x == num;
}
}
368. Largest Divisible Subset
https://leetcode.com/problems/largest-divisible-subset/
- 对数组中每个元素,先找出其拥有的最大子集的长度
- 得到最长子集长度
maxIndex - 将
nums[Index]至0范围内,属于最大子集的数添加到结果
class Solution {
public static List<Integer> largestDivisibleSubset(int[] nums) {
List<Integer> result = new ArrayList<Integer>();
if (nums == null || nums.length == 0) {
return result;
}
Arrays.sort(nums);
int[] dp = new int[nums.length];
Arrays.fill(dp ,1);
dp[0] = 1;
// for each element in nums, find the length of largest subset it has.
for (int i = 1; i < nums.length; i++) {
for (int j = i-1; j >= 0; j--) {
if (nums[i] % nums[j] == 0) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
// pick the index of the largest element in dp.
int maxIndex = 0;
for (int i = 1; i < nums.length; i++) {
maxIndex = dp[i] > dp[maxIndex] ? i : maxIndex;
}
// from nums[maxIndex] to 0, add every element belongs to the largest subset.
int temp = nums[maxIndex];
int curDp = dp[maxIndex];
for (int i = maxIndex; i >= 0; i--) {
if (temp % nums[i] == 0 && dp[i] == curDp) {
result.add(nums[i]);
temp = nums[i];
curDp--;
}
}
return result;
}
}
369. Plus One Linked List
https://leetcode.com/problems/plus-one-linked-list/
使用DFS求进位,即先算完当前位后面的位产生的进位,再计算当前位的进位,同时修改当前位的值。如果到最高位进位不是0,还需要补上一个’1’。
/**
* 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 plusOne(ListNode head) {
if (dfs(head) == 0) {
return head;
} else {
// need to add '1' in the leftmost place
ListNode newHead = new ListNode(1);
newHead.next = head;
return newHead;
}
}
// use dfs to get carry
private int dfs(ListNode node) {
if (node == null) {
return 1;
}
// get carry after current node
int carry = dfs(node.next);
if (carry == 0) {
return 0;
}
// current node's calculation
int val = node.val + 1;
node.val = val % 10;
return val / 10; // carry
}
}
370. Range Addition
https://leetcode.com/problems/range-addition/
使用一个数组temp进行区间累加得到结果。对于每一次update,都让temp[start] += value,表示从start起,之后的累加中每个数都会多增加value,并且再让temp[end + 1] -= value,表示从end + 1起,之后的累加中每个数都会减少value。需要注意如果end已经是数组的最后一个数时,不需要更新temp数组,因为不存在影响后续累加的问题了。
class Solution {
public int[] getModifiedArray(int length, int[][] updates) {
int[] temp = new int[length];
int[] result = new int[length];
for (int[] update : updates) {
int start = update[0];
int end = update[1];
int value = update[2];
// from this position and on, all subsequent sum will each increase by value
temp[start] += value;
// from this position and on, all subsequent sum will each decrease by value
if (end < length - 1) {
temp[end + 1] -= value;
}
}
int sum = 0;
for (int i = 0; i < length; i++) {
sum += temp[i];
result[i] = sum;
}
return result;
}
}
371. Sum of Two Integers
https://leetcode.com/problems/sum-of-two-integers/
解题流程共3步:
- 提前找到所有会发生进位的位置
- 用异或模拟二进制加
- 将所有进位向左移动一位,到达正确的位置
持续上述过程,一直到进位为0没有进位为止。
class Solution {
public int getSum(int a, int b) {
int carry;
while (b != 0) {
// find the carry
carry = a & b;
// perform basic add operarion
a = a ^ b;
// shift the carry to the right position
b = (carry) << 1;
}
return a;
}
}
372. Super Pow
https://leetcode.com/problems/super-pow/
利用ax+y = ax · ay的性质,逐位计算次幂。次幂函数可以使用递归,不断降次来突破位数限制。
class Solution {
public int superPow(int a, int[] b) {
int result = 1;
for (int i : b) {
result = pow(result, 10) * pow(a, i) % 1337;
}
return result;
}
private int pow(int x, int y) {
if (y == 0) {
return 1;
}
if (y == 1) {
return x % 1337;
}
return pow(x % 1337, y / 2) * pow(x % 1337, y - y / 2) % 1337;
}
}
373. Find K Pairs with Smallest Sums
https://leetcode.com/problems/find-k-pairs-with-smallest-sums/
由于两个数组都是固定的,因此可以采用固定一个数组nums1,循环遍历另一个数组nums2的策略,穷举每一个数对。使用一个优先队列pq记录两个数在各自数组中的下标,已经他们的和,根据和的大小来规定优先队列的排序规则。如果已经添加的数对不足k个,就把当前数对添加进去,并且如果nums2尚未遍历结束,就将nums2中的下一个数提取出来,和目前nums1中的数组成新数对,添加到优先队列中。
class Solution {
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
List<List<Integer>> result = new ArrayList<>();
// index1, index2, sum
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[2] - b[2]);
for (int i = 0; i < Math.min(k, nums1.length); i++) {
pq.offer(new int[]{i, 0, nums1[i] + nums2[0]});
}
while (!pq.isEmpty()) {
k--;
int[] cur = pq.poll();
result.add(List.of(nums1[cur[0]], nums2[cur[1]]));
if (k == 0) {
return result;
}
if (cur[1] + 1 < nums2.length) {
pq.offer(new int[]{cur[0], cur[1] + 1, nums1[cur[0]] + nums2[cur[1] + 1]});
}
}
return result;
}
}
374. Guess Number Higher or Lower
https://leetcode.com/problems/guess-number-higher-or-lower/
题干关于guess API的描述,几乎是直接对应了二分查找模版1的三种情形,可以是说天造地设。需要注意,此题的设定使得一定能找到pick的数,换言之代码一定能在while循环中返回到最终结果,所以while循环之后的后续处理中(找不到target数的情况),随便return任意数都无所谓。
/**
* Forward declaration of guess API.
* @param num your guess
* @return -1 if num is higher than the picked number
* 1 if num is lower than the picked number
* otherwise return 0
* int guess(int num);
*/
public class Solution extends GuessGame {
public int guessNumber(int n) {
int left = 1, right = n;
while (left <= right) {
int mid = left + (right - left) / 2;
if (guess(mid) == 0) {
return mid;
} else if (guess(mid) == 1) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
}
375. Guess Number Higher or Lower II
https://leetcode.com/problems/guess-number-higher-or-lower-ii/
Min-Max问题,对于一个范围[l, h],如果要猜其中一个数i并且错了,所需要的最小成本就是i + max([l, i-1], [i+1, h]),因为这两段的选择是任意的。
class Solution {
public int getMoneyAmount(int n) {
int[][] dp = new int[n + 1][n + 1];
return minCost(dp, 1, n);
}
private int minCost(int[][] dp, int l, int h) {
if (l >= h) {
return 0;
}
if (l + 1 == h) {
return l;
}
if (dp[l][h] != 0) {
return dp[l][h];
}
int minCost = Integer.MAX_VALUE;
int mid = l + (h - l) / 2;
for (int i = h - 1; i >= mid; i -= 2) {
minCost = Math.min(minCost, i + Math.max(minCost(dp, l, i - 1), minCost(dp, i + 1, h)));
}
dp[l][h] = minCost;
return minCost;
}
}
376. Wiggle Subsequence
https://leetcode.com/problems/wiggle-subsequence/
使用贪心算法。循环遍历数组,使用两个变量up和down分别表示当前数如果作为最后一个符合条件的上升数或下降数,wiggle sequence相应的长度是多少。
class Solution {
public int wiggleMaxLength(int[] nums) {
if (nums.length == 0) {
return 0;
}
int up = 1, down = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i] < nums[i - 1]) {
down = up + 1;
} else if (nums[i] > nums[i - 1]) {
up = down + 1;
}
}
return Math.max(up, down);
}
}
377. Combination Sum IV
https://leetcode.com/problems/combination-sum-iv/
使用动态规划,从0开始递推到target。遍历数组时,对于每一个数,如果这个数比target小,就递归计算以target - nums[i]为目标,有多少种组合方式。每完成一个数的计算,就将结果更新到动态规划数组中。
class Solution {
private int[] dp;
public int combinationSum4(int[] nums, int target) {
dp = new int[target + 1];
Arrays.fill(dp, -1);
dp[0] = 1;
return helper(nums, target);
}
private int helper(int[] nums, int target) {
if (dp[target] != -1) {
return dp[target];
}
int result = 0;
for (int i = 0; i < nums.length; i++) {
if (target >= nums[i]) {
result += helper(nums, target - nums[i]);
}
}
dp[target] = result;
return result;
}
}
378. Kth Smallest Element in a Sorted Matrix
https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/
使用范围二分搜索,初始范围是二维数组的第一个数(最小)到最后一个数(最大)。每次算出中至mid后,遍历每一行检查一共有多少个数小于等于k。如果发现这个数量小于k,说明目前的mid选的太小了,因此下一次的搜索范围就要从mid+1开始;反之亦然。
class Solution {
public int kthSmallest(int[][] matrix, int k) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return -1;
}
int low = matrix[0][0], high = matrix[matrix.length - 1][matrix[0].length - 1] + 1;
while (low < high) {
int mid = low + (high - low) / 2;
int count = 0, j = matrix[0].length - 1;
for (int i = 0; i < matrix.length; i++) {
// find how many numbers are smaller than or equal to mid
while (j >= 0 && matrix[i][j] > mid) {
j--;
}
count += (j + 1);
}
if (count < k) {
// mid is smaller than k
low = mid + 1;
} else {
// mid is larger than k
high = mid;
}
}
return low;
}
}
379. Design Phone Directory
https://leetcode.com/problems/design-phone-directory/
使用一个HashSet存储所有已经分配出去的号码,再使用一个queue存储所有可用的号码。
- 初始化:将号码段内所有号码放进queue
- get:如果可用号码队列不空,选队首的号码分配,同时将其放入HashSet
- check:检查这个号码是否在HashSet中
- release:将这个号码从HashSet中删除,并且重新放回queue末尾
class PhoneDirectory {
// store the assigned phone number
HashSet<Integer> AssignedSet;
Queue<Integer> q = new ArrayDeque<Integer>();
/** Initialize your data structure here
@param maxNumbers - The maximum numbers that can be stored in the phone directory. */
public PhoneDirectory(int maxNumbers) {
AssignedSet = new HashSet<>();
for (int i = 0; i < maxNumbers; i++) {
q.offer(i);
}
}
/** Provide a number which is not assigned to anyone.
@return - Return an available number. Return -1 if none is available. */
public int get() {
if (q.isEmpty()) {
return -1;
}
int number = q.poll();
AssignedSet.add(number);
return number;
}
/** Check if a number is available or not. */
public boolean check(int number) {
return !AssignedSet.contains(number);
}
/** Recycle or release a number. */
public void release(int number) {
if (AssignedSet.contains(number)) {
AssignedSet.remove(number);
// add back to available queue
q.offer(number);
}
}
}
380. Insert Delete GetRandom O(1)
https://leetcode.com/problems/insert-delete-getrandom-o1/
使用一个list用于存储set中的每一个数,再用一个map记录每个数对应的下标。
- 添加元素,需要确保这个元素不在map中,再将这个数添加到list结尾,更新map
- 删除元素,实际上是将list中原先的最后一个数“移动”到现在被删除的位置上,具体就是更新这个数在map中的下标,拷贝值覆盖list这个位置的值,再将list中的最后一个值删除
- 获取随机元素,根据list的长度生成随机下标访问list
class RandomizedSet {
private List<Integer> list;
private Map<Integer, Integer> map;
Random rand;
public RandomizedSet() {
this.list = new ArrayList<>();
this.map = new HashMap<>();
this.rand = new Random();
}
/** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
public boolean insert(int val) {
if (map.containsKey(val)) {
return false;
}
list.add(val);
map.put(val, list.size() - 1);
return true;
}
/** Removes a value from the set. Returns true if the set contained the specified element. */
public boolean remove(int val) {
if (!map.containsKey(val)) {
return false;
}
int index = map.get(val);
int preTailVal = list.get(list.size() - 1);
// move the pre-tail element to current val position
map.put(preTailVal, index); // update pre-tail's index
list.set(index, preTailVal); // copy pre-tail's value
list.remove(list.size() - 1); // delete pre-tail
// remove in map
map.remove(val);
return true;
}
/** Get a random element from the set. */
public int getRandom() {
return list.get(rand.nextInt(list.size()));
}
}
381. Insert Delete GetRandom O(1) - Duplicates allowed
https://leetcode.com/problems/insert-delete-getrandom-o1-duplicates-allowed/
因为有duplication存在,因此同一个元素可能在list中出现多次,map不能再使用<Integer, Integer>的格式,value部分也应该使用一个list存储全部的下标。
- 添加元素,根据这个元素是否在map中决定是否需要创建一个新的
indexList,并将新添加的元素加入到indexList尾部 - 删除元素,总是删除其对应
indexList里的第一个元素,即从第一次出现的位置开始,把list中的元素改成null,再修改indexList。需要注意当最后一个数也被删去时,同时要更新map - 获取随机元素,根据list的长度生成随机下标访问list。由于list在删除时可能有null,因此要使用循环,保证获取到的随机数也不是null
class RandomizedCollection {
private Map<Integer, List<Integer>> map; // all key indices
List<Integer> list;
Random rand;
/** Initialize your data structure here. */
public RandomizedCollection() {
map = new HashMap<>();
list = new ArrayList<>();
rand = new Random();
}
/** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */
public boolean insert(int val) {
list.add(val);
if (map.containsKey(val)) {
List<Integer> indexList = map.get(val);
indexList.add(list.size() - 1);
map.put(val, indexList);
return false;
} else {
List<Integer> indexList = new ArrayList<>();
indexList.add(list.size() - 1);
map.put(val, indexList);
return true;
}
}
/** Removes a value from the collection. Returns true if the collection contained the specified element. */
public boolean remove(int val) {
List<Integer> indexList = map.get(val);
if (indexList == null) {
return false;
}
// always remove the first element
list.set(indexList.get(0), null);
indexList.remove(0);
if (indexList.size() == 0) {
map.remove(val);
}
return true;
}
/** Get a random element from the collection. */
public int getRandom() {
int random = rand.nextInt(list.size());
while (list.get(random) == null) {
random = rand.nextInt(list.size());
}
return list.get(random);
}
}
382. Linked List Random Node
https://leetcode.com/problems/linked-list-random-node/
使用一个计数器i。遍历list,每遍历一个数计数器就加1,同时用一个随机数在[0, i]之间生成随机数,如果得到的数是i,就更新当前的值为当前的数。按此做法,如果遍历了i个数,能始终保持选中当前数的概率是1/i。
/**
* 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 {
private ListNode dummyHead;
Random random;
/** @param head The linked list's head.
Note that the head is guaranteed to be not null, so it contains at least one node. */
public Solution(ListNode head) {
dummyHead = head;
random = new Random();
}
/** Returns a random node's value. */
public int getRandom() {
ListNode cur = dummyHead;
int result = cur.val;
for (int i = 1; cur.next != null; i++) {
cur = cur.next;
if (random.nextInt(i + 1) == i) {
result = cur.val;
}
}
return result;
}
}
383. Ransom Note
https://leetcode.com/problems/ransom-note/
map法,先检查magazine中每个字符有多少个,再判断ransomNote里够不够用。
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
if (magazine == null || ransomNote.length() > magazine.length()) {
return false;
}
int[] map = new int[256];
Arrays.fill(map, 0);
// occurrence map
for (char c : magazine.toCharArray()) {
map[c] += 1;
}
// update map
for (char c : ransomNote.toCharArray()) {
map[c] -= 1;
if (map[c] < 0) {
return false;
}
}
return true;
}
}
384. Shuffle an Array
https://leetcode.com/problems/shuffle-an-array/
每次shuffle时,使用一个循环,每次循环复制进来一个数,再将这个数与一个已经复制过的数进行交换。循环结束后,得到的新数组就是结果。
class Solution {
Random r = new Random();
private int[] nums;
public Solution(int[] nums) {
this.nums = nums;
}
/** Resets the array to its original configuration and return it. */
public int[] reset() {
return nums;
}
/** Returns a random shuffling of the array. */
public int[] shuffle() {
int[] result = new int[nums.length];
for (int i = 0; i < result.length; i++) {
result[i] = nums[i]; // copy the original number
// swap this number with another copied number
int randomIdx = r.nextInt(i+1);
int tmp = result[randomIdx];
result[randomIdx] = result[i];
result[i] = tmp;
}
return result;
}
}
385. Mini Parser
https://leetcode.com/problems/mini-parser/
使用递归,对于NestedList而言,遇到[表示深度+1,遇到]表示深度-1。初始化深度是0,因此每一次递归调用时,应该要确保这一层能直接提取到的NestedInteger都在第0层;否则的话,就一定是一个NestedList。
具体获取时,使用startIdx计算下一个substring的起始坐标即可。
/**
* // This is the interface that allows for creating nested lists.
* // You should not implement it, or speculate about its implementation
* public interface NestedInteger {
* // Constructor initializes an empty nested list.
* public NestedInteger();
*
* // Constructor initializes a single integer.
* public NestedInteger(int value);
*
* // @return true if this NestedInteger holds a single integer, rather than a nested list.
* public boolean isInteger();
*
* // @return the single integer that this NestedInteger holds, if it holds a single integer
* // Return null if this NestedInteger holds a nested list
* public Integer getInteger();
*
* // Set this NestedInteger to hold a single integer.
* public void setInteger(int value);
*
* // Set this NestedInteger to hold a nested list and adds a nested integer to it.
* public void add(NestedInteger ni);
*
* // @return the nested list that this NestedInteger holds, if it holds a nested list
* // Return empty list if this NestedInteger holds a single integer
* public List<NestedInteger> getList();
* }
*/
class Solution {
public NestedInteger deserialize(String s) {
NestedInteger result = new NestedInteger();
if (s == null || s.length() == 0) {
return result;
}
// only one integer
if (s.charAt(0) != '[') {
result.setInteger(Integer.parseInt(s));
} else if (s.length() > 2) {
// a nestedlist and should not be '[]'
int startIdx = 1, depth = 0;
for (int i = 1; i < s.length(); i++) {
char c = s.charAt(i);
// recursion to track nested depth, always start at current level with depth == 0
if (depth == 0 && (c == ',' || i == s.length() - 1)) {
result.add(deserialize(s.substring(startIdx, i)));
startIdx = i + 1;
} else if (c == '[') {
depth++;
} else if (c == ']') {
depth--;
}
}
}
return result;
}
}
386. Lexicographical Numbers
https://leetcode.com/problems/lexicographical-numbers/
使用dfs,每次限定范围[i*10, (i+1)*10]进行遍历,即:保持当前的i,之后马上处理剩余位数,一直到i*10超出范围。
class Solution {
public List<Integer> lexicalOrder(int n) {
List<Integer> result = new ArrayList<>(n);
process(result, 1, 10, n);
return result;
}
private void process(List<Integer> result, int start, int end, int limit) {
for (int i = start; i < end && i <= limit; i++) {
result.add(i);
// keep current digits (i), and handle the rest of digits
if (i * 10 <= limit) {
process(result, i*10, (i+1)*10, limit);
}
}
}
}
387. First Unique Character in a String
https://leetcode.com/problems/first-unique-character-in-a-string/
先使用map统计每个字符出现的频率,再从头开始检查第一个频率为1的字符。
class Solution {
public int firstUniqChar(String s) {
int freq[] = new int[26];
// add frequency
for (int i = 0; i < s.length(); i++) {
freq[s.charAt(i) - 'a']++;
}
// check first letter which appears one time
for (int i = 0; i < s.length(); i++) {
if (freq[s.charAt(i) - 'a'] == 1) {
return i;
}
}
return -1;
}
}
388. Longest Absolute File Path
https://leetcode.com/problems/longest-absolute-file-path/
使用一个数组存储截止到每一层的路径长度。用split()函数去掉所有的\n,同时得到每一个文件的相对路径。根据每个相对路径前\t的长度能够得出这个文件位于第几层,因此截止到这个文件的长度就是上层所有文件夹的长度 + 当前路径的长度 - 当前路径前所有\t的长度。同时,如果发现当前路径包括.,说明这个路径已经是最终的文件路径,因此可以在此更新最大值结果。
class Solution {
public int lengthLongestPath(String input) {
String[] files = input.split("\n");
int result = 0;
// store all levels' length
int[] stack = new int[files.length + 1];
stack[0] = 0;
for (String path : files) {
int level = path.lastIndexOf("\t") + 1; // get level
// upper directories' total length + current length - all '\t' length in current path
int curLength = stack[level] + path.length() - level + 1;
stack[level + 1] = curLength;
if (path.contains(".")) {
result = Math.max(result, curLength - 1);
}
}
return result;
}
}
389. Find the Difference
https://leetcode.com/problems/find-the-difference/
使用位运算,对s和t中的每一个字母进行异或,最终的结果就是多出来的字母。
class Solution {
public char findTheDifference(String s, String t) {
char c = 0;
for (int i = 0; i < s.length(); i++) {
c ^= s.charAt(i);
}
for (int i = 0; i < t.length(); i++) {
c ^= t.charAt(i);
}
return c;
}
}
390. Elimination Game
https://leetcode.com/problems/elimination-game/
跟踪当前剩下的第一个数字head,如果正在从左向右走,或者从右向左时剩下的数字有奇数个,第一个数字一定会被消掉,因此要将head挪到下一个数字。每一趟走完,更新方向、步长和剩余数字。最后head就是剩下的最后一个数字。
class Solution {
public int lastRemaining(int n) {
int head = 1; // head number
int step = 1;
boolean leftToRight = true;
int remainNum = n;
while (remainNum > 1) {
// move head forward
if (leftToRight || remainNum % 2 == 1) {
head = head + step;
}
// change direction and update step & remain numbers
leftToRight = !leftToRight;
step = step * 2;
remainNum = remainNum / 2;
}
return head;
}
}
391. Perfect Rectangle
https://leetcode.com/problems/perfect-rectangle/
使用TreeSet,自定义comparator发现是否存在overlap,如果有的话根据Treeset重复性可以视其为重复元素;否则,就根据面积法校验,每次添加一个矩形,既要判断当前总的矩形面积和,又要计算所有矩形最外的坐标组成的大矩形的面积,判断这二者是否相等,如果不相等说明有缺角。
class Solution {
public boolean isRectangleCover(int[][] rectangles) {
int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE;
int maxY = 0, maxX = 0, area = 0;
TreeSet<int[]> sortedRectangles = new TreeSet<>((first, second) -> {
if (first[0] >= second[2] || first[1] >= second[3]) {
// second is on the right of first
return 1;
} else if (first[2] <= second[0] || first[3] <= second[1]) {
// second is on the top of first
return -1;
} else {
// overlap
return 0;
}
});
for (int[] rectangle : rectangles) {
if (sortedRectangles.contains(rectangle)) {
return false;
} else {
sortedRectangles.add(rectangle);
}
minX = Math.min(rectangle[0], minX);
minY = Math.min(rectangle[1], minY);
maxX = Math.max(rectangle[2], maxX);
maxY = Math.max(rectangle[3], maxY);
area += (rectangle[3] - rectangle[1]) * (rectangle[2] - rectangle[0]);
}
return area == (maxY - minY) * (maxX - minX);
}
}
392. Is Subsequence
https://leetcode.com/problems/is-subsequence/
双指针法,遍历t的时候检查s的字符是否满足,如果s的指针达到结尾说明符合条件。
class Solution {
public boolean isSubsequence(String s, String t) {
if (s == null || t == null || s.length() == 0) {
return true;
}
int sp = 0, tp = 0;
while (tp < t.length()) {
if (s.charAt(sp) == t.charAt(tp)) {
sp++;
if (sp == s.length()) {
return true;
}
}
tp++;
}
return false;
}
}
393. UTF-8 Validation
https://leetcode.com/problems/utf-8-validation/
非常无聊的题目,前提是要读懂utf-8的编码规则。。。
class Solution {
public boolean validUtf8(int[] data) {
int count = 0;
for (int c : data) {
if (count == 0) {
if ((c >> 5) == 0b110) {
count = 1;
} else if ((c >> 4) == 0b1110) {
count = 2;
} else if ((c >> 3) == 0b11110) {
count = 3;
} else if ((c >> 7) != 0) {
return false;
}
} else {
if ((c >> 6) != 0b10) {
return false;
}
count--;
}
}
return count == 0;
}
}
394. Decode String
https://leetcode.com/problems/decode-string/
使用两个栈,一个栈存字符串重复的次数,另一个存处理好的子字符串。遍历输入字符串,根据 数字 / [ / ] / 其他字符 这四种情况进行讨论处理。
class Solution {
public String decodeString(String s) {
StringBuilder sb = new StringBuilder();
Stack<Integer> cStack = new Stack<>();
Stack<String> strStack = new Stack<>();
int index = 0;
while (index < s.length()) {
// get repeated times
if (s.charAt(index) >= '0' && s.charAt(index) <= '9') {
int count = 0;
while (s.charAt(index) >= '0' && s.charAt(index) <= '9') {
count = 10 * count + (s.charAt(index) - '0');
index++;
}
cStack.push(count);
} else if (s.charAt(index) == '[') {
// store current string, and prepare to get new string
strStack.push(sb.toString());
sb = new StringBuilder();
index++;
} else if (s.charAt(index) == ']') {
// repeat times of current finished string
int repeat = cStack.pop();
StringBuilder temp = new StringBuilder(strStack.pop());
String str = sb.toString();
for (int i = 0; i < repeat; i++) {
// add repeated string
temp.append(str);
}
// replace current string builder
sb = temp;
index++;
} else {
sb.append(s.charAt(index));
index++;
}
}
return sb.toString();
}
}
395. Longest Substring with At Least K Repeating Characters
https://leetcode.com/problems/longest-substring-with-at-least-k-repeating-characters/
先获取到总的字符串的字母频率,再检查每一个子字符串范围内的字母频率,判断这一段内是否每个字母都满足要求。如果发现一个字母不满足,就拆分搜索,寻找这个字母之前或之后范围内的子字符串。
class Solution {
public int longestSubstring(String s, int k) {
if (s == null || s.length() == 0) {
return 0;
}
if (k < 2) {
return s.length();
}
return helper(s, 0, s.length(), k);
}
public int helper(String s, int l, int r, int k) {
if (l >= r) {
return 0;
}
// build freq map
int[] freq = new int[26];
for (int i = l; i < r; i++) {
freq[s.charAt(i) - 'a']++;
}
// check if valid
boolean valid = true;
for (int i = 0; i < 26 && valid; i++) {
if (freq[i] > 0 && freq[i] < k) {
valid = false;
}
}
if (valid) {
return r-l;
}
// if not for each invalid character start a new split search
int result = 0, start = l;
for (int i = l; i < r; i++) {
if (freq[s.charAt(i) -'a'] < k) {
result = Math.max(result, helper(s, start, i, k));
start = i+1;
}
}
// complete search
result = Math.max(result, helper(s, start, r, k));
return result;
}
}
396. Rotate Function
https://leetcode.com/problems/rotate-function/
数学推导:
F(k) = 0 * Bk[0] + 1 * Bk[1] + ... + (n-1) * Bk[n-1]
F(k-1) = 0 * Bk-1[0] + 1 * Bk-1[1] + ... + (n-1) * Bk-1[n-1]
= 0 * Bk[1] + 1 * Bk[2] + ... + (n-2) * Bk[n-1] + (n-1) * Bk[0]
F(k) - F(k-1) = Bk[1] + Bk[2] + ... + Bk[n-1] + (1-n)Bk[0]
= (Bk[0] + ... + Bk[n-1]) - nBk[0]
= sum - nBk[0]
F(k) = F(k-1) + sum - nBk[0]
对于Bk[0],有:
k = 0; B[0] = A[0];
k = 1; B[0] = A[len-1];
k = 2; B[0] = A[len-2];
...
class Solution {
public int maxRotateFunction(int[] nums) {
int sum = 0;
int len = nums.length;
int F = 0;
for (int i = 0; i < len; i++) {
F += i * nums[i];
sum += nums[i];
}
int result = F;
for (int i = len - 1; i >= 1; i--) {
F = F + sum - len * nums[i];
result = Math.max(F, result);
}
return result;
}
}
397. Integer Replacement
https://leetcode.com/problems/integer-replacement/
主要根据是否为4k+3型的整数进行判断,决定递归时用+1还是-1。
class Solution {
public int integerReplacement(int n) {
if (n == 1) {
return 0;
}
if (n == 3) {
return 2;
}
if (n % 2 == 0) {
return 1 + integerReplacement(n / 2);
}
return n % 4 == 3 ? 2 + integerReplacement(n / 2 + 1) : 2 + integerReplacement(n / 2);
}
}
398. Random Pick Index
https://leetcode.com/problems/random-pick-index/
用HashMap存储每个数出现的所有下标,pick()的时候再随机抽一个。
class Solution {
private int[] nums;
private Map<Integer, List<Integer>> map;
Random random;
public Solution(int[] nums) {
this.nums = nums;
this.random = new Random();
this.map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (this.map.containsKey(nums[i])) {
List<Integer> list = this.map.get(nums[i]);
list.add(i);
this.map.put(nums[i], list);
} else {
List<Integer> list = new ArrayList<>();
list.add(i);
this.map.put(nums[i], list);
}
}
}
public int pick(int target) {
List<Integer> list = this.map.get(target);
int idx = random.nextInt(list.size());
return list.get(idx);
}
}
399. Evaluate Division
https://leetcode.com/problems/evaluate-division/
自定义一个图的结构,对于每一个节点,存储和这个点连接的所有边(每条边记录另一个节点,以及这条边对应的数值)。之后在query里使用dfs,根据边的连通性判断是否能实现除法操作。
class Solution {
private class Edge {
String v;
double d;
public Edge(String v, double d) {
this.v = v;
this.d = d;
}
}
private double dfs(Map<String, List<Edge>> map, Edge root, String dst, double cur, Set<String> visited) {
if (root.v.equals(dst)) {
return cur;
}
if (visited.contains(root)) {
return cur;
}
visited.add(root.v);
if (map.get(root.v) != null) {
for (Edge e : map.get(root.v)) {
if (!visited.contains(e.v)) {
double tr = dfs(map, e, dst, cur, visited);
if (tr != -1) {
return tr * e.d;
}
}
}
}
return -1;
}
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
// each graph is <vertex, all edges connected to vertex>
Map<String, List<Edge>> map = new HashMap<>();
int n = equations.size();
// add all existed edge to the graph
for (int i = 0; i < n; i++) {
List<String> e = equations.get(i);
map.putIfAbsent(e.get(0), new ArrayList<Edge>());
map.putIfAbsent(e.get(1), new ArrayList<Edge>());
map.get(e.get(0)).add(new Edge(e.get(1), values[i]));
map.get(e.get(1)).add(new Edge(e.get(0), 1.0 / values[i]));
}
double[] result = new double[queries.size()];
int idx = 0;
for (List<String> q : queries) {
String src = q.get(0);
String dst = q.get(1);
// undefined node
if (map.get(src) == null || map.get(dst) == null) {
result[idx++] = -1;
continue;
}
// self
if (src == dst) {
result[idx++] = 1;
continue;
}
double dis = dfs(map, new Edge(src, 1), dst, 1, new HashSet<String>());
result[idx++] = dis;
}
return result;
}
}
400. Nth Digit
https://leetcode.com/problems/nth-digit/
- 确定第
n位数处在一个几位整数中 - 确定第
n位数处在哪一个整数中 - 转String后直接用字符串相关函数提取
class Solution {
public int findNthDigit(int n) {
int len = 1; // the length of the number where the nth digit is from
long count = 9; // how many numbers in current length
int start = 1; // start of current length number
while (n > len * count) {
n -= len * count;
len += 1;
count *= 10;
start *= 10;
}
// the number which contains Nth digit
start += (n - 1) / len;
String s = Integer.toString(start);
return Character.getNumericValue(s.charAt((n - 1) % len));
}
}