Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 4 additions & 195 deletions DSA/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
# 学习数据结构与算法

从 230903 开始,做 POTD 时,无法在 40 分钟内 AC 的一律 ❌,禁止任何留恋。

文件夹说明:

- lecture 内容来自 [算法老师左神的 B 站视频](https://www.bilibili.com/video/BV13g41157hK/)。
- POTD 内容来自 [geeksforgeeks 的 Problem Of The Day](https://practice.geeksforgeeks.org/problem-of-the-day)。确保每天更新
- [AcWing](https://www.acwing.com/activity/1/competition/) 暂时只记录周赛题目。
- [LeeCode](https://leetcode.cn/problemset/all/) 暂不确定。

TODO:

- [ ] 整理仓库。仓库现已变得臃肿了,笔记中的某些内容已经变得过于啰嗦。
Expand All @@ -17,189 +8,7 @@ TODO:

学习技巧:

- 写算法时,永远记得考虑边界问题
- 人的优势在于使用工具。不要认为只靠想就做出一道算法题有多厉害,真正厉害的应该是能画出来,理清逻辑,并且能给别人讲明白。所以要多画图!

## 基础班内容

### lecture01

- 时间复杂度
- 选择排序、冒泡排序
- 异或运算
- 交换两个数
- 求一个出现奇数次的数字
- 求两个出现奇数次的数字
- 二分法
- 在有序数组中查找某数的位置
- 在有序数组中查找最左侧/最右侧的位置
- 求取局部最小值/最大值
- 对数器
- 插入排序

### lecture02

- 递归
- master 公式计算时间复杂度
- 归并排序
- 小和问题
- 快速排序
- 二项划分
- 三项划分

### lecture03

- 数组实现完全二叉树
- 堆:大根堆和小根堆(优先级队列)
- heapify
- heapInsert
- 堆存储中位数
- 堆排序
- 完全二叉树转换为大根堆
- 堆扩容
- 比较器
- 非比较排序(桶排序)
- 计数排序
- 基数排序
- 基数排序优化

### lecture04

- 排序算法总结(时间复杂度、稳定性)
- 哈希表(map, set)、有序表
- 链表
- 反转链表
- 打印有序链表公共部分
- 快慢指针
- 单链表回文
- 单链表划分
- 复制含有随机指针节点的链表
- 判断链表成环
- 判断链表相交
- 二叉树(节点形式)
- 递归遍历二叉树(先序、中序、后序)
- 非递归遍历二叉树(先序、中序、后序)
- 层序遍历
- 求取最大宽度

### lecture05

- 二叉树
- 搜索二叉树
- 完全二叉树
- 满二叉树
- 平衡二叉树
- 二叉树递归套路(树形 DP)
- 最近公共祖先节点
- 查找(中序遍历)后继结点
- 二叉树的序列化和反序列化(先序、后序和层序)
- 折纸问题

### lecture06

- 图
- 基本概念(顶点、边、无向图、有向图、邻接表、邻接矩阵、出度、入度、权重、邻接点、邻接边)
- 转换图结构
- 宽度优先遍历
- 深度优先遍历
- 拓扑排序
- 最小生成树
- Kruskal 算法
- Prim 算法
- 最短路径(Dijkstra 算法)

### lecture07

- 字符串前缀树
- 贪心算法
- 安排会议顺序
- 最小字典序
- 哈夫曼编码
- 项目利润
- 暴力递归
- 汉诺塔问题
- 打印字符串全部子序列
- 打印字符串全排列
- 先手后手问题
- 逆转栈
- 数字转26进制所有结果
- 暴力背包
- N 皇后问题

## 基础提升班内容

### lecture08

- 哈希
- 哈希的特性(一致性、抗碰撞性、离散型、高效性)
- 哈希表原理
- 找出出现次数最多的数字
- 设计 RandomPool 结构
- 布隆过滤器
- 位图
- 一致性哈希原理
- 虚拟节点技术

### lecture09

- 并查集
- 岛问题
- 实现并查集
- 并行处理岛问题
- KMP 算法

### lecture10

- manacher 算法
- 回文子串
- 滑动窗口
- 双端队列
- 维护最大值
- 单调栈
- 求两侧的第一个小于/大于自己的值

### lecture11

- 树形 DP
- 求一棵树的最大距离
- 最大快乐值
- Morris 遍历二叉树
- 先序中序后序
- 判断搜索二叉树
- 大数据
- 低内存查找没出现过的数字

### lecture12

- 大数据(资源限制)
- TOP 词汇
- 统计出现两次的数字
- 外排序
- 位运算
- 最大值
- 判断 2 的幂次
- 加减乘除

### lecture13

- 动态规划
- 机器人步数问题
- 最少硬币

### lecture14

- 动态规划
- 先手后手
- 象棋跳马(三维)
- 生存概率(三维)
- 凑零钱(斜率优化)

### lecture15

- 有序表
- 搜索二叉树
- 树的左旋和右旋
- 平衡二叉树(AVL 树)
- SB 树
- 红黑树(不必深研)
- 跳表(多链表)
- 多画
- 多看
- 多敲
- 睡觉
6 changes: 5 additions & 1 deletion DSA/____全新笔记/KMP.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ const getNext2 = (pattern) => {
k++
j++
// 我们是统一右移了一位,所以这里是在 j++ 后再赋值
// 换句话说,我们这里求的 next[j] 的值,其实是
// t0...!tj 字符串(不包含 tj)的最长相同前后缀长度
next[j] = k
} else {
// 加快处理。这里有动态规划的思想
Expand Down Expand Up @@ -216,7 +218,7 @@ const getNext1 = (pattern) => {
const next = [0]
let j = 1
while (j < pattern.length) {
if (pattern[k] === pattern[j]) {
if (pattern[k] === pattern[j]) {
// 相同,则可以直接在前人的基础上 + 1
next[j] = k + 1
k++
Expand All @@ -230,6 +232,8 @@ const getNext1 = (pattern) => {
j++
}
}
// 如果你在这里往头插入一个任意元素,那么 next 数组就变成了
// 第二种 next 数组了。
return next
}
```
Expand Down
108 changes: 36 additions & 72 deletions DSA/____全新笔记/二叉树的遍历.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,47 +121,26 @@ def pre_order_binary_tree(root, print_arr):
stack.append(head.right) if head.right is not None else None
stack.append(head.left) if head.left is not None else None
```
```js
function preorderTraversal(root) {
```ts
function preorderTraversal(root: TreeNode | null): number[] {
const ans = []
if (!root) return ans

// 先序遍历,一个头换两个子
const stack = [root]
while (stack.length > 0) {
const head = stack.pop()
ans.push(head.val)

// 记得入栈是先右后左哦
if (head.right) stack.push(head.right)
if (head.left) stack.push(head.left)
}

return ans
}
```
```js
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function(root) {
const ans = []

const stack = [root]
while (stack.length > 0){
const head = stack.pop()
// 我们可以选择在这里进行空节点的判断
// 在这里判断,代码更简洁。
// 但要知道,简洁的代价就是所有空节点都会经历入栈出栈,吞吐量翻倍
if (!head) continue

ans.push(head.val)

// 颠倒
// 注意这里是颠倒的
stack.push(head.right)
stack.push(head.left)
}

return ans
}
};
```
:::

Expand Down Expand Up @@ -191,52 +170,32 @@ def pos_order_binary_tree(root, print_arr):
while len(collect) != 0:
print_arr.append(collect.pop().val)
```
```js
function postorderTraversal(root) {
if (!root) return []

const ans = []
```ts
function postorderTraversal(root: TreeNode | null): number[] {
const ansReversed = []
// 后序,就是前序的相反结果
const stack = [root]
const postStack = []
while (stack.length > 0) {
const head = stack.pop()
postStack.push(head.val)
if (!head) continue

// 这里就是负负得正,所以是 left, right
// 等会我们还会从 ansReversed 中反向取出,所以
// 要先让头入栈,这样,这样头才会最晚出栈(输出)
ansReversed.push(head.val)

// 负负得正
// 第一个负,指的是先序遍历中,stack 需要反着入栈
// 第二个负,指的是我们后面对 postStack 再次取反
// 第二个负,指的是我们后面对 ansReversed 再次出栈
// 所以这里需要再负一次,结果就变成了正!
if (head.left) stack.push(head.left)
if (head.right) stack.push(head.right)
}
return postStack.toReversed()
}
```
```js
/**
* @param {TreeNode} root
* @return {number[]}
*/
var postorderTraversal = function(root) {

const reversedAns = []
const stack = [root]
while (stack.length > 0) {
const head = stack.pop()
if (!head) continue

// 后序,要的是 左右头
// 所以我们的 reversedAns 中应该存储 头右左
reversedAns.push(head.val)
// 这里是放入栈中,我们需要右左,所以放入时应该是左右
stack.push(head.left)
stack.push(head.right)
}

// 出栈后返回的结果就是后序遍历结果 左右头
return reversedAns.toReversed()

}
ansReversed.reverse()
return ansReversed
// return ansReversed.toReversed() 该 api 较新
};
```
:::

Expand Down Expand Up @@ -273,26 +232,31 @@ def in_order_binary_tree(root, print_arr):
# 同时“头”给出右子节点的位置
p = p.right
```
```js
function inorderTraversal(root) {
const inorder = []
```ts
function inorderTraversal(root: TreeNode | null): number[] {
const ans = []
// 中序遍历,先左后头,那就是不断压左
const stack = []
let p = root
while (stack.length > 0 || p) {
// 2. 因为在添加之前,我们已经进行了判断
if (p) {
// 不断压左
stack.push(p)
p = p.left
} else {
// 1. 我们这里保证 head 不为 null
const head = stack.pop()
inorder.push(head.val)
// 此时,cur 既是左,也是头
// 1. 并且保证 cur 非空
const cur = stack.pop()
ans.push(cur.val)

p = head.right
// 最后才是右
p = cur.right
}
}
return inorder
}
return ans

};
```
:::

Expand Down
Loading