刷题笔记《编程能力基础》
-
- 1. 单调数列
-
- 题解:递归、模拟、API
- 2. 实现 strStr()
-
- 题解:API、窗户暴力滑动
- 3. 平衡二叉树
-
- 题解:迭代
- 4. 重复子字符串
-
- 解题:模拟,技巧
- 5. 逆波兰表达式求值
-
- 题解:用栈模拟,用数组模拟
- 6. 加一
-
- 问题解分情况模拟和优化
- 7. 二叉树列表
-
- 题解:递归
- 8. 字符串相乘
- 9. 二进制求和
-
- 题解 :模拟
- 10. 整数加法的数组形式
-
- 题解:模拟
- 11. 每日温度
-
- 题解:暴力单调 栈
- 12.最后一个单词的长度
-
- 题解:API
- 13. 旋转矩阵
-
- 题解:模拟 技巧
- 14. 轮转后判断矩阵是否一致
-
- 题解:模拟
- 15. 螺旋矩阵
-
- 题解:模拟
- 16. K点最接近原点
-
- 问题解数组排序,优先级队列
- 17. 等差子数组
-
- 题解:暴力模拟
- 18. N 叉树的层次遍历
-
- 题解:BFS
- 19. 下一个更大的元素 II
-
- 题解:暴力单调栈
- 20. 下一个更大的元素 III
-
- 解题:排序模拟
- 21. 通知所有员工所需的时间 TODO
- 22. 字母异位词分组
-
- 题解:哈希
- 23. 在字符串中找到所有字母异位词 todo
-
- 题解
- 24. 子数组乘积小于K to do
-
- 题解
- 25. 二维区域和检索 - 矩阵不可变
-
- 前缀和
- 26. 最小差值 II
-
- 题解
- 27. 重排链表
-
- 解题:模拟拆分 拼接
- 28. 用随机指针复制链表
-
- 题解:哈希
- 29. 两数相加
-
- 题解:模拟
- 30. 两数相加 II
-
- 问题解反转链表3次,栈
- 31. 旋转链表
-
- 题解:后拼接
- 32. 二叉搜索树迭代器
- 33. 座位预约管理系统
-
- 题解:优先队列
- 34. 柠檬水找零
-
- 题解:贪心 模拟
- 35. 最小栈
-
- 题解:优先队列、双栈、链表
- 36. 扁平化嵌套列表迭代器
-
- 题解:递归扁平化
- 37. 设计验证系统
-
- 解题:模拟解读题
- 38. 设计链表
-
- 题解:基础功
- 39. O(1) 时间插入、删除和获取随机元素
-
- 题解:List 与 Map
- 40. 循环链表的设计
-
- 题解:基本功
- 41. 我的日程安排表 I
-
- 题解:暴力模拟
刷题代码:https://gitee.com/szluyu99/my-leet-code
1. 单调数列
题目:896. 单调数列
问题:递归、模拟、API
因为题目看起来很想递归,所以先送上一种递归方式:(强行递归,必须加班)
/** * 递归(超时) */ function isMonotonic(nums: number[]): boolan {
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
}
—
然后才是正文。
:
- 通过
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;
}
:
- 如果没有先判断出序列是 递增 还是 递减,也没关系
- 可以先假设又递增又递减,然后判断时如果遇到一个不满足条件的情况就可以排除这个假设了
- 中间如果加上一步减枝(可以快 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;
}
:然后就是利用语言特性和 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()
题目:28. 实现 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:技巧
这个思路是参考其他大佬的,确实很奇妙,学习了,可以留个印象
简单明了!!关于java两行代码实现的思路来源
[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. 逆波兰表达式求值
题目:150. 逆波兰表达式求值
题解:用栈模拟、用数组模拟
解法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:最朴素的模拟做法,这个基本上是第一反映想到的