├── .vscode
└── settings.json
├── .DS_Store
├── README.md
├── docs
├── .vitepress
│ ├── cache
│ │ └── deps
│ │ │ ├── package.json
│ │ │ ├── _metadata.json
│ │ │ ├── @theme_index.js.map
│ │ │ └── @theme_index.js
│ └── config.ts
├── algorithm
│ ├── sort
│ │ ├── countSort.md
│ │ ├── quickSort.md
│ │ ├── selectionSort.md
│ │ ├── mergeSort.md
│ │ ├── heapSort.md
│ │ ├── insertSort.md
│ │ ├── shellSort.md
│ │ └── bubbleSort.md
│ ├── math.md
│ ├── 链表
│ │ ├── 反转链表.md
│ │ ├── 移除链表元素.md
│ │ ├── 删除链表的倒数第n个结点.md
│ │ ├── k个一组翻转链表.md
│ │ ├── 相交链表.md
│ │ └── 环形链表II.md
│ ├── 字符串
│ │ ├── 反转字符串II.md
│ │ ├── 最长不含重复字符的子字符串.md
│ │ ├── 千位分隔数.md
│ │ ├── 字符串的排列.md
│ │ ├── 回文系列.md
│ │ └── 最小覆盖子串.md
│ ├── 回溯
│ │ ├── 总结.md
│ │ ├── 子集.md
│ │ ├── 子集II.md
│ │ ├── 字符串的排列.md
│ │ ├── 组合总和.md
│ │ ├── N皇后.md
│ │ ├── 回溯分割.md
│ │ └── 回溯排列.md
│ ├── 动态规划
│ │ ├── 动态规划理论基础.md
│ │ ├── 背包系列.md
│ │ ├── 爬楼梯.md
│ │ ├── 连续.md
│ │ ├── 不同路径.md
│ │ ├── 买卖股票的最佳时机.md
│ │ └── 打家劫舍系列.md
│ ├── 数组
│ │ ├── 两数之和.md
│ │ ├── 长度最小的子数组.md
│ │ ├── 合并两个有序数组.md
│ │ └── 双指针.md
│ ├── 二叉树
│ │ ├── 二叉树其他题目.md
│ │ ├── 二叉树的公共祖先.md
│ │ ├── 二叉树的遍历方式.md
│ │ ├── 二叉树的修改与构造2.md
│ │ ├── 求二叉搜索树的属性.md
│ │ ├── 二叉树的修改与构造.md
│ │ └── 二叉树的属性.md
│ ├── 深度遍历
│ │ ├── 岛屿数量.md
│ │ ├── 单词搜索.md
│ │ └── 螺旋矩阵.md
│ ├── bfs-dfs.md
│ ├── binarySearch.md
│ ├── 贪心算法
│ │ └── 贪心入门.md
│ └── index.md
├── network-protocol
│ ├── cdn.md
│ ├── index.md
│ ├── 12.http优缺点.md
│ ├── 06.tcp和udp的区别.md
│ ├── 15.https的tsl连接过程.md
│ ├── 13.http队头阻塞.md
│ ├── 01.网络模型.md
│ ├── dns.md
│ ├── 07.http报文结构.md
│ ├── 05.tcp中syn攻击.md
│ ├── 02.tcp报文.md
│ ├── 10.http请求体和请求头.md
│ ├── 09.http状态码.md
│ ├── 18.http2剖析.md
│ ├── 04.tcp四次挥手.md
│ ├── 03.tcp三次握手.md
│ ├── 11.cookie.md
│ ├── 08.http的请求方法.md
│ ├── 17.http2新功能.md
│ ├── 16.https证书.md
│ ├── 14.https改进了什么.md
│ ├── 20.http3.md
│ └── 19.http2服务器推送功能.md
├── index.md
└── ai
│ └── index.md
├── pnpm-workspace.yaml
├── .gitignore
├── package.json
├── .github
└── workflows
│ └── deploy.yml
└── quick-push.py
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | }
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alvin0216/note/HEAD/.DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hello World
2 |
3 | Hi, I'm Alvin, a front-end developer.
4 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "module"
3 | }
4 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'code/*'
3 | # all packages in direct subdirs of packages/
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | yarn.lock
3 | yarn.error
4 | /public
5 | .cache
6 | dist
7 | /docs/.vitepress/cache
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "@vitejs/plugin-vue-jsx": "^3.0.1",
4 | "vitepress": "1.0.0-alpha.61"
5 | },
6 | "scripts": {
7 | "dev": "vitepress dev docs",
8 | "build": "vitepress build docs",
9 | "push": "node push.js"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/docs/algorithm/sort/countSort.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 基数排序
3 | date: 2020-05-19 15:05:55
4 | sidebar: auto
5 | tags:
6 | - 算法与数据结构
7 | - 排序算法
8 | categories:
9 | - 算法与数据结构
10 | ---
11 |
12 | 动画来源
13 |
14 | ---
15 |
16 | - [图解面试算法](https://github.com/MisterBooo/LeetCodeAnimation)
17 |
--------------------------------------------------------------------------------
/docs/algorithm/math.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 数学推算
3 | date: 2020-05-24 23:37:51
4 | sidebar: auto
5 | tags:
6 | - 数学推算
7 | categories:
8 | - 算法与数据结构
9 | ---
10 |
11 | ## 求和
12 |
13 | 求和公式 1+2+…+n
14 |
15 | ```js
16 | ((1 + n) * n) / 2;
17 | ```
18 |
19 | 1+2+3+4+5 = (1 + 5) + (2 + 4) + 3 =....
20 |
--------------------------------------------------------------------------------
/docs/network-protocol/cdn.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: CDN
3 | date: 2018-09-28 17:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - cdn
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | 
12 |
13 | - [高性能利器:CDN 我建议你好好学一下!](https://juejin.cn/post/7002781373014474759)
14 |
--------------------------------------------------------------------------------
/docs/algorithm/链表/反转链表.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 反转链表
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 链表
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ```js
12 | /**
13 | * @param {ListNode} head
14 | * @return {ListNode}
15 | */
16 | var reverseList = function (head) {
17 | let cur = head;
18 | let prev = null;
19 |
20 | while (cur) {
21 | const next = cur.next;
22 | cur.next = prev;
23 | prev = cur;
24 | cur = next;
25 | }
26 |
27 | return prev;
28 | };
29 | ```
30 |
--------------------------------------------------------------------------------
/docs/network-protocol/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: HTTP 脑图
3 | date: 2020-12-16 20:09:24
4 | sidebar: 'auto'
5 | tags:
6 | - http
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | 图片来源 [六张图从 HTTP/0.9 进化到 HTTP3.0](https://juejin.im/post/6856036933723521032?utm_source=gold_browser_extension)
12 |
13 | ## HTTP1.1
14 |
15 | 
16 |
17 | ## HTTP2
18 |
19 | 
20 |
21 | ## HTTP3
22 |
23 | 
24 |
--------------------------------------------------------------------------------
/docs/network-protocol/12.http优缺点.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 简要概括一下 HTTP 的优缺点?
3 | date: 2018-09-17 13:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - http
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | ## HTTP 优点
12 |
13 | 1. 灵活可扩展:不仅仅可以传输文本,还能传输图片、视频等任意数据,非常方便。
14 | 2. 可靠传输:HTTP 基于 TCP/IP,因此把这一特性继承了下来。这属于 TCP 的特性,不具体介绍了。
15 | 3. 请求-应答:也就是一发一收、有来有回,
16 | 4. 无状态:这服务器没有状态差异,可以很容易地组成集群。
17 |
18 | ## HTTP 缺点
19 |
20 | 1. 无状态:无法记录请求的对象信息,为此 cookie 存在的意义。。
21 | 2. 明文传输:容易被抓包,窃取、篡改、冒充。
22 | 3. 队头阻塞问题:由 HTTP 基本的“请求 - 应答”模型所导致的,因为 HTTP 规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。这个问题将
23 |
24 | 在如何解决 HTTP 的队头阻塞问题特殊讲解
25 |
--------------------------------------------------------------------------------
/docs/algorithm/字符串/反转字符串II.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 反转字符串II
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 字符串
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ```js
12 | /**
13 | * @param {string} s
14 | * @param {number} k
15 | * @return {string}
16 | */
17 | var reverseStr = function (s, k) {
18 | let arr = s.split('');
19 |
20 | for (let i = 0; i < s.length; i += 2 * k) {
21 | let y = Math.min(i + k, s.length) - 1;
22 | for (let x = i; x < y; x++, y--) {
23 | [arr[x], arr[y]] = [arr[y], arr[x]];
24 | }
25 | }
26 |
27 | return arr.join('');
28 | };
29 | ```
30 |
--------------------------------------------------------------------------------
/docs/algorithm/回溯/总结.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 回溯法总结
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 回溯算法
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | - [代码随想录](https://programmercarl.com/)
12 |
13 | ## 模板
14 |
15 | ```js
16 | result = [];
17 |
18 | function backtrack(路径, 选择列表) {
19 | if ('满足结束条件') {
20 | // 这里就是对答案做更新,依据实际题目出发
21 | result.push(路径);
22 | return;
23 | } else {
24 | for (let i = 0; i < 选择列表.length; i++) {
25 | // 对一个选择列表做相应的选择
26 |
27 | 做选择;
28 |
29 | backtrack(路径, 选择列表);
30 |
31 | // 既然是回溯算法,那么在一次分岔路做完选择后
32 | // 需要回退我们之前做的操作
33 |
34 | 撤销选择;
35 | }
36 | }
37 | }
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/algorithm/链表/移除链表元素.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 移除链表元素
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 链表
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ```js
12 | /**
13 | * @param {ListNode} head
14 | * @param {number} val
15 | * @return {ListNode}
16 | */
17 | var removeElements = function (head, val) {
18 | let prev = new ListNode(undefined, head); //
19 | let cur = prev;
20 | // 我们以下个节点的值作为评判标准
21 | // 命中 则 cur.next = cur.next.next
22 | // 所以我们可以构造前置节点 来排除头节点的情况
23 | while (cur.next) {
24 | if (cur.next.val === val) cur.next = cur.next.next;
25 | else cur = cur.next;
26 | }
27 |
28 | return prev.next;
29 | };
30 | ```
31 |
--------------------------------------------------------------------------------
/docs/network-protocol/06.tcp和udp的区别.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: TCP 和 UDP 的区别概述
3 | date: 2018-09-28 15:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - tcp
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | - `TCP` 是一个面向连接的、可靠的、基于字节流的传输层协议。
12 | - 而 `UDP` 是一个面向无连接的传输层协议。
13 |
14 | 1. **面向连接**。所谓的连接,指的是客户端和服务器的连接,在双方互相通信之前,TCP 需要三次握手建立连接,而 UDP 没有相应建立连接的过程。
15 | 2. **可靠性**。TCP 花了非常多的功夫保证连接的可靠,这个可靠性体现在哪些方面呢?一个是有状态,另一个是可控制。
16 |
17 | TCP 会精准记录哪些数据发送了,哪些数据被对方接收了,哪些没有被接收到,而且保证数据包按序到达,不允许半点差错。这是**有状态**。
18 |
19 | 当意识到丢包了或者网络环境不佳,TCP 会根据具体情况调整自己的行为,控制自己的发送速度或者重发。这是**可控制**。
20 |
21 | 相应的,UDP 就是无状态, 不可控的。
22 |
23 | 3. **面向字节流**。UDP 的数据传输是基于数据报的,这是因为仅仅只是继承了 IP 层的特性,而 TCP 为了维护状态,将一个个 IP 包变成了字节流。
24 |
--------------------------------------------------------------------------------
/docs/algorithm/链表/删除链表的倒数第n个结点.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 删除链表的倒数第n个结点
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 链表
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ```js
12 | /**
13 | * @param {ListNode} head
14 | * @param {number} n
15 | * @return {ListNode}
16 | */
17 | var removeNthFromEnd = function (head, n) {
18 | let fast = head;
19 | while (n-- && fast) {
20 | fast = fast.next;
21 | }
22 |
23 | let prev = new ListNode(undefined, head);
24 | let slow = prev;
25 |
26 | while (fast) {
27 | fast = fast.next;
28 | slow = slow.next;
29 | }
30 |
31 | slow.next = slow.next.next;
32 |
33 | return prev.next;
34 | };
35 | ```
36 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/_metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "hash": "f78c0ae0",
3 | "browserHash": "55079684",
4 | "optimized": {
5 | "vue": {
6 | "src": "../../../../node_modules/.pnpm/vue@3.4.27/node_modules/vue/dist/vue.runtime.esm-bundler.js",
7 | "file": "vue.js",
8 | "fileHash": "f7341a93",
9 | "needsInterop": false
10 | },
11 | "@theme/index": {
12 | "src": "../../../../node_modules/.pnpm/vitepress@1.0.0-alpha.61_@a_b03ddc81f2f192b764cbb197b970210d/node_modules/vitepress/dist/client/theme-default/index.js",
13 | "file": "@theme_index.js",
14 | "fileHash": "3a6b41bf",
15 | "needsInterop": false
16 | }
17 | },
18 | "chunks": {}
19 | }
--------------------------------------------------------------------------------
/docs/algorithm/sort/quickSort.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 快速排序(基准)
3 | date: 2020-05-19 15:05:55
4 | sidebar: auto
5 | tags:
6 | - 算法与数据结构
7 | - 排序算法
8 | categories:
9 | - 算法与数据结构
10 | ---
11 |
12 | 和归并一样,采用分治思想。不同的是,快排不是将数组一分为二,而是采用一个 “基准” 值。
13 |
14 | 也即 `[...rec(left), 基准, ...rec(right)]`,算法复杂度 $O(nlog(n))$
15 |
16 | ```js
17 | function quickSort(arr) {
18 | let len = arr.length;
19 | if (len < 2) return arr; // 临界条件
20 |
21 | const pivot = arr[0];
22 | const left = [];
23 | const right = [];
24 |
25 | // 分治
26 | for (let i = 1; i < len; i++) {
27 | arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i]);
28 | }
29 |
30 | return [...quickSort(left), pivot, ...quickSort(right)];
31 | }
32 | ```
33 |
--------------------------------------------------------------------------------
/docs/algorithm/动态规划/动态规划理论基础.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 动态规划理论基础
3 | date: 2022-03-31 20:56:00
4 | sidebar: auto
5 | tags:
6 | - 动态规划
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | 个人理解动态规划是递推公式,是后面的结果需要依赖前面的计算结果才能成立。
12 |
13 | 举个例子 [打家劫舍 I](./打家劫舍系列.md)
14 |
15 | 求小偷偷到最大金额。
16 |
17 | ```js
18 | [1, 2, 3, 4];
19 | // 只有 1 间房可以偷: 1
20 | // 只有 2 间房可以偷: Math.max(1, 2)
21 | // 只有 3 间房可以偷: 1 + 3 > 2 => 4
22 | // 第 i 间房:dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i])
23 | // Math.max(隔壁那间房,相邻那件房 + 现在这间房)
24 | ```
25 |
26 | 递推得公式:
27 |
28 | ```js
29 | var rob = function (nums) {
30 | let dp = [nums[0], Math.max(nums[0], nums[1])];
31 | let len = nums.length;
32 | for (let i = 2; i < len; i++) {
33 | dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
34 | }
35 | return dp[len - 1];
36 | };
37 | ```
38 |
--------------------------------------------------------------------------------
/docs/algorithm/动态规划/背包系列.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 背包系列
3 | date: 2022-06-05 12:32:19
4 | sidebar: auto
5 | tags:
6 | - 动态规划
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ## 416. 分割等和子集
12 |
13 | [力扣题目链接](https://leetcode-cn.com/problems/partition-equal-subset-sum/): 给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
14 |
15 | 注意: 每个数组中的元素不会超过 100,数组的大小不会超过 200
16 |
17 | ```js
18 | 示例 1:
19 | 输入: [1, 5, 11, 5]
20 | 输出: true
21 | 解释: 数组可以分割成 [1, 5, 5] 和 [11].
22 |
23 | 示例 2:
24 | 输入: [1, 2, 3, 5]
25 | 输出: false
26 | 解释: 数组不能分割成两个元素和相等的子集.
27 | ```
28 |
29 | 提示:
30 |
31 | - 1 <= nums.length <= 200
32 | - 1 <= nums[i] <= 100
33 |
34 | ### 思路
35 |
36 | 本题可以看成是 0-1 背包问题,给一个可装载重量为 `sum / 2` 的背包和 N 个物品,每个物品的重量记录在 nums 数组中,问是否在一种装法,能够恰好将背包装满?`dp[i][j]`表示前 i 个物品是否能装满容积为 j 的背包,当 `dp[i][j]`为 true 时表示恰好可以装满。每个数都有放入背包和不放入两种情况,分析方法和 0-1 背包问题一样。
37 |
--------------------------------------------------------------------------------
/docs/algorithm/链表/k个一组翻转链表.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: k个一组翻转链表
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 链表
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ```js
12 | /**
13 | * @param {ListNode} head
14 | * @param {number} k
15 | * @return {ListNode}
16 | */
17 | var reverseKGroup = function (head, k) {
18 | let p = head,
19 | len = 0;
20 | while (p) {
21 | p = p.next;
22 | len++;
23 | }
24 |
25 | function dfs(node, len) {
26 | if (len < k) return node;
27 |
28 | // 反转链表
29 | let prev = null,
30 | cur = node;
31 | for (let i = 0; i < k; i++) {
32 | let next = cur.next;
33 | cur.next = prev;
34 | prev = cur;
35 | cur = next;
36 | }
37 |
38 | node.next = dfs(cur, len - k);
39 | return prev;
40 | }
41 |
42 | return dfs(head, len);
43 | };
44 | ```
45 |
--------------------------------------------------------------------------------
/docs/network-protocol/15.https的tsl连接过程.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: tsl 握手的过程(1.2)
3 | date: 2018-09-28 13:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - https
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | > 最终是使用会话密钥的方式进行对称加密的密文传输,所以拿到服务器的公钥加密随机数,让两端生成绘话密钥尤为重要!
12 |
13 | 
14 |
15 | - web `- client hellow ->` server
16 | - 客户端:我生成一个随机数 A,还有我支持的加密套件有哪些给你知道,当前我用的是 tsl 1.2 版本
17 | - web `<- server hello -` server
18 | - 服务端:我也生成一个随机数 B,我挑了一个加密套件 咱们使用这个套件进行加密
19 | - web `<- server key exchange -` server
20 | - 服务端:我给你发个证书,里面有我的公钥,你可以用公钥加密,黑客破解不了。
21 | - web `<- server hello done -` server
22 | - 服务端:通知一下你 我完成啦
23 | - web `- client key exchange ->` server
24 | - 客户端:检查了一下你的证书是有效的,拿到了公钥。我在生成了一个随机数 C,用你给我的公钥加密 发给你了
25 | - 服务端:我拿到了第三个随机数,我们使用同样的加密方式生成我们的会话密钥。以后咱们使用这个密钥进行通信吧
26 | - 客户端:好的,会话密钥只应用于当前会话,咱们传输很安全了。
27 |
--------------------------------------------------------------------------------
/docs/algorithm/回溯/子集.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 子集
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 回溯算法
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | [力扣题目链接](https://leetcode-cn.com/problems/subsets/)
12 |
13 | 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
14 |
15 | 说明:解集不能包含重复的子集。
16 |
17 | ```js
18 | 输入: nums = [1, 2, 3];
19 | 输出: [[3], [1], [2], [1, 2, 3], [1, 3], [2, 3], [1, 2], []];
20 | ```
21 |
22 | ```js
23 | /**
24 | * @param {number[]} nums
25 | * @return {number[][]}
26 | */
27 | var subsets = function (nums) {
28 | let result = [];
29 | function backtrack(track, idx) {
30 | result.push([...track]);
31 |
32 | for (var i = idx; i < nums.length; i++) {
33 | track.push(nums[i]);
34 | backtrack(track, i + 1);
35 | track.pop();
36 | }
37 | }
38 |
39 | backtrack([], 0);
40 |
41 | return result;
42 | };
43 | ```
44 |
--------------------------------------------------------------------------------
/docs/network-protocol/13.http队头阻塞.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 如何解决 HTTP 的队头阻塞问题?
3 | date: 2018-09-15 13:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - http
7 | - 队头阻塞
8 | categories:
9 | - 网络协议
10 | ---
11 |
12 | ## 什么是队头阻塞
13 |
14 | **http1.x 采用长连接(Connection:keep-alive),可以在一个 TCP 请求上,发送多个 http 请求。**
15 |
16 | 因为 HTTP 规定报文必须是 “**一发一收**”,这就形成了一个先进先出的“串行”队列。
17 |
18 | 队列里的请求没有轻重缓急的优先级,只有入队的先后顺序,排在最前面的请求被最优先处理。
19 |
20 | 如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本。
21 |
22 | ## 并发连接
23 |
24 | 浏览器一个域名采用 `6-8` 个 TCP 连接,并发 `HTTP` 请求.
25 |
26 | 但其实,即使是提高了并发连接,还是不能满足人们对性能的需求。
27 |
28 | ## 域名分片
29 |
30 | 一个域名不是可以并发 6 个长连接吗?那我就多分几个域名。比如 content1.alvin.run 、content2.alvin.run。
31 |
32 | 这样一个 `alvin.run` 域名下可以分出非常多的二级域名,而它们都指向同样的一台服务器,能够并发的长连接数更多了,事实上也更好地解决了队头阻塞的问题。
33 |
34 | :::danger http1.1 没有真正解决了队头阻塞问题
35 |
36 | 即使使用上面的方式,也是治标不治本,http2 采用了**多路复用的方式**解决了这个问题,请看后续。
37 |
38 | :::
39 |
--------------------------------------------------------------------------------
/docs/algorithm/数组/两数之和.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 两数之和
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 哈希
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | [力扣题目链接](https://leetcode-cn.com/problems/two-sum/)
12 |
13 | 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
14 |
15 | 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
16 |
17 | **示例:**
18 |
19 | ```js
20 | 给定 nums = [2, 7, 11, 15], target = 9
21 |
22 | 因为 nums[0] + nums[1] = 2 + 7 = 9
23 |
24 | 所以返回 [0, 1]
25 | ```
26 |
27 | 非常简单,看代码吧:
28 |
29 | ```js
30 | /**
31 | * @param {number[]} nums
32 | * @param {number} target
33 | * @return {number[]}
34 | */
35 | var twoSum = function (nums, target) {
36 | let map = new Map();
37 | for (let i = 0; i < nums.length; i++) {
38 | if (map.has(target - nums[i])) {
39 | return [map.get(target - nums[i]), i];
40 | }
41 | map.set(nums[i], i);
42 | }
43 | return [];
44 | };
45 | ```
46 |
--------------------------------------------------------------------------------
/docs/algorithm/回溯/子集II.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 子集II
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 回溯算法
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | [力扣题目链接](https://leetcode-cn.com/problems/subsets-ii/)
12 |
13 | 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
14 |
15 | 说明:解集不能包含重复的子集。
16 |
17 | 示例:
18 |
19 | ```js
20 | - 输入: [1,2,2]
21 | - 输出: [[2], [1], [1, 2, 2], [2, 2], [1, 2], []];
22 | ```
23 |
24 | ```js
25 | /**
26 | * @param {number[]} nums
27 | * @return {number[][]}
28 | */
29 | var subsetsWithDup = function (nums) {
30 | let result = [];
31 | nums.sort((a, b) => a - b);
32 | function backtrack(track, idx) {
33 | result.push([...track]);
34 |
35 | for (var i = idx; i < nums.length; i++) {
36 | if (nums[i] === nums[i - 1] && i > idx) continue;
37 | track.push(nums[i]);
38 | backtrack(track, i + 1);
39 | track.pop();
40 | }
41 | }
42 |
43 | backtrack([], 0);
44 |
45 | return result;
46 | };
47 | ```
48 |
--------------------------------------------------------------------------------
/docs/algorithm/二叉树/二叉树其他题目.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 二叉树其他题目
3 | date: 2022-05-14 22:22:39
4 | sidebar: auto
5 | tags:
6 | - 二叉树
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ## 求根节点到叶节点数字之和
12 |
13 | [leetcode](https://leetcode.cn/problems/sum-root-to-leaf-numbers)
14 |
15 | 
16 |
17 | ```js
18 | 输入:root = [1,2,3]
19 | 输出:25
20 | 解释:
21 | 从根到叶子节点路径 1->2 代表数字 12
22 | 从根到叶子节点路径 1->3 代表数字 13
23 | 因此,数字总和 = 12 + 13 = 25
24 | ```
25 |
26 | ```js
27 | /**
28 | * @param {TreeNode} root
29 | * @return {number}
30 | */
31 | var sumNumbers = function (root) {
32 | if (!root) return 0;
33 | let sum = 0;
34 | function dfs(root, value) {
35 | if (!root) return;
36 | let v = `${value}${root.val}`;
37 |
38 | if (!root.left && !root.right) {
39 | sum += Number(v);
40 | return;
41 | }
42 | dfs(root.left, v);
43 | dfs(root.right, v);
44 | }
45 |
46 | dfs(root, '');
47 |
48 | return sum;
49 | };
50 | ```
51 |
--------------------------------------------------------------------------------
/docs/network-protocol/01.网络模型.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 网络模型
3 | date: 2018-09-28 18:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - http
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | 数据链路层(mac) -> 网络层(IP)-> 传输层(TCP/UDP)-> 应用层(HTTP、SMTP、FTP)
12 |
13 | 
14 |
15 | ## 数据链路层(Mac)
16 |
17 | 负责在以太网、WiFi 这样的底层网络上发送原始数据包,工作在网卡这个层次,使用 `MAC` 地址来标记网络上的设备,所以有时候也叫 `MAC` 层。
18 |
19 | ## 网络层(IP)
20 |
21 | IP 协议就处在这一层。因为 IP 协议定义了“IP 地址”的概念,所以就可以在“链接层”的基础上,用 IP 地址取代 MAC 地址,把许许多多的局域网、广域网连接成一个虚拟的巨大网络,在这个网络里找设备时只要把 IP 地址再“翻译”成 MAC 地址就可以了。
22 |
23 | ## 传输层(TCP/UDP)
24 |
25 | 传输层负责为两台主机中的进程提供通信服务,它使用 16 位的端口号来标识端口,当两个计算机中的进程要进行通讯时,除了要知道对方的 IP 地址外,还需要知道对方的端口。该层主要有以下两个协议:用户数据报协议(UDP,User Datagram Protocol)和传输控制协议(TCP,Transmission Control Protocol):
26 |
27 | ## 应用层(HTTP/SMTP/FTP...)
28 |
29 | 由于下面的三层把基础打得非常好,所以在这一层就“百花齐放”了,有各种面向具体应用的协议。例如 Telnet、SSH、FTP、SMTP 等等,当然还有我们的 HTTP。
30 |
31 | 相关链接 [详解 四层、五层、七层 计算机网络模型](https://juejin.im/post/6844904049800642568)
32 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v4
14 | with:
15 | fetch-depth: 0 # 获取所有历史记录以便更好的 git 信息
16 |
17 | - name: Setup pnpm
18 | uses: pnpm/action-setup@v4
19 | with:
20 | version: latest # 或指定版本如 8.x
21 |
22 | - name: Setup Node.js
23 | uses: actions/setup-node@v4
24 | with:
25 | node-version: '20' # 推荐使用 LTS 版本
26 | cache: 'pnpm'
27 |
28 | - name: Install dependencies
29 | run: pnpm install --filter .
30 |
31 | - name: Build
32 | run: pnpm run build
33 | working-directory: ./docs
34 |
35 | - name: Deploy
36 | uses: peaceiris/actions-gh-pages@v3
37 | with:
38 | github_token: ${{ secrets.GITHUB_TOKEN }}
39 | publish_dir: docs/.vitepress/dist
40 |
--------------------------------------------------------------------------------
/docs/algorithm/sort/selectionSort.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 选择排序
3 | date: 2020-05-19 15:05:55
4 | sidebar: auto
5 | tags:
6 | - 算法与数据结构
7 | - 排序算法
8 | categories:
9 | - 算法与数据结构
10 | ---
11 |
12 | 选择排序是外层循环,内循环找到最小或者最大的值的 index, 如果当前的元素不等于 index,则交换位置,算法复杂度 $O(n^2)$
13 |
14 | ```TS
15 | function swap(arr, i, j) {
16 | [arr[i], arr[j]] = [arr[j], arr[i]]
17 | }
18 |
19 | function selectionSort(arr) {
20 | for (let i = 0; i < arr.length; i++) {
21 | let minIndex = i
22 |
23 | for (let j = i + 1; j < arr.length; j++) {
24 | if (arr[minIndex] > arr[j]) {
25 | minIndex = j
26 | }
27 | }
28 |
29 | if (minIndex !== i) swap(arr, minIndex, i)
30 | }
31 | return arr
32 | }
33 |
34 |
35 | // test
36 | let arr = [4, 2, 3, 6, 5]
37 | console.log(selectionSort(arr)) // [ 2, 3, 4, 5, 6 ]
38 | ```
39 |
40 | 
41 |
42 | - 动画来源 [图解面试算法](https://github.com/MisterBooo/LeetCodeAnimation)
43 | - 参考 [优雅的 JavaScript 排序算法(ES6)](https://juejin.im/post/5ab62ec36fb9a028cf326c49)
44 |
--------------------------------------------------------------------------------
/docs/network-protocol/dns.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: DNS 解析
3 | date: 2018-09-28 17:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - dns
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | 
12 |
13 | ## 域名结构
14 |
15 | 就以 `mail.baidu.com` 域名为例,域名最后一个.的右侧部分我们称之为顶级域名,倒数第二个.右侧部分称之为二级域名,以此类推,就会有三级域名,四级域名等等。
16 |
17 | 在 `mail.baidu.com` 的域名中,`com` 成为顶级域名,`baidu.com` 称为二级域名,`mail.baidu.com` 称为三级域名。
18 |
19 | 域名由两个或两个以上的词组成,常见域名为二级域名+顶级域名组成,所以一般我们会将域名分为顶级域名、二级域名,除此之外,还有国家代码顶级域名。
20 |
21 | ## 查询顺序
22 |
23 | 现在我们来看看怎么去根据域名查询一台服务器的 IP 地址。
24 |
25 | 1. **检查浏览器缓存**中是否存在该域名与 IP 地址的映射关系,如果有则解析结束,没有则继续
26 | 2. 到系统本地查找映射关系,一般在 **hosts 文件**中,如果有则解析结束,否则继续
27 | 3. 到**本地域名服务器**去查询,有则结束,否则继续
28 | 4. **本地域名服务器查询根域名服务器**,该过程并不会返回映射关系,只会告诉你去下级服务器(顶级域名服务器)查询
29 | 5. **本地域名服务器查询顶级域名服务器**(即 com 服务器),同样不会返回映射关系,只会引导你去二级域名服务器查询
30 | 6. **本地域名服务器查询二级域名服务器**(即 `baidu.com` 服务器),引导去三级域名服务器查询
31 | 7. **本地域名服务器查询三级域名服务器**(即 mail.baidu.com 服务器),此时已经是最后一级了,如果有则返回映射关系,则本地域名服务器加入自身的映射表中,方便下次查询或其他用户查找,同时返回给该用户的计算机,没有找到则网页报错
32 |
33 | - [浏览器之 DNS 解析过程详解](https://juejin.cn/post/6909041150728863752)
34 |
--------------------------------------------------------------------------------
/docs/algorithm/回溯/字符串的排列.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 字符串的排列
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 回溯算法
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | [剑指 Offer 38. 字符串的排列](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/)
12 |
13 | ```js
14 | 输入:s = "abc"
15 | 输出:["abc","acb","bac","bca","cab","cba"]
16 | ```
17 |
18 | ```js
19 | /**
20 | * @param {string} s
21 | * @return {string[]}
22 | */
23 | var permutation = function (s) {
24 | let result = [];
25 | let visited = [];
26 |
27 | s = s.split('').sort((a, b) => a.charCodeAt() - b.charCodeAt());
28 |
29 | function backtrack(track) {
30 | if (track.length === s.length) {
31 | result.push([...track].join(''));
32 | return;
33 | }
34 |
35 | for (let i = 0; i < s.length; i++) {
36 | if (visited[i] || (!visited[i - 1] && s[i] === s[i - 1])) continue;
37 | track.push(s[i]);
38 | visited[i] = true;
39 | backtrack(track);
40 | track.pop();
41 | visited[i] = false;
42 | }
43 | }
44 |
45 | backtrack([]);
46 | return result;
47 | };
48 |
49 | console.log(permutation('abb'));
50 | ```
51 |
--------------------------------------------------------------------------------
/docs/algorithm/字符串/最长不含重复字符的子字符串.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 最长不含重复字符的子串
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 字符串
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ```js
12 | 输入: "abcabcbb"
13 | 输出: 3
14 | 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
15 |
16 | 输入: "bbbbb"
17 | 输出: 1
18 | 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
19 |
20 | 输入: "pwwkew"
21 | 输出: 3
22 | 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
23 | 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
24 | ```
25 |
26 | ## 思路
27 |
28 | 1. 这里用哈希 + 双指针。end++,记录每次字符出现的位置
29 | 2. 如果发现字符在 map 里面,表示当前重复了,那么需要更新 start 指针
30 |
31 | ## 代码
32 |
33 | ```js
34 | /**
35 | * @param {string} s
36 | * @return {number}
37 | */
38 | var lengthOfLongestSubstring = function (s) {
39 | let start = 0,
40 | end = 0,
41 | max = 0,
42 | map = new Map();
43 |
44 | while (end < s.length) {
45 | if (map.has(s[end])) {
46 | // 更新左指针
47 | // 因为上次 char 的记录 可能未更新,所以判断 l 指针每次都是向前的!
48 | start = Math.max(start, map.get(s[end]) + 1);
49 | }
50 | map.set(s[end], end);
51 | end++;
52 | max = Math.max(max, end - start);
53 | }
54 |
55 | return max;
56 | };
57 | ```
58 |
--------------------------------------------------------------------------------
/docs/network-protocol/07.http报文结构.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: HTTP 报文结构是怎样的?
3 | date: 2018-09-22 13:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - http
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | 对于 TCP 而言,在传输的时候分为两个部分: **TCP 头**和**数据部分**。
12 |
13 | 而 HTTP 类似,也是 `header + body` 的结构,具体而言:
14 |
15 | ```ts
16 | 起始行 + 头部 + 空行 + 实体;
17 | ```
18 |
19 | :::details
20 |
21 | 
22 |
23 | :::
24 |
25 | ## 起始行
26 |
27 | 对于请求报文来说:
28 |
29 | ```ts
30 | GET /home HTTP/1.1
31 | ```
32 |
33 | 也就是`方法` + `路径` + `http 版本`。
34 |
35 | 对于响应报文来说,起始行一般长这个样:
36 |
37 | ```ts
38 | HTTP/1.1 200 OK
39 | ```
40 |
41 | `http 版本` + `状态码` + `原因`。
42 |
43 | ## 头部
44 |
45 | 
46 |
47 | 不管是请求头还是响应头,其中的字段是相当多的,而且牵扯到 http 非常多的特性,这里就不一一列举的,重点看看这些头部字段的格式:
48 |
49 | - 字段名不区分大小写
50 | - 字段名不允许出现空格,不可以出现下划线 `_`
51 | - 字段名后面必须紧接着 `:`
52 |
53 | ## 空行
54 |
55 | `空行` 用于区分**头部**和**实体**
56 |
57 | :::warning 如果说在头部中间故意加一个空行会怎么样?
58 |
59 | 那么空行后的内容全部被视为实体。
60 |
61 | :::
62 |
63 | ## 实体
64 |
65 | 就是具体的数据了,也就是 body 部分。请求报文对应请求体, 响应报文对应响应体。
66 |
--------------------------------------------------------------------------------
/docs/algorithm/sort/mergeSort.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 归并排序(分治)
3 | date: 2020-05-19 15:05:55
4 | sidebar: auto
5 | tags:
6 | - 算法与数据结构
7 | - 排序算法
8 | categories:
9 | - 算法与数据结构
10 | ---
11 |
12 |
13 |
14 | 用到分治算法,将大数组二分为一为两个小数组,递归去比较排序。算法复杂度 $O(nlog(n))$
15 |
16 | ```js
17 | var mergeSort = function (arr) {
18 | const len = arr.length;
19 | if (len === 1) return arr;
20 | const mid = Math.floor(len / 2);
21 |
22 | // 分
23 | const left = mergeSort(arr.slice(0, mid));
24 | const right = mergeSort(arr.slice(mid));
25 |
26 | // 治
27 | let res = [];
28 | while (left.length > 0 || right.length > 0) {
29 | // shift 推出
30 | if (left.length > 0 && right.length > 0) {
31 | res.push(left[0] < right[0] ? left.shift() : right.shift());
32 | } else if (left.length > 0) {
33 | res.push(left.shift());
34 | } else {
35 | res.push(right.shift());
36 | }
37 | }
38 |
39 | return res;
40 | };
41 | ```
42 |
43 | - 动画来源 [图解面试算法](https://github.com/MisterBooo/LeetCodeAnimation)
44 | - 参考 [优雅的 JavaScript 排序算法(ES6)](https://juejin.im/post/5ab62ec36fb9a028cf326c49)
45 |
--------------------------------------------------------------------------------
/docs/network-protocol/05.tcp中syn攻击.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 半连接队列和 SYN Flood 攻击
3 | date: 2018-09-28 14:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - tcp
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | ## 半连接队列
12 |
13 | 三次握手时,服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。
14 |
15 | 当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。
16 |
17 | 这里在补充一点关于 **SYN-ACK 重传次数**的问题:
18 | 服务器发送完 `SYN-ACK` 包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。
19 | 注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s......
20 |
21 | ## SYN 攻击
22 |
23 | 服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到 `SYN 洪泛攻击`。
24 |
25 | :::warning SYN 攻击是一种典型的 DoS/DDoS 攻击。
26 | SYN 攻击就是 Client 在短时间内伪造大量不存在的 IP 地址,并向 Server 不断地发送 SYN 包,Server 则回复确认包,并等待 Client 确认,由于源地址不存在,因此 Server 需要不断重发直至超时,这些伪造的 SYN 包将长时间占用未连接队列,导致正常的 SYN 请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。
27 | :::
28 |
29 | 检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源 IP 地址是随机的,基本上可以断定这是一次 SYN 攻击。在 `Linux/Unix` 上可以使用系统自带的 `netstats` 命令来检测 SYN 攻击。
30 |
31 | ```bash
32 | netstat -n -p TCP | grep SYN_RECV
33 | ```
34 |
35 | 常见的防御 SYN 攻击的方法有如下几种:
36 |
37 | - 缩短超时(SYN Timeout)时间
38 | - 增加最大半连接数
39 | - 过滤网关防护
40 | - SYN cookies 技术
41 |
--------------------------------------------------------------------------------
/docs/algorithm/字符串/千位分隔数.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 千位分隔数
3 | date: 2022-05-14 17:40:58
4 | sidebar: auto
5 | tags:
6 | - 字符串
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | 给你一个整数 n,请你每隔三位添加点(即 "." 符号)作为千位分隔符,并将结果以字符串格式返回。
12 |
13 | ```js
14 | 输入:n = 987
15 | 输出:"987"
16 |
17 | 输入:n = 1234
18 | 输出:"1.234"
19 |
20 | 输入:n = 123456789
21 | 输出:"123.456.789"
22 |
23 | 输入:n = 0
24 | 输出:"0"
25 | ```
26 |
27 | 无情的 api 杀手!
28 |
29 | ```js
30 | /**
31 | * @param {number} n
32 | * @return {string}
33 | */
34 | var thousandSeparator = function (n) {
35 | return n.toLocaleString(3).replace(/,/g, '.');
36 | };
37 | ```
38 |
39 | 当然不,正规解法:
40 |
41 | ```js
42 | /**
43 | * @param {number} n
44 | * @return {string}
45 | */
46 | var thousandSeparator = function (n) {
47 | let s = n.toString();
48 | let j = s.length;
49 | let result = [];
50 | while (j - 3 > 0) {
51 | result.unshift(s.slice(j - 3, j));
52 | j -= 3;
53 | }
54 | // 前面的数 也要判断
55 | if (j > 0) result.unshift(s.slice(0, j));
56 |
57 | return result.join('.');
58 | };
59 | ```
60 |
61 | Better
62 |
63 | ```js
64 | function thousandSeparator(n: number): string {
65 | const rec = (v: string) => (v.length <= 3 ? v : rec(v.slice(0, -3)) + '.' + v.slice(-3));
66 | return rec(String(n));
67 | }
68 | ```
69 |
--------------------------------------------------------------------------------
/docs/algorithm/回溯/组合总和.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 组合总和
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 回溯算法
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | [力扣题目链接](https://leetcode-cn.com/problems/combination-sum/)
12 |
13 | 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
14 |
15 | candidates 中的数字可以无限制重复被选取。
16 |
17 | 说明:
18 |
19 | 所有数字(包括 target)都是正整数。
20 | 解集不能包含重复的组合。
21 |
22 | ```js
23 | 示例 1: 输入:candidates = [2,3,6,7], target = 7, 所求解集为: [ [7], [2,2,3] ]
24 |
25 | 示例 2: 输入:candidates = [2,3,5], target = 8, 所求解集为: [ [2,2,2,2], [2,3,3], [3,5] ]
26 | ```
27 |
28 | ```js
29 | /**
30 | * @param {number[]} candidates
31 | * @param {number} target
32 | * @return {number[][]}
33 | */
34 | var combinationSum = function (candidates, target) {
35 | let result = [];
36 |
37 | function backtrack(track, idx, sum) {
38 | if (sum === target) return result.push([...track]);
39 | if (sum > target) return;
40 |
41 | for (let i = idx; i < candidates.length; i++) {
42 | if (candidates[i] > target - sum) continue;
43 | track.push(candidates[i]);
44 | sum += candidates[i];
45 | backtrack(track, i, sum);
46 | sum -= candidates[i];
47 | track.pop();
48 | }
49 | }
50 |
51 | backtrack([], 0, 0);
52 | return result;
53 | };
54 | ```
55 |
--------------------------------------------------------------------------------
/docs/algorithm/字符串/字符串的排列.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 字符串的排列
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 字符串
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | [567. 字符串的排列](https://leetcode.cn/problems/permutation-in-string): 给你两个字符串 `s1` 和 `s2` ,写一个函数来判断 `s2` 是否包含 `s1` 的排列。如果是,返回 `true` ;否则,返回 `false` 。
12 |
13 | 换句话说,s1 的排列之一是 s2 的 子串 。
14 |
15 | ```js
16 | 输入:s1 = "ab" s2 = "eidbaooo"
17 | 输出:true
18 | 解释:s2 包含 s1 的排列之一 ("ba").
19 |
20 | 输入:s1= "ab" s2 = "eidboaoo"
21 | 输出:false
22 | ```
23 |
24 | ```js
25 | /**
26 | * @param {string} s1
27 | * @param {string} s2
28 | * @return {boolean}
29 | */
30 | var checkInclusion = function (s1, s2) {
31 | let map = {};
32 | for (let char of s1) {
33 | if (map[char]) map[char] += 1;
34 | else map[char] = 1;
35 | }
36 |
37 | let slider = {};
38 | let start = 0;
39 | let end = 0;
40 | while (end < s2.length) {
41 | let char = s2[end];
42 |
43 | if (slider[char]) slider[char] += 1;
44 | else slider[char] = 1;
45 |
46 | end++;
47 |
48 | // 调整窗口,判断左侧窗口是否要收缩
49 | if (end - start > s1.length) {
50 | slider[s2[start]]--;
51 | start++;
52 | }
53 |
54 | if (Object.entries(map).every(([key, value]) => slider[key] === value)) {
55 | return true;
56 | }
57 | }
58 |
59 | return false;
60 | };
61 | ```
62 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # layout: home
3 |
4 | title: VitePress
5 | titleTemplate: Vite & Vue Powered Static Site Generator
6 |
7 | hero:
8 | name: VitePress
9 | text: Vite & Vue Powered Static Site Generator
10 | tagline: Simple, powerful, and performant. Meet the modern SSG framework you've always wanted.
11 | actions:
12 | - theme: brand
13 | text: Get Started
14 | link: /home/accessibility
15 | - theme: alt
16 | text: View on GitHub
17 | link: https://github.com/vuejs/vitepress
18 | imgage:
19 | src: https://vitepress.dev/vitepress-logo-large.webp
20 |
21 | features:
22 | - title: "Vite: The DX that can't be beat"
23 | details: Feel the speed of Vite. Instant server start and lightning fast HMR that stays fast regardless of the app size.
24 | - title: Designed to be simplicity first
25 | details: With Markdown-centered content, it's built to help you focus on writing and deployed with minimum configuration.
26 | - title: Power of Vue meets Markdown
27 | details: Enhance your content with all the features of Vue in Markdown, while being able to customize your site with Vue.
28 | - title: Fully static yet still dynamic
29 | details: Go wild with true SSG + SPA architecture. Static on page load, but engage users with 100% interactivity from there.
30 | ---
31 |
32 | World's really stomping on your dreams, huh?
33 |
--------------------------------------------------------------------------------
/docs/algorithm/数组/长度最小的子数组.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 长度最小的子数组
3 | date: 2022-05-15 11:44:48
4 | sidebar: auto
5 | tags:
6 | - 数组
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | 给定一个含有 n 个正整数的数组和一个正整数 target 。
12 |
13 | 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
14 |
15 | ```js
16 | 输入:target = 7, nums = [2,3,1,2,4,3]
17 | 输出:2
18 | 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
19 |
20 | 输入:target = 4, nums = [1,4,4]
21 | 输出:1
22 |
23 | 输入:target = 11, nums = [1,1,1,1,1,1,1,1]
24 | 输出:0
25 | ```
26 |
27 | - 1 <= target <= 109
28 | - 1 <= nums.length <= 105
29 | - 1 <= nums[i] <= 105
30 |
31 | 进阶:
32 |
33 | 如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。
34 |
35 | ## O(n) 解法
36 |
37 | 从左往右滑; 满足条件了就压缩左边界,不满足条件就扩大右
38 |
39 | ```js
40 | /**
41 | * @param {number} target
42 | * @param {number[]} nums
43 | * @return {number}
44 | */
45 | var minSubArrayLen = function (target, nums) {
46 | let sum = 0;
47 | let i = 0,
48 | j = 0,
49 | min = 0;
50 |
51 | while (j < nums.length) {
52 | // 主旋律是扩张,找可行解
53 | sum += nums[j];
54 |
55 | // 间歇性收缩,优化可行解
56 | while (sum >= target) {
57 | if (min === 0) min = j - i + 1;
58 | else min = Math.min(min, j - i + 1);
59 | sum -= nums[i];
60 | i++;
61 | }
62 |
63 | j++;
64 | }
65 |
66 | return min;
67 | };
68 | ```
69 |
70 | ## O(n log(n))
71 |
--------------------------------------------------------------------------------
/docs/algorithm/深度遍历/岛屿数量.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 岛屿数量
3 | date: 2022-05-10 15:27:04
4 | sidebar: auto
5 | tags:
6 | - 深度遍历
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | [leetcode](https://leetcode.cn/problems/number-of-islands): 给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
12 |
13 | 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
14 |
15 | 此外,你可以假设该网格的四条边均被水包围。
16 |
17 | ```js
18 | 输入:grid = [
19 | ["1","1","1","1","0"],
20 | ["1","1","0","1","0"],
21 | ["1","1","0","0","0"],
22 | ["0","0","0","0","0"]
23 | ]
24 | 输出:1
25 |
26 | 输入:grid = [
27 | ["1","1","0","0","0"],
28 | ["1","1","0","0","0"],
29 | ["0","0","1","0","0"],
30 | ["0","0","0","1","1"]
31 | ]
32 | 输出:3
33 | ```
34 |
35 | ```js
36 | /**
37 | * @param {character[][]} grid
38 | * @return {number}
39 | */
40 | var numIslands = function (grid) {
41 | let count = 0;
42 | let [m, n] = [grid.length, grid[0].length];
43 |
44 | function toZero(i, j) {
45 | if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] === '0') return;
46 | grid[i][j] = '0';
47 | toZero(i + 1, j);
48 | toZero(i - 1, j);
49 | toZero(i, j + 1);
50 | toZero(i, j - 1);
51 | }
52 |
53 | for (let i = 0; i < m; i++) {
54 | for (let j = 0; j < n; j++) {
55 | if (grid[i][j] === '1') {
56 | count++;
57 | toZero(i, j);
58 | }
59 | }
60 | }
61 |
62 | return count;
63 | };
64 | ```
65 |
--------------------------------------------------------------------------------
/docs/algorithm/bfs-dfs.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: DFS & BFS
3 | date: 2020-05-24 23:37:51
4 | sidebar: auto
5 | tags:
6 | - 深度优先遍历
7 | - 广度优先遍历
8 | categories:
9 | - 算法与数据结构
10 | ---
11 |
12 | ## DFS 深度优先遍历 递归实现
13 |
14 | ```js
15 | /**
16 | * Definition for a binary tree node.
17 | * function TreeNode(val) {
18 | * this.val = val;
19 | * this.left = this.right = null;
20 | * }
21 | */
22 |
23 | // 3
24 | // / \
25 | // 1 4
26 | // \
27 | // 2
28 | // 输出: 1 2 3 4
29 |
30 | function dfs(node) {
31 | if (!node) return;
32 | node.left && dfs(node.left);
33 | console.log(node.val); // 中序遍历
34 | node.right && dfs(node.right);
35 | }
36 | ```
37 |
38 | ## DFS 深度优先遍历 栈实现
39 |
40 | ```js
41 | function dfs(root) {
42 | const stack = [];
43 | let nums = [];
44 | let current = root;
45 |
46 | while (current || stack.length > 0) {
47 | while (current) {
48 | stack.push(current);
49 | current = current.left;
50 | }
51 | current = stack.pop();
52 | nums.push(current.val);
53 | current = current.right;
54 | }
55 |
56 | console.log(nums); // [ 1, 2, 3, 4 ]
57 | }
58 | ```
59 |
60 | ## BFS 广度优先遍历 队列实现
61 |
62 | ```js
63 | // 3
64 | // / \
65 | // 1 4
66 | // \
67 | // 2
68 | // 输出: 3 1 4 2
69 | function bfs(root) {
70 | let queue = [root];
71 |
72 | while (queue.length) {
73 | const node = queue.shift();
74 | console.log(node.val); //
75 | node.left && queue.push(node.left);
76 | node.right && queue.push(node.right);
77 | }
78 | }
79 | ```
80 |
--------------------------------------------------------------------------------
/docs/algorithm/链表/相交链表.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 相交链表
3 | date: 2022-05-15 11:02:11
4 | sidebar: auto
5 | tags:
6 | - 链表
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
12 |
13 | 图示两个链表在节点 c1 开始相交:
14 |
15 | 
16 |
17 | 题目数据 保证 整个链式结构中不存在环。
18 |
19 | 注意,函数返回结果后,链表必须 保持其原始结构 。
20 |
21 | ## 哈希解法
22 |
23 | ```js
24 | /**
25 | * Definition for singly-linked list.
26 | * function ListNode(val) {
27 | * this.val = val;
28 | * this.next = null;
29 | * }
30 | */
31 |
32 | /**
33 | * @param {ListNode} headA
34 | * @param {ListNode} headB
35 | * @return {ListNode}
36 | */
37 | var getIntersectionNode = function (headA, headB) {
38 | // hash
39 | let set = new Set();
40 | let cur = headA;
41 | while (cur) {
42 | set.add(cur);
43 | cur = cur.next;
44 | }
45 | cur = headB;
46 | while (cur) {
47 | if (set.has(cur)) return cur;
48 | cur = cur.next;
49 | }
50 |
51 | return null;
52 | };
53 | ```
54 |
55 | ## 双指针
56 |
57 | 若相交,链表 A: `a+c`, 链表 B : `b+c`. `a+c+b+c = b+c+a+c` 。则会在公共处 `c` 起点相遇。若不相交,`a +b = b+a` 。因此相遇处是 NULL
58 |
59 | ```js
60 | pA:1->2->3->4->5->6->null->9->5->6->null
61 | pB:9->5->6->null->1->2->3->4->5->6->null
62 | ```
63 |
64 | ```js
65 | var getIntersectionNode = function (headA, headB) {
66 | if (!headA || !headB) return null;
67 |
68 | let pA = headA,
69 | pB = headB;
70 | while (pA !== pB) {
71 | pA = pA ? pA.next : headB;
72 | pB = pB ? pB.next : headA;
73 | }
74 | \
75 | return pA;
76 | };
77 | ```
78 |
--------------------------------------------------------------------------------
/docs/algorithm/链表/环形链表II.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 环形链表II
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 链表
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | [力扣题目链接](https://leetcode-cn.com/problems/linked-list-cycle-ii/)
12 |
13 | 题意:
14 | 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
15 |
16 | 为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
17 |
18 | **说明**:不允许修改给定的链表。
19 |
20 | 
21 |
22 | ## 思路
23 |
24 | 
25 |
26 | 指针 slow 每次走一步,fast 每次走两步,如果有环,则一定会出现相遇的情况,注意这里是要返回环链接的起始位置。那么可以进行推断。
27 |
28 | 第一次相遇时
29 |
30 | - `slow` 走了 `x + y`
31 | - `fast` 走了 `x + y + n(y + z)`, n 指的是 fast 走了 n 次环
32 |
33 | 指针 slow 每次走一步,fast 每次走两步,得到公式 `2(x + y) = x + y + n(y + z)` , 也即 `x= n(y + z) - y`
34 |
35 | 整理公式之后为如下公式: `x = (n - 1)(y + z) + z`
36 |
37 | 先拿 n 为 1 的情况来举例,意味着 fast 指针在环形里转了一圈之后,就遇到了 slow 指针了。
38 |
39 | 当 n 为 1 的时候,公式就化解为 x = z,
40 |
41 | 这就意味着,**从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点**。
42 |
43 | ## 代码
44 |
45 | ```js
46 | /**
47 | * @param {ListNode} head
48 | * @return {ListNode}
49 | */
50 | var detectCycle = function (head) {
51 | let slow = head;
52 | let fast = head;
53 |
54 | while (fast && fast.next) {
55 | fast = fast.next.next;
56 | slow = slow.next;
57 | if (fast === slow) {
58 | // 相遇,但这里要求的是环的起始节点
59 | let slow = head;
60 | // slow 重头继续走,和相遇过的 fast 指针一样,每次只走一步,肯定会再头节点相遇
61 | while (slow !== fast) {
62 | slow = slow.next;
63 | fast = fast.next;
64 | }
65 | return slow;
66 | }
67 | }
68 |
69 | return null;
70 | };
71 | ```
72 |
--------------------------------------------------------------------------------
/docs/network-protocol/02.tcp报文.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: TCP 报文结构
3 | date: 2018-09-28 13:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - tcp
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | TCP 报文是 TCP 层传输的数据单元,也叫报文段。
12 |
13 | 
14 |
15 | ## 源端口、目标端口
16 |
17 | 如何标识唯一标识一个连接?答案是 TCP 连接的四元组——源 **IP、源端口、目标 IP 和目标端口。**
18 |
19 | 那 TCP 报文怎么没有源 IP 和目标 IP 呢?这是因为在 IP 层就已经处理了 IP 。TCP 只需要记录两者的端口即可。
20 |
21 | ## 序列号
22 |
23 | 即 `Sequence number`, 指的是本报文段第一个字节的序列号。
24 |
25 | 序列号在 TCP 通信的过程中有两个作用:
26 |
27 | - 在 SYN 报文中交换彼此的初始序列号。
28 | - 保证数据包按正确的顺序组装。
29 |
30 | ## ISN
31 |
32 | 即 `Initial Sequence Number`(初始序列号),在三次握手的过程当中,双方会用过 SYN 报文来交换彼此的 ISN。
33 |
34 | ISN 并不是一个固定的值,而是每 4 ms 加一,溢出则回到 0,这个算法使得猜测 ISN 变得很困难。那为什么要这么做?
35 |
36 | 如果 ISN 被攻击者预测到,要知道源 IP 和源端口号都是很容易伪造的,当攻击者猜测 ISN 之后,直接伪造一个 RST 后,就可以强制连接关闭的,这是非常危险的。
37 |
38 | 而动态增长的 ISN 大大提高了猜测 ISN 的难度。
39 |
40 | ## 确认号
41 |
42 | 即 `ACK`(Acknowledgment number)。用来告知对方下一个期望接收的序列号,小于 ACK 的所有字节已经全部收到。
43 |
44 | ## 标记位
45 |
46 | 常见的标记位有 `SYN`,`ACK`,`FIN`,`RST`,`PSH`。
47 |
48 | `SYN` 和 `ACK` 已经在上文说过,后三个解释如下: FIN: 即 Finish,表示发送方准备断开连接。
49 |
50 | `RST`:即 `Reset`,用来强制断开连接。
51 |
52 | `PSH`: 即 `Push`, 告知对方这些数据包收到后应该马上交给上层的应用,不能缓存。
53 |
54 | ## 窗口大小
55 |
56 | 占用两个字节,也就是 16 位,但实际上是不够用的。因此 TCP 引入了窗口缩放的选项,作为窗口缩放的比例因子,这个比例因子的范围在 0 ~ 14,比例因子可以将窗口的值扩大为原来的 2 ^ n 次方。
57 |
58 | ## 校验和
59 |
60 | 占用两个字节,防止传输过程中数据包有损坏,如果遇到校验和有差错的报文,TCP 直接丢弃之,等待重传。
61 |
62 | ## 可选项
63 |
64 | 可选项的格式如下:
65 |
66 | 
67 |
68 | 常用的可选项有以下几个:
69 |
70 | - TimeStamp: TCP 时间戳,后面详细介绍。
71 | - MSS: 指的是 TCP 允许的从对方接收的最大报文段。
72 | - SACK: 选择确认选项。
73 | - Window Scale: 窗口缩放选项。
74 |
--------------------------------------------------------------------------------
/docs/algorithm/sort/heapSort.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 堆排序
3 | date: 2020-05-19 15:05:55
4 | sidebar: auto
5 | tags:
6 | - 算法与数据结构
7 | - 排序算法
8 | categories:
9 | - 算法与数据结构
10 | ---
11 |
12 | ## 简明解释
13 |
14 | 堆的定义
15 |
16 | 堆其实是一种特殊的树。只要满足这两点,它就是一个堆。
17 |
18 | 堆排序可以认为是选择排序的改进版,像选择排序一样将输入划分为已排序和待排序。
19 |
20 | 不一样的是堆排序利用堆这种近似完全二叉树的良好的数据结构来实现排序,本质上使用了二分的思想。
21 |
22 | 
23 |
24 | ## 算法步骤
25 |
26 | - 创建一个堆 H[0……n-1];
27 | - 把堆首(最大值)和堆尾互换;
28 | - 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
29 | - 重复步骤 2,直到堆的尺寸为 1。
30 |
31 | ## 基本实现
32 |
33 | ```js
34 | function heapSort(arr) {
35 | let size = arr.length;
36 |
37 | // 初始化堆,i 从最后一个父节点开始调整,直到节点均调整完毕
38 | for (let i = Math.floor(size / 2) - 1; i >= 0; i--) {
39 | heapify(arr, i, size);
40 | }
41 | // 堆排序:先将第一个元素和已拍好元素前一位作交换,再重新调整,直到排序完毕
42 | for (let i = size - 1; i > 0; i--) {
43 | swap(arr, 0, i);
44 | size -= 1;
45 | heapify(arr, 0, size);
46 | }
47 |
48 | return arr;
49 | }
50 |
51 | function heapify(arr, index, size) {
52 | let largest = index;
53 | let left = 2 * index + 1;
54 | let right = 2 * index + 2;
55 |
56 | if (left < size && arr[left] > arr[largest]) {
57 | largest = left;
58 | }
59 | if (right < size && arr[right] > arr[largest]) {
60 | largest = right;
61 | }
62 | if (largest !== index) {
63 | swap(arr, index, largest);
64 | heapify(arr, largest, size);
65 | }
66 | }
67 |
68 | // test
69 | const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
70 | console.log(heapSort(arr));
71 | ```
72 |
73 | ---
74 |
75 | - 动画来源 [图解面试算法](https://github.com/MisterBooo/LeetCodeAnimation)
76 | - 参考 [优雅的 JavaScript 排序算法(ES6)](https://juejin.im/post/5ab62ec36fb9a028cf326c49)
77 |
--------------------------------------------------------------------------------
/docs/algorithm/深度遍历/单词搜索.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 单词搜索
3 | date: 2022-05-17 16:21:09
4 | sidebar: auto
5 | tags:
6 | - 深度遍历
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ## 单词搜索
12 |
13 | 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
14 |
15 | 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
16 |
17 | 
18 |
19 | ```js
20 | 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
21 | 输出:true
22 | ```
23 |
24 | 
25 |
26 | ```js
27 | 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"
28 | 输出:true
29 | ```
30 |
31 | 
32 |
33 | ```js
34 | 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB"
35 | 输出:false
36 | ```
37 |
38 | ```js
39 | /**
40 | * @param {character[][]} board
41 | * @param {string} word
42 | * @return {boolean}
43 | */
44 | var exist = function (board, word) {
45 | let n = board.length,
46 | m = board[0].length;
47 |
48 | function dfs(i, j, word) {
49 | if (!word) return true;
50 | if (i < 0 || i >= n || j < 0 || j >= m) return false;
51 | let char = word[0];
52 | let next = word.slice(1);
53 | if (board[i][j] !== char) return false;
54 | board[i][j] = undefined;
55 | let res = dfs(i - 1, j, next) || dfs(i + 1, j, next) || dfs(i, j + 1, next) || dfs(i, j - 1, next);
56 | board[i][j] = char;
57 | return res;
58 | }
59 |
60 | for (let i = 0; i < n; i++) {
61 | for (let j = 0; j < m; j++) {
62 | if (dfs(i, j, word)) return true;
63 | }
64 | }
65 | return false;
66 | };
67 | ```
68 |
--------------------------------------------------------------------------------
/docs/algorithm/数组/合并两个有序数组.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 合并两个有序数组
3 | date: 2022-05-14 12:15:34
4 | sidebar: auto
5 | tags:
6 | - 数组
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | [leetcode](https://leetcode.cn/problems/merge-sorted-array): 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
12 |
13 | 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
14 |
15 | 最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n
16 |
17 | ```js
18 | 输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
19 | 输出:[1,2,2,3,5,6]
20 | 解释:需要合并 [1,2,3] 和 [2,5,6] 。
21 | 合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
22 |
23 | 输入:nums1 = [1], m = 1, nums2 = [], n = 0
24 | 输出:[1]
25 | 解释:需要合并 [1] 和 [] 。
26 | 合并结果是 [1] 。
27 |
28 | 输入:nums1 = [0], m = 0, nums2 = [1], n = 1
29 | 输出:[1]
30 | 解释:需要合并的数组是 [] 和 [1] 。
31 | 合并结果是 [1] 。
32 | 注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
33 | ```
34 |
35 | [题解](https://leetcode.cn/problems/merge-sorted-array/solution/ni-xiang-shuang-zhi-zhen-he-bing-liang-g-ucgj/)
36 |
37 | ---
38 |
39 | 思路 后面往前合并
40 |
41 | 
42 |
43 | ```js
44 | /**
45 | * @param {number[]} nums1
46 | * @param {number} m
47 | * @param {number[]} nums2
48 | * @param {number} n
49 | * @return {void} Do not return anything, modify nums1 in-place instead.
50 | */
51 | var merge = function (nums1, m, nums2, n) {
52 | let i = m - 1,
53 | j = n - 1,
54 | k = nums1.length - 1;
55 |
56 | while (i >= 0 && j >= 0) {
57 | if (nums1[i] > nums2[j]) nums1[k--] = nums1[i--];
58 | else nums1[k--] = nums2[j--];
59 | }
60 |
61 | // 到最后 nums2 还有数据,继续插入 nums1 前面
62 | while (j >= 0) {
63 | nums1[k--] = nums2[j--];
64 | }
65 | };
66 | ```
67 |
--------------------------------------------------------------------------------
/docs/network-protocol/10.http请求体和请求头.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 请求体以及 Accept
3 | date: 2018-09-18 13:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - http
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | ## 数据格式
12 |
13 | HTTP 它支持非常多的数据格式,那么这么多格式的数据一起到达客户端,客户端怎么知道它的格式呢?
14 |
15 | 具体体现在 `MIME`(Multipurpose Internet Mail Extensions, 多用途互联网邮件扩展)。它首先用在电子邮件系统中,让邮件可以发任意类型的数据,这对于 HTTP 来说也是通用的。
16 |
17 | 因此,HTTP 从 `MIME type` 取了一部分来标记报文 body 部分的数据类型,这些类型体现在 `Content-Type` 这个字段,当然这是针对于发送端而言,接收端想要收到特定类型的数据,也可以用 `Accept` 字段。
18 |
19 | 具体而言,这两个字段的取值可以分为下面几类:
20 |
21 | - `text`: text/html, text/plain, text/css 等
22 | - `image`: image/gif, image/jpeg, image/png 等
23 | - `audio/video`: audio/mpeg, video/mp4 等
24 | - `application`: application/json, application/javascript, application/pdf, application/octet-stream
25 |
26 | ## 压缩方式
27 |
28 | 当然一般这些数据都是会进行编码压缩的,采取什么样的压缩方式就体现在了发送方的 `Content-Encoding` 字段上, 同样的,接收什么样的压缩方式体现在了接受方的 `Accept-Encoding` 字段上。这个字段的取值有下面几种:
29 |
30 | - gzip:GNU zip 压缩格式,也是互联网上最流行的压缩格式;
31 | - deflate:zlib(deflate)压缩格式,流行程度仅次于 gzip;
32 | - br:一种专门为 HTTP 优化的新压缩算法(Brotli)。
33 |
34 | ```js
35 | // 发送端
36 | Content-Encoding: gzip
37 | // 接收端
38 | Accept-Encoding: gzip
39 | ```
40 |
41 | ## 支持语言
42 |
43 | 对于发送方而言,还有一个 `Content-Language` 字段,在需要实现国际化的方案当中,可以用来指定支持的语言,在接受方对应的字段为 `Accept-Language`。如:
44 |
45 | ```js
46 | // 发送端
47 | Content-Language: zh-CN, zh, en
48 | // 接收端
49 | Accept-Language: zh-CN, zh, en
50 | ```
51 |
52 | ## 字符集
53 |
54 | 最后是一个比较特殊的字段, 在接收端对应为 `Accept-Charset`,指定可以接受的字符集,而在发送端并没有对应的 `Content-Charset`, 而是直接放在了 `Content-Type` 中,以 `charset` 属性指定。如:
55 |
56 | ```js
57 | // 发送端
58 | Content-Type: text/html; charset=utf-8
59 | // 接收端
60 | Accept-Charset: charset=utf-8
61 | ```
62 |
63 | 最后以一张图来总结一下吧:
64 |
65 | 
66 |
--------------------------------------------------------------------------------
/docs/algorithm/二叉树/二叉树的公共祖先.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 二叉树的公共祖先
3 | date: 2022-05-13 22:20:58
4 | sidebar: auto
5 | tags:
6 | - 二叉树
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ## 二叉树的最近公共祖先
12 |
13 | [leetcode](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree)给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
14 |
15 | 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
16 |
17 | 
18 |
19 | ```js
20 | 输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
21 | 输出:3
22 | 解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
23 |
24 | 输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
25 | 输出:5
26 | 解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
27 |
28 | 输入:root = [1,2], p = 1, q = 2
29 | 输出:1
30 | ```
31 |
32 | ---
33 |
34 | 思考:中序遍历 有公共节点 就会有路径重复的情况...
35 |
36 | ```js
37 | /**
38 | * @param {TreeNode} root
39 | * @param {TreeNode} p
40 | * @param {TreeNode} q
41 | * @return {TreeNode}
42 | */
43 | var lowestCommonAncestor = function (root, p, q) {
44 | // 当前节点就是 p 或者 q 直接返回
45 | if (!root || root === p || root === q) return root;
46 |
47 | // 都不是,继续递归
48 | let left = lowestCommonAncestor(root.left, p, q);
49 | let right = lowestCommonAncestor(root.right, p, q);
50 |
51 | // 当前节点为公共节点
52 | if (left && right) return root;
53 |
54 | // 返回其中一个节点
55 | return left || right;
56 | };
57 | ```
58 |
59 | ## 二叉搜索树的最近公共祖先
60 |
61 | ```js
62 | var lowestCommonAncestor = function (root, p, q) {
63 | if (!root || root === p || root === q) return root;
64 | // 都不是,继续递归
65 | let left = lowestCommonAncestor(root.left, p, q);
66 | let right = lowestCommonAncestor(root.right, p, q);
67 |
68 | // 当前节点为公共节点
69 | if (left && right) return root;
70 |
71 | // 返回其中一个节点
72 | return left || right;
73 | };
74 | ```
75 |
--------------------------------------------------------------------------------
/docs/ai/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: AI
3 | date: 2025-12-15 12:32:19
4 | sidebar: auto
5 | tags:
6 | - AI
7 | categories:
8 | - AI
9 | ---
10 |
11 | ### Awesome
12 |
13 | https://mmh1.top/#/ai-tutorial
14 |
15 | 理解模型的核心概念确实是实践前的重要一步。下面我用一个表格来清晰解释你提到的这几个关键术语。
16 |
17 | ### 📖 核心概念解析
18 |
19 | | 概念 | 核心定义 | 类比解释 | 关键原因/意义 | 关联实践 |
20 | | :----------------------- | :----------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- |
21 | | **大模型 (LLM)** | 参数量巨大(通常数十亿以上)、基于海量数据训练、能处理多种复杂任务的自然语言处理模型。 | 像一个**博览群书的超级学者**,通识能力极强,能回答广泛问题、写作、编程等。 | 其涌现能力(未经专门训练就能完成新任务)是 AI 发展的突破,成为当前 AI 应用的基础。 | 直接使用(提问/对话),或作为基座模型进行**微调**。 |
22 | | **微调 (Fine-tuning)** | 在预训练好的**大模型**基础上,用特定领域的小规模数据继续训练,使其适应专门任务。 | 让“超级学者”**攻读一个专业学位**(如法律、医疗),成为该领域的专家。 | 以较低成本让通用模型获得专业能力,是定制化 AI 应用的主要方法。 | 需要准备领域数据,使用框架(如 PyTorch)或平台(如 Hugging Face)进行。 |
23 | | **过拟合 (Overfitting)** | 模型在训练数据上表现完美,但在未见过的测试数据上表现很差,即“**学得太死,不会举一反三**”。 | 学生**死记硬背了所有习题答案**,但考题稍一变化就不会做。 | 是模型训练中的核心挑战,衡量模型是否真正学会了“规律”而非“记忆”。 | 通过划分训练/验证集、早停、正则化、数据增强等技术来避免。 |
24 | | **Transformer** | 一种基于**自注意力机制**的神经网络架构,是现代**大模型**(如 GPT、BERT)的**核心引擎**。 | 像一个**超级高效的阅读理解系统**,能同时权衡句子中所有词之间的关系,捕捉长远依赖。 | 解决了传统模型(如 RNN)处理长文本的瓶颈,并行计算效率高,成为大模型的基石。 | 理解其结构是深入 NLP 的关键;实际中我们直接调用基于它构建的模型(如 GPT)。 |
25 |
--------------------------------------------------------------------------------
/docs/algorithm/回溯/N皇后.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: N皇后
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 回溯算法
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | [力扣题目链接](https://leetcode-cn.com/problems/n-queens/)
12 |
13 | n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
14 |
15 | 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
16 |
17 | 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
18 |
19 | 示例 1:
20 |
21 | 
22 |
23 | ```js
24 | - 输入:n = 4
25 | - 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
26 | - 解释:如上图所示,4 皇后问题存在两个不同的解法。
27 | ```
28 |
29 | 示例 2:
30 |
31 | ```js
32 | - 输入:n = 1
33 | - 输出:[["Q"]]
34 | ```
35 |
36 | ```js
37 | /**
38 | * @param {number} n
39 | * @return {string[][]}
40 | */
41 | var solveNQueens = function (n) {
42 | let result = [];
43 |
44 | // 初始化棋盘
45 | let chessBoard = new Array(n).fill([]).map(() => new Array(n).fill('.'));
46 |
47 | function backtrack(chessBoard, row) {
48 | if (row === n) {
49 | // end 终止条件
50 |
51 | result.push(chessBoard.map((item) => item.join(''))); // 推入res数组
52 | return;
53 | }
54 |
55 | for (let col = 0; col < n; col++) {
56 | // isVaild
57 | if (!isVaild(chessBoard, row, col)) continue;
58 |
59 | chessBoard[row][col] = 'Q';
60 | backtrack(chessBoard, row + 1);
61 | chessBoard[row][col] = '.';
62 | }
63 | }
64 |
65 | // 皇后不能处于同一行 同一列 统一斜角上!
66 | function isVaild(chessBoard, row, col) {
67 | // 之前的行
68 | for (let i = 0; i < row; i++) {
69 | // 所有的列
70 | for (let j = 0; j < n; j++) {
71 | if (
72 | chessBoard[i][j] == 'Q' && // 发现了皇后,并且和自己同列/对角线
73 | (j === col || i === row || i + j === row + col || i - j === row - col)
74 | ) {
75 | return false; // 不是合法的选择
76 | }
77 | }
78 | }
79 | return true;
80 | }
81 |
82 | backtrack(chessBoard, 0);
83 | console.log(result);
84 | return result;
85 | };
86 | ```
87 |
--------------------------------------------------------------------------------
/quick-push.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | from datetime import datetime
3 | import os
4 |
5 |
6 | def git_auto_commit_time():
7 | """
8 | 自动提交并附带时间戳的提交信息
9 | """
10 | try:
11 | # 检查是否有需要提交的更改
12 | result = subprocess.run(
13 | ["git", "status", "--porcelain"], capture_output=True, text=True
14 | )
15 |
16 | if not result.stdout.strip():
17 | print("📭 没有需要提交的更改")
18 | return True
19 |
20 | # 获取当前时间
21 | current_time = datetime.now()
22 |
23 | # 格式化时间选项(多种格式)
24 | time_formats = {
25 | "standard": current_time.strftime("%Y-%m-%d %H:%M:%S"),
26 | "compact": current_time.strftime("%Y%m%d_%H%M%S"),
27 | "readable": current_time.strftime("%b %d %Y, %I:%M %p"),
28 | "timestamp": str(int(current_time.timestamp())),
29 | }
30 |
31 | # 使用标准格式
32 | time_str = time_formats["standard"]
33 |
34 | # 获取当天提交次数(用于生成序列号)
35 | log_result = subprocess.run(
36 | ["git", "log", "--oneline", "--since=midnight", "--pretty=format:%s"],
37 | capture_output=True,
38 | text=True,
39 | )
40 |
41 | # 生成提交信息(带序号)
42 | commit_message = f"Auto commit at {time_str}"
43 |
44 | # 执行git操作
45 | print(f"🕒 提交时间: {time_str}")
46 | print(f"📝 提交信息: {commit_message}")
47 |
48 | subprocess.run(["git", "add", "."], check=True)
49 | subprocess.run(["git", "commit", "-m", commit_message], check=True)
50 |
51 | # 尝试推送
52 | push_result = subprocess.run(["git", "push"], capture_output=True, text=True)
53 |
54 | if push_result.returncode == 0:
55 | print("✅ 提交并推送成功")
56 | return True
57 | else:
58 | print("⚠️ 提交成功但推送失败")
59 | print(f" 错误信息: {push_result.stderr[:100]}...")
60 | return False
61 |
62 | except Exception as e:
63 | print(f"❌ 操作失败: {e}")
64 | return False
65 |
66 |
67 | # 使用示例
68 | if __name__ == "__main__":
69 | git_auto_commit_time()
70 |
--------------------------------------------------------------------------------
/docs/network-protocol/09.http状态码.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 如何理解 HTTP 状态码?
3 | date: 2018-09-20 13:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - http
7 | - 状态码
8 | categories:
9 | - 网络协议
10 | ---
11 |
12 | RFC 标准把状态码分成了五类
13 |
14 | - 1××:提示信息,表示目前是协议处理的中间状态,还需要后续的操作;
15 | - 2××:成功,报文已经收到并被正确处理;
16 | - 3××:重定向,资源位置发生变动,需要客户端重新发送请求;
17 | - 4××:客户端错误,请求报文有误,服务器无法处理;
18 | - 5××:服务器错误,服务器在处理请求时内部发生了错误。
19 |
20 | ## 1××
21 |
22 | 1×× 类状态码属于提示信息,是协议处理的中间状态,实际能够用到的时候很少。
23 |
24 | **101 Switching Protocols**。在 `HTTP` 升级为 `WebSocket` 的时候,如果服务器同意变更,就会发送状态码 101。
25 |
26 | ## 2××
27 |
28 | | 状态码 | 描述 |
29 | | ------------------- | ---------------------------------------------------------------------------------------------------- |
30 | | 200 OK | 是最常见的成功状态码,通常在响应体中放有数据。 |
31 | | 204 No Content | 表示成功,但是响应体中没有 body 数据。 |
32 | | 206 Partial Content | 表示部分内容,它的使用场景为 HTTP 分块下载和断电续传,当然也会带上相应的响应头字段 `Content-Range`。 |
33 |
34 | ## 3××
35 |
36 | | 状态码 | 描述 |
37 | | ------ | -------------------------- |
38 | | 301 | 永久重定向 |
39 | | 302 | 临时重定向,不会做缓存优化 |
40 | | 304 | 协商缓存 |
41 |
42 | ## 4xx
43 |
44 | | 状态码 | 描述 |
45 | | ------ | -------------------------------- |
46 | | 400 | 笼统的错误码 |
47 | | 401 | 用户没有访问权限 |
48 | | 403 | Forbidden 表示服务器禁止访问资源 |
49 | | 404 | 资源在本服务器上未找到 |
50 | | 405 | 请求方法不被服务器端允许 |
51 | | 409 | 多个请求发生了冲突 |
52 | | 413 | 请求体的数据过大 |
53 |
54 | ## 5××
55 |
56 | | 状态码 | 描述 |
57 | | ------ | -------------------------------------------------------- |
58 | | 500 | 笼统的错误码 |
59 | | 501 | 表示客户端请求的功能还不支持 |
60 | | 502 | 服务器自身是正常的,但访问的时候出错了,啥错误咱也不知道 |
61 | | 503 | 表示服务器当前很忙,暂时无法响应服务 |
62 |
--------------------------------------------------------------------------------
/docs/network-protocol/18.http2剖析.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: HTTP2 内核剖析
3 | date: 2018-09-23 13:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - http2
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | 下面的这张图对比了 HTTP/1、HTTPS 和 HTTP/2 的协议栈,你可以清晰地看到,HTTP/2 是建立在“HPack”“Stream”“TLS1.2”基础之上的,比 HTTP/1、HTTPS 复杂了一些。
12 |
13 | 
14 |
15 | ## 连接前言
16 |
17 | 由于 HTTP/2“事实上”是基于 TLS,所以在正式收发数据之前,会有 TCP 握手和 TLS 握手,这两个步骤相信你一定已经很熟悉了,所以这里就略过去不再细说。
18 |
19 | TLS 握手成功之后,客户端必须要发送一个“**连接前言**”(connection preface),用来确认建立 HTTP/2 连接。
20 |
21 | 这个“连接前言”是标准的 HTTP/1 请求报文,使用纯文本的 ASCII 码格式,请求方法是特别注册的一个关键字“PRI”,全文只有 24 个字节:
22 |
23 | ```js
24 | PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
25 | ```
26 |
27 | 只要服务器收到这个“有魔力的字符串”,就知道客户端在 TLS 上想要的是 HTTP/2 协议,而不是其他别的协议,后面就会都使用 HTTP/2 的数据格式。
28 |
29 | :::details nginx 开启 http2 也比较简单
30 |
31 | ```yaml
32 | server {
33 | listen 443 http2 ssl; # 443 端口 http2 ssl
34 | server_name alvin.run;
35 | #... 这是是上面的配置
36 | }
37 | ```
38 |
39 | :::
40 |
41 | ## 头部压缩 (略)
42 |
43 | 上文讲过
44 |
45 | ## 二进制帧
46 |
47 | HTTP/2 中传输的帧结构如下图所示
48 |
49 | 
50 |
51 | 每个帧分为**帧头**和**帧体**。先是三个字节的帧长度,这个长度表示的是**帧体**的长度。
52 |
53 | 然后是帧类型,大概可以分为**数据帧**和**控制帧**两种。数据帧用来存放 HTTP 报文,控制帧用来管理流的传输。
54 |
55 | 接下来的一个字节是**帧标志**,里面一共有 8 个标志位,常用的有 `END_HEADERS` 表示头数据结束,`END_STREAM` 表示单方向数据发送结束。
56 |
57 | 后 4 个字节是 `Stream ID`, 也就是**流标识符**,有了它,接收方就能从乱序的二进制帧中选择出 ID 相同的帧,按顺序组装成请求/响应报文。
58 |
59 | ### 流的状态变化
60 |
61 | 从前面可以知道,在 HTTP/2 中,**所谓的流,其实就是二进制帧的双向传输的序列**。那么在 HTTP/2 请求和响应的过程中,流的状态是如何变化的呢?
62 |
63 | HTTP/2 其实也是借鉴了 TCP 状态变化的思想,根据帧的标志位来实现具体的状态改变。这里我们以一个普通的请求-响应过程为例来说明:
64 |
65 | 
66 |
67 | 最开始两者都是空闲状态,当客户端发送 Headers 帧后,开始分配 `Stream ID`, 此时客户端的流打开, 服务端接收之后服务端的流也打开,两端的流都打开之后,就可以互相传递数据帧和控制帧了。
68 |
69 | 当客户端要关闭时,向服务端发送 `END_STREAM` 帧,进入半关闭状态, 这个时候客户端只能接收数据,而不能发送数据。
70 |
71 | 服务端收到这个 END_STREAM 帧后也进入半关闭状态,不过此时服务端的情况是只能发送数据,而不能接收数据。随后服务端也向客户端发送 END_STREAM 帧,表示数据发送完毕,双方进入关闭状态。
72 |
73 | 如果下次要开启新的流,流 ID 需要自增,直到上限为止,到达上限后开一个新的 TCP 连接重头开始计数。由于流 ID 字段长度为 4 个字节,最高位又被保留,因此范围是 0 ~ 2 的 31 次方,大约 21 亿个。
74 |
--------------------------------------------------------------------------------
/docs/network-protocol/04.tcp四次挥手.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: TCP 四次挥手的过程
3 | date: 2018-09-28 16:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - tcp
7 | categories:
8 | - 网络协议
9 | ---
10 |
11 | 四次挥手即终止 TCP 连接,就是指断开一个 TCP 连接时,需要客户端和服务端总共发送 4 个包以确认连接的断开。
12 |
13 | 
14 |
15 | ## 挥手过程
16 |
17 | 假设客户端发起关闭连接请求
18 |
19 | | 挥手 | 描述 |
20 | | :--: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21 | | 1️⃣ | 客户端发送 `FIN,ACK` 包给服务端,告诉他我要关闭请求了,此时进入 `FIN_WAIT` 状态 |
22 | | 2️⃣ | 服务端接收到关闭请求后,发送 `ACK` 包给客户端,告诉他我知道了。
但是我还不确定数据传输完了没,等我确定或者处理完,我这边就发个关闭请求包给你。
此时客户端进入 `FIN_WAIT2` 状态,服务端进入 `CLOSE_WAIT` 状态 |
23 | | 3️⃣ | 服务端传输完数据了,发送 `FIN,ACK` 给客户端,告诉他 我传输完数据了,你可以关闭了。 |
24 | | 4️⃣ | 客户端收到服务端的关闭请求包后,发送 `ACK` 包给服务端,告诉他我知道了,你可以关闭了。
服务端收到后,则进入 `CLOSED` 状态。
由于不确定服务端是否关闭了,客户端还需等待 `2MSL` 后才关闭连接 |
25 |
26 | ## 挥手为什么需要四次?
27 |
28 | 由于 TCP 协议是一种面向连接的、可靠的、基于字节流的运输层通信协议,TCP 是**全双工模式**。
29 |
30 | 这就意味着,关闭连接时,当 Client 端发出 FIN 报文段时,只是表示 Client 端告诉 Server 端数据已经发送完毕了。
31 |
32 | 当 Server 端收到 FIN 报文并返回 ACK 报文段,表示它已经知道 Client 端没有数据发送了,但是 Server 端还是可以发送数据到 Client 端的,所以 Server 很可能并不会立即关闭 SOCKET,直到 Server 端把数据也发送完毕。
33 |
34 | 当 Server 端也发送了 FIN 报文段时,这个时候就表示 Server 端也没有数据要发送了,就会告诉 Client 端,我也没有数据要发送了,之后彼此就会愉快的中断这次 TCP 连接。
35 |
36 | ## 四次挥手释放连接时,等待 2MSL 的意义?
37 |
38 | 如果不等待,客户端直接跑路,当服务端还有很多数据包要给客户端发,且还在路上的时候,若客户端的端口此时刚好被新的应用占用,那么就接收到了无用数据包,造成数据包混乱。所以,最保险的做法是等服务器发来的数据包都死翘翘再启动新的应用。
39 | 那,照这样说一个 MSL 不就不够了吗,为什么要等待 2 MSL?
40 |
41 | - **1 个 MSL 确保四次挥手中主动关闭方最后的 ACK 报文最终能达到对端**
42 | - **1 个 MSL 确保对端没有收到 ACK 重传的 FIN 报文可以到达**
43 |
44 | 这就是等待 2MSL 的意义。
45 |
--------------------------------------------------------------------------------
/docs/algorithm/深度遍历/螺旋矩阵.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 螺旋矩阵
3 | date: 2022-05-17 16:21:09
4 | sidebar: auto
5 | tags:
6 | - 深度遍历
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ## 螺旋矩阵
12 |
13 | 给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
14 |
15 | 
16 |
17 | ```js
18 | 输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
19 | 输出:[1,2,3,6,9,8,7,4,5]
20 | ```
21 |
22 | 维护四个状态值即可:
23 |
24 | ```js
25 | /**
26 | * @param {number[][]} matrix
27 | * @return {number[]}
28 | */
29 | var spiralOrder = function (matrix) {
30 | let [l, r, t, b] = [0, matrix[0].length - 1, 0, matrix.length - 1];
31 | let result = [];
32 | let len = matrix[0].length * matrix.length;
33 |
34 | while (result.length < len) {
35 | // l -> r
36 | for (let i = l; i <= r && result.length < len; i++) result.push(matrix[t][i]);
37 | t++;
38 | // t -> b
39 | for (let i = t; i <= b && result.length < len; i++) result.push(matrix[i][r]);
40 | r--;
41 | // r -> l
42 | for (let i = r; i >= l && result.length < len; i--) result.push(matrix[b][i]);
43 | b--;
44 | // b -> t
45 | for (let i = b; i >= t && result.length < len; i--) result.push(matrix[i][l]);
46 | l++;
47 | }
48 |
49 | return result;
50 | };
51 | ```
52 |
53 | ## 螺旋矩阵 II
54 |
55 | 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
56 |
57 | 
58 |
59 | ```js
60 | 输入:n = 3
61 | 输出:[[1,2,3],[8,9,4],[7,6,5]]
62 | ```
63 |
64 | 同理
65 |
66 | ```js
67 | /**
68 | * @param {number} n
69 | * @return {number[][]}
70 | */
71 | var generateMatrix = function (n) {
72 | let [l, r, t, b] = [0, n - 1, 0, n - 1];
73 | let matrix = new Array(n).fill().map(() => new Array(n).fill(undefined));
74 | let num = 1;
75 | let endNum = n * n;
76 | while (num <= endNum) {
77 | for (let i = l; i <= r; i++) matrix[t][i] = num++; // left to right.
78 | t++;
79 | for (let i = t; i <= b; i++) matrix[i][r] = num++; // top to bottom.
80 | r--;
81 | for (let i = r; i >= l; i--) matrix[b][i] = num++; // right to left .
82 | b--;
83 | for (let i = b; i >= t; i--) matrix[i][l] = num++; // bottom to top.
84 | l++;
85 | }
86 |
87 | return matrix;
88 | };
89 | ```
90 |
--------------------------------------------------------------------------------
/docs/algorithm/字符串/回文系列.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 回文系列
3 | date: 2022-05-14 16:43:54
4 | sidebar: auto
5 | tags:
6 | - 字符串
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ## 最长回文串
12 |
13 | [leetcode](https://leetcode.cn/problems/longest-palindrome) 给定一个包含大写字母和小写字母的字符串 s ,返回通过这些字母构造成的 **最长的回文串** 。
14 |
15 | 在构造过程中,请注意 **区分大小写** 。比如 "Aa" 不能当做一个回文字符串。
16 |
17 | ```js
18 | 输入:s = "abccccdd"
19 | 输出:7
20 | 解释: 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
21 |
22 | 输入:s = "a"
23 | 输入:1
24 |
25 | 输入:s = "bb"
26 | 输入: 2
27 | ```
28 |
29 | ### 思路
30 |
31 | 解体的关键在于计算有多少对相同的字符。**就类似于打牌,抽到一对牌就打出来。当所有的牌都抽完了,手里如果还有剩余的未成对的牌,我能还能抽出一张**。
32 |
33 | 这里我用 map 来存放抽到的牌,如果当前抽到的牌已经在手里有了,那么把手里的那个牌和当前的牌凑成一对打出来,结果+2。最后如果手里还有牌,结果+1
34 |
35 | ---
36 |
37 | 1. 回文串的特点是都是成双成对的(除了奇数回文串中心节点之外,其它字符都是成双成对的)
38 | 2. 现在给我们一个字符串,让我们用这个字符串来构建回文串
39 | 3. 关键在于计算有多少对相同的字符
40 |
41 | ### 代码
42 |
43 | ```js
44 | /**
45 | * @param {string} s
46 | * @return {number}
47 | */
48 | var longestPalindrome = function (s) {
49 | let map = new Map();
50 | let sum = 0;
51 | for (let char of s) {
52 | if (map.has(char)) {
53 | sum += 2;
54 | map.delete(char);
55 | } else {
56 | map.set(char);
57 | }
58 | }
59 |
60 | // 如果手上还有牌,则 + 1
61 | let temp = map.size > 0 ? 1 : 0;
62 | return sum + temp;
63 | };
64 | ```
65 |
66 | ## 最长回文子串
67 |
68 | 给你一个字符串 s,找到 s 中最长的回文子串。
69 |
70 | ```js
71 | 输入:s = "babad"
72 | 输出:"bab"
73 | 解释:"aba" 同样是符合题意的答案。
74 |
75 | 输入:s = "cbbd"
76 | 输出:"bb"
77 | ```
78 |
79 | ```js
80 | /**
81 | * @param {string} s
82 | * @return {string}
83 | */
84 | var longestPalindrome = function (s) {
85 | let res = s[0];
86 | let len = s.length;
87 | for (let i = 1; i < len; i++) {
88 | let [l, r] = [i, i];
89 | // 左回文 bb 情况
90 | while (l > 0 && s[i] === s[l - 1]) l--;
91 |
92 | // 右回文
93 | while (r < len - 1 && s[i] === s[r + 1]) r++;
94 |
95 | // 左右回文 bab 情况
96 | while (l > 0 && r < len - 1 && s[l - 1] === s[r + 1]) {
97 | l--;
98 | r++;
99 | }
100 |
101 | if (l < r) {
102 | let cur = s.slice(l, r + 1);
103 | res = cur.length > res.length ? cur : res;
104 | }
105 | }
106 |
107 | return res;
108 | };
109 | ```
110 |
--------------------------------------------------------------------------------
/docs/algorithm/sort/insertSort.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 插入排序
3 | date: 2020-05-19 15:05:55
4 | sidebar: auto
5 | tags:
6 | - 算法与数据结构
7 | - 排序算法
8 | categories:
9 | - 算法与数据结构
10 | ---
11 |
12 | ## 算法步骤
13 |
14 | 1. 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
15 | 2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
16 |
17 | 
18 |
19 | ## 基本实现
20 |
21 | ```js
22 | function insertSort(arr) {
23 | for (let i = 1; i < arr.length; i++) {
24 | let current = arr[i];
25 | let prevIndex = i - 1;
26 |
27 | while (arr[prevIndex] > current) {
28 | arr[prevIndex + 1] = arr[prevIndex]; // 如果前一个比 current 大,则往后移动一位,prevIndex-- 继续循环
29 | prevIndex--;
30 | }
31 | arr[prevIndex + 1] = current; // 插入
32 | }
33 |
34 | return arr;
35 | }
36 | ```
37 |
38 | ## 使用二分查找
39 |
40 | 首先把二分查找算法做一点小修改,以适应我们的插入排序:
41 |
42 | ```js
43 | function binarySearch(arr, maxIndex, value) {
44 | let min = 0;
45 | let max = maxIndex;
46 |
47 | while (min <= max) {
48 | const mid = Math.floor((min + max) / 2);
49 |
50 | if (arr[mid] <= value) {
51 | min = mid + 1;
52 | } else {
53 | max = mid - 1;
54 | }
55 | }
56 |
57 | return min;
58 | }
59 | ```
60 |
61 | 然后在查找插入位置时使用二分查找的方式来优化性能:
62 |
63 | ```js
64 | function insertionSort2(arr) {
65 | for (let i = 1, len = arr.length; i < len; i++) {
66 | const current = arr[i];
67 | const insertIndex = binarySearch(arr, i - 1, current); // 找到当前需要插入的 index
68 |
69 | for (let prevIndex = i - 1; prevIndex >= insertIndex; prevIndex--) {
70 | arr[prevIndex + 1] = arr[prevIndex];
71 | }
72 | arr[insertIndex] = current; // 恢复
73 | }
74 |
75 | return arr;
76 | }
77 |
78 | // test
79 | const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24];
80 | console.log(insertionSort2(arr));
81 | ```
82 |
83 | - 稳定
84 | - 适合场景:对快要排序完成的数组时间复杂度为 O(n)
85 | - 非常低的开销
86 | - 时间复杂度 `O(n²)`
87 |
88 | > 由于它的优点(自适应,低开销,稳定,几乎排序时的 `O(n)`时间),插入排序通常用作递归基本情况(当问题规模较小时)针对较高开销分而治之排序算法, 如希尔排序或快速排序。
89 |
90 | - 高性能(特别是接近排序完毕时的数组),低开销,且稳定
91 | - 利用二分查找来优化
92 |
93 | ---
94 |
95 | - 动画来源 [图解面试算法](https://github.com/MisterBooo/LeetCodeAnimation)
96 | - 参考 [优雅的 JavaScript 排序算法(ES6)](https://juejin.im/post/5ab62ec36fb9a028cf326c49)
97 |
--------------------------------------------------------------------------------
/docs/.vitepress/cache/deps/@theme_index.js.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "sources": ["../../../../node_modules/.pnpm/vitepress@1.0.0-alpha.61_@a_b03ddc81f2f192b764cbb197b970210d/node_modules/vitepress/dist/client/theme-default/index.js", "../../../../node_modules/.pnpm/vitepress@1.0.0-alpha.61_@a_b03ddc81f2f192b764cbb197b970210d/node_modules/vitepress/dist/client/theme-default/without-fonts.js"],
4 | "sourcesContent": ["import './styles/fonts.css';\nexport * from './without-fonts';\nexport { default as default } from './without-fonts';\n", "import './styles/vars.css';\nimport './styles/base.css';\nimport './styles/utils.css';\nimport './styles/components/custom-block.css';\nimport './styles/components/vp-code.css';\nimport './styles/components/vp-code-group.css';\nimport './styles/components/vp-doc.css';\nimport './styles/components/vp-sponsor.css';\nimport VPBadge from './components/VPBadge.vue';\nimport Layout from './Layout.vue';\n// Note: if we add more optional components here, i.e. components that are not\n// used in the theme by default unless the user imports them, make sure to update\n// the `lazyDefaultThemeComponentsRE` regex in src/node/build/bundle.ts.\nexport { default as VPHomeHero } from './components/VPHomeHero.vue';\nexport { default as VPHomeFeatures } from './components/VPHomeFeatures.vue';\nexport { default as VPHomeSponsors } from './components/VPHomeSponsors.vue';\nexport { default as VPDocAsideSponsors } from './components/VPDocAsideSponsors.vue';\nexport { default as VPTeamPage } from './components/VPTeamPage.vue';\nexport { default as VPTeamPageTitle } from './components/VPTeamPageTitle.vue';\nexport { default as VPTeamPageSection } from './components/VPTeamPageSection.vue';\nexport { default as VPTeamMembers } from './components/VPTeamMembers.vue';\nconst theme = {\n Layout,\n enhanceApp: ({ app }) => {\n app.component('Badge', VPBadge);\n }\n};\nexport default theme;\n"],
5 | "mappings": ";AAAA,OAAO;;;ACAP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO,aAAa;AACpB,OAAO,YAAY;AAInB,SAAoB,WAAXA,gBAA6B;AACtC,SAAoB,WAAXA,gBAAiC;AAC1C,SAAoB,WAAXA,gBAAiC;AAC1C,SAAoB,WAAXA,gBAAqC;AAC9C,SAAoB,WAAXA,gBAA6B;AACtC,SAAoB,WAAXA,gBAAkC;AAC3C,SAAoB,WAAXA,gBAAoC;AAC7C,SAAoB,WAAXA,gBAAgC;AACzC,IAAM,QAAQ;AAAA,EACV;AAAA,EACA,YAAY,CAAC,EAAE,IAAI,MAAM;AACrB,QAAI,UAAU,SAAS,OAAO;AAAA,EAClC;AACJ;AACA,IAAO,wBAAQ;",
6 | "names": ["default"]
7 | }
8 |
--------------------------------------------------------------------------------
/docs/algorithm/binarySearch.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 二分查找
3 | date: 2020-05-24 23:37:51
4 | sidebar: auto
5 | ---
6 |
7 | ## 模板 1 【左区右闭】
8 |
9 | ```js
10 | function binarySearch(nums, target) {
11 | let l = 0,
12 | r = target.length - 1,
13 | mid = undefined;
14 |
15 | while (l <= r) {
16 | mid = (l + r) >>> 1;
17 | if (nums[mid] === target) return mid;
18 | else if (nums[mid] < target) l = mid + 1;
19 | else r = mid - 1;
20 | }
21 |
22 | return -1;
23 | }
24 | ```
25 |
26 | ## 模板 2 【左区右开】
27 |
28 | 主要是找极值用的多,比如找元素最左的位置
29 |
30 | ```js
31 | function getTargetLeft(nums, target) {
32 | let l = 0,
33 | r = nums.length,
34 | mid = undefined;
35 |
36 | while (l < r) {
37 | mid = (l + r) >>> 1;
38 | // target <= mid r
39 | if (nums[mid] >= target) r = mid;
40 | else l = mid + 1;
41 | }
42 |
43 | return nums[l] === target ? l : -1;
44 | }
45 |
46 | getTargetLeft([5, 7, 7, 8, 8, 10], 8); // 3
47 | ```
48 |
49 | 找元素最右的位置
50 |
51 | ```js
52 | function getTargeRight(nums, target) {
53 | let l = 0,
54 | r = nums.length,
55 | mid = undefined;
56 |
57 | while (l < r) {
58 | mid = (l + r) >>> 1;
59 | // target < mid r
60 | if (nums[mid] > target) r = mid;
61 | else l = mid + 1;
62 | }
63 | // 终止条件 l = r
64 | return nums[l - 1] === target ? l - 1 : -1;
65 | }
66 |
67 | getTargeRight([5, 7, 7, 8, 8, 10], 8); // 4
68 | ```
69 |
70 | ## 搜索旋转排序数组
71 |
72 | [题目链接](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/)
73 |
74 | 输入:nums = [4,5,6,7,0,1,2], target = 0
75 | 输出:4
76 |
77 | 输入:nums = [4,5,6,7,0,1,2], target = 3
78 | 输出:-1
79 |
80 | ```js
81 | /**
82 | * @param {number[]} nums
83 | * @param {number} target
84 | * @return {number}
85 | */
86 | var search = function (nums, target) {
87 | let l = 0,
88 | r = nums.length - 1,
89 | mid = undefined;
90 |
91 | while (l <= r) {
92 | mid = (l + r) >>> 1;
93 | if (nums[mid] === target) return mid;
94 | // 右递增
95 | if (nums[mid] < nums[r]) {
96 | // 注意 target <= nums[r]
97 | if (nums[mid] < target && target <= nums[r]) l = mid + 1;
98 | else r = mid - 1;
99 | } else {
100 | if (nums[l] <= target && target < nums[mid]) r = mid - 1;
101 | else l = mid + 1;
102 | }
103 | }
104 |
105 | return -1;
106 | };
107 | ```
108 |
--------------------------------------------------------------------------------
/docs/algorithm/动态规划/爬楼梯.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 爬楼梯系列
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 动态规划
7 | categories:
8 | - leetcode
9 | ---
10 |
11 |
24 |
25 | ## 爬楼梯
26 |
27 | 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
28 |
29 | 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
30 |
31 | ```js
32 | var climbStairs = function (n) {
33 | let dp = [1, 1];
34 |
35 | for (let i = 2; i <= n; i++) {
36 | dp[i] = dp[i - 1] + dp[i - 2];
37 | }
38 |
39 | return dp[n];
40 | };
41 | ```
42 |
43 | ## 使用最小花费爬楼梯
44 |
45 | [leetcode](https://leetcode.cn/problems/min-cost-climbing-stairs): 给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
46 |
47 | 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
48 |
49 | 请你计算并返回达到楼梯顶部的最低花费。
50 |
51 | ```js
52 | 输入:cost = [10,15,20]
53 | 输出:15
54 | 解释:你将从下标为 1 的台阶开始。
55 | - 支付 15 ,向上爬两个台阶,到达楼梯顶部。
56 | 总花费为 15 。
57 |
58 | 输入:cost = [1,100,1,1,1,100,1,1,100,1]
59 | 输出:6
60 | 解释:你将从下标为 0 的台阶开始。
61 | - 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
62 | - 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
63 | - 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
64 | - 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
65 | - 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
66 | - 支付 1 ,向上爬一个台阶,到达楼梯顶部。
67 | 总花费为 6 。
68 | ```
69 |
70 | 思路
71 |
72 | 第 i 个台阶可以由**第 i 个台阶** 或者 **第 i -1 个台阶** 到达。
73 |
74 | 要到达 第 i 个台阶,则取决于前面两次 dp 结果:`dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]`
75 |
76 | 最终比较 i i-1 即可。
77 |
78 | ```js
79 | /**
80 | * @param {number[]} cost
81 | * @return {number}
82 | */
83 | var minCostClimbingStairs = function (cost) {
84 | // dp[i] = Math.min()
85 | let len = cost.length;
86 | let dp = [cost[0], cost[1]];
87 | for (let i = 2; i < len; i++) {
88 | dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];
89 | }
90 |
91 | //已经在楼顶了,在前两步中找花费最少的
92 | return Math.min(dp[len - 1], dp[len - 2]);
93 | };
94 | ```
95 |
96 | 优化空间复杂度:
97 |
98 | ```js
99 | /**
100 | * @param {number[]} cost
101 | * @return {number}
102 | */
103 | var minCostClimbingStairs = function (cost) {
104 | let dp = [cost[0], cost[1]];
105 | for (let i = 2; i < cost.length; i++) {
106 | let dpi = Math.min(dp[0], dp[1]) + cost[i];
107 | dp[0] = dp[1];
108 | dp[1] = dpi;
109 | }
110 |
111 | //已经在楼顶了,在前两步中找花费最少的
112 | return Math.min(dp[0], dp[1]);
113 | };
114 | ```
115 |
--------------------------------------------------------------------------------
/docs/algorithm/回溯/回溯分割.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 回溯分割系列
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 回溯算法
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ## 分割回文串
12 |
13 | [分割回文串](https://leetcode-cn.com/problems/palindrome-partitioning/)
14 |
15 | ```js
16 | 输入:s = "aab"
17 | 输出:[["a","a","b"],["aa","b"]]
18 | ```
19 |
20 | ```js
21 | /**
22 | * @param {string} s
23 | * @return {string[][]}
24 | */
25 | var partition = function (s) {
26 | let result = [];
27 |
28 | function backtrack(track, i) {
29 | if (i >= s.length) {
30 | result.push([...track]);
31 | return;
32 | }
33 |
34 | for (let j = i; j < s.length; j++) {
35 | if (!isHuiWen(s.slice(i, j + 1))) continue;
36 |
37 | // 判断是否回文
38 | track.push(s.slice(i, j + 1));
39 | backtrack(track, j + 1);
40 | track.pop();
41 | }
42 | }
43 |
44 | function isHuiWen(s) {
45 | let tmp = s.split('');
46 | while (tmp.length > 1) {
47 | if (tmp.pop() !== tmp.shift()) return false;
48 | }
49 | return true;
50 | }
51 |
52 | backtrack([], 0);
53 |
54 | return result;
55 | };
56 | ```
57 |
58 | ## 复原 IP 地址
59 |
60 | 有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。
61 |
62 | 例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效 IP 地址。
63 | 给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。
64 |
65 | ```js
66 | 输入:s = "25525511135"
67 | 输出:["255.255.11.135","255.255.111.35"]
68 |
69 | 输入:s = "0000"
70 | 输出:["0.0.0.0"]
71 |
72 | 输入:s = "101023"
73 | 输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
74 | ```
75 |
76 | ```js
77 | /**
78 | * @param {string} s
79 | * @return {string[]}
80 | */
81 | var restoreIpAddresses = function (s) {
82 | let result = [];
83 | let path = [];
84 |
85 | function backtracking(i) {
86 | const len = path.length;
87 | if (len > 4) return;
88 | if (len === 4 && i === s.length) {
89 | result.push(path.join('.'));
90 | return;
91 | }
92 | for (let j = i; j < s.length; j++) {
93 | let str = s.slice(i, j + 1);
94 | if (str.length > 3 || Number(str) > 255) break;
95 | if (str.length > 1 && str[0] === '0') break; // 如果当前字符超过1位,但是前导位是0 则跳过
96 | path.push(str);
97 | backtracking(j + 1);
98 | path.pop();
99 | }
100 | }
101 | backtracking(0);
102 | return result;
103 | };
104 | ```
105 |
--------------------------------------------------------------------------------
/docs/algorithm/动态规划/连续.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 动态规划 - 连续
3 | date: 2022-05-10 14:56:33
4 | sidebar: auto
5 | tags:
6 | - 动态规划
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ## 最大子数组和
12 |
13 | 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
14 |
15 | 子数组 是数组中的一个连续部分。
16 |
17 | ```js
18 | 输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
19 | 输出:6
20 | 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
21 |
22 | 输入:nums = [1]
23 | 输出:1
24 |
25 | 输入:nums = [5,4,-1,7,8]
26 | 输出:23
27 | ```
28 |
29 | ---
30 |
31 | 动态规划 `dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);`
32 |
33 | ```js
34 | /**
35 | * @param {number[]} nums
36 | * @return {number}
37 | */
38 | var maxSubArray = function (nums) {
39 | let dp = [nums[0]];
40 | let len = nums.length;
41 |
42 | for (let i = 1; i < len; i++) {
43 | dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
44 | }
45 |
46 | return Math.max(...dp);
47 | };
48 | ```
49 |
50 | 代码优化
51 |
52 | ```js
53 | var maxSubArray = function (nums) {
54 | let max = nums[0];
55 | let pre = max;
56 | let len = nums.length;
57 | for (let i = 1; i < len; i++) {
58 | pre = Math.max(pre + nums[i], nums[i]);
59 | max = Math.max(max, pre);
60 | }
61 |
62 | return max;
63 | };
64 | ```
65 |
66 | ## 最长重复子数组
67 |
68 | [力扣题目链接](https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/): 给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。
69 |
70 | ```js
71 | 输入:
72 | A: [1,2,3,2,1]
73 | B: [3,2,1,4,7]
74 | 输出:3
75 | 解释:长度最长的公共子数组是 [3, 2, 1] 。
76 | ```
77 |
78 | ### 动态规划
79 |
80 | 
81 |
82 | ```js
83 | const findLength = (A, B) => {
84 | const m = A.length;
85 | const n = B.length;
86 | const dp = new Array(m + 1);
87 | for (let i = 0; i <= m; i++) {
88 | // 初始化整个dp矩阵,每个值为0
89 | dp[i] = new Array(n + 1).fill(0);
90 | }
91 | let res = 0;
92 | // i=0或j=0的base case,初始化时已经包括
93 | for (let i = 1; i <= m; i++) {
94 | // 从1开始遍历
95 | for (let j = 1; j <= n; j++) {
96 | // 从1开始遍历
97 | if (A[i - 1] == B[j - 1]) {
98 | dp[i][j] = dp[i - 1][j - 1] + 1;
99 | } // A[i-1]!=B[j-1]的情况,初始化时已包括了
100 | res = Math.max(dp[i][j], res);
101 | }
102 | }
103 | return res;
104 | };
105 | ```
106 |
107 | ### 滑动窗口解法
108 |
109 | 非常好理解,想象两把尺子,错开之后比较相同的部分,找最长相同的串就好了。
110 |
111 | 
112 |
--------------------------------------------------------------------------------
/docs/algorithm/二叉树/二叉树的遍历方式.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 二叉树遍历方式
3 | date: 2022-03-31 20:46:00
4 | sidebar: auto
5 | tags:
6 | - 二叉树
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ```js
12 | // 3
13 | // / \
14 | // 1 4
15 | // \
16 | // 2
17 | ```
18 |
19 | - 中序遍历(左根右):`1 2 3 4`
20 | - 前序遍历(根左右):`3 1 2 4`
21 | - 后序遍历(左右根):`2 1 4 3`
22 |
23 | ---
24 |
25 | - dfs 通过栈实现(x 序遍历)
26 | - bfs 通过队列实现(层序遍历)
27 |
28 | ## 二叉树的前序遍历
29 |
30 | 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
31 |
32 | ### 递归版本
33 |
34 | ```js
35 | /**
36 | * @param {TreeNode} root
37 | * @return {number[]}
38 | */
39 | var preorderTraversal = function (root) {
40 | if (!root) return [];
41 | return [root.val, ...preorderTraversal(root.left), ...preorderTraversal(root.right)];
42 | };
43 | ```
44 |
45 | ### 栈版本
46 |
47 | ```js
48 | var preorderTraversal = function (root) {
49 | if (!root) return [];
50 | let stack = [root];
51 | let result = [];
52 | while (stack.length) {
53 | const node = stack.pop();
54 | result.push(node.val);
55 | node.right && stack.push(node.right); // 注意 先进后出 这里是右节点先进,然后后出
56 | node.left && stack.push(node.left);
57 | }
58 | return result;
59 | };
60 | ```
61 |
62 | ## 二叉树的后序遍历
63 |
64 | ```js
65 | var postorderTraversal = function (root) {
66 | if (!root) return [];
67 | return [...postorderTraversal(root.left), ...postorderTraversal(root.right), root.val];
68 | };
69 | ```
70 |
71 | ## 二叉树的中序遍历
72 |
73 | ```js
74 | var inorderTraversal = function (root) {
75 | if (!root) return [];
76 | return [...inorderTraversal(root.left), root.val, ...inorderTraversal(root.right)];
77 | };
78 | ```
79 |
80 | ## 二叉树的层序遍历
81 |
82 | - [leetcode](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/):给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
83 |
84 | 
85 |
86 | ```js
87 | 输入:root = [3,9,20,null,null,15,7]
88 | 输出:[[3],[9,20],[15,7]]
89 | ```
90 |
91 | ```js
92 | /**
93 | * @param {TreeNode} root
94 | * @return {number[][]}
95 | */
96 | var levelOrder = function (root) {
97 | if (!root) return [];
98 | let result = [];
99 | let queue = [root];
100 |
101 | while (queue.length) {
102 | let len = queue.length;
103 | let temp = [];
104 | for (let i = 0; i < len; i++) {
105 | let node = queue.shift();
106 | temp.push(node.val);
107 | node.left && queue.push(node.left);
108 | node.right && queue.push(node.right);
109 | }
110 | result.push(temp);
111 | }
112 | return result;
113 | };
114 | ```
115 |
--------------------------------------------------------------------------------
/docs/network-protocol/03.tcp三次握手.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 三次握手的过程是怎样的?
3 | date: 2018-09-15 13:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - tcp
7 | - 三次握手
8 | categories:
9 | - 网络协议
10 | ---
11 |
12 | TCP 的三次握手,也是需要确认双方的两样能力: 发送的能力和接收的能力。于是便会有下面的三次握手的过程:
13 |
14 | 
15 |
16 | ## 握手过程
17 |
18 | 刚开始客户端处于 `Closed` 的状态,服务端处于 `Listen` 状态。 进行三次握手:
19 |
20 | | 握手 | 描述 |
21 | | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
22 | | 1️⃣ | 客户端给服务端发一个 `SYN` 报文,并指明客户端的初始化序列号 ISN(c)。此时客户端处于 `SYN_SEND` 状态。 |
23 | | 2️⃣ | 服务器收到客户端的 SYN 报文之后,会以自己的 `SYN + ACK` 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s)。 同时会把客户端的 ISN + 1 作为 ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 `SYN_REVD` 的状态。 |
24 | | 3️⃣ | 客户端收到 SYN 报文之后,会发送一个 `ACK` 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 `ESTABLISHED` 状态。服务器收到 ACK 报文之后,也处于 `ESTABLISHED` 状态,此时,双方已建立起了连接。 |
25 |
26 | 从图中可以看出,SYN 是需要消耗一个序列号的,下次发送对应的 ACK 序列号要加 1,为什么呢?只需要记住一个规则:
27 |
28 | **凡是需要对端确认的,一定消耗 TCP 报文的序列号。**
29 |
30 | SYN 需要对端的确认, 而 ACK 并不需要,因此 SYN 消耗一个序列号而 ACK 不需要。
31 |
32 | ## 为什么需要三次握手,两次不行吗?
33 |
34 | 试想如果是用两次握手,则会出现下面这种情况:
35 |
36 | 如果是两次,你现在发了 SYN 报文想握手,但**是这个包滞留在了当前的网络中**迟迟没有到达,TCP 以为这是丢了包,于是重传,两次握手建立好了连接。
37 |
38 | 看似没有问题,但是连接关闭后,如果这个滞留在网路中的包到达了服务端呢?这时候由于是两次握手,服务端只要接收到然后发送相应的数据包,就默认建立连接,但是现在客户端已经断开了。
39 |
40 | 看到问题的吧,这就带来了连接资源的浪费。
41 |
42 | ## 三次握手过程中可以携带数据吗?
43 |
44 | **第三次握手的时候,可以携带。前两次握手不能携带数据。**
45 |
46 | 如果前两次握手能够携带数据,那么一旦有人想攻击服务器,那么他只需要在第一次握手中的 SYN 报文中放大量数据,那么服务器势必会消耗更多的时间和内存空间去处理这些数据,增大了服务器被攻击的风险。
47 |
48 | 第三次握手的时候,客户端已经处于 `ESTABLISHED` 状态,并且已经能够确认服务器的接收、发送能力正常,这个时候相对安全了,可以携带数据。
49 |
50 | ## 同时打开会怎样?
51 |
52 | 如果双方同时发 `SYN` 报文,状态变化会是怎样的呢?
53 |
54 | 这是一个可能会发生的情况。
55 |
56 | 状态变迁如下:
57 |
58 |
59 |
60 | 在发送方给接收方发 `SYN` 报文的同时,接收方也给发送方发 `SYN` 报文,两个人刚上了!
61 |
62 | 发完 SYN,两者的状态都变为 `SYN-SENT`。
63 |
64 | 在各自收到对方的 `SYN` 后,两者状态都变为 `SYN-REVD`。
65 |
66 | 接着会回复对应的 `ACK + SYN`,这个报文在对方接收之后,两者状态一起变为 `ESTABLISHED`。
67 |
68 | 这就是同时打开情况下的状态变迁。
69 |
--------------------------------------------------------------------------------
/docs/algorithm/字符串/最小覆盖子串.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 最小覆盖子串
3 | date: 2022-05-14 10:09:26
4 | sidebar: auto
5 | tags:
6 | - 字符串
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | [leetcode](https://leetcode.cn/problems/minimum-window-substring) 给你一个字符串 `s` 、一个字符串 `t` 。返回 `s` 中涵盖 `t` 所有字符的最小子串。如果 `s` 中不存在涵盖 `t` 所有字符的子串,则返回空字符串 "" 。
12 |
13 | 注意
14 |
15 | - 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
16 | - 如果 s 中存在这样的子串,我们保证它是唯一的答案。
17 |
18 | ```js
19 | 输入:s = "ADOBECODEBANC", t = "ABC"
20 | 输出:"BANC"
21 |
22 | 输入:s = "a", t = "a"
23 | 输出:"a"
24 |
25 | 输入: s = "a", t = "aa"
26 | 输出: ""
27 | 解释: t 中两个字符 'a' 均应包含在 s 的子串中,
28 | 因此没有符合条件的子字符串,返回空字符串。
29 | ```
30 |
31 | ## 思路
32 |
33 | 使用滑动窗口
34 |
35 | 用 i,j 表示滑动窗口的左边界和右边界,通过改变 i,j 来扩展和收缩滑动窗口,可以想象成一个窗口在字符串上游走,当这个窗口包含的元素满足条件,即包含字符串 T 的所有元素,记录下这个滑动窗口的长度 j-i+1,这些长度中的最小值就是要求的结果。
36 |
37 | 1. 不断增加 j 使滑动窗口增大,直到窗口包含了 T 的所有元素
38 | 2. 不断增加 i 使滑动窗口缩小,因为是要求最小字串,所以将不必要的元素排除在外,使长度减小,直到碰到一个必须包含的元素,这个时候不能再扔了,再扔就不满足条件了,记录此时滑动窗口的长度,并保存最小值
39 | 3. 让 i 再增加一个位置,这个时候滑动窗口肯定不满足条件了,那么继续从步骤一开始执行,寻找新的满足条件的滑动窗口,如此反复,直到 j 超出了字符串 S 范围。
40 |
41 | 图示
42 |
43 | 以 S="DOABECODEBANC",T="ABC" 为例
44 |
45 | 初始状态:
46 |
47 | 
48 |
49 | 步骤一:不断增加 j 使滑动窗口增大,直到窗口包含了 T 的所有元素
50 |
51 | 
52 |
53 | 步骤二:不断增加 i 使滑动窗口缩小,直到碰到一个必须包含的元素 A,此时记录长度更新结果
54 |
55 | 
56 |
57 | 步骤三:让 i 再增加一个位置,开始寻找下一个满足条件的滑动窗口
58 |
59 | 
60 |
61 | ## 代码
62 |
63 | ```js
64 | /**
65 | * @param {string} s
66 | * @param {string} t
67 | * @return {string}
68 | */
69 | var minWindow = function (s, t) {
70 | // 双指针滑动窗口问题
71 | let i = 0;
72 | let j = 0;
73 | let result = '';
74 |
75 | let map = {};
76 | for (const c of t) {
77 | map[c] = map[c] ? map[c] + 1 : 1;
78 | }
79 |
80 | let count = Object.keys(map).length;
81 |
82 | // 右移指针
83 | while (j < s.length) {
84 | const char = s[j];
85 | if (map[char] !== undefined) map[char]--;
86 | // 如果匹配到一个字符,需要再匹配的数量减 1
87 | if (map[char] === 0) count--;
88 |
89 | while (count === 0) {
90 | const matchStr = s.slice(i, j + 1);
91 | if (!result || matchStr.length < result.length) result = matchStr;
92 |
93 | // 如果当前左指针匹配中了
94 | if (map[s[i]] !== undefined) {
95 | map[s[i]]++;
96 | // 注意!这里 count++ 的条件基于当前 map[s[i]] === 1, 否则可能会重复
97 | if (map[s[i]] === 1) count++;
98 | }
99 |
100 | i++;
101 | }
102 | j++;
103 | }
104 |
105 | return result;
106 | };
107 | ```
108 |
109 | 需要注意的细节点
110 |
111 | ```js
112 | if (map[char] === 0) count--;
113 | if (map[char] === 1) count++;
114 | ```
115 |
--------------------------------------------------------------------------------
/docs/algorithm/贪心算法/贪心入门.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 贪心入门
3 | date: 2022-05-20 00:52:14
4 | sidebar: auto
5 | tags:
6 | - 贪心算法
7 | categories:
8 | - leetcode
9 | ---
10 |
11 | ## 什么是贪心
12 |
13 | **贪心的本质是选择每一阶段的局部最优,从而达到全局最优。**
14 |
15 | 这么说有点抽象,来举一个例子:
16 |
17 | 例如,有一堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?
18 |
19 | 指定每次拿最大的,最终结果就是拿走最大数额的钱。
20 |
21 | 每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优。
22 |
23 | 再举一个例子如果是 有一堆盒子,你有一个背包体积为 n,如何把背包尽可能装满,如果还每次选最大的盒子,就不行了。这时候就需要动态规划。
24 |
25 | ## 分发饼干
26 |
27 | 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
28 |
29 | 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。**你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。**
30 |
31 | ```js
32 | 输入: g = [1,2,3], s = [1,1]
33 | 输出: 1
34 | 解释: 你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
35 | 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
36 | 所以你应该输出1。
37 |
38 | 输入: g = [1,2], s = [1,2,3]
39 | 输出: 2
40 | 解释: 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
41 | 你拥有的饼干数量和尺寸都足以让所有孩子满足。
42 | 所以你应该输出2.
43 | ```
44 |
45 | ---
46 |
47 | **思路**
48 |
49 | 为了满足更多的小孩,就不要造成饼干尺寸的浪费。
50 |
51 | 大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子,那么就应该优先满足胃口大的。
52 |
53 | **这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩。**
54 |
55 | 可以尝试使用贪心策略,先将饼干数组和小孩数组排序。然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量。
56 |
57 | ---
58 |
59 | ```js
60 | /**
61 | * @param {number[]} g
62 | * @param {number[]} s
63 | * @return {number}
64 | */
65 | var findContentChildren = function (g, s) {
66 | g.sort((a, b) => a - b);
67 | s.sort((a, b) => a - b);
68 |
69 | let count = 0;
70 | let [i, j] = [g.length - 1, s.length - 1];
71 | while (i >= 0) {
72 | if (j >= 0 && s[j] >= g[i]) {
73 | count++;
74 | j--; // 消耗一个饼干
75 | }
76 | i--;
77 | }
78 |
79 | return count;
80 | };
81 | ```
82 |
83 | ## 柠檬水找零
84 |
85 | 在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
86 |
87 | 每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
88 |
89 | 注意,一开始你手头没有任何零钱。
90 |
91 | 给你一个整数数组 bills ,其中 `bills[i]` 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
92 |
93 | ```js
94 | 输入:bills = [5,5,5,10,20]
95 | 输出:true
96 | 解释:
97 | 前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
98 | 第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
99 | 第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
100 | 由于所有客户都得到了正确的找零,所以我们输出 true。
101 | ```
102 |
103 | ---
104 |
105 | 贪心思路:有大额零钱先找大额零钱,没有再找小额零钱。比如 20 找 15,如果手头有一张 10 块和 5 块,和手头有一张 10 块,3 张五块。那么为了让自己零钱最大,则要优先采取第一个方案。
106 |
107 | ```js
108 | /**
109 | * @param {number[]} bills
110 | * @return {boolean}
111 | */
112 | var lemonadeChange = function (bills) {
113 | let [five, ten] = [0, 0];
114 | for (let bill of bills) {
115 | if (bill === 5) five++;
116 | else if (bill === 10) {
117 | if (five === 0) return false; // 没钱找
118 | five--;
119 | ten++;
120 | } else {
121 | // 优先找 10 块的
122 | if (five && ten) [five, ten] = [five - 1, ten - 1];
123 | else if (five >= 3) five -= 3;
124 | else return false;
125 | }
126 | }
127 |
128 | return true;
129 | };
130 | ```
131 |
132 | ## K 次取反后最大化的数组和
133 |
--------------------------------------------------------------------------------
/docs/network-protocol/11.cookie.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Cookie 有哪些属性和功能?
3 | date: 2018-09-16 13:00:28
4 | sidebar: 'auto'
5 | tags:
6 | - http
7 | - cookie
8 | categories:
9 | - 网络协议
10 | ---
11 |
12 | ## cookie 简介
13 |
14 | 基于 HTTP 传输是无状态的,也就是每次 HTTP 请求服务端和客户端都不知道数据是哪来的,而 `cookie` 服务器拥有“记忆能力”。
15 |
16 | :::: tabs
17 |
18 | ::: tab 简述
19 |
20 | 第一次访问网站的时候,浏览器发出请求,服务器响应请求后,会将 `cookie` 放入到响应请求中,在浏览器第二次发请求的时候,会把 `cookie` 带过去,服务端会辨别用户身份,当然服务器也可以修改 `cookie` 内容
21 |
22 | :::
23 |
24 | ::: tab 流程图
25 |
26 | 
27 |
28 | :::
29 |
30 | ::: tab 设置 cookie 代码示例
31 |
32 | ```js
33 | const http = require('http');
34 |
35 | http
36 | .createServer(function (request, response) {
37 | if (request.url === '/') {
38 | response.writeHead(200, {
39 | 'Content-Type': 'text/html;charset=utf-8',
40 | 'Set-Cookie': 'id=123',
41 | });
42 | response.end(`
43 |
Bundle 1 loaded
'; 186 | ``` 187 | 188 | **bandle2** 189 | 190 | ```js 191 | console.log('Bundle 2'); 192 | document.body.innerHTML += 'Bundle 2 loaded
'; 193 | ``` 194 | 195 | ::: 196 | 197 | ::: details ssl 198 | 199 | **cert.pem** 200 | 201 | ```js 202 | -----BEGIN CERTIFICATE----- 203 | MIIDtTCCAp2gAwIBAgIJAN9jdYICeY33MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 204 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 205 | aWRnaXRzIFB0eSBMdGQwHhcNMTcwODE2MTMyNDA2WhcNMjcwODE0MTMyNDA2WjBF 206 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 207 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 208 | CgKCAQEA4NGiIEgDf3NdOlpGAROY8ZNPJrhR6gNPTAIZKz314wDUJTF54a8ioeUn 209 | rsKL0Z5H67MmZ5LcsZO9jprrBaSFAIyFdiqQKOSWkXA+LZCEhqaCR0cOIOy0TEKb 210 | MxsABW+6hSoBXk1tK3IQntEcs22qQZll+Y++jUBck+XaCwAmpsk4ofqfLPHcvidj 211 | gHOYkKQg/fFIm81M1NKgdbQpW2/akob56kdh4scZrpwa6MxEylKppTjZ0jfF0cnv 212 | rrs4QTWvoIowfAMrrd/TJV5P8Ei5KcckJe8shlgoEOxtRrAjhIEUmZOT0yho7BcA 213 | C+Zudul0XJhcAPjbHirQ0+G4Agy3FwIDAQABo4GnMIGkMB0GA1UdDgQWBBQqMeql 214 | vTY7xAXbu5YXhnD4lBK1xjB1BgNVHSMEbjBsgBQqMeqlvTY7xAXbu5YXhnD4lBK1 215 | xqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV 216 | BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAN9jdYICeY33MAwGA1UdEwQF 217 | MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEm90w4C0rFqxVVVojAYXrcvLD0E5iwA 218 | 3eEn+uD1dMy56he0LZNXlf3s3mZlRYE8+oGvydcMuXrmisVcvIuFQlES2M6y8S2b 219 | CW6xeir4+VKWR97c3p+M48rUfV8YGb2d6YBLNlekAT/S55Bhfy15sYWt+9LIi4i+ 220 | ToqywlIfiMcwpBZLD1UTPs7b66Rx7LHmMFXpNfDugp8JHphLJRmXFNgZNNmi0Re0 221 | vDMgUWlgVzV9oMTjp0Ioafbtqcykg0JS+3KsXHWVQPWrw1wsBjje4XJLIIY1+W75 222 | tHBk3PL42xP5vPgRJ/8X22pFXnC/0MK6fKg5VdvwJQDBlYxbpcM3W+g= 223 | -----END CERTIFICATE----- 224 | ``` 225 | 226 | **key.pem** 227 | 228 | ```js 229 | -----BEGIN RSA PRIVATE KEY----- 230 | MIIEpAIBAAKCAQEA4NGiIEgDf3NdOlpGAROY8ZNPJrhR6gNPTAIZKz314wDUJTF5 231 | 4a8ioeUnrsKL0Z5H67MmZ5LcsZO9jprrBaSFAIyFdiqQKOSWkXA+LZCEhqaCR0cO 232 | IOy0TEKbMxsABW+6hSoBXk1tK3IQntEcs22qQZll+Y++jUBck+XaCwAmpsk4ofqf 233 | LPHcvidjgHOYkKQg/fFIm81M1NKgdbQpW2/akob56kdh4scZrpwa6MxEylKppTjZ 234 | 0jfF0cnvrrs4QTWvoIowfAMrrd/TJV5P8Ei5KcckJe8shlgoEOxtRrAjhIEUmZOT 235 | 0yho7BcAC+Zudul0XJhcAPjbHirQ0+G4Agy3FwIDAQABAoIBAHHi4B04PcVffHel 236 | 6VZ8RfsCY5M6xgwklxPq8DMOlTPkZJNex95CqOmYOwz1cnzCkK5et3K6W9/89oZ6 237 | Bdp66AFKLgWZNCPzAC82y9irH+dSDCbtYMPfBMqo5xPxdoZKfhMdH0pVMJtUkgTR 238 | 65cdU6UdfyH35lCJrRwi0NzHu8y6qMyH5tOcNOIXwn/LfZ6ZwVx2lKAjthmac2z0 239 | 35qknvCNfgadrsl5PLWZBYwy3qx208pUUojMciL9RtfO17F6ofMhueOoChnaHv6h 240 | Lra3NpcuJlYdTTt3tgkTvfMXiZCMsWoV9q1+y9AcSojDJk4V6qiy0Nu8uQmSCRX8 241 | g/2ilqECgYEA/9yejCxaUfgHlA6LkFFMNNkgh3AwHxfIiEBzCDxkUPlu0J1z+65S 242 | UJLGLIgQoZN0UmN6a0YHHDxh0oxIvxoq+wc4PPIr9mmSsEt6YTUjYbYNIJ/5978b 243 | CH+41VGCszqzd0NSJTW7aKL3XFE34kDascgTiDd82KmqaC5chShCoZECgYEA4PC4 244 | qoNnAzILI9yM9hJcolrW9hf4pUXqG9orO7pr5/ETDVi74pNdKkb7MKE1snjTQBdu 245 | rV5q09wu9+Owa5AoHLkvTify+EXlUrc4UeVxEdlOfoc/laONZkGaENxn4DTH4rMj 246 | 6nlzMNPcR0Hxud/UqLpWiI20I+dJwVzBA3z3eicCgYEAqNb+LQPLqlGhNpuOj3KG 247 | dk1dwOJQbwQzyW22OxYXILQo4zMz6T50hUUFzzcOuoDifse0bfutD33tE5KNIsZy 248 | 3Go8O0OXrSinqvxzypfVPFJ1QTUwL8OFZEtcPjBmrj0rVqUvHOzjOb5ouxvBY+Vm 249 | K3EbKoVrNlJn6A3H8frKVXECgYEAsnQnfRdcbTuRfPTnW/07Qo6gxYJFABGUZl5S 250 | OENwggVOoSMJg/p3Sigf9fefWyTiK5Gre51RURz4oi8f8mXefNMpxW6KIw+InHPB 251 | Ga/WYVuuG1F/T17+ueZHrSK+wi/9eEu4rbeGfHFH67xUYqtB0k5qglExXd6LM/07 252 | H2JQD7cCgYAeU4FtW0VZdEtUkBLJhHHZ0l/UBtN/XhkpFjdrG9uJryX/2rnA8Zoh 253 | vKhSG5iWdTDak4R2dt9yJrTr1sriI5P176t76eAR4jg0F3xeRpiaDDk7wdznwh9F 254 | OKOJlLEHd9G2gryXAtUU4KPii3/Awgj/Kd37j7fssuMTQLB9bWIygQ== 255 | -----END RSA PRIVATE KEY----- 256 | ``` 257 | 258 | ::: 259 | 260 | Node.js 8.4.0 已经开始支持 HTTP/2,执行 node 命令时,加上 `--expose-http2` 选项就可以使用了。 261 | -------------------------------------------------------------------------------- /docs/algorithm/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 🔥 Algorithms 3 | date: 2020-07-22 09:23:33 4 | sidebar: auto 5 | --- 6 | 7 | ## 字符串 8 | 9 | - [1556. 千位分隔数](./字符串/千位分隔数.md) 10 | - [反转字符串 II](./字符串/反转字符串II.md) 11 | - [字符串的排列](./字符串/字符串的排列.md) 12 | - [🌟 最长不含重复字符的子字符串](./字符串/最长不含重复字符的子字符串.md) 13 | - [🌟 76. 最小覆盖子串](./字符串/最小覆盖子串.md) 14 | - 回文系列 15 | - [409. 最长回文串](./字符串/回文系列.md#最长回文串) 16 | - [5. 最长回文子串](./字符串/回文系列.md#最长回文子串) 17 | 18 | ## 数组 19 | 20 | - [两数之和](./数组/两数之和.md) 21 | - [合并两个有序数组](./数组/合并两个有序数组.md) 22 | - [长度最小的子数组](./数组/长度最小的子数组.md) 23 | - 双指针 24 | - [🌟 31. 下一个排列](./数组/双指针.md#下一个排列) 25 | - [🌟 15. 三数之和](./数组/双指针.md#三数之和) 26 | - [🌟 42. 接雨水](./数组/双指针.md#接雨水) 27 | 28 | ## 链表 29 | 30 | - [移除链表元素](./链表/移除链表元素.md) 31 | - [删除链表的倒数第 n 个结点](./链表/删除链表的倒数第n个结点.md) 32 | - [反转链表](./链表/反转链表.md) 33 | - [环形链表 II](./链表/环形链表II.md) 34 | - [160. 相交链表](./链表/相交链表.md) 35 | - [k 个一组翻转链表](./链表/k个一组翻转链表.md) 36 | 37 | ## 二叉树 38 | 39 | - 遍历方式 40 | - [144. 二叉树的前序遍历](./二叉树/二叉树的遍历方式.md#二叉树的前序遍历) 41 | - [145. 二叉树的后序遍历](./二叉树/二叉树的遍历方式.md#二叉树的后序遍历) 42 | - [94. 二叉树的中序遍历](./二叉树/二叉树的遍历方式.md#二叉树的中序遍历) 43 | - [🌟 102. 二叉树的层序遍历](./二叉树/二叉树的遍历方式.md#二叉树的层序遍历) 44 | - 二叉树的属性 45 | - [101. 对称二叉树](./二叉树/二叉树的属性.md#对称二叉树) 46 | - [104. 二叉树的最大深度](./二叉树/二叉树的属性.md#二叉树的最大深度) 47 | - [111. 二叉树的最小深度](./二叉树/二叉树的属性.md#二叉树的最小深度) 48 | - [222. 完全二叉树的节点个数](./二叉树/二叉树的属性.md#完全二叉树的节点个数) 49 | - [110. 平衡二叉树](./二叉树/二叉树的属性.md#平衡二叉树) 50 | - [257. 二叉树的所有路径](./二叉树/二叉树的属性.md#二叉树的所有路径) 51 | - [513. 找树左下角的值](./二叉树/二叉树的属性.md#找树左下角的值) 52 | - [112. 路径总和](./二叉树/二叉树的属性.md#路径总和) 53 | - 二叉树的修改与构造 54 | - [226. 翻转二叉树](./二叉树/二叉树的修改与构造.md#翻转二叉树) 55 | - [🌟 106. 从中序与后序遍历序列构造二叉树](./二叉树/二叉树的修改与构造.md#从中序与后序遍历序列构造二叉树) 56 | - [🌟 【剑指】 07. 重建二叉树](./二叉树/二叉树的修改与构造.md#重建二叉树) 57 | - [654. 最大二叉树](./二叉树/二叉树的修改与构造.md#最大二叉树) 58 | - [617. 合并二叉树](./二叉树/二叉树的修改与构造.md#合并二叉树) 59 | - 二叉树的修改与构造 II 60 | - [701. 二叉搜索树中的插入操作](./二叉树/二叉树的修改与构造2.md#二叉搜索树中的插入操作) 61 | - [🌟 450. 删除二叉搜索树中的节点](./二叉树/二叉树的修改与构造2.md#删除二叉搜索树中的节点) 62 | - [🌟 669. 修剪二叉搜索树](./二叉树/二叉树的修改与构造2.md#修剪二叉搜索树) 63 | - [🌟 108. 将有序数组转换为二叉搜索树](./二叉树/二叉树的修改与构造2.md#将有序数组转换为二叉搜索树) 64 | - 求二叉搜索树的属性 65 | - [700. 二叉搜索树中的搜索](./二叉树/求二叉搜索树的属性.md#二叉搜索树中的搜索) 66 | - [530. 二叉搜索树的最小绝对差](./二叉树/求二叉搜索树的属性.md#二叉搜索树的最小绝对差) 67 | - [510. 二叉搜索树中的众数](./二叉树/求二叉搜索树的属性.md#二叉搜索树中的众数) 68 | - [538. 把二叉搜索树转换为累加树](./二叉树/求二叉搜索树的属性.md#把二叉搜索树转换为累加树) 69 | - 二叉树的公共祖先 70 | - [236. 二叉树的最近公共祖先](./二叉树/二叉树的公共祖先.md#二叉树的最近公共祖先) 71 | - [235. 二叉搜索树的最近公共祖先](./二叉树/二叉树的公共祖先.md#二叉搜索树的最近公共祖先) 72 | - 二叉树其他题目 73 | - [129. 求根到叶子节点数字之和](./二叉树/二叉树其他题目.md#求根到叶子节点数字之和) 74 | 75 | ## 动态规划 76 | 77 | - [动态规划理论基础](./动态规划/动态规划理论基础.md) 78 | - 基础入门 79 | - [70. 爬楼梯](./动态规划/爬楼梯.md#爬楼梯) 80 | - [746. 使用最小花费爬楼梯](./动态规划/爬楼梯.md#使用最小花费爬楼梯) 81 | - [62. 不同路径](./动态规划/不同路径.md) 82 | - [63. 不同路径 II](./动态规划/不同路径.md#不同路径-ii) 83 | - 打家劫舍 84 | - [198. 打家劫舍 I](./动态规划/打家劫舍系列.md) 85 | - [213. 打家劫舍 II](./动态规划/打家劫舍系列.md#打家劫舍-ii) 86 | - [337. 打家劫舍 III](./动态规划/打家劫舍系列.md#打家劫舍-iii) 87 | - 买卖股票的最佳时机 88 | - [121. 买卖股票的最佳时机](./动态规划/买卖股票的最佳时机.md) 89 | - [122. 买卖股票的最佳时机 II](./动态规划/买卖股票的最佳时机.md#买卖股票的最佳时机-ii) 90 | - [123. 买卖股票的最佳时机 III](./动态规划/买卖股票的最佳时机.md#买卖股票的最佳时机-iii) 91 | - 背包问题 92 | - 背包系列 93 | - 完全背包 94 | - 多重背包 95 | - 子序列问题 96 | - 不连续 97 | - 最长上升子序列 98 | - 最长公共子序列 99 | - 不想交的线 100 | - 连续 101 | - 最长连续递增序列 102 | - [53. 最大子数组和](./动态规划/连续.md#最大子数组和) 103 | - [718. 最长重复子数组](./动态规划/连续.md#最长重复子数组) 104 | - 最大子序和 105 | - 编辑距离 106 | - 判断子序列 107 | - 不同的子序列 108 | - 两个字符串的删除操作 109 | - 编辑距离 110 | - 回文 111 | - 回文子串 112 | - 最长回文序列 113 | 114 | ## 回溯算法 115 | 116 | - [总结](./回溯/总结.md) 117 | - 回溯排列 118 | - [46. 全排列](./回溯/回溯排列.md#全排列) 119 | - [47. 全排列 II](./回溯/回溯排列.md#全排列-ii) 120 | - [剑指 38. 字符串的排列](./回溯/回溯排列.md#字符串的排列) 121 | - [784. 字母大小写全排列](./回溯/回溯排列.md#字母大小写全排列) 122 | - 回溯分割 123 | - [131. 分割回文串](./回溯/回溯分割.md#分割回文串) 124 | - [93. 复原 IP 地址](./回溯/回溯分割.md#复原-ip-地址) 125 | - 子集 126 | - [78. 子集](./回溯/子集.md) 127 | - [90. 子集 II](./回溯/子集II.md) 128 | - [组合总和](./回溯/组合总和.md) 129 | 130 | - [N 皇后](./回溯/N皇后.md) 131 | 132 | ## 贪心算法 133 | 134 | - 贪心入门 135 | - [455. 分发饼干](./贪心算法/贪心入门.md#分发饼干) 136 | - [860. 柠檬水找零](./贪心算法/贪心入门.md#柠檬水找零) 137 | - [1005. K 次取反后最大化的数组和](./贪心算法/贪心入门.md#K-次取反后最大化的数组和) 138 | 139 | ## DFS 遍历 140 | 141 | - [200. 岛屿数量](./深度遍历/岛屿数量.md) 142 | - [79. 单词搜索](./深度遍历/单词搜索.md) 143 | - [54. 螺旋矩阵](./深度遍历/螺旋矩阵.md) 144 | - [5. 螺旋矩阵 II](./深度遍历/螺旋矩阵.md#螺旋矩阵-ii) 145 | - 岛屿的周长 146 | - 岛屿的最大面积 147 | 148 | ## leetcode top 100 149 | 150 | | 题目 | 出现次数 | 链接 | 151 | | --------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------- | 152 | | ✅ 3. 无重复字符的最长子串 | 18 | https://leetcode-cn.com/problems/longest-substring-without-repeating-characters | 153 | | ✅ 88. 合并两个有序数组 | 17 | https://leetcode-cn.com/problems/merge-sorted-array | 154 | | ✅ 129. 求根到叶子节点数字之和 | 15 | https://leetcode-cn.com/problems/sum-root-to-leaf-numbers | 155 | | ✅ 112. 路径总和 | 13 | https://leetcode-cn.com/problems/path-sum | 156 | | ✅ 53. 最大子序和 | 12 | https://leetcode-cn.com/problems/maximum-subarray | 157 | | ✅ 1. 两数之和 | 11 | https://leetcode-cn.com/problems/two-sum | 158 | | ✅ 165. 比较版本号 | 11 | https://leetcode-cn.com/problems/compare-version-numbers | 159 | | 215. 数组中的第 K 个最大元素 | 10 | https://leetcode-cn.com/problems/kth-largest-element-in-an-array | 160 | | ✅ 209. 长度最小的子数组 | 8 | https://leetcode-cn.com/problems/minimum-size-subarray-sum | 161 | | ✅ 剑指 Offer 22. 链表中倒数第 k 个节点 | 8 | https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof | 162 | | ✅ 415. 字符串相加 | 8 | https://leetcode-cn.com/problems/add-strings | 163 | | ✅ 46. 全排列 | 8 | https://leetcode-cn.com/problems/permutations | 164 | | ✅ 206. 反转链表 | 7 | https://leetcode-cn.com/problems/reverse-linked-list | 165 | | ✅ 102. 二叉树的层序遍历 | 6 | https://leetcode-cn.com/problems/binary-tree-level-order-traversal | 166 | | ✅ 70. 爬楼梯 | 6 | https://leetcode-cn.com/problems/climbing-stairs | 167 | | ✅ 54. 螺旋矩阵 | 6 | https://leetcode-cn.com/problems/spiral-matrix | 168 | | ✅ 93. 复原 IP 地址 | 5 | https://leetcode-cn.com/problems/restore-ip-addresses | 169 | | ✅ 200. 岛屿数量 | 5 | https://leetcode-cn.com/problems/number-of-islands | 170 | | ✅ 230. 二叉搜索树中第 K 小的元素 | 5 | https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst | 171 | | ✅ 15. 三数之和 | 5 | https://leetcode-cn.com/problems/3sum | 172 | | ✅ 141. 环形链表 | 5 | https://leetcode-cn.com/problems/linked-list-cycle | 173 | | ✅ 429. N 叉树的层序遍历 | 5 | https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal | 174 | | ✅ 226. 翻转二叉树 | 4 | https://leetcode-cn.com/problems/invert-binary-tree | 175 | | ✅ 121. 买卖股票的最佳时机 | 4 | https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock | 176 | | ✅ 718. 最长重复子数组 | 4 | https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray | 177 | | ✅ 160. 相交链表 | 4 | https://leetcode-cn.com/problems/intersection-of-two-linked-lists | 178 | | ✅ 695. 岛屿的最大面积 | 4 | https://leetcode-cn.com/problems/max-area-of-island | 179 | | ✅ 62. 不同路径 | 4 | https://leetcode-cn.com/problems/unique-paths | 180 | | 剑指 Offer 62. 圆圈中最后剩下的数字 | 4 | https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof | 181 | | ✅ 94. 二叉树的中序遍历 | 3 | https://leetcode-cn.com/problems/binary-tree-inorder-traversal | 182 | | ✅ 104. 二叉树的最大深度 | 3 | https://leetcode-cn.com/problems/maximum-depth-of-binary-tree | 183 | | ✅ 21. 合并两个有序链表 | 3 | https://leetcode-cn.com/problems/merge-two-sorted-lists | 184 | | ✅ 509. 斐波那契数 | 3 | https://leetcode-cn.com/problems/fibonacci-number | 185 | | ✅ 113. 路径总和 II | 3 | https://leetcode-cn.com/problems/path-sum-ii | 186 | | ✅ 468. 验证 IP 地址 | 3 | https://leetcode-cn.com/problems/validate-ip-address | 187 | | ✅ 31. 下一个排列 | 3 | https://leetcode-cn.com/problems/next-permutation | 188 | | ✅ 20. 有效的括号 | 3 | https://leetcode-cn.com/problems/valid-parentheses | 189 | | ✅ 349. 两个数组的交集 | 3 | https://leetcode-cn.com/problems/intersection-of-two-arrays | 190 | | ✅ 5. 最长回文子串 | 3 | https://leetcode-cn.com/problems/longest-palindromic-substring | 191 | | ✅ 236. 二叉树的最近公共祖先 | 3 | https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree | 192 | | ✅ 146. LRU 缓存机制 | 3 | https://leetcode-cn.com/problems/lru-cache | 193 | | ✅ 322. 零钱兑换 | 2 | https://leetcode-cn.com/problems/coin-change | 194 | | 394. 字符串解码 | 2 | https://leetcode-cn.com/problems/decode-string | 195 | | ✅ 19. 删除链表的倒数第 N 个节点 | 2 | https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list | 196 | | ✅ 剑指 Offer 24. 反转链表 | 2 | https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof | 197 | | ✅ 42. 接雨水 | 2 | https://leetcode-cn.com/problems/trapping-rain-water | 198 | | 155. 最小栈 | 2 | https://leetcode-cn.com/problems/min-stack | 199 | | 169. 多数元素 | 2 | https://leetcode-cn.com/problems/majority-element | 200 | | 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 | 2 | https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof | 201 | | 67. 二进制求和 | 2 | https://leetcode-cn.com/problems/add-binary | 202 | | 144. 二叉树的前序遍历 | 2 | https://leetcode-cn.com/problems/binary-tree-preorder-traversal | 203 | | 198. 打家劫舍 | 2 | https://leetcode-cn.com/problems/house-robber | 204 | | 14. 最长公共前缀 | 2 | https://leetcode-cn.com/problems/longest-common-prefix | 205 | | 补充题 3. 求区间最小数乘区间和的最大值 | 2 | https://mp.weixin.qq.com/s/UFv7pt_djjZoK_gzUBrRXA | 206 | | 199. 二叉树的右视图 | 2 | https://leetcode-cn.com/problems/binary-tree-right-side-view | 207 | | 剑指 Offer 09. 用两个栈实现队列 | 2 | https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof | 208 | | 56. 合并区间 | 2 | https://leetcode-cn.com/problems/merge-intervals | 209 | | 300. 最长上升子序列 | 2 | https://leetcode-cn.com/problems/longest-increasing-subsequence | 210 | | 剑指 Offer 38. 字符串的排列 | 2 | https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof | 211 | | 补充题 14. 阿拉伯数字转中文数字 | 2 | | 212 | | 400. 第 N 个数字 | 2 | https://leetcode-cn.com/problems/nth-digit | 213 | | 101. 对称二叉树 | 2 | https://leetcode-cn.com/problems/symmetric-tree | 214 | | 剑指 Offer 48. 最长不含重复字符的子字符串 | 1 | https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof | 215 | | 680. 验证回文字符串 Ⅱ | 1 | https://leetcode-cn.com/problems/valid-palindrome-ii | 216 | | 124. 二叉树中的最大路径和 | 1 | https://leetcode-cn.com/problems/binary-tree-maximum-path-sum | 217 | | 221. 最大正方形 | 1 | https://leetcode-cn.com/problems/maximal-square | 218 | | 98. 验证二叉搜索树 | 1 | https://leetcode-cn.com/problems/validate-binary-search-tree | 219 | | 1047. 删除字符串中的所有相邻重复项 | 1 | https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string | 220 | | 44. 通配符匹配 | 1 | https://leetcode-cn.com/problems/wildcard-matching | 221 | | 130. 被围绕的区域 | 1 | https://leetcode-cn.com/problems/surrounded-regions | 222 | | 16. 最接近的三数之和 | 1 | https://leetcode-cn.com/problems/3sum-closest | 223 | | 498. 对角线遍历 | 1 | https://leetcode-cn.com/problems/diagonal-traverse | 224 | | 224. 基本计算器 | 1 | https://leetcode-cn.com/problems/basic-calculator | 225 | | 227. 基本计算器 II | 1 | https://leetcode-cn.com/problems/basic-calculator-ii | 226 | | 剑指 Offer 04. 二维数组中的查找 | 1 | https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof | 227 | | 443. 压缩字符串 | 1 | https://leetcode-cn.com/problems/string-compression | 228 | | ✅ 142. 环形链表 II | 1 | https://leetcode-cn.com/problems/linked-list-cycle-ii | 229 | | 984. 不含 AAA 或 BBB 的字符串 | 1 | https://leetcode-cn.com/problems/string-without-aaa-or-bbb | 230 | | 151. 翻转字符串里的单词 | 1 | https://leetcode-cn.com/problems/reverse-words-in-a-string | 231 | | 213. 打家劫舍 II | 1 | https://leetcode-cn.com/problems/house-robber-ii | 232 | | 114. 二叉树展开为链表 | 1 | https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list | 233 | | 109. 有序链表转换二叉搜索树 | 1 | https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree | 234 | | 242. 有效的字母异位词 | 1 | https://leetcode-cn.com/problems/valid-anagram | 235 | | 862. 和至少为 K 的最短子数组 | 1 | https://leetcode-cn.com/problems/shortest-subarray-with-sum-at-least-k | 236 | | 1498. 满足条件的子序列数目 | 1 | https://leetcode-cn.com/problems/number-of-subsequences-that-satisfy-the-given-sum-condition | 237 | | 257. 二叉树的所有路径 | 1 | https://leetcode-cn.com/problems/binary-tree-paths | 238 | | 63. 不同路径 II | 1 | https://leetcode-cn.com/problems/unique-paths-ii | 239 | | 609. 在系统中查找重复文件 | 1 | https://leetcode-cn.com/problems/find-duplicate-file-in-system | 240 | | 232. 用栈实现队列 | 1 | https://leetcode-cn.com/problems/implement-queue-using-stacks | 241 | | 39. 组合总和 | 1 | https://leetcode-cn.com/problems/combination-sum | 242 | | 剑指 Offer 10- I. 斐波那契数列 | 1 | https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof | 243 | | 361. 轰炸敌人 | 1 | https://leetcode-cn.com/problems/bomb-enemy | 244 | | 525. 连续数组 | 1 | https://leetcode-cn.com/problems/contiguous-array | 245 | | 96. 不同的二叉搜索树 | 1 | https://leetcode-cn.com/problems/unique-binary-search-trees | 246 | | 103. 二叉树的锯齿形层次遍历 | 1 | https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal | 247 | | 717. 1 比特与 2 比特字符 | 1 | https://leetcode-cn.com/problems/1-bit-and-2-bit-characters | 248 | | 1353. 最多可以参加的会议数目 | 1 | https://leetcode-cn.com/problems/maximum-number-of-events-that-can-be-attended | 249 | | 剑指 Offer 63. 股票的最大利润 | 1 | https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof | 250 | | 剑指 Offer 27. 二叉树的镜像 | 1 | https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof | 251 | | LCP 04. 覆盖 | 1 | https://leetcode-cn.com/problems/broken-board-dominoes | 252 | | 剑指 Offer 39. 数组中出现次数超过一半的数字 | 1 | https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof | 253 | | 剑指 Offer 07. 重建二叉树 | 1 | https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof | 254 | | 829. 连续整数求和 | 1 | https://leetcode-cn.com/problems/consecutive-numbers-sum | 255 | | 1356. 根据数字二进制下 1 的数目排序 | 1 | https://leetcode-cn.com/problems/sort-integers-by-the-number-of-1-bits | 256 | | 6. Z 字形变换 | 1 | https://leetcode-cn.com/problems/zigzag-conversion | 257 | | 704. 二分查找 | 1 | https://leetcode-cn.com/problems/binary-search | 258 | | 111. 二叉树的最小深度 | 1 | https://leetcode-cn.com/problems/minimum-depth-of-binary-tree | 259 | | 105. 从前序与中序遍历序列构造二叉树 | 1 | https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal | 260 | | 783. 二叉搜索树节点最小距离 | 1 | https://leetcode-cn.com/problems/minimum-distance-between-bst-nodes | 261 | | 剑指 Offer 42. 连续子数组的最大和 | 1 | https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof | 262 | | 55. 跳跃游戏 | 1 | https://leetcode-cn.com/problems/jump-game | 263 | | 2. 两数相加 | 1 | https://leetcode-cn.com/problems/add-two-numbers | 264 | | 1410. HTML 实体解析器 | 1 | https://leetcode-cn.com/problems/html-entity-parser | 265 | | 125. 验证回文串 | 1 | https://leetcode-cn.com/problems/valid-palindrome | 266 | | 12. 整数转罗马数字 | 1 | https://leetcode-cn.com/problems/integer-to-roman | 267 | | 45. 跳跃游戏 II | 1 | https://leetcode-cn.com/problems/jump-game-ii | 268 | | 43. 字符串相乘 | 1 | https://leetcode-cn.com/problems/multiply-strings | 269 | --------------------------------------------------------------------------------