7 Commits

Author SHA1 Message Date
4f622abc34 rename leetcode-20230109.md 2023-01-10 01:34:02 +08:00
ad0267d685 fix 20230109 2023-01-10 01:29:33 +08:00
cdc0582e1d Merge branch 'leetcode-20230106' into leetcode-20230109 2023-01-10 01:26:45 +08:00
b94ff8de58 leetcode-20230106.md 2023-01-10 01:23:49 +08:00
49120378d4 增加注释和c++sort,删去最后一页 2023-01-07 22:12:27 +08:00
39604791e9 fix typo 2023-01-06 21:08:24 +08:00
5612aafda7 leetcode 20230109 2023-01-06 18:35:16 +08:00
3 changed files with 500 additions and 33 deletions

View File

@@ -1,28 +1,34 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>reveal.js</title>
<link rel="stylesheet" href="dist/reset.css">
<link rel="stylesheet" href="dist/reveal.css">
<link rel="stylesheet" href="dist/theme/black.css">
<link rel="stylesheet" href="dist/reset.css" />
<link rel="stylesheet" href="dist/reveal.css" />
<link rel="stylesheet" href="dist/theme/black.css" />
<!-- Theme used for syntax highlighted code -->
<link rel="stylesheet" href="plugin/highlight/monokai.css">
<link rel="stylesheet" href="plugin/highlight/monokai.css" />
</head>
<body>
<div class="reveal">
<div class="slides">
<section>Slide 1</section>
<section>Slide 2</section>
<section data-markdown="leetcode-20230106.md">
</section>
<section data-markdown="leetcode-20230109.md">
</section>
</div>
</div>
<script src="dist/reveal.js"></script>
<script src="plugin/notes/notes.js"></script>
<script src="plugin/math/math.js"></script>
<script src="plugin/markdown/markdown.js"></script>
<script src="plugin/highlight/highlight.js"></script>
<script>
@@ -31,9 +37,11 @@
// - https://revealjs.com/config/
Reveal.initialize({
hash: true,
height: 1000,
width: 1000,
// Learn about plugins: https://revealjs.com/plugins/
plugins: [ RevealMarkdown, RevealHighlight, RevealNotes ]
plugins: [RevealMarkdown, RevealHighlight, RevealNotes, RevealMath.KaTeX],
});
</script>
</body>

154
leetcode-20230106.md Normal file
View File

@@ -0,0 +1,154 @@
### Leetcode 💻 寒假 20230106
---
### 20. Valid Parentheses
验证左右括号是否匹配 (利用Stack)
```cpp [|3-11|12-20|22-24|26|28|29-32|34-40|44|]
class Solution {
public:
// 判断左右括号是否相等
inline bool match(const char &left, const char &right) {
switch (left) {
case '(': return right == ')';
case '{': return right == '}';
case '[': return right == ']';
}
return false;
}
// 判断是不是左括号
inline bool is_left(const char &c) {
switch (c) {
case '(': return true;
case '{': return true;
case '[': return true;
}
return false;
}
bool isValid(string s) {
// 判断长度是否为偶数
if (s.length() % 2 != 0)
return false;
stack<char> myStack;
for (auto const &c : s) {
if (is_left(c)) {
myStack.push(c);
continue;
}
if (myStack.empty())
return false;
if (!match(myStack.top(), c))
return false;
myStack.pop();
}
// 如果结束之后 stack 中还有元素
return myStack.empty();
}
};
```
Note:
3-10 首先有两个辅助函数一个叫match,用来判断左右括号是否相等,
12-20 然后是另一个函数慵懒判断是不是左括号。
22-24 进入主函数,先判断一下字符串长度是基数还是偶数,
如果是基数说明里面的括号肯定是不能左右匹配的就可以直接返回false
26 然后创建一个stack类型的变量myStack
28 然后用变量c去遍历s,这里用到的是c++11的语法auto是自动推断类型
const表示c是不能被更改的and符号表示这是一个左值引用
c的类型可以简单理解为字符串中的字符
29-32 如果c是左括号那么直接往stack中添加这个c,然后直接进入下一个循环
34-40 刚刚的语句已经把做括号的情况处理掉了那么剩下的情况就是c是右括号的情况。
我们先检查stack中是否为空为空的话说明stack中没有括号可以和c互相对应
那么让函数返回false。
接著取stack中最顶的括号和c进行配对如果不匹配返回false。
最后调用stack的pop方法移除stack中最顶部的括号。
44 最后的最后所有字符都匹配完了如果stack中还残留有括号
说明这个字符串也是不匹配的
---
### 84. Largest Rectangle in Histogram
```cpp [|2-4|6-8|26-28|9-10|12-14|16-20|23|26-31|]
int largestRectangleArea(vector<int> &heights) {
int ans = 0;
// 定义 stack 储存增序长方形
stack<int> order;
// 往 stack 中添加位于 index 的高度 height 的长方形
auto append = [&ans, &order, &heights]
(int index, int value) -> void {
// 计算单个长方形面积
ans = max(ans, value);
// stack 不为空 且 需要添加的长方形矮于 stack 中的长方形
while (!order.empty() &&
value < heights[order.top()]) {
// 计算面积
int h = heights[order.top()];
order.pop();
int w = index - 1 - (order.empty() ? -1 : order.top());
ans = max(ans, h * w);
}
order.push(index);
};
for (int i = 0; i < heights.size(); i++) {
append(i, heights[i]);
}
// 处理edge case, 在最末尾添加一个高度为-1的长方形
// 这将会计算stack中所有残留的长方形的面积
append(heights.size(), -1);
return ans;
}
```
Note:
2-4 首先定义一个整数变量answer,代表这道题的计算结果,
然后定义一个stack,元素类型是整数,用来储存长方形的索引,
注意是储存的是长方形的索引不是长方形的高度。
6-8 然后由于这部分代码要在多个地方被调用所以这里我用了c++11的匿名函数
append是函数名它接受两个参数一个是index一个是value,它没有返回值所以是void。
同时这个函数可以访问并修改ans,order,height,这里的and符号是表示捕获引用变量的意思
各位理解为这个函数可以在函数内修改到函数外的变量就行了。
26-28 定义好函数之后会在一个循环中遍历题目给的这个高度列表,
然后调用函数,传入每个长方形的索引和高度
9-10 进入函数后首先计算单个长方形的面积
12-14 然后进入一个循环,
循环的条件是stack 不为空 且 需要添加的长方形矮于 stack 中的长方形
16-20 在循环内获得高度h和宽度w,注意这里先调用了pop然后在调用top,
如果stack里面是空的那么调用top会引起奇怪的错误所以一定要检查是否为空。
这里用了一个三元表达式,如果空则返回-1,否则正常返回top.
23 循环结束之后也就是需要添加的长方形比stack中的长方形都高的时候
我们可以把它添加仅stack了
26-31 这是刚刚看到调用append的代码
注意代码运行到这里stack中是还有长方形的stack中残留的长方形还没有计算面积。
所以在最末尾的位置添加一个高度为-1的长方形
由于stack中的所有长方形都是正数加个-1进去会计算stack中残留的长方形面积。

305
leetcode-20230109.md Normal file
View File

@@ -0,0 +1,305 @@
# Leetcode 💻 寒假 20230109
---
### 169. Majority Element
> The majority element is the element that appears more than [n / 2] times.
```python [3]
class Solution:
def majorityElement(self, nums: List[int]) -> int:
return sorted(nums)[(len(nums)) // 2]
```
以下操作等价
```python
print(int(5 / 2)) # 2
print(5 // 2) # 2
```
Note:
第169题找出列表中出现次数最多的元素。
题目给了一个关键信息就是这个要找的元素出现次数超过n/2,
也就是超过这个列表长度的一半。
那么最简单的写法就是对数组进行排序,然后返回他的中位数。
注意我这里写了两个除号这是python整除的意思
---
### 56. Merge Intervals
```python [|2|4|5-7|9-12|14-15]
def merge(self, intervals):
intervals.sort(key = lambda i: i[0])
ret = []
for begin, end in intervals:
if not ret:
ret.append([begin, end])
continue
# overlap
if ret[-1][1] >= begin:
ret[-1][1] = max(end, ret[-1][1])
continue
ret.append([begin, end])
return ret
```
Note:
2首先对intervals进行排序注意intervals里灭个元素都是一个列表
这里key参数我们给他传入一个lambda临时函数
这个函数是干什么的呢他的任务就是输入一个i,然后返回i的第零个元素
意思是告诉排序函数你在排序的时候要用i的第零个元素作为排序的依据
4接着我们用begin和end这两个变量遍历intervals数组
5-7我们先处理第一种特殊情况就是ret数组为空
这种情况直接把当前遍历到的begin和end添加进去就行了
9-12第二种情况是发生了overlap我们需要把ret最后一个元素的end更新为最大的end.
14-15如果上面两种特殊情况都没有发生那么我们正常把begin和end添加到列表里就行了
---
### 避免嵌套
坏👎
```python []
if gpa < 2:
return 'failed'
else:
if gpa < 3:
return "good"
else:
return "excellent"
```
好👍
```python []
if gpa < 2:
return "failed"
if gpa < 3:
return "good"
return "excellent"
```
Note:
这里有个函数根据一个整数gpa变量返回对应的字符串。
假设这里用 if 判断第一种比较接近自然语言如果gpa小于2则返回failed
不然的话接着判断如果gpa小于3则返回good,不然就返回4。
第二种方法则比较符合程序设计思想,
先从条件范围小的情况开始处理先处理gpa小于2,然后处理gpa小于3
最后一个return是精髓保证这个函数无论在什么情况下都有一个返回值。
第一种情况下很多人写着写着就忘了返回值,然后各种内存报错又找不到在哪里出错,非常痛苦
---
### 避免嵌套基本思想
```python []
if 特殊情况:
处理特殊情况()
return
if 其他特殊情况:
处理其他特殊情况()
return
做该做的事情()
```
Note:
避免嵌套的基本思想就是early return,就是说进入函数之后,
先处理特殊情况和各种边界情况处理完之后直接return这个函数也就是early return.
最后再开始做函数该做的事情一般来说这都是比较好的写法如果你去看golang代码
你能看到大量这样的写法这也是golang社区和官方建议的写法。
---
### 15. 3Sum
```python [|4-8|6]
import itertools
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
ret = {
tuple(sorted(test))
for test in itertools.combinations(nums, 3)
if sum(test) == 0
}
return list(ret)
```
### List Comprehensions
求 0 到 100 所有能被 7 整除数的平方和
```python []
sum([i ** 2 for i in range(100) if i % 7 == 0])
```
### 迭代器
标准库提供的多种高效排列组合工具
```python []
list(itertools.combinations([1,2,3,4], 3))
# [(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]
```
Note:
这题的解法其实也就一行就是4-8行的这个列表生成式那么列表生成式是什么捏。
列表生成式又叫做List comprehensions, 这里有个例子假设我们要求0到100所有能被7整除数的平方和
这个计算用列表生成式一行就能实现。首先最开始是个sum函数sum函数里面是一个列表
这个列表是中的每一个元素都是i的平方那么i从哪里来的呢i是for i in range(100) 这个循环中出来的,
并且i满足i除以7的余数是0这个条件。
回到4-8行ret是一个集合集合中每个元素是 **经过排序** 的 **元组** test,
test从哪里来呢test 是 第七行 这个for循环迭代出来的变量并且这个test满足第八行的这个求和等于0的条件。
那么这个itertools.combinations是什么函数呢它是标准库中提供的排列组合迭代器。
下面给各位回忆一下高中排列组合的知识假设我们有一个列表列表中有元素1 2 3 4,每次取三个不同的元素,
那么一共有多少中不同的排列组合呢。用combinations函数就能非常方便的帮我们遍历所有排列组合。
理论上,这题就这么可以解出来了,但是实际上是不行的
```
---
但是时间复杂度 `$O(n^3)$`
3000 数组长度就是 27,000,000,000 次循环(百亿)😢
python for 循环在一般电脑上一秒钟能跑一千万次左右
思路:哈希表(字典)具有 `$O(1)$` 查找速度,使用字典代替最深的一层循环,将算法优化为 `$O(n^2)$`
```python [|5-7|9-14|16-17|18-21|22-25|27-34|36-38]
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
# 处理全是相同数字的特殊情况
if min(nums) == max(nums) and sum(nums) == 0:
return [[min(nums)] * 3]
# 生成反向字典,之后用于加速查找
reverse = {}
for key, val in enumerate(nums):
if reverse.get(val) is None:
reverse[val] = []
reverse[val].append(key)
# 创建集合,集合中元素是不可重复的
ret = set()
for index_i in range(len(nums)-2):
i = nums[index_i]
for index_j in range(index_i+1, len(nums)-1):
j = nums[index_j]
# 由于 sum((i,j,k)) == 0计算出需要的 k 是多少
k = -(i + j)
test = reverse.get(k)
# 情况1字典没有满足 sum((i,j,k))==0 的k
if test is None:
continue
# 情况2&3字典中有满足需求的k但它的索引是 i 或者 j
if len(test) == 1 and (index_i in test or index_j in test):
continue
if len(test) == 2 and (index_i in test and index_j in test):
continue
# 对 (i,j,k) 排序后放入元组中
# 利用元组不可重复的特性去掉重复结果
ret.add(tuple(sorted([i, j, k])))
return list(ret)
```
---
### 406. Queue Reconstruction by height
利用 list.insert() 能在指定位置插入元素,并把后面的元素往后移动的特点
```python [|2-3,6-8|]
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
# 按 p[0] 降序排p[0] 相同的情况下按 p[1] 升序排
people.sort(key = lambda p: (-p[0], p[1]))
res = []
for p in people:
# 在 p[1] 位置插入 p后面的元素会向后移动一位
# [TODO] 使用链表优化性能但这已经AC了能AC的就是好的👍
res.insert(p[1], p)
return res
# [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
```
⚠ python 标准库中有双向链表 `collections.deque`
⚠ c++ 标准库中也有链表 list
Note:
这道题其实也就两行代码第一行是将people排序第二行是在新数组中指定位置插入元素。
解题的关键就在利用了插入排序的特性,列表中后来插入的元素把之前插入的元素往后推一个位置。
但题外话我想提一下,
我们写算法关注算法性能,这没问题,但我自己个人是把代码简洁度看得比性能更重的。
写程序像写作一样,不光要机器能跑,还要其他人能看得懂,不光要看得懂,还要看得舒服,
怎么才算看得舒服呢,我个人认为是代码符合思维直觉,一个屏幕长度内的代码表达的信息量恰到好处,
不像流水账一样冗余,也不像汇编语言那样要盯着一行思考很久,当然我说了不算,
有本书叫《代码简洁之道》就专门讨论这类软件工程问题,感兴趣的可以找来看看。
还有就是标准库很重要,当然不是说要你把标注库背下来,
只是说要了解标准库能做到什么,还有了解你用的语言有哪些语法糖,
比如说三元表达式,不知道三元表达式这个语法糖的可以自己去查一下,
大部分现代语言比如python或者c++都有三元表达式这个语法糖,它能让代码更简短更简洁,
总的来说标准库的作用就是等你要用到相关功能的时候就知道去哪里查,而不是说自己重复造轮子
---
## C++
```cpp [|4|6-7|9-12|14-18]
#include <algorithm>
int main() {
std::array<int, 10> s = {5, 7, 4, 2, 8, 6, 1, 9, 0, 3};
// 默认升序排序
std::sort(s.begin(), s.end());
// 自定义降序排序函数
std::sort(s.begin(), s.end, [](int a, int b) {
return a > b;
})
// 循环输出
for (auto const &i : s) {
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
```
---
### C++ 标准库中的排序以C11为例
- 快速排序平均复杂度为 `$O(N log N)$` ,最坏情况下为 `$O(N^2)$`,快排递归带来额外开销
- 堆排序比快排慢,但最坏情况下为 `$(N log N)$`
- 插入排序在大致有序的情况下表现非常好
`std::sort` 实现了 Introspective sorting集成了三种算法各自的优点
---
## End 🎉