《编程能力基础》刷题笔记
- 1. 单调数列
- 2. 实现 strStr()
- 3. 平衡二叉树
- 4. 重复的子字符串
- 5. 逆波兰表达式求值
- 6. 加一
- 7. 二叉树中的列表
- 8. 字符串相乘
- 9. 二进制求和
- 10. 数组形式的整数加法
- 11. 每日温度
- 12.最后一个单词长度
- 13. 旋转矩阵
- 14. 判断矩阵经轮转后是否一致
- 15. 螺旋矩阵
- 16. 最接近原点的K个点
- 17. 等差子数组
- 18. N 叉树的层次遍历
- 19. 下一个更大元素 II
- 20. 下一个更大元素 III
- 21. 通知所有员工所需的时间 TODO
- 22. 字母异位词分组
- 23. 找到字符串中所有字母异位词 todo
- 24. 乘积小于K的子数组 to do
- 25. 二维区域和检索 - 矩阵不可变
- 26. 最小差值 II
- 27. 重排链表
- 28. 复制带随机指针的链表
- 29. 两数相加
- 30. 两数相加 II
- 31. 旋转链表
- 32. 二叉搜索树迭代器
- 33. 座位预约管理系统
- 34. 柠檬水找零
- 35. 最小栈
- 36. 扁平化嵌套列表迭代器
- 37. 设计一个验证系统
- 38. 设计链表
- 39. O(1) 时间插入、删除和获取随机元素
- 40. 设计循环链表
- 41. 我的日程安排表 I
刷题代码:https://gitee.com/szluyu99/my-leet-code
1. 单调数列
题目:896. 单调数列
题解:递归、模拟、API
因为题目看着让人很想递归,先送上个递归做法:(强行递归,必定超时)
/**
* 递归(超时)
*/
function isMonotonic(nums: number[]): boolean {
return nums[0] < nums[nums.length - 1] ?
isMonotonicIncrease(nums) : isMonotonicDecrease(nums)
};
// 递归判断是否单调递
const isMonotonicIncrease = function (nums: number[]): boolean {
if (nums.length == 1) return true
if (nums[0] > nums[1]) return false
return isMonotonicIncrease(nums.slice(1)) ? true : false
}
// 递归判断是否单调减
const isMonotonicDecrease = function (nums: number[]): boolean {
if (nums.length == 1) return true
if (nums[0] < nums[1]) return false
return isMonotonicDecrease(nums.slice(1)) ? true : false
}
—
然后才是正文。
解法1:
- 通过
nums[0]
和nums[1]
可以判断这个序列是 递增 还是 递减 - 明确了目标,然后只需要遍历时两两比较即可,遇到不满足条件的情况直接返回 false
/**
* 通过 nums[0] 和 nums[nums.length - 1] 判断出增减方向
* 遍历数组一次,两两比较
*/
public boolean isMonotonic1(int[] nums) {
// 先判断 递增 还是 递减
if (nums[0] <= nums[nums.length - 1]) { // 递增
for (int i = 1; i < nums.length; i++)
if (nums[i - 1] > nums[i]) return false;
} else { // 递减
for (int i = 1; i < nums.length; i++)
if (nums[i - 1] < nums[i]) return false;
}
return true;
}
解法2:
- 如果没有先判断出序列是 递增 还是 递减,也没关系
- 可以先假设又递增又递减,然后判断时如果遇到一个不满足条件的情况就可以排除这个假设了
- 中间如果加上一步减枝(可以快 1s)
public boolean isMonotonic(int[] nums) {
boolean inc = true, dec = true;
for (int i = 0; i < nums.length - 1; i++) {
if (nums[i] > nums[i + 1]) inc = false;
if (nums[i] < nums[i + 1]) dec = false;
// 剪枝操作, 已经明确既不递增也不递减, 直接返回 false
if (!inc && !dec) return false;
}
return inc || dec;
}
解法3:然后就是利用语言特性和 API 的做法了,比如 JS
var isMonotonic = function (nums) {
return isSorted(nums) || isSorted(nums.reverse())
}
// 判断是否单调增
function isSorted(nums) {
return nums.slice(1).every((item, i) => nums[i] <= item)
}
2. 实现 strStr()
题解:API、暴力、滑动窗口
先来一个每个人必定会尝试一下的解法:库函数
public int strStr(String haystack, String needle) {
return haystack.indexOf(needle);
}
再来个比较常规但是会超时的暴力解法:暴力
public int strStr1(String haystack, String needle) {
if (haystack.length() < needle.length()) return -1;
if (needle.isEmpty()) return 0;
for (int i = 0; i < haystack.length(); i++) {
int tmpIdx = 0;
while (i + tmpIdx < haystack.length()
&& haystack.charAt(i + tmpIdx) == needle.charAt(tmpIdx++))
if (tmpIdx == needle.length()) return i;
}
return -1;
}
然后来个可以正常通过的解法:滑动窗口
public int strStr2(String haystack, String needle) {
if (haystack.length() < needle.length()) return -1;
// 窗口长度
int width = needle.length();
// 当前索引
int idx = 0;
// 只访问不会越界的部分
while (idx + width <= haystack.length()) {
// 找到则返回索引
if (needle.equals(haystack.substring(idx, idx + width)))
return idx;
idx++;
}
return -1;
}
至于其他比较强力的算法如 KMP 等等,待我功力深厚以后再回来将其拿下!
3. 平衡二叉树
题目:110. 平衡二叉树
题解:迭代
解法1:常规思路,先写一个 获取二叉树节点高度 的函数,然后对二叉树进行 遍历 + 判断
const isBalanced = function (root: TreeNode | null): boolean {
if (!root) return true
if (Math.abs(getHeight(root.left) - getHeight(root.right)) > 1)
return false
return isBalanced(root.left) && isBalanced(root.right)
}
// 获取二叉树节点的高度
const getHeight = (root: TreeNode | null) => {
if (!root) return 0
return Math.max(getHeight(root.left), getHeight(root.right)) + 1
}
解法2:这个有点难理解,其实是解法1的升级版
- 解法1一定会遍历完整个树,解法2不一定,遍历时发现不满足条件就会直接结束
const isBalanced = function (root: TreeNode | null): boolean {
return recur(root) != -1
}
const recur = function (root: TreeNode | null): number {
if (!root) return 0
let left = recur(root.left)
if (left == -1) return -1
let right = recur(root.right)
if (right == -1) return -1
return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1
}
4. 重复的子字符串
题目:459. 重复的子字符串
题解:模拟、技巧
这道题比较好想的思路是模拟,但是模拟也是有讲究的,要尽量减少暴力的次数(俗称剪枝)
在我的理解中,剪枝属于一种优化,其实把剪枝部分去掉应该也可以正常运行,但是效率会变低
解法1:模拟 + 剪枝
const repeatedSubstringPattern = function (s: string): boolean {
if (s.length < 2) return false
// 只需要遍历一半即可, 一半以后必然不能重复
for (let i = 1; i <= s.length / 2; i++) {
let sub = s.substring(0, i)
// 剪枝:字符串的长度不是串的整数倍, 必然不满足条件
if (s.length % i != 0) continue
// 剪枝:最后不是以该子串结尾, 必然不满足条件
if (sub != s.substring(s.length - sub.length)) continue
// 利用 repeat API, 查看是否满足满足要求
if (sub.repeat(s.length / sub.length) == s)
return true
}
return false
}
解法2:技巧
这个思路是参考其他大佬的,确实很奇妙,学习了,可以留个印象
[1] s = acd
[2] ss = acdacd
[3] ss.substring(1, ss.length - 1) = cdac (去头去尾)
判断: [3] 中包含 [1] 则满足条件, s = 'acd' 不满足条件
---
[1] s = acaaca
[2] ss = acaacaacaaca
[3] ss.substring(1, ss.length - 1) = caacaacaac (去头去尾)
判断: [3] 中包含 [1] 则满足条件, s = 'acaaca' 满足条件
const repeatedSubstringPattern = function (s: string): boolean {
let ss = s.repeat(2)
return ss.substring(1, ss.length - 1).indexOf(s) != -1
}
5. 逆波兰表达式求值
题解:用栈模拟、用数组模拟
解法1:用栈模拟,会使这道题万分简单
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for (String token : tokens) {
if ("+".equals(token)) {
Integer num1 = stack.pop();
Integer num2 = stack.pop();
stack.push(num2 + num1);
} else if ("-".equals(token)) {
Integer num1 = stack.pop();
Integer num2 = stack.pop();
stack.push(num2 - num1);
} else if ("*".equals(token)) {
Integer num1 = stack.pop();
Integer num2 = stack.pop();
stack.push(num2 * num1);
} else if ("/".equals(token)) {
Integer num1 = stack.pop();
Integer num2 = stack.pop();
stack.push(num2 / num1);
} else {
stack.push(Integer.parseInt(token));
}
}
return stack.pop();
}
代码丑陋,略微优化一下:(效率无影响)
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for (String token : tokens) {
if ("+".equals(token))
stack.push(stack.pop() + stack.pop());
else if ("-".equals(token))
stack.push(-stack.pop() + stack.pop());
else if ("*".equals(token))
stack.push(stack.pop() * stack.pop());
else if ("/".equals(token)) {
Integer num1 = stack.pop(), num2 = stack.pop();
stack.push(num2 / num1);
} else
stack.push(Integer.parseInt(token));
}
return stack.pop();
}
解法2:用数组代替栈模拟
class Solution {
public int evalRPN(String[] tokens) {
int[] res = new int[tokens.length];
int cur = 1; // 索引从 - 1 开始, 因为必须放进元素才能开始计算
for (String token : tokens) {
if ("/*-+".contains(token)) {
int b = res[cur--], a = res[cur--];
res[++cur] = calc(a, b, token);
} else
res[++cur] = Integer.parseInt(token);
}
return res[cur];
}
public int calc(int a, int b, String op) {
if (op.equals("+")) return a + b;
else if (op.equals("-")) return a - b;
else if (op.equals("*")) return a * b;
else if (op.equals("/")) return a / b;
else return -1;
}
}
6. 加一
题目:66. 加一
题解:分情况模拟及其优化
解法1:最朴素的模拟做法,这个基本上是第一反映想到的,速度也可以击败 100%
public int[] plusOne(int[] digits) {
int len = digits.length;
// 1 不会进位的情况(最后一位不为 9)
// 例如: 123 --> 124
if (digits[len - 1] != 9) {
digits[len - 1] += 1;
return digits;
}
// 2 全是9的特殊情况:
// 例如: 999 --> 1000
boolean flag = true; // 是否全是9
for (int i = 0; i < len; i++) {
if (digits[i] != 9) {
flag = false;
break;
}
}
if (flag) {
int[] res = new int[len + 1];
res[0] = 1;
return res;
}
// 3 最后一位是9, 但是不全为 9
// 例如: 129 --> 130
digits[len - 1] += 1;
while (len > 1) {
// 无需进位, 直接跳出返回
if (digits[len - 1] != 10) break;
// 计算进位
digits[len - 1] = 0;
digits[--len - 1] += 1;
}
return digits;
}
解法2:其实是对以上代码的优化
public int[] plusOne(int[] digits) {
for (int i = digits.length - 1; i >= 0; i--) {
// 最后一位不为 9, 直接 + 1 返回结果
if (digits[i] != 9) {
digits[i]++;
return digits;
}
// 最后一位为 9 或 10, 需要进位
digits[i] = 0;
}
// 走完 for 还没有 return, 说明数字全是 9, 直接构造出结果并返回
int[] res = new int[digits.length + 1];
res[0] = 1;
return res;
}
7. 二叉树中的列表
题解:递归
解法1:一个递归(超时)
boolean isSubPath(ListNode head, TreeNode root) {
if (root == null) return false;
if (head == null) return true;
// 找到了与链表首节点相同的树节点
if (root.val == head.val) {
// 走到链表尾部, 表示已经找到和链表对应的路径
if (head.next == null) return true;
// 沿着这条路径走下去, 能对应上的则返回true
if (root.left != null && head.next.val == root.left.val
&& isSubPath(head.next, root.left))
return true;
if (root.right != null && head.next.val == root.right.val
&& isSubPath(head.next, root.right))
return true;
}
return isSubPath(head, root.left) || isSubPath(head, root.right);
}
解法2:两个递归(可 AC)
class Solution {
boolean isSubPath(ListNode head, TreeNode root) {
if (root == null) return false;
if (dfs(head, root)) return true;
return isSubPath(head, root.left) || isSubPath(head, root.right);
}
boolean dfs(ListNode head, TreeNode root) {
// 链表全部匹配完, 匹配成功
if (head == null) return true;
// 二叉树访问到空节点, 匹配失败
if (root == null) return false;
// 当前二叉树上的节点与链表节点值不相等, 匹配失败
if (root.val != head.val) return false;
return dfs(head.next, root.left) || dfs(head.next, root.right);
}
}
8. 字符串相乘
题目:43. 字符串相乘
9. 二进制求和
题目:67. 二进制求和
题解 :模拟
这题可以根据题意直接模拟,最多也就是代码长点,if 条件多点,先做出来再说!
以下两个解法就是代码长短问题,效率是一样的。
解法1:模拟 + 各种 if + StringBuilder
public String addBinary(String a, String b) {
StringBuilder sb = new StringBuilder();
int carry = 0;
int p1 = a.length(), p2 = b.length();
while (p1 >= 0 || p2 >= 0) {
char ca = (--p1 >= 0) ? a.charAt(p1) : '0';
char cb = (--p2 >= 0) ? b.charAt(p2) : '0';
if (ca == '1' && cb == '1') {
if (carry == 1) sb.insert(0, "1");
else sb.insert(0, "0");
carry = 1;
continue;
}
if (ca == '0' && cb == '0') {
if (carry == 1) sb.insert(0, "1");
else sb.insert(0, "0");
carry = 0;
continue;
}
if (carry == 1) {
sb.insert(0, "0");
carry = 1;
} else {
sb.insert(0, "1");
carry = 0;
}
}
return sb.charAt(0) == '0' ? sb.substring(1).toString() : sb.toString();
}
题解2:善用 'a' - '0'
这种方式转数字,优化代码
public String addBinary(String a, String b) {
StringBuilder sb = new StringBuilder();
int i = a.length() - 1, j = b.length() - 1;
int c = 0; // 进位
while (i >= 0 || j >= 0) {
if (i >= 0) c += a.charAt(i--) - '0';
if (j >= 0) c += b.charAt(j--) - '0';
sb.append(c & 1);
c >>= 1;
}
String res = sb.reverse().toString();
return c > 0 ? "1" + res : res;
}
10. 数组形式的整数加法
题解:模拟
这题常规思路是比较容易做出来的,就是从后往前遍历,然后相加,同时保存一个进位,每次注意更新进位的值就行了
基于以上思路,Java 中使用 ArrayList 的话,前插 res.Add(0, val)
效率不如直接后插然后反转。。。
解法1:模拟,用 ArrayList 往后插入数据,然后反转容器
public List<Integer> addToArrayForm(int[] num, int k) {
List<Integer> res = new ArrayList<>();
int carry = 0, len = num.length - 1;
while (len >= 0 || k != 0) {
int n1 = len >= 0 ? num[len--] : 0;
int n2 = k % 10;
k /= 10;
res.add((n1 + n2 + carry) % 10);
carry = n1 + n2 + carry >= 10 ? 1 : 0;
}
if (carry == 1) res.add(1);
Collections.reverse(res);
return res;
}
但是如果使用 LinkedList 的前插 res.addFirst(val)
效果会很快
解法2:模拟,用 LinkedList 直接往前插入数据
public List<Integer> addToArrayForm(int[] num, int k) {
LinkedList<Integer> res = new LinkedList<>();
int carry = 0, len = num.length - 1;
while (len >= 0 || k != 0) {
int n1 = len >= 0 ? num[len--] : 0;
int n2 = k % 10;
k /= 10;
res.addFirst((n1 + n2 + carry) % 10);
carry = n1 + n2 + carry >= 10 ? 1 : 0;
}
if (carry == 1) res.addFirst(1);
return res;
}
解法3:还有很多其他做法,比如将数字转成 int 数组后操作…
public List<Integer> addToArrayForm(int[] num, int k) {
String[] ss = String.valueOf(k).split("");
// string[] => int[]
int[] is = Arrays.stream(ss).mapToInt(Integer::valueOf).toArray();
List<Integer> res = new ArrayList<>();
int carry = 0;
int p1 = num.length - 1, p2 = is.length - 1;
while (p1 >= 0 || p2 >= 0) {
int n1 = p1 >= 0 ? num[p1--] : 0;
int n2 = p2 >= 0 ? is[p2--] : 0;
int sum = (n1 + n2 + carry) % 10;
carry = (n1 + n2 + carry) >= 10 ? 1 : 0;
res.add(0, sum);
}
if (carry == 1) res.add(0, 1);
return res;
}
11. 每日温度
题目:739. 每日温度
题解:暴力、单调 栈
解法1:暴力
public int[] dailyTemperatures(int[] T) {
int[] res = new int[T.length];
for (int i = 0; i < T.length; i++) {
for (int j = i + 1; j < T.length; j++) {
if (T[j] > T[i]) {
res[i] = j - i;
break;
}
}
}
return res;
}
解法2:单调递减栈
注意:Java 中建议使用
Deque
而不是用Stack
public int[] dailyTemperatures(int[] T) {
// 单调递减栈(栈中存储的是下标)
Deque<Integer> stack = new ArrayDeque<>();
int[] res = new int[T.length];
for (int i = 0; i < T.length; i++) {
// 当前元素 > 栈顶下标对应元素
while (!stack.isEmpty() && T[i] > T[stack.peekLast()]) {
int temp = stack.removeLast(); // 获取栈顶下标
res[temp] = i - temp; // 计算下标距离
}
stack.addLast(i); // 当前位置入栈
}
return res;
}
12.最后一个单词长度
题解:API
解法1:暴力使用 API
public int lengthOfLastWord(String s) {
String[] words = s.split("\\s+");
return words[words.length - 1].length();
}
解法2:轻度使用 API
public int lengthOfLastWord(String s) {
s = s.trim();
for (int i = s.length() - 1; i >= 0; i--)
if (s.charAt(i) == ' ')
return s.length() - i - 1;
return s.length();
}
解法3:不使用 API
public int lengthOfLastWord(String s) {
int i = s.length() - 1;
// 去除末尾的空字符
while (s.charAt(i) == ' ') i--;
int cnt = 0;
while (i >= 0 && s.charAt(i) != ' ') {
cnt++;
i--;
}
return cnt;
}
13. 旋转矩阵
题目:48. 旋转图像
题解:模拟 + 技巧
解法1:暴力模拟
- 找到每个要交换的点的位置,然后模拟进行交换操作
public void rotate(int[][] matrix) {
int n = matrix.length;
for (int i = 0; i < n / 2; i++) {
for (int j = i; j < n - i - 1; j++) {
// 00 -> 02
// 01 -> 12
int temp = matrix[j][n - i - 1];
matrix[j][n - i - 1] = matrix[i][j];
// 02 -> 22
// 12 -> 21
int temp2 = matrix[n - i - 1][n - j - 1];
matrix[n - i - 1][n - j - 1] = temp;
// 22 -> 20
// 21 -> 10
temp = matrix[n - j - 1][i];
matrix[n - j - 1][i] = temp2;
// 20 -> 00
// 10 -> 01
matrix[i][j] = temp;
}
}
}
优化以上代码:
public void rotate(int[][] matrix) {
int n = matrix.length - 1;
for (int i = 0; i <= matrix.length / 2; i++) {
for (int j = i; j < n - i; j++) {
// 获取各顶点的值
int a = matrix[i][j]; // 左上角
int b = matrix[j][n - i]; // 右上角
int c = matrix[n - i][n - j]; // 右下角
int d = matrix[n - j][i]; // 左下角
// 交换各顶点的值
matrix[i][j] = d;
matrix[j][n - i] = a;
matrix[n - i][n - j] = b;
matrix[n - j][i] = c;
}
}
}
解法2:技巧
- 先沿对角线翻转,再沿垂直竖线翻转,即可实现旋转矩阵
public void rotate(int[][] matrix) {
int n = matrix.length;
// 先沿对角线翻转
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// 再沿垂直竖线翻转
for (int i = 0; i < n; i++) {
for (int j = 0, k = n - 1; j < k; j++, k--) {
int temp = matrix[i][k];
matrix[i][k] = matrix[i][j];
matrix[i][j] = temp;
}
}
}
14. 判断矩阵经轮转后是否一致
n x n
矩阵旋转总结:
- 90 度:沿左上到右下翻转,再沿垂直中线翻转
- 180 度:沿水平中线翻转,沿垂直中线翻转
- 270 度:沿左上到右下翻转,再沿水平中线翻转
- 360 度:本身
题解:模拟
n x n
矩阵旋转总结:
- 90 度:沿左上到右下翻转,再沿垂直中线翻转
- 180 度:沿水平中线翻转,沿垂直中线翻转
- 270 度:沿左上到右下翻转,再沿水平中线翻转
- 360 度:本身
但是如果不是要一下子求出结果,而是要每个轮流判断,只需要利用转 90 度就行。
本题解法:暴力模拟
- 先判断不转(相当于转 360度)是否满足两矩阵相等
- 再判断转 90度 是否满足两矩阵相等
- 再判断转 180度 满足两矩阵相等(在上面基础上再转 90度)
- 再判断转 270度 满足两矩阵相等(在上面基础上再转 90度)
转 90 度的两种思路:
- 思路1:暴力模拟,计算好 4 个顶点,模拟移动到下一个位置
public void rotate(int[][] matrix) {
int n = matrix.length - 1;
for (int i = 0; i <= matrix.length / 2; i++) {
for (int j = i; j < n - i; j++) {
// 获取各顶点的值
int a = matrix[i][j]; // 左上角
int b = matrix[j][n - i]; // 右上角
int c = matrix[n - i][n - j]; // 右下角
int d = matrix[n - j][i]; // 左下角
// 交换各顶点的值
matrix[i][j] = d;
matrix[j][n - i] = a;
matrix[n - i][n - j] = b;
matrix[n - j][i] = c;
}
}
}
思路2:技巧,也就是上面的矩阵旋转总结中,沿左上到右下翻转,再沿垂直中线翻转
public void rotate(int[][] matrix) {
int n = matrix.length;
// 先沿对角线翻转
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// 再沿垂直竖线翻转
for (int i = 0; i < n; i++) {
for (int j = 0, k = n - 1; j < k; j++, k--) {
int temp = matrix[i][k];
matrix[i][k] = matrix[i][j];
matrix[i][j] = temp;
}
}
}
根据以上的基础,可以知道本题代码为下:
class Solution {
public boolean findRotation(int[][] mat, int[][] target) {
// 不转
if (checkMatrix(mat, target)) return true;
// 转 90 度
rotate(mat);
if (checkMatrix(mat, target)) return true;
// 转 180 度(再转 90 度)
rotate(mat);
if (checkMatrix(mat, target)) return true;
// 转 270 度(再转 90 度)
rotate(mat);
if (checkMatrix(mat, target)) return true;
return false;
}
/**
* 将矩阵旋转 90 度
*/
void rotate(int [][] matrix) {
int n = matrix.length;
// 沿 左上->右下 翻转
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// 沿 垂直中线 翻转
for (int i = 0; i < n; i++) {
for (int j = 0, k = n -1; j < k; j++, k--) {
int temp = matrix[i][k];
matrix[i][k] = matrix[i][j];
matrix[i][j] = temp;
}
}
}
/**
* 检查两矩阵是否相等
*/
boolean checkMatrix(int[][] m1, int[][] m2) {
for (int i = 0; i < m1.length; i++) {
for (int j = 0; j < m1[0].length; j++) {
if (m1[i][j] != m2[i][j]) return false;
}
}
return true;
}
}
15. 螺旋矩阵
题目:54. 螺旋矩阵
题解:模拟
最朴素易懂的做法:
public List<Integer> spiralOrder(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
List<Integer> res = new ArrayList<>();
int direction = 1; // 1234 - 右下左上
boolean[][] visited = new boolean[m][n]; // 记录访问过的坐标, 防止重复访问
int x = 0, y = 0; // 当前访问坐标
// 初始化第一个位置
res.add(matrix[0][0]);
visited[0][0] = true;
while (res.size() < m * n) {
// 一直往 右 走
while (direction == 1 && y + 1 < n && !visited[x][y + 1]) {
res.add(matrix[x][++y]);
visited[x][y] = true;
}
direction = 2; // 更改方向为 下
// 一直往 下 走
while (direction == 2 && x + 1 < m && !visited[x + 1][y]) {
res.add(matrix[++x][y]);
visited[x][y] = true;
}
direction = 3; // 更改方向为 左
// 一直往 左 走
while (direction == 3 && y - 1 >= 0 && !visited[x][y - 1]) {
res.add(matrix[x][--y]);
visited[x][y] = true;
}
direction = 4; // 更改方向为 上
// 一直往 上 走
while (direction == 4 && x - 1 >= 0 && !visited[x - 1][y]) {
res.add(matrix[--x][y]);
visited[x][y] = true;
}
direction = 1; // 更改方向为 右
}
return res;
}
16. 最接近原点的K个点
题解:数组排序、优先级队列
解法1:自定义排序规则后,利用数组的排序函数
public int[][] kClosest(int[][] points, int k) {
Queue<int[]> queue = new PriorityQueue<>(
Comparator.comparingDouble(o -> o[0] * o[0] + o[1] * o[1]));
queue.addAll(Arrays.asList(points));
int[][] res = new int[k][2];
for (int i = 0; i < k; i++) res[i] = queue.poll();
return res;
}
解法2:利用优先级队列
public int[][] kClosest(int[][] points, int k) {
Queue<int[]> queue = new PriorityQueue<>(
Comparator.comparingDouble(o -> o[0] * o[0] + o[1] * o[1]));
queue.addAll(Arrays.asList(points));
int[][] res = new int[k][2];
for (int i = 0; i < k; i++) res[i] = queue.poll();
return res;
}
对优先级队列做法的优化:只维护 size = k 的优先队列
public int[][] kClosest(int[][] points, int k) {
PriorityQueue<int[]> queue = new PriorityQueue<>((a, b) -> cmp(a, b));
// 遍历的同时维护 size = k 的优先级队列
for (int i = 0; i < points.length; i++) {
// 堆中元素数量 < k, 直接放入
if (queue.size() < k)
queue.add(points[i]);
// 堆中已经有 k 个元素, 比较堆顶元素和当前遍历元素
else {
int[] temp = queue.peek();
if (cmp(points[i], temp) > 0) {
queue.poll();
queue.add(points[i]);
}
}
}
int[][] res = new int[k][2];
for (int i = 0; i < k; i++)
res[i] = queue.poll();
return res;
}
int cmp(int[] a, int[] b) {
return (b[0] * b[0] + b[1] * b[1]) - (a[0] * a[0] + a[1] * a[1]);
}
17. 等差子数组
题目:1630. 等差子数组
题解:暴力模拟
思路:暴力模拟
public List<Boolean> checkArithmeticSubarrays(int[] nums, int[] l, int[] r) {
List<Boolean> res = new ArrayList<>();
for (int i = 0; i < l.length; i++)
res.add(isArithmetic(Arrays.copyOfRange(nums, l[i], r[i] + 1)));
return res;
}
boolean isArithmetic(int[] arr) {
if (arr.length <= 1) return true;
Arrays.sort(arr);
int diff = arr[1] - arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] - arr[i - 1] != diff)
return false;
}
return true;
}
18. N 叉树的层次遍历
题解:BFS
let levelOrder = function (root: Node | null): number[][] {
if (!root) return []
let queue = [root], res = []
while (queue.length) {
let temp = []
for (let i = queue.length - 1; i >= 0; i--) {
let node = queue.pop()
temp.unshift(node.val)
queue.unshift(...node.children)
}
res.push(temp)
}
return res
};
19. 下一个更大元素 II
题解:暴力、单调栈
暴力:
- 读懂题目就能做,注意判断特殊情况
- 复杂度为 O(n * n)
const nextGreaterElements = function (nums: number[]): number[] {
let len = nums.length
if (len <= 1) return [-1]
let res = new Array(len).fill(null)
for (let i = 0; i < len; i++) {
for (let j = i + 1; j < len + i; j++) {
if (nums[j % len] > nums[i]) {
res[i] = nums[j % len]
break
}
}
if (res[i] == null) res[i] = -1
}
return res
};
单调栈:
- 「单调栈」就是栈内元素满足单调性的栈结构。
- 单调栈的根本作用在于求得「每一个数字在原始序列中左 / 右边第一个大于 / 小于它自身的数字」
- 由于每一个数字只会入栈一次且最多出栈一次,因此总的时间复杂度为 O(n)
public int[] nextGreaterElements(int[] nums) {
int[] res = new int[nums.length];
Arrays.fill(res, -1);
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < nums.length * 2; i++) {
// 栈不为空, 且当前元素 > 栈顶下标对应的元素
while (!stack.isEmpty() && nums[i % nums.length] > nums[stack.getLast()]) {
// 更新栈顶下标对应的结果
res[stack.removeLast()] = nums[i % nums.length];
}
stack.addLast(i % nums.length);
}
return res;
}
20. 下一个更大元素 III
题解:排序模拟
此题和 31.下一个排列 几乎一样,加了一个返回值判断而已
思路:
-
对于 [2, 6, 3, 5, 4, 1]
-
先从后往前找到一个逆序数 N, 即为 3
-
然后再从 N 后面找到一个比它大的 “最小数”
-
即在 [5, 4, 1] 中找到比 3 大的最小数, 为 4
(这里可以直接再次从后往前找,因为这段数字倒着遍历必然是正序的)
-
交换两者位置,则为 [2, 6, 4, 5, 3, 1]
-
然后对一开始 N 后面位置进行反转
-
即从 [2, 6, 4, 5, 3, 1] 到 [2, 6, 4, 1, 3, 5]
public int nextGreaterElement(int n) {
char[] cs = String.valueOf(n).toCharArray();
int i = cs.length - 2;
// 从后往前找到第一个逆序的数字 N
while (i >= 0 && cs[i] >= cs[i + 1]) i--;
if (i < 0) return -1;
// 找到 N 后面比 N 大的 "最小数字"
int j = cs.length - 1;
while (j >= 0 && cs[j] <= cs[i]) j--;
// 交换 N 和 比N大的"最小数字"的位置
swap(cs, i, j);
// 将原来的 N 位置后面的数字变为最小序(本就是倒序,反转一下即可)
reverse(cs, i + 1);
// 转换成 int, 越界则返回 -1
long res = 0;
for (char c : cs) res = res * 10 + (c - '0');
return res > Integer.MAX_VALUE ? -1 : (int)res;
}
void reverse(char[] cs, int start) {
int i = start, j = cs.length - 1;
while (i < j) swap(cs, i++, j--);
}
void swap(char[] cs, int i, int j) {
char temp = cs[i];
cs[i] = cs[j];
cs[j] = temp;
}
21. 通知所有员工所需的时间 TODO
22. 字母异位词分组
题目:49. 字母异位词分组
题解:哈希
其实这题可以将问题转化一下,如何确定一个 “字母异位词” 的 “唯一值”:
- 对于 “abc”, “acb”, “bca” 等都属于字母异位词,如何找到一个 “唯一值” 将他们进行分组
- 最直接的做法就是排序,他们经过排序后都会变成:“abc”,也就找到了那个 “唯一值”
- 所以如果可以将找到唯一值的过程优化的更快,整体效率也就更快(解法 2)
解法1:排序来找到唯一值
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (int i = 0; i < strs.length; i++) {
String temp = getFeature(strs[i]);
if (!map.containsKey(temp)) map.put(temp, new ArrayList<>());
map.get(temp).add(strs[i]);
}
return new ArrayList<>(map.values());
}
String getFeature(String s) {
char[] cs = s.toCharArray();
Arrays.sort(cs);
return String.valueOf(cs);
}
解法2:编码出唯一值
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String s : strs) {
String encode = encode(s);
if (!map.containsKey(encode)) map.put(encode, new ArrayList<>());
map.get(encode).add(s);
}
return new ArrayList<>(map.values());
}
// 根据出现的次数进行编码,26位
public String encode(String s) {
char[] code = new char[26];
for (char c : s.toCharArray()) {
int delta = c - 'a';
code[delta]++;
}
return new String(code);
}
23. 找到字符串中所有字母异位词 todo
题解
暴力 + 排序:
public List<Integer> findAnagrams(String s, String p) {
int len = p.length();
char[] pcs = p.toCharArray();
Arrays.sort(pcs);
List<Integer> res = new ArrayList<>();
for (int i = 0; i <= s.length() - len; i++) {
char[] cs = s.substring(i, i + len).toCharArray();
Arrays.sort(cs);
if (Arrays.equals(cs, pcs)) res.add(i);
}
return res;
}
24. 乘积小于K的子数组 to do
题解
暴力:
public int numSubarrayProductLessThanK(int[] nums, int k) {
int count = 0;
for (int i = 0; i < nums.length; i++) {
int sum = nums[i];
if (sum < k) count++;
for (int j = i + 1; j < nums.length; j++) {
sum *= nums[j];
if (sum < k) count++;
else break;
}
}
return count;
}
25. 二维区域和检索 - 矩阵不可变
题解:前缀和
暴力可以过:
class NumMatrix {
int[][] m;
public NumMatrix(int[][] matrix) {
m = matrix;
}
public int sumRegion(int row1, int col1, int row2, int col2) {
int sum = 0;
for (int i = row1; i <= row2; i++)
for (int j = col1; j <= col2; j++)
sum += m[i][j];
return sum;
}
}
前缀和一:一维前缀和的角度,计算每一行的前缀和
class NumMatrix {
int[][] pre;
public NumMatrix(int[][] matrix) {
int n = matrix.length, m = (n == 0) ? 0 : matrix[0].length;
pre = new int[n + 1][m + 1];
for (int i = 0; i < n; i++) {
int tempSum = 0; // 每一行的前缀和数组
for (int j = 0; j < m; j++) {
tempSum += matrix[i][j];
pre[i + 1][j + 1] = tempSum;
}
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
int sum = 0;
for (int i = row1; i <= row2; i++)
sum += pre[i + 1][col2 + 1] - pre[i + 1][col1];
return sum;
}
}
前缀和二:二维前缀和的角度
class NumMatrix {
int[][] sum;
public NumMatrix(int[][] matrix) {
int n = matrix.length, m = (n == 0) ? 0 : matrix[0].length;
sum = new int[n + 1][m + 1];
// 预处理前缀和数组(模板)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + matrix[i - 1][j - 1];
}
public int sumRegion(int x1, int y1, int x2, int y2) {
x1++;y1++;x2++;y2++;
return sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];
}
}
26. 最小差值 II
题目:910. 最小差值 II
题解
public boolean reachingPoints(int sx, int sy, int tx, int ty) {
while (tx > 0 && ty > 0){ // 因为sx, sy, tx, ty 是范围在 [1, 10^9] 的整数,逆推不能出界
if (sx == tx && sy == ty) return true; // 判断是否到达了起始值
// 每次逆推只能有tx、ty中较大值减去若干个较小值
if (tx > ty) tx -= ty;
else ty -= tx;
}
return false;
}
public boolean reachingPoints1(int sx, int sy, int tx, int ty) {
while (tx > 0 && ty > 0){ // 因为sx, sy, tx, ty 是范围在 [1, 10^9] 的整数,逆推不能出界
if (sx == tx && sy == ty) return true; // 判断是否到达了起始值
// 此时只能有tx减去ty
// tx - sx是目标与起始值在x的差距,我们需要一次减去n * ty达到快速逼近sx的目的
if (tx > ty) tx -= Math.max((tx - sx) / ty, 1) * ty;
// 此时只能有ty减去tx
// ty - sy是目标与起始值在y的差距,我们需要一次减去n * tx达到快速逼近sy的目的
else ty -= Math.max((ty - sy) / tx, 1) * tx;
}
return false;
}
27. 重排链表
题目:143. 重排链表
题解:模拟、拆分 + 拼接
暴力模拟:实现一个方法:将最链表的最后一个节点移动到首位,然后不停的调用这个方法就行
public void reorderList(ListNode head) {
ListNode cur = head;
while (cur != null && cur.next != null && cur.next.next != null) {
moveList(cur);
cur = cur.next.next;
}
}
/**
* 将最后一个节点移到首位
* @param head
*/
void moveList(ListNode head) {
ListNode last = head, pre = null;
while (last.next != null) {
pre = last;
last = last.next;
}
ListNode temp = head.next;
head.next = last;
last.next = temp;
pre.next = null;
}
拆分 + 拼接:
- 快慢指针将链表拆分成 两段
- 反转后段链表
- 将反转后的后段链表插入到前段的的节点之间
public void reorderList(ListNode head) {
// 快慢指针分出两段链表
ListNode slow = head, fast = head.next;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 后半部分的链表头节点
ListNode later = slow.next;
slow.next = null; // 断开两部分链表的连接
later = reverseList(later); // 反转后半部分链表
// 拼接两段链表
ListNode front = head;
while (front != null && later != null) {
ListNode laterNext = later.next;
ListNode frontNext = front.next;
front.next = later;
later.next = frontNext;
front = frontNext;
later = laterNext;
}
}
ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode node = reverseList(head.next);
head.next.next = head;
head.next = null;
return node;
}
28. 复制带随机指针的链表
题解:哈希
public Node copyRandomList(Node head) {
// 使用 map 存储 旧结点 和 新结点 的映射
Map<Node, Node> map = new HashMap<>();
Node node = head;
while (node != null) {
map.put(node, new Node(node.val));
node = node.next;
}
node = head;
while (node != null) {
map.get(node).next = map.get(node.next);
map.get(node).random = map.get(node.random);
node = node.next;
}
return map.get(head);
}
29. 两数相加
题目:2. 两数相加
难度中等7863收藏分享切换为英文接收动态反馈
题解:模拟
思路:模拟遍历,同时维护一个进位变量
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode root = new ListNode(0), cursor = root;
int carry = 0;
while (l1 != null || l2 != null || carry > 0) {
int n1 = (l1 == null) ? 0 : l1.val;
int n2 = (l2 == null) ? 0 : l2.val;
int sumVal = n1 + n2 + carry; // 计算数字 + 进位和
carry = sumVal / 10; // 计算下一个进位
cursor.next = new ListNode(sumVal % 10);
cursor = cursor.next;
if (l1 != null) l1 = l1.next;
if (l2 != null) l2 = l2.next;
}
return root.next;
}
30. 两数相加 II
题目:445. 两数相加 II
题解:反转链表3次、栈
思路1:
- 反转 l1、反转 l2
- 计算 l1 + l2
- 反转 l1 + l2 的结果
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
l1 = reverList(l1);
l2 = reverList(l2);
ListNode root = new ListNode(0), cursor = root;
int carry = 0;
while (l1 != null || l2 != null || carry > 0) {
int n1 = (l1 == null) ? 0 : l1.val;
int n2 = (l2 == null) ? 0 : l2.val;
int sumVal = n1 + n2 + carry; // 计算数字 + 进位和
carry = sumVal / 10; // 计算下一个进位
cursor.next = new ListNode(sumVal % 10);
cursor = cursor.next;
if (l1 != null) l1 = l1.next;
if (l2 != null) l2 = l2.next;
}
return reverList(root.next);
}
ListNode reverList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode node = reverList(head.next);
head.next.next = head;
head.next = null;
return node;
}
}
思路2:栈
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
LinkedList<Integer> s1 = new LinkedList<>(), s2 = new LinkedList<>();
while(l1 != null) {
s1.addFirst(l1.val);
l1 = l1.next;
}
while(l2 != null) {
s2.addFirst(l2.val);
l2 = l2.next;
}
int carry = 0;
ListNode lastNode = null;
while(!s1.isEmpty() || !s2.isEmpty() || carry > 0) {
int n1 = s1.isEmpty() ? 0 : s1.removeFirst();
int n2 = s2.isEmpty() ? 0 : s2.removeFirst();
int sum = n1 + n2 + +carry;
carry = sum / 10;
ListNode curNode = new ListNode(sum % 10);
curNode.next = lastNode;
lastNode = curNode;
}
return lastNode;
}
31. 旋转链表
题目:61. 旋转链表
题解:往后拼接
public ListNode rotateRight(ListNode head, int k) {
if (head == null || k == 0) return head;
// 将某个节点指向链表最后, 此时顺便拿到链表长度
ListNode node = head;
int size = 1;
while (node.next != null) {
size++;
node = node.next;
}
// 计算头节点后移的次数
int i = (size - k % size);
// 将头节点的值往后拼接并后移
while (i > 0) {
node.next = new ListNode(head.val);
head = head.next;
node = node.next;
i--;
}
return head;
}
32. 二叉搜索树迭代器
class BSTIterator {
List<Integer> list;
public BSTIterator(TreeNode root) {
list = new LinkedList<>();
dfs(root);
}
void dfs(TreeNode root) {
if (root == null)
return;
dfs(root.left);
list.add(root.val);
dfs(root.right);
}
public int next() {
return list.remove(0);
}
public boolean hasNext() {
return !list.isEmpty();
}
}
33. 座位预约管理系统
题解:优先级队列
class SeatManager {
Queue<Integer> q;
public SeatManager(int n) {
q = new PriorityQueue<>();
for (int i = 1; i <= n; i++) q.add(i);
}
public int reserve() {
return q.poll();
}
public void unreserve(int seatNumber) {
q.add(seatNumber);
}
}
34. 柠檬水找零
题目:860. 柠檬水找零
题解:贪心 + 模拟
这道题是很好的 贪心 + 模拟:
public boolean lemonadeChange(int[] bills) {
// 20 根本不用关心
int five = 0, ten = 0;
for (int bill: bills) {
// 5 不用找
if (bill == 5) five++;
// 10 必须找 5
else if (bill == 10) {
if (five == 0) return false;
five--;
ten++;
// 20 从 10, 5 依次找, 凑不到 15 则无法找
} else if (bill == 20) {
int temp = 15;
while (temp != 0) {
if (ten > 0 && temp >= 10) {
temp -= 10;
ten--;
} else if (five > 0) {
temp -= 5;
five--;
} else return false;
}
}
}
return true;
}
35. 最小栈
题目:155. 最小栈
题解:优先级队列、双栈、链表
思路1:优先级队列
class MinStack {
Deque<Integer> stack;
Queue<Integer> queue;
public MinStack() {
stack = new ArrayDeque<>();
queue = new PriorityQueue<>();
}
public void push(int val) {
stack.addLast(val);
queue.add(val);
}
public void pop() {
queue.remove(stack.removeLast());
}
public int top() {
return stack.getLast();
}
public int getMin() {
return queue.peek();
}
}
思路2:维护两个栈
class MinStack {
Deque<Integer> stack, minStack;
public MinStack() {
stack = new ArrayDeque<>();
minStack = new ArrayDeque<>();
}
public void push(int val) {
if (minStack.isEmpty() || val <= minStack.getLast())
minStack.addLast(val);
stack.addLast(val);
}
public void pop() {
if (minStack.getLast().equals(stack.removeLast()))
minStack.removeLast();
}
public int top() {
return stack.getLast();
}
public int getMin() {
return minStack.getLast();
}
}
思路3:维护一个链表,在链表的属性上维护 min
class MinStack {
private Node head;
public void push(int x) {
if (head == null)
head = new Node(x, x);
else
head = new Node(x, Math.min(x, head.min), head);
}
public void pop() {
head = head.next;
}
public int top() {
return head.val;
}
public int getMin() {
return head.min;
}
private class Node {
int val;
int min;
Node next;
private Node(int val, int min) {
this(val, min, null);
}
private Node(int val, int min, Node next) {
this.val = val;
this.min = min;
this.next = next;
}
}
}
36. 扁平化嵌套列表迭代器
题解:递归扁平化
public class NestedIterator implements Iterator<Integer> {
int idx = 0;
List<Integer> list; // 存储扁平化以后的列表
// 递归扁凭化列表
void add(List<NestedInteger> nestedList) {
for (NestedInteger integer : nestedList) {
if (!integer.isInteger()) { // 不是数字, 递归
add(integer.getList());
} else { // 是数字, 直接加到 list
list.add(integer.getInteger());
}
}
}
public NestedIterator(List<NestedInteger> nestedList) {
list = new ArrayList<>();
add(nestedList);
}
@Override
public Integer next() {
return list.get(idx++);
}
@Override
public boolean hasNext() {
return idx < list.size();
}
}
37. 设计一个验证系统
题解:读题模拟
class AuthenticationManager {
int timeToLive; // 过期时间
Map<String, Integer> map; // 验证码与其过期时间
public AuthenticationManager(int timeToLive) {
this.map = new HashMap<>();
this.timeToLive = timeToLive;
}
public void generate(String tokenId, int currentTime) {
map.put(tokenId, currentTime + timeToLive);
}
public void renew(String tokenId, int currentTime) {
// 没有这个验证码、验证码已经过期, 或者正好过期
if (!map.containsKey(tokenId) || map.get(tokenId) <= currentTime) return;
map.put(tokenId, currentTime + timeToLive);
}
public int countUnexpiredTokens(int currentTime) {
int count = 0;
for (Map.Entry<String, Integer> entry: map.entrySet()) {
if (entry.getValue() > currentTime) count++;
}
return count;
}
}
38. 设计链表
题目:设计链表
题解:基础功
class MyLinkedList {
class Node {
int val;
Node next;
Node() {
this.val = 0;
this.next = null;
}
Node(int val, Node next) {
this.val = val;
this.next = next;
}
}
Node head; // 虚拟头结点
Node tail; // 尾节点, 方便从最后插入
public MyLinkedList() {
this.head = new Node(); // 虚拟头节点
this.tail = this.head; // 一开始没有尾节点
}
public int get(int index) {
Node node = head.next;
while (index > 0) {
node = node.next;
index--;
if (node == null) return -1;
}
return node.val;
}
public void addAtHead(int val) {
Node newNode = new Node(val, head.next);
head.next = newNode;
maintainTail();
}
public void addAtTail(int val) {
Node newNode = new Node(val, null);
tail.next = newNode;
maintainTail();
}
public void addAtIndex(int index, int val) {
if (index <= 0) {
addAtHead(val); // index <= 0, 直接往前面插入
return;
}
Node node = head;
while (index > 0) {
node = node.next;
index--;
// index 大于链表长度, 不会插入节点
if (node == null) return;
}
// index 等于链表长度, 在后面插入节点
if (node.next == null) {
addAtTail(val);
return;
}
Node newNode = new Node(val, node.next);
node.next = newNode;
maintainTail();
}
public void deleteAtIndex(int index) {
if (index < 0) return;
Node node = head;
while (index > 0) {
node = node.next;
index--;
if (node == null) return;
}
tail = node; // 需要维护一下尾节点, 直接删除有可能让 tail 乱掉
if (node.next != null) {
node.next = node.next.next;
} else {
node.next = null;
}
maintainTail();
}
// 维护尾节点
void maintainTail() {
while (tail.next != null) {
tail = tail.next;
}
}
}
39. O(1) 时间插入、删除和获取随机元素
题解:List 与 Map
class RandomizedSet {
Map<Integer, Integer> map;
List<Integer> list;
Random random;
public RandomizedSet() {
map = new HashMap<>();
list = new ArrayList<>();
random = new Random();
}
public boolean insert(int val) {
if (map.containsKey(val))
return false;
list.add(val);
map.put(val, list.size() - 1);
return true;
}
public boolean remove(int val) {
if (!map.containsKey(val))
return false;
int index = map.get(val);
int last = list.get(list.size() - 1); // 最后一个元素的值
// 用最后一个元素覆盖要删除的元素(同时维护 map 和 list)
list.set(index, last);
map.put(last, index);
list.remove(list.size() - 1);
map.remove(val);
return true;
}
public int getRandom() {
int randomIdx = random.nextInt(list.size());
return list.get(randomIdx);
}
}
40. 设计循环链表
题目:622. 设计循环队列
题解:基础功
class MyCircularQueue {
int[] nums; // 元素, nums.length - 队列容量
int head; // 当前指针
int size; // 当前元素数量
public MyCircularQueue(int k) {
nums = new int[k];
}
public boolean enQueue(int value) {
if (isFull()) return false;
nums[(head + size) % nums.length] = value;
size++;
return true;
}
public boolean deQueue() {
if (isEmpty()) return false;
head = (head + 1) % nums.length;
size--;
return true;
}
public int Front() {
return (isEmpty()) ? -1 : nums[head];
}
public int Rear() {
return (isEmpty()) ? -1 : nums[(head + size - 1) % nums.length];
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return nums.length == size;
}
}
41. 我的日程安排表 I
题解:暴力模拟
public class MyCalendar {
List<int[]> calendar;
MyCalendar() {
calendar = new ArrayList();
}
public boolean book(int start, int end) {
for (int[] iv : calendar) {
if (iv[0] < end && start < iv[1]) return false;
}
calendar.add(new int[] { start, end });
return true;
}
}