diff --git "a/\346\213\233\350\201\230\350\246\201\346\261\202.md" b/Jobs.md similarity index 100% rename from "\346\213\233\350\201\230\350\246\201\346\261\202.md" rename to Jobs.md diff --git a/ToDo.md b/ToDo.md index 0f1fd2ff..591788dd 100644 --- a/ToDo.md +++ b/ToDo.md @@ -42,7 +42,75 @@ Updates Log - 机器学习实战-部分代码(K-Means等) - 判断相似二叉树及优化 - `max(x, y)` 的期望 - +- n个[0,n)的数,求每个数的出现次数(不能开辟额外空间) +- 加速网络收敛的方法 +- 回归树、基尼指数 +- 长短地址转换 +- 直方图蓄水问题 + ``` + int foo(vector ns) { + + vector > dp(ns.size(), vector(2, 0)); + + for (int i = 1; i lo) + lo = max(lo, ns[j]); + } + dp[i][0] = lo; + + for (int k = i+1; k < ns.size(); k++) { + if (ns[k] > hi) + hi = max(hi, ns[k]); + } + dp[i][1] = hi; + } + + int ret = 0; + for (int i=1; i < ns.size()-1; i++) { + int mx = min(dp[i][0], dp[i][1]); + if (mx > ns[i]) + ret += mx - ns[i]; + } + + return ret; + } + + int bar(vector ns) { + + int n = ns.size(); + vector dp_fw(ns); + vector dp_bw(ns); + + int lo = ns[0]; + for (int i=1; i=0; i--) { + dp[i] = max(dp[i+1], dp[i]); + } + + int ret = 0; + for (int i=1; i < n-1; i++) { + int mx = min(dp_fw[i], dp_bw[i]); + ret += mx - ns[i]; + } + } + ``` +- 判断两个链表是否相交(CSDN) + +## 2018-9-3 +- 深度学习基础 - 正则化 - Batch Normalization(修订) + +## 2018-9-2 +- 集成学习专题整理 + +## 2018-9-1 +- 笔试面经 - 头条 4 面小结 +- 算法 - 字符串 - 进制转换/长短地址转换 +- 算法 - LeetCode - 连续子数组和 ## 2018-8-31 - 数据结构 - 字符串 - 中缀表达式转后缀表达式(逆波兰式) @@ -126,7 +194,7 @@ Updates Log ## 2018-8-20 - DP-鹰蛋问题 -- DL-专题-优化算法 +- 基础-术语表 - 指数衰减平均的说明 - LeetCode - 数据-双指针 diff --git "a/assets/TIM\346\210\252\345\233\27620180903222433.png" "b/assets/TIM\346\210\252\345\233\27620180903222433.png" new file mode 100644 index 00000000..a478e2e3 Binary files /dev/null and "b/assets/TIM\346\210\252\345\233\27620180903222433.png" differ diff --git "a/assets/TIM\346\210\252\345\233\27620180903224842.png" "b/assets/TIM\346\210\252\345\233\27620180903224842.png" new file mode 100644 index 00000000..032beed7 Binary files /dev/null and "b/assets/TIM\346\210\252\345\233\27620180903224842.png" differ diff --git "a/assets/\345\205\254\345\274\217_20180902220459.png" "b/assets/\345\205\254\345\274\217_20180902220459.png" new file mode 100644 index 00000000..4d3919d6 Binary files /dev/null and "b/assets/\345\205\254\345\274\217_20180902220459.png" differ diff --git "a/assets/\345\205\254\345\274\217_20180903203229.png" "b/assets/\345\205\254\345\274\217_20180903203229.png" new file mode 100644 index 00000000..fd6b2cbf Binary files /dev/null and "b/assets/\345\205\254\345\274\217_20180903203229.png" differ diff --git "a/assets/\345\205\254\345\274\217_20180903210625.png" "b/assets/\345\205\254\345\274\217_20180903210625.png" new file mode 100644 index 00000000..d4c4dea0 Binary files /dev/null and "b/assets/\345\205\254\345\274\217_20180903210625.png" differ diff --git "a/assets/\345\205\254\345\274\217_20180903212935.png" "b/assets/\345\205\254\345\274\217_20180903212935.png" new file mode 100644 index 00000000..902984e9 Binary files /dev/null and "b/assets/\345\205\254\345\274\217_20180903212935.png" differ diff --git "a/assets/\345\205\254\345\274\217_20180903213109.png" "b/assets/\345\205\254\345\274\217_20180903213109.png" new file mode 100644 index 00000000..f58d520e Binary files /dev/null and "b/assets/\345\205\254\345\274\217_20180903213109.png" differ diff --git "a/assets/\345\205\254\345\274\217_20180903213410.png" "b/assets/\345\205\254\345\274\217_20180903213410.png" new file mode 100644 index 00000000..30b9f50c Binary files /dev/null and "b/assets/\345\205\254\345\274\217_20180903213410.png" differ diff --git "a/assets/\345\205\254\345\274\217_20180903220828.png" "b/assets/\345\205\254\345\274\217_20180903220828.png" new file mode 100644 index 00000000..02baec21 Binary files /dev/null and "b/assets/\345\205\254\345\274\217_20180903220828.png" differ diff --git "a/assets/\345\205\254\345\274\217_20180903223427.png" "b/assets/\345\205\254\345\274\217_20180903223427.png" new file mode 100644 index 00000000..e0d669b8 Binary files /dev/null and "b/assets/\345\205\254\345\274\217_20180903223427.png" differ diff --git "a/assets/\345\205\254\345\274\217_20180903224323.png" "b/assets/\345\205\254\345\274\217_20180903224323.png" new file mode 100644 index 00000000..91adaf42 Binary files /dev/null and "b/assets/\345\205\254\345\274\217_20180903224323.png" differ diff --git "a/assets/\345\205\254\345\274\217_20180903224557.png" "b/assets/\345\205\254\345\274\217_20180903224557.png" new file mode 100644 index 00000000..33e76bc4 Binary files /dev/null and "b/assets/\345\205\254\345\274\217_20180903224557.png" differ diff --git a/code/algorithm/Viterbi.py b/code/algorithm/Viterbi.py new file mode 100644 index 00000000..45e1ee20 --- /dev/null +++ b/code/algorithm/Viterbi.py @@ -0,0 +1,27 @@ +Weather = ('Rainy', 'Sunny') +Activity = ('walk', 'shop', 'clean') + +obs = list(range(len(Activity))) # 观测序列 +states_h = list(range(len(Weather))) # 隐状态 + +# 初始概率(隐状态) +start_p = [0.6, 0.4] +# 转移概率(隐状态) +trans_p = [[0.7, 0.3], + [0.4, 0.6]] +# 发射概率(隐状态表现为显状态的概率) +emit_p = [[0.1, 0.4, 0.5], + [0.6, 0.3, 0.1]] + + +def viterbi(obs, states_h, start_p, trans_p, emit_p): + """维特比算法""" + dp = [[0.0] * len(states_h)] * len(obs) + path = [[0] * len(obs)] * len(states_h) + + # 初始化 + for i in start_p: + dp[0][i] = states_h[i] * emit_p[i][obs[0]] + path[i][0] = i + + diff --git a/code/my_tensorflow/src/layers/cnn.py b/code/my_tensorflow/src/layers/cnn.py index 345b610a..5168b29f 100644 --- a/code/my_tensorflow/src/layers/cnn.py +++ b/code/my_tensorflow/src/layers/cnn.py @@ -6,7 +6,7 @@ import tensorflow as tf from ..activations import relu -from ..utils import get_wb +from ..utils import get_wb, get_shape # TODO(huay) @@ -21,8 +21,8 @@ def conv2d(x, kernel_size, out_channels, name=None, reuse=None): """2-D 卷积层 - Input shape: [batch_size, in_w, in_h, in_channels] - Output shape: [batch_size, out_w, out_h, out_channels] + Input shape: [batch_size, in_h, in_w, in_channels] + Output shape: [batch_size, out_h, out_w, out_channels] Args: x(tf.Tensor): @@ -45,8 +45,8 @@ def conv2d(x, kernel_size, out_channels, assert len(kernel_size) == 2 assert len(strides) == 4 - in_channels = int(x.get_shape()[-1]) - kernel_shape = list(kernel_size) + [in_channels, out_channels] + in_channels = get_shape(x)[-1] + kernel_shape = list(kernel_size) + [in_channels, out_channels] # [kernel_h, kernel_w, in_channels, out_channels] with tf.variable_scope(name or "conv2d", reuse=reuse): W, b = get_wb(kernel_shape) diff --git a/code/my_tensorflow/src/layers/match/attention_flow.py b/code/my_tensorflow/src/layers/match/attention_flow.py index a5b4b363..5df55ff7 100644 --- a/code/my_tensorflow/src/layers/match/attention_flow.py +++ b/code/my_tensorflow/src/layers/match/attention_flow.py @@ -91,7 +91,7 @@ def attention_flow(h, u, T=None, J=None, d=None, name=None, reuse=None): W_s = get_w([3 * d, 1]) # [3d, 1] # similarity matrix - S = tf.reshape(tf.einsum("ntjd,do->ntjo", h_u_hu, W_s), [-1, T, J]) + S = tf.reshape(tf.einsum("ntjd,do->ntjo", h_u_hu, W_s), [-1, T, J]) # [N, T, J] # 以上操作等价于 # S = tf.reshape(tf.matmul(tf.reshape(h_u_hu, [-1, 3*d]), W_s), [-1, T, J]) diff --git a/code/my_tensorflow/src/utils/__init__.py b/code/my_tensorflow/src/utils/__init__.py index 21938771..a17b7e3c 100644 --- a/code/my_tensorflow/src/utils/__init__.py +++ b/code/my_tensorflow/src/utils/__init__.py @@ -40,7 +40,7 @@ def get_wb(shape, w_initializer=truncated_normal, b_initializer=zeros, w_regularizer=l2_regularizer, - b_regularizer=l2_regularizer, + b_regularizer=None, # 一般不对偏置做权重惩罚,可能会导致欠拟合 name=None): """""" name = "" if name is None else name + '_' diff --git "a/papers/[2006]-CNN\345\206\205\351\203\250\345\256\236\347\216\260\357\274\210Caffe\357\274\211.pdf" "b/papers/[2006].CNN\345\206\205\351\203\250\345\256\236\347\216\260\357\274\210Caffe\357\274\211.pdf" similarity index 100% rename from "papers/[2006]-CNN\345\206\205\351\203\250\345\256\236\347\216\260\357\274\210Caffe\357\274\211.pdf" rename to "papers/[2006].CNN\345\206\205\351\203\250\345\256\236\347\216\260\357\274\210Caffe\357\274\211.pdf" diff --git a/papers/[2014]-GRU.pdf b/papers/[2014].GRU.pdf similarity index 100% rename from papers/[2014]-GRU.pdf rename to papers/[2014].GRU.pdf diff --git a/papers/[2015].Batch_Normalization.v3.pdf b/papers/[2015].Batch_Normalization.v3.pdf new file mode 100644 index 00000000..dd8cd67f Binary files /dev/null and b/papers/[2015].Batch_Normalization.v3.pdf differ diff --git a/papers/[2015]-CharCNN.pdf b/papers/[2015].CharCNN.pdf similarity index 100% rename from papers/[2015]-CharCNN.pdf rename to papers/[2015].CharCNN.pdf diff --git a/papers/[2015]-Highway.pdf b/papers/[2015].Highway.pdf similarity index 100% rename from papers/[2015]-Highway.pdf rename to papers/[2015].Highway.pdf diff --git "a/papers/[2015]-PointerNet\357\274\210\346\214\207\351\222\210\347\275\221\347\273\234\357\274\211.v2.pdf" "b/papers/[2015].PointerNet\357\274\210\346\214\207\351\222\210\347\275\221\347\273\234\357\274\211.v2.pdf" similarity index 100% rename from "papers/[2015]-PointerNet\357\274\210\346\214\207\351\222\210\347\275\221\347\273\234\357\274\211.v2.pdf" rename to "papers/[2015].PointerNet\357\274\210\346\214\207\351\222\210\347\275\221\347\273\234\357\274\211.v2.pdf" diff --git "a/papers/[2015]-\345\272\217\345\210\227\346\240\207\346\263\250\357\274\210Bi-LSTM+CRF\357\274\211.pdf" "b/papers/[2015].\345\272\217\345\210\227\346\240\207\346\263\250\357\274\210Bi-LSTM+CRF\357\274\211.pdf" similarity index 100% rename from "papers/[2015]-\345\272\217\345\210\227\346\240\207\346\263\250\357\274\210Bi-LSTM+CRF\357\274\211.pdf" rename to "papers/[2015].\345\272\217\345\210\227\346\240\207\346\263\250\357\274\210Bi-LSTM+CRF\357\274\211.pdf" diff --git "a/project/NLP-\344\272\213\345\256\236\347\261\273\351\227\256\347\255\224\350\257\204\346\265\213.md" "b/project/NLP-\344\272\213\345\256\236\347\261\273\351\227\256\347\255\224\350\257\204\346\265\213.md" new file mode 100644 index 00000000..732b8cdc --- /dev/null +++ "b/project/NLP-\344\272\213\345\256\236\347\261\273\351\227\256\347\255\224\350\257\204\346\265\213.md" @@ -0,0 +1,40 @@ +NLP-事实类问答评测 +=== + +Index +--- + + +- [任务描述](#任务描述) +- [基础模型 - BiDAF](#基础模型---bidaf) + + + +## 任务描述 +- 针对每个问题 q,给定与之对应的若干候选答案篇章 a1,a2,…,an,要求设计算法从候选篇章中**抽取合适的词语、短语或句子**,形成一段正确、完整、简洁的文本,作为预测答案 apred,目标是 apred 能够正确、完整、简洁地回答问题 q。 + +- **示例** + ``` + 问题: 中国最大的内陆盆地是哪个 + 答案:塔里木盆地 + 材料: + 1. 中国新疆的塔里木盆地,是世界上最大的内陆盆地,东西长约1500公里,南北最宽处约600公里。盆地底部海拔1000米左右,面积53万平方公里。 + 2. 中国最大的固定、半固定沙漠天山与昆仑山之间又有塔里木盆地,面积53万平方公里,是世界最大的内陆盆地。盆地中部是塔克拉玛干大沙漠,面积33.7万平方公里,为世界第二大流动性沙漠。 + ``` + +- **数据下载** + - [CIPS-SOGOU问答比赛](http://task.www.sogou.com/cips-sogou_qa/) (少量) + - [百度 WebQA V2.0](http://ai.baidu.com/broad/download) + - [百度 WebQA V1.0 预处理版](https://pan.baidu.com/s/1SADkZjF7kdH2Qk37LTdXKw)(密码: kc2q) + > [【语料】百度的中文问答数据集WebQA](https://spaces.ac.cn/archives/4338) - 科学空间|Scientific Spaces + + +## 基础模型 - BiDAF +> [1611.01603] [Bidirectional Attention Flow for Machine Comprehension](https://arxiv.org/abs/1611.01603) + +**5/6 层模型结构** +1. 嵌入层(字+词) +1. Encoder 层 +1. Attention 交互层 +1. Decoder 层 +1. 输出层 \ No newline at end of file diff --git a/project/ref/[2009].The_BellKor_Solution_to_the_Netflix_Grand_Prize.pdf b/project/ref/[2009].The_BellKor_Solution_to_the_Netflix_Grand_Prize.pdf new file mode 100644 index 00000000..2d87af40 Binary files /dev/null and b/project/ref/[2009].The_BellKor_Solution_to_the_Netflix_Grand_Prize.pdf differ diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/Base-A-\346\234\257\350\257\255\350\241\250.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/Base-A-\346\234\257\350\257\255\350\241\250.md" new file mode 100644 index 00000000..d76b8db7 --- /dev/null +++ "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/Base-A-\346\234\257\350\257\255\350\241\250.md" @@ -0,0 +1,41 @@ +术语表 +=== + +Index +--- + + +- [指数加权平均(指数衰减平均)](#指数加权平均指数衰减平均) + - [偏差修正](#偏差修正) + + + +## 指数加权平均(指数衰减平均) +> [什么是指数加权平均、偏差修正? - 郭耀华](http://www.cnblogs.com/guoyaohua/p/8544835.html) - 博客园 +- **加权平均** + - 假设 `θi` 的权重分别为 `ρi`,则 `θi` 的加权平均为: +
+ +- **指数加权平均** +
+ + > 注意到越久前的记录其权重呈**指数衰减**,因此指数加权平均也称**指数衰减平均** +- **示例**:设 `ρ=0.9, v0=0` + +
+ + > 其中 `v_t` 可以**近似**认为是最近 `1/1-ρ` 个值的滑动平均(`ρ=0.9`时,`0.1 * 0.9^9 ≈ 0.038`),更久前的记录其权重已近似为 0。 + +### 偏差修正 +- 指数加权平均在前期会存在较大的**误差** +
+ + - 注意到只有当 `t -> ∞` 时,所有权重的和才接近 1,当 `t` 比较小时,并不是标准的加权平均 +- **示例**:设 `ρ=0.9, v0=0` +
+ + - 当 `t` 较小时,与希望的加权平均结果差距较大 +- **引入偏差修正** +
+ + - 偏差修正只对**前期**的有修正效果,**后期**当 `t` 逐渐增大时 `1-ρ^t -> 1`,将不再影响 `v_t`,与期望相符 \ No newline at end of file diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/Base-B-\344\270\223\351\242\230-\345\267\245\345\205\267\345\272\223.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/Base-B-\345\267\245\345\205\267\345\272\223.md" similarity index 100% rename from "\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/Base-B-\344\270\223\351\242\230-\345\267\245\345\205\267\345\272\223.md" rename to "\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/Base-B-\345\267\245\345\205\267\345\272\223.md" diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-A-\346\267\261\345\272\246\345\255\246\344\271\240\345\237\272\347\241\200.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-A-\346\267\261\345\272\246\345\255\246\344\271\240\345\237\272\347\241\200.md" index ea279250..e875b2f2 100644 --- "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-A-\346\267\261\345\272\246\345\255\246\344\271\240\345\237\272\347\241\200.md" +++ "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-A-\346\267\261\345\272\246\345\255\246\344\271\240\345\237\272\347\241\200.md" @@ -34,6 +34,9 @@ Index - [Batch Normalization(批标准化)](#batch-normalization批标准化) - [动机](#动机) - [基本原理](#基本原理) + - [BN 在训练和测试时分别是怎么做的?](#bn-在训练和测试时分别是怎么做的) + - [为什么训练时不采用移动平均?](#为什么训练时不采用移动平均) + - [相关阅读](#相关阅读) - [L1/L2 范数正则化](#l1l2-范数正则化) - [L1/L2 范数的作用、异同](#l1l2-范数的作用异同) - [为什么 L1 和 L2 正则化可以防止过拟合?](#为什么-l1-和-l2-正则化可以防止过拟合) @@ -57,6 +60,7 @@ Index - 反映在**评价指标**上,就是模型在训练集上表现良好,但是在测试集和新数据上表现一般(**泛化能力差**); ## 降低过拟合风险的方法 +> 所有为了**减少测试误差**的策略统称为**正则化方法**,这些方法可能会以增大训练误差为代价。 - **数据增强** - 图像:平移、旋转、缩放 @@ -250,7 +254,10 @@ Index # 正则化 ## Batch Normalization(批标准化) -- BN 是一种正则化方法,目的是**加速**网络的训练,并**防止过拟合**。 +- BN 是一种**正则化**方法(减少泛化误差),主要作用有: + - **加速网络的训练**(缓解梯度消失,支持更大的学习率) + - **防止过拟合** + - 降低了**参数初始化**的要求。 ### 动机 - **训练的本质是学习数据分布**。如果训练数据与测试数据的分布不同会**降低**模型的**泛化能力**。因此,应该在开始训练前对所有输入数据做归一化处理。 @@ -267,7 +274,45 @@ Index
其中 `γ` 和 `β` 为可训练参数。 - + +**小结** +- 以上过程可归纳为一个 **`BN(x)` 函数**: +
+ + 其中 +
+ +- **完整算法**: +
+ +### BN 在训练和测试时分别是怎么做的? +- **训练时**每次会传入一批数据,做法如前述; +- 当**测试**或**预测时**,每次可能只会传入**单个数据**,此时模型会使用**全局统计量**代替批统计量; + - 训练每个 batch 时,都会得到一组`(均值,方差)`; + - 所谓全局统计量,就是对这些均值和方差求其对应的数学期望; + - 具体计算公式为: +
+ + > 其中 `μ_i` 和 `σ_i` 分别表示第 i 轮 batch 保存的均值和标准差;`m` 为 batch_size,系数 `m/(m-1)` 用于计算**无偏方差估计** + >> 原文称该方法为**移动平均**(moving averages) + +- 此时,`BN(x)` 调整为: +
+ +- **完整算法**: +
+ +#### 为什么训练时不采用移动平均? +> 群里一位同学的面试题 +- 使用 BN 的目的就是为了保证每批数据的分布稳定,使用全局统计量反而违背了这个初衷; +- BN 的作者认为在训练时采用移动平均可能会与梯度优化存在冲突; + > 【**原文**】"It is natural to ask whether we could simply **use the moving averages** µ, σ to perform the normalization **during training**, since this would remove the dependence of the normalized activations on the other example in the minibatch. This, however, has been observed to lead to the model blowing up. As argued in [6], such use of moving averages would cause the gradient optimization and the normalization to counteract each other. For example, the gradient step may increase a bias or scale the convolutional weights, in spite of the fact that the normalization would cancel the effect of these changes on the loss. This would result in unbounded growth of model parameters without actually improving the loss. It is thus crucial to use the minibatch moments, and to backpropagate through them." + >> [1702.03275] [Batch Renormalization](https://arxiv.org/abs/1702.03275) + +### 相关阅读 +- [深入理解Batch Normalization批标准化 - 郭耀华](https://www.cnblogs.com/guoyaohua/p/8724433.html) - 博客园 +- [深度学习中批归一化的陷阱](http://ai.51cto.com/art/201705/540230.htm) - 51CTO + ## L1/L2 范数正则化 > 《深度学习》 7.1.1 L2 参数正则化 & 7.1.2 - L1 参数正则化 diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-B-\344\270\223\351\242\230-CNN.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-B-\344\270\223\351\242\230-CNN.md" index 7649c8f0..236b7ade 100644 --- "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-B-\344\270\223\351\242\230-CNN.md" +++ "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-B-\344\270\223\351\242\230-CNN.md" @@ -5,7 +5,9 @@ Index --- -- [为什么使用 CNN 代替 RNN?](#为什么使用-cnn-代替-rnn) +- [CNN 的基本特征](#cnn-的基本特征) + - [动机](#动机) + - [意义](#意义) - [卷积的内部实现](#卷积的内部实现) - [Theano 中的实现](#theano-中的实现) - [Caffe 中的实现](#caffe-中的实现) @@ -20,29 +22,25 @@ Index - [门卷积](#门卷积) - [门卷积的作用](#门卷积的作用) - [门卷积是如何防止梯度消失的](#门卷积是如何防止梯度消失的) +- [其他](#其他) + - [为什么使用 CNN 代替 RNN?](#为什么使用-cnn-代替-rnn) - [Reference](#reference) -## 为什么使用 CNN 代替 RNN? -> [关于序列建模,是时候抛弃RNN和LSTM了](https://www.jiqizhixin.com/articles/041503) | 机器之心 [[原文]](https://towardsdatascience.com/the-fall-of-rnn-lstm-2d1594c74ce0) +## CNN 的基本特征 +- **稀疏交互**和**参数共享** -**RNN/LSTM 存在的问题(3)** -1. RNN 与目前的硬件加速技术不匹配 - > 训练 RNN 和 LSTM 非常困难,因为计算能力受到内存和带宽等的约束。简单来说,每个 LSTM 单元需要四个仿射变换,且每一个时间步都需要运行一次,这样的仿射变换会要求非常多的内存带宽。**添加更多的计算单元很容易,但添加更多的内存带宽却很难**——这与目前的硬件加速技术不匹配,一个可能的解决方案就是让计算在存储器设备中完成。 -1. RNN 容易发生**梯度消失**,包括 LSTM - > 在长期信息访问当前处理单元之前,需要按顺序地通过所有之前的单元。这意味着它很容易遭遇梯度消失问题;LSTM 一定程度上解决了这个问题,但 LSTM 网络中依然存在顺序访问的序列路径;实际上,现在这些路径甚至变得更加复杂 -1. **注意力机制模块**(记忆模块)的应用 - - 注意力机制模块可以同时**前向预测**和**后向回顾**。 - - **分层注意力编码器**(Hierarchical attention encoder) -
+### 动机 +- **局部特征**——卷积的核心思想 +- 平移等变性 - - 分层注意力模块通过一个**层次结构**将过去编码向量**汇总**到一个**上下文向量**`C_t` ——这是一种更好的**观察过去信息**的方式(观点) - - **分层结构**可以看做是一棵**树**,其路径长度为 `logN`,而 RNN/LSTM 则相当于一个**链表**,其路径长度为 `N`,如果序列足够长,那么可能 `N >> logN` - > [放弃 RNN/LSTM 吧,因为真的不好用!望周知~](https://blog.csdn.net/heyc861221/article/details/80174475) - CSDN博客 - -**任务角度(1)** -1. 从任务本身考虑,我认为也是 CNN 更有利,LSTM 因为能记忆比较长的信息,所以在推断方面有不错的表现(直觉);但是在事实类问答中,并不需要复杂的推断,答案往往藏在一个 **n-gram 短语**中,而 CNN 能很好的对 n-gram 建模。 +### 意义 +- **提高统计效率** + - 当处理一张图像时,输入的图像可能包含成千上万个像素点,但是我们可以通过只占用几十到上百个像素点的核来检测一些局部但有意义的特征,例如图像的边缘。 +- **减少参数数量** + - 减少存储需求 + - 加速计算 ## 卷积的内部实现 @@ -233,5 +231,28 @@ Index > > [基于CNN的阅读理解式问答模型:DGCNN](https://kexue.fm/archives/5409#门机制) - 科学空间|Scientific Spaces +## 其他 + +### 为什么使用 CNN 代替 RNN? +> [关于序列建模,是时候抛弃RNN和LSTM了](https://www.jiqizhixin.com/articles/041503) | 机器之心 [[原文]](https://towardsdatascience.com/the-fall-of-rnn-lstm-2d1594c74ce0) + +**RNN/LSTM 存在的问题(3)** +1. RNN 与目前的硬件加速技术不匹配 + > 训练 RNN 和 LSTM 非常困难,因为计算能力受到内存和带宽等的约束。简单来说,每个 LSTM 单元需要四个仿射变换,且每一个时间步都需要运行一次,这样的仿射变换会要求非常多的内存带宽。**添加更多的计算单元很容易,但添加更多的内存带宽却很难**——这与目前的硬件加速技术不匹配,一个可能的解决方案就是让计算在存储器设备中完成。 +1. RNN 容易发生**梯度消失**,包括 LSTM + > 在长期信息访问当前处理单元之前,需要按顺序地通过所有之前的单元。这意味着它很容易遭遇梯度消失问题;LSTM 一定程度上解决了这个问题,但 LSTM 网络中依然存在顺序访问的序列路径;实际上,现在这些路径甚至变得更加复杂 +1. **注意力机制模块**(记忆模块)的应用 + - 注意力机制模块可以同时**前向预测**和**后向回顾**。 + - **分层注意力编码器**(Hierarchical attention encoder) +
+ + - 分层注意力模块通过一个**层次结构**将过去编码向量**汇总**到一个**上下文向量**`C_t` ——这是一种更好的**观察过去信息**的方式(观点) + - **分层结构**可以看做是一棵**树**,其路径长度为 `logN`,而 RNN/LSTM 则相当于一个**链表**,其路径长度为 `N`,如果序列足够长,那么可能 `N >> logN` + > [放弃 RNN/LSTM 吧,因为真的不好用!望周知~](https://blog.csdn.net/heyc861221/article/details/80174475) - CSDN博客 + +**任务角度(1)** +1. 从任务本身考虑,我认为也是 CNN 更有利,LSTM 因为能记忆比较长的信息,所以在推断方面有不错的表现(直觉);但是在事实类问答中,并不需要复杂的推断,答案往往藏在一个 **n-gram 短语**中,而 CNN 能很好的对 n-gram 建模。 + + ## Reference - [首次超越LSTM : Facebook 门卷积网络新模型能否取代递归模型?](http://www.dataguru.cn/article-10314-1.html) - 炼数成金 \ No newline at end of file diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-B-\344\270\223\351\242\230-RNN.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-B-\344\270\223\351\242\230-RNN.md" index 257f2148..141092ac 100644 --- "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-B-\344\270\223\351\242\230-RNN.md" +++ "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-B-\344\270\223\351\242\230-RNN.md" @@ -74,7 +74,7 @@ Index ### RNN 相比前馈网络/CNN 有什么特点? - **前馈网络/CNN 处理序列数据时存在的问题:** - - 一般的**前馈网络**,通常接受一个**定长**向量作为输入,然后输出一个定长的表示; + - 一般的**前馈网络**,通常接受一个**定长**向量作为输入,然后输出一个定长的表示;它需要一次性接收所有的输入,因而忽略了序列中的顺序信息; - **CNN** 在处理**变长序列**时,通过**滑动窗口+池化**的方式将输入转化为一个**定长的向量表示**,这样做可以捕捉到序列中的一些**局部特征**,但是很难学习到序列间的**长距离依赖**。 - **RNN 处理时序数据时的优势:** diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/Base-A-\344\270\223\351\242\230-\344\274\230\345\214\226\347\256\227\346\263\225.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-B-\344\270\223\351\242\230-\344\274\230\345\214\226\347\256\227\346\263\225.md" similarity index 98% rename from "\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/Base-A-\344\270\223\351\242\230-\344\274\230\345\214\226\347\256\227\346\263\225.md" rename to "\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-B-\344\270\223\351\242\230-\344\274\230\345\214\226\347\256\227\346\263\225.md" index 5a0654ff..d88ef317 100644 --- "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/Base-A-\344\270\223\351\242\230-\344\274\230\345\214\226\347\256\227\346\263\225.md" +++ "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-B-\344\270\223\351\242\230-\344\274\230\345\214\226\347\256\227\346\263\225.md" @@ -156,9 +156,7 @@ Index > Hinton, 2012 - RMSProp 主要是为了解决 AdaGrad 方法中**学习率过度衰减**的问题—— AdaGrad 根据平方梯度的**整个历史**来收缩学习率,可能使得学习率在达到局部最小值之前就变得太小而难以继续训练; - RMSProp 使用**指数衰减平均**(递归定义)以丢弃遥远的历史,使其能够在找到某个“凸”结构后快速收敛;此外,RMSProp 还加入了一个超参数 `ρ` 用于控制衰减速率。 - > 所谓**指数衰减平均**,可以参考以下公式(**仅做参考**): - >
- > 可以看到越久之前的历史,影响会越小 + > ./术语表/[指数衰减平均](./Base-A-术语表.md#指数加权平均指数衰减平均) - 具体来说(对比 AdaGrad 的算法描述),即修改 `r` 为
记 @@ -193,6 +191,7 @@ Index - 除了加入**历史梯度平方的指数衰减平均**(`r`)外, - 还保留了**历史梯度的指数衰减平均**(`s`),相当于**动量**。 - Adam 行为就像一个带有摩擦力的小球,在误差面上倾向于平坦的极小值。 + > ./术语表/[指数衰减平均](./Base-A-术语表.md#指数加权平均指数衰减平均) - **Adam 算法描述**
diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-C-\344\270\223\351\242\230-Attention.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-C-\344\270\223\351\242\230-Attention.md" new file mode 100644 index 00000000..1a561b3c --- /dev/null +++ "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-C-\344\270\223\351\242\230-Attention.md" @@ -0,0 +1,23 @@ +DL-专题-Attention +=== + +Index +--- + + +- [Self-Attention](#self-attention) +- [Cross-Attention](#cross-attention) + + + +## Self-Attention +- **Self-Attention**(自注意力)是一种将**单个序列内部的不同位置**联系起来的注意力机制,用于计算序列的**表示**。 + > Self-Attention 也称 Intra-Attention(内部注意力) +- Self-Attention 的一些应用: + - 阅读理解 + - 自动摘要 + - 学习任务无关的 Sentence Embedding + > ./专题-序列建模/[学习任务无关的 Sentence Embedding](./DL-C-专题-序列建模.md#学习任务无关的-sentence-embedding) + + +## Cross-Attention \ No newline at end of file diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-C-\344\270\223\351\242\230-\345\272\217\345\210\227\345\273\272\346\250\241.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-C-\344\270\223\351\242\230-\345\272\217\345\210\227\345\273\272\346\250\241.md" index 69424679..361f52c5 100644 --- "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-C-\344\270\223\351\242\230-\345\272\217\345\210\227\345\273\272\346\250\241.md" +++ "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/DL-C-\344\270\223\351\242\230-\345\272\217\345\210\227\345\273\272\346\250\241.md" @@ -16,6 +16,8 @@ Index - [维特比(Viterbi)算法 TODO](#维特比viterbi算法-todo) - [其他最短路径算法](#其他最短路径算法) - [构建 Seq2Seq 一般做法](#构建-seq2seq-一般做法) +- [序列的表示学习](#序列的表示学习) + - [学习任务无关的 Sentence Embedding](#学习任务无关的-sentence-embedding) - [CNN 与序列建模](#cnn-与序列建模) - [一维卷积](#一维卷积) - [时间卷积网络(TCN)](#时间卷积网络tcn) @@ -104,6 +106,20 @@ Index - **Attention 机制** > [Attention 专题](./DL-C-专题-Attention.md) + +## 序列的表示学习 +- 序列的表示学习指学习单个序列的特征表示,通常作为另一个任务的子过程,或者用于迁移学习等。 +- 整个学习的过程相当于 Seq2Seq 中的 Encoder 部分 + +### 学习任务无关的 Sentence Embedding +> [1703.03130] [A Structured Self-attentive Sentence Embedding](https://arxiv.org/abs/1703.03130) + +**模型基本结构**,更多细节参考原文 +- 待学习的句子 + + + + ## CNN 与序列建模 - 一般认为 CNN 擅长处理**网格结构的数据**,比如图像(二维像素网络) - 卷积层试图将神经网络中的每一小块进行更加深入的分析,从而得出抽象程度更高的特征。 diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/ML-\346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/ML-A-\346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.md" similarity index 56% rename from "\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/ML-\346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.md" rename to "\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/ML-A-\346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.md" index fe38f11c..368c4cc0 100644 --- "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/ML-\346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.md" +++ "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/ML-A-\346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.md" @@ -5,55 +5,77 @@ Index --- -- [过拟合与欠拟合](#过拟合与欠拟合) - [偏差与方差](#偏差与方差) - - [偏差与方差的权衡](#偏差与方差的权衡) + - [导致偏差和方差的原因](#导致偏差和方差的原因) + - [深度学习中的偏差与方差](#深度学习中的偏差与方差) + - [偏差/方差 与 Boosting/Bagging](#偏差方差-与-boostingbagging) + - [偏差与方差的计算公式](#偏差与方差的计算公式) + - [偏差与方差的权衡(过拟合与模型复杂度的权衡)](#偏差与方差的权衡过拟合与模型复杂度的权衡) - [生成模型与判别模型](#生成模型与判别模型) - [先验概率与后验概率](#先验概率与后验概率) -## 过拟合与欠拟合 -> ./深度学习基础/[过拟合与欠拟合](./DL-A-深度学习基础.md#过拟合与欠拟合) ## 偏差与方差 > 《机器学习》 2.5 偏差与方差 - 周志华 -- **偏差**与**方差**都是用于衡量一个模型**泛化能力**的指标 - - 偏差用于描述模型的**拟合能力**;方差用于描述模型的**稳定性** -
- -- 模型的**泛化误差**(Error)可分解为偏差、方差与噪声之和。 +- **偏差**与**方差**分别是用于衡量一个模型**泛化误差**的两个方面; + - 模型的**偏差**,指的是模型预测的**期望值**与**真实值**之间的差; + - 模型的**方差**,指的是模型预测的**期望值**与**预测值**之间的差平方和; +- 在**监督学习**中,模型的**泛化误差**可**分解**为偏差、方差与噪声之和。
- - 记在**训练集 D** 上学得的模型为 -
+- **偏差**用于描述模型的**拟合能力**;
+ **方差**用于描述模型的**稳定性**。 +
+ +### 导致偏差和方差的原因 +- **偏差**通常是由于我们对学习算法做了**错误的假设**,或者模型的复杂度不够; + - 比如真实模型是一个二次函数,而我们假设模型为一次函数,这就会导致偏差的增大(欠拟合); + - **由偏差引起的误差**通常在**训练误差**上就能体现,或者说训练误差主要是由偏差造成的 +- **方差**通常是由于**模型的复杂度相对于训练集过高**导致的; + - 比如真实模型是一个简单的二次函数,而我们假设模型是一个高次函数,这就会导致方差的增大(过拟合); + - **由方差引起的误差**通常体现在测试误差相对训练误差的**增量**上。 + +### 深度学习中的偏差与方差 +- 神经网络的拟合能力非常强,因此它的**训练误差**(偏差)通常较小; +- 但是过强的拟合能力会导致较大的方差,使模型的测试误差(**泛化误差**)增大; +- 因此深度学习的核心工作之一就是研究如何降低模型的泛化误差,这类方法统称为**正则化方法**。 + > ./深度学习基础/[正则化](./DL-A-深度学习基础.md#正则化) - 模型的**期望预测**为 -
- - - **偏差**(Bias) -
+### 偏差/方差 与 Boosting/Bagging +> ./集成学习专题/[Boosting/Bagging 与 偏差/方差 的关系](./ML-B-专题-集成学习.md#boostingbagging-与-偏差方差-的关系) - > **偏差**度量了学习算法的期望预测与真实结果的偏离程度,即刻画了学习算法本身的拟合能力; - - **方差**(Variance) -
+### 偏差与方差的计算公式 +- 记在**训练集 D** 上学得的模型为 +
- > **方差**度量了同样大小的**训练集的变动**所导致的学习性能的变化,即刻画了数据扰动所造成的影响(模型的稳定性); - - **噪声** -
+ 模型的**期望预测**为 +
+ +- **偏差**(Bias) +
- > **噪声**则表达了在当前任务上任何学习算法所能达到的期望泛化误差的下界,即刻画了学习问题本身的难度。 + > **偏差**度量了学习算法的期望预测与真实结果的偏离程度,即刻画了学习算法本身的拟合能力; +- **方差**(Variance) +
+ + > **方差**度量了同样大小的**训练集的变动**所导致的学习性能的变化,即刻画了数据扰动所造成的影响(模型的稳定性); + + +- **噪声**则表达了在当前任务上任何学习算法所能达到的期望泛化误差的下界,即刻画了学习问题本身的难度。 - “**偏差-方差分解**”表明模型的泛化能力是由算法的能力、数据的充分性、任务本身的难度共同决定的。 -### 偏差与方差的权衡 -- 偏差与方差是有冲突的,需要折衷(trade-off) +### 偏差与方差的权衡(过拟合与模型复杂度的权衡) +> ./深度学习基础/[过拟合与欠拟合](./DL-A-深度学习基础.md#过拟合与欠拟合) - 给定学习任务, - 当训练不足时,模型的**拟合能力不够**(数据的扰动不足以使模型产生显著的变化),此时**偏差**主导模型的泛化误差; - 随着训练的进行,模型的**拟合能力增强**(模型能够学习数据发生的扰动),此时**方差**逐渐主导模型的泛化误差; - 当训练充足后,模型的**拟合能力过强**(数据的轻微扰动都会导致模型产生显著的变化),此时即发生**过拟合**(训练数据自身的、非全局的特征也被模型学习了) -- 偏差和方差的关系和**模型容量**、**欠拟合**和**过拟合**的概念紧密相联 +- 偏差和方差的关系和**模型容量**(模型复杂度)、**欠拟合**和**过拟合**的概念紧密相联
- 当模型的容量增大(x 轴)时, 偏差(用点表示)随之减小,而方差(虚线)随之增大 diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/ML-B-\344\270\223\351\242\230-\351\233\206\346\210\220\345\255\246\344\271\240.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/ML-B-\344\270\223\351\242\230-\351\233\206\346\210\220\345\255\246\344\271\240.md" new file mode 100644 index 00000000..f8412b54 --- /dev/null +++ "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/ML-B-\344\270\223\351\242\230-\351\233\206\346\210\220\345\255\246\344\271\240.md" @@ -0,0 +1,129 @@ +ML-专题-集成学习 +=== + +Index +--- + + +- [集成学习基本问题](#集成学习基本问题) + - [集成学习的基本思想](#集成学习的基本思想) + - [集成学习为什么有效?](#集成学习为什么有效) + - [集成学习的基本策略](#集成学习的基本策略) + - [Boosting 方法](#boosting-方法) + - [Bagging 方法(Booststrap AGGregatING)](#bagging-方法booststrap-aggregating) + - [Stacking 方法](#stacking-方法) + - [为什么使用决策树作为基学习器?](#为什么使用决策树作为基学习器) + - [为什么不稳定的学习器更适合作为基学习器?](#为什么不稳定的学习器更适合作为基学习器) + - [还有哪些模型也适合作为基学习器?](#还有哪些模型也适合作为基学习器) + - [Bagging 方法中能使用线性分类器作为基学习器吗? Boosting 呢?](#bagging-方法中能使用线性分类器作为基学习器吗-boosting-呢) + - [Boosting/Bagging 与 偏差/方差 的关系](#boostingbagging-与-偏差方差-的关系) +- [AdaBoost 算法](#adaboost-算法) +- [GBDT 算法](#gbdt-算法) + + + +## 集成学习基本问题 +- 集成学习的核心是将多个 + +### 集成学习的基本思想 +- 结合多个学习器组合成一个性能更好的学习器 + +### 集成学习为什么有效? +- 不同的模型通常会在**测试集**上产生不同的误差;如果成员的误差是独立的,集成模型将显著地比其成员表现更好。 + +### 集成学习的基本策略 + +#### Boosting 方法 +- 基于**串行策略**:基学习器之间存在依赖关系,新的学习器需要根据上一个学习器生成。 +- **基本思路**: + - 先从**初始训练集**训练一个基学习器;初始训练集中各样本的权重是相同的; + - 根据上一个基学习器的表现,**调整样本权重**,使分类错误的样本得到更多的关注; + - 基于调整后的样本分布,训练下一个基学习器; + - 测试时,对各基学习器**加权**得到最终结果 +- **特点**: + - 每次学习都会使用全部训练样本 +- **代表算法**: + - [AdaBoost 算法](#adaboost-算法) + - [GBDT 算法](#gbdt-算法) + +#### Bagging 方法(Booststrap AGGregatING) +- 基于**并行策略**:基学习器之间不存在依赖关系,可同时生成。 +- **基本思路**: + - 利用**自助采样法**对训练集随机采样,重复进行 `T` 次; + - 基于每个采样集训练一个基学习器,并得到 `T` 个基学习器; + - 预测时,集体**投票决策****。 + > **自助采样法**:对 m 个样本的训练集,有放回的采样 m 次;此时,样本在 m 次采样中始终没被采样的概率约为 `0.368`,即每次自助采样只能采样到全部样本的 `63%` 左右。 +
+ +- **特点**: + - 训练每个基学习器时只使用一部分样本; + - 偏好**不稳定**的学习器作为基学习器; + > 所谓不稳定的学习器,指的是对**样本分布**较为敏感的学习器。 + +#### Stacking 方法 +- 基于**串行策略**:初级学习器与次级学习器之间存在依赖关系,初学习器的输出作为次级学习器的输入。 +- **基本思路**: + - 先从初始训练集训练 `T` 个**不同的初级学习器**; + - 利用每个初级学习器的**输出**构建一个**次级数据集**,该数据集依然使用初始数据集的标签; + - 根据新的数据集训练**次级学习器**; + - **多级学习器**的构建过程类似。 +> 周志华-《机器学习》中没有将 Stacking 方法当作一种集成策略,而是作为一种**结合策略**,比如**加权平均**和**投票**都属于结合策略。 + +- 为了降低过拟合的风险,一般会利用**交叉验证**的方法使不同的初级学习器在**不完全相同的子集**上训练 + ```tex + 以 k-折交叉验证为例: + - 初始训练集 D={(x_i, y_i)} 被划分成 D1, D2, .., Dk; + - 记 h_t 表示第 t 个学习器,并在除 Dj 外的数据上训练; + - 当 h_t 训练完毕后,有 z_it = h_t(x_i); + - T 个初级学习器在 x_i 上共产生 T 个输出; + - 这 T 个输出共同构成第 i 个次级训练数据 z_i = (z_i1, z_i2, ..., z_iT),标签依然为 y_i; + - 在 T 个初级学习器都训练完毕后,得到次级训练集 D'={(z_i, y_i)} + ``` + +### 为什么使用决策树作为基学习器? +- **类似问题** + - 基学习器有什么特点? + - 基学习器有什么要求? + +- 使用决策树作为基学习器的原因: + ```tex + (1). 决策树的表达能力和泛化能力,可以通过剪枝快速调整; + (2). 决策树可以方便地将**样本的权重**整合到训练过程中; + (3). 决策树是一种**不稳定**的学习器; + 所谓不稳定,指的是数据样本的扰动会对决策树的结果产生较大的影响; + ``` + - 后两点分别适合 Boosting 策略和 Bagging 策略;所以它们一般都使用决策树作为基学习器。 + +#### 为什么不稳定的学习器更适合作为基学习器? +- 不稳定的学习器容易受到**样本分布**的影响(方差大),很好的引入了**随机性**;这有助于在集成学习(特别是采用 **Bagging** 策略)中提升模型的**泛化能力**。 +- 为了更好的引入随机性,有时会随机选择一个**属性子集**中的最优分裂属性,而不是全局最优(**随机森林**) + +#### 还有哪些模型也适合作为基学习器? +- **神经网络** + - 神经网络也属于**不稳定**的学习器; + - 此外,通过调整神经元的数量、网络层数,连接方式初始权重也能很好的引入随机性和改变模型的表达能力和泛化能力。 + +#### Bagging 方法中能使用线性分类器作为基学习器吗? Boosting 呢? +- Bagging 方法中**不推荐** + - 线性分类器都属于稳定的学习器(方差小),对数据不敏感; + - 甚至可能因为 Bagging 的采样,导致在训练中难以收敛,增大集成分类器的**偏差** +- Boosting 方法中可以使用 + - Boosting 方法主要通过降低**偏差**的方式来提升模型的性能,而线性分类器本身具有方差小的特点,所以两者有一定相性 + - XGBoost 中就支持以线性分类器作为基学习器。 + +### Boosting/Bagging 与 偏差/方差 的关系 +> ./机器学习基础/[偏差与方差](./ML-A-机器学习基础.md#偏差与方差) + +- 简单来说,**Boosting** 能提升弱分类器性能的原因是降低了**偏差**;**Bagging** 则是降低了**方差**; +- **Boosting** 方法: + - Boosting 的**基本思路**就是在不断减小模型的**训练误差**(拟合残差或者加大错类的权重),加强模型的学习能力,从而减小偏差; + - 但 Boosting 不会显著降低方差,因为其训练过程中各基学习器是强相关的,缺少独立性。 +- **Bagging** 方法: + - 对 `n` 个**独立不相关的模型**预测结果取平均,方差是原来的 `1/n`; + - 假设所有基分类器出错的概率是独立的,**超过半数**基分类器出错的概率会随着基分类器的数量增加而下降。 +- 泛化误差、偏差、方差、过拟合、欠拟合、模型复杂度(模型容量)的关系图: +
+ +## AdaBoost 算法 + +## GBDT 算法 \ No newline at end of file diff --git "a/\347\256\227\346\263\225/\344\270\223\351\242\230-\347\273\217\345\205\270\347\256\227\346\263\225\351\227\256\351\242\230.md" "b/\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/ML-C-\344\270\223\351\242\230-\346\246\202\347\216\207\345\233\276\346\250\241\345\236\213.md" similarity index 100% rename from "\347\256\227\346\263\225/\344\270\223\351\242\230-\347\273\217\345\205\270\347\256\227\346\263\225\351\227\256\351\242\230.md" rename to "\346\234\272\345\231\250\345\255\246\344\271\240-\346\267\261\345\272\246\345\255\246\344\271\240-NLP/ML-C-\344\270\223\351\242\230-\346\246\202\347\216\207\345\233\276\346\250\241\345\236\213.md" diff --git "a/\347\254\224\350\257\225\351\235\242\347\273\217/README.md" "b/\347\254\224\350\257\225\351\235\242\347\273\217/README.md" index 446d4ec3..229b3560 100644 --- "a/\347\254\224\350\257\225\351\235\242\347\273\217/README.md" +++ "b/\347\254\224\350\257\225\351\235\242\347\273\217/README.md" @@ -20,10 +20,15 @@ Index --- -- [今日头条-算法工程师-实习](#今日头条-算法工程师-实习) +- [头条/字节跳动-深度学习/NLP 方向](#头条字节跳动-深度学习nlp-方向) - [一面](#一面) - [二面](#二面) - [三面](#三面) + - [四面(非加面)](#四面非加面) +- [今日头条-算法工程师-实习](#今日头条-算法工程师-实习) + - [一面](#一面-1) + - [二面](#二面-1) + - [三面](#三面-1) - [2019 美团 AI - NLP 提前批](#2019-美团-ai---nlp-提前批) - [一面(NLP平台)](#一面nlp平台) - [一面(广告平台)](#一面广告平台) @@ -36,6 +41,78 @@ Index +## 头条/字节跳动-深度学习/NLP 方向 + +### 一面 +- 自我介绍 +- 聊项目 +- 深度学习基本问题 +- 【算法】手写 K-Means + > 磕磕绊绊算是写出来一个框架,内部细节全是问题,面试官比较宽容,勉强算过了 + +### 二面 +- 自我介绍 +- 聊项目 +- 深度学习基本问题 +- 【算法】找数组中前 k 大的数字 + > 我说了两个思路:最小堆和快排中的 partition 方法;让我选一个实现,我选的堆方法,然后又让我实现调整堆的方法 + +### 三面 +- 自我介绍 +- 为什么会出现梯度消失和梯度爆炸 + - 分别说了下前馈网络和 RNN 出现梯度消失的情况 +- 有哪些解决方法 + - 因为提到了残差和门机制,所以又问 + - 分别说下它们为什么能缓解梯度消失 + - 因为说残差的时候提到了 ResNet,让我介绍下 ResNet(没用过,随便说了几句) +- 其他加速网络收敛的方法(除了残差和门机制) + - 我从优化方法的角度说了一点(SGB 的改进:动量方法、Adam) + - 提示我 BN,然后我就把 BN 的做法说了一下 + - 然后问 BN 为什么能加速网络的收敛(从数据分布的角度随便说了几句) +- 传统的机器学习方法(简历上写用过 GBDT) + - 简单介绍下 XGBoost + - CART 树怎么选择切分点(基尼系数) + - 基尼系数的动机、原理(不会) +- 【算法】直方图蓄水问题 + > LeetCode [42. 接雨水](https://leetcode-cn.com/problems/trapping-rain-water/description/); + >> 当时太紧张没想出 `O(N)` 解法,面试一结束就想出来了,哎 + - 附 AC 代码 + ```C++ + class Solution { + public: + int trap(vector& H) { + int n = H.size(); + + vector dp_fw(H); + vector dp_bw(H); + + for(int i=1; i=0; i--) // 记录每个位置右边的最高点 + dp_bw[i] = max(dp_bw[i+1], dp_bw[i]); + + int ret = 0; + for (int i=1; i 因为流程出了问题,其实还是三面 +- 【算法】和为 K 的连续子数组,返回首尾位置 + > LeetCode [560. 和为K的子数组](https://leetcode-cn.com/problems/subarray-sum-equals-k/description/) + >> 很熟悉的题,但就是没想出来;然后面试官降低了难度,数组改成有序且为正整数,用双指针勉强写了出来;但是边界判断有问题,被指了出来;然后又问无序的情况或者有负数的情况能不能也用双指针做,尬聊了几分钟,没说出个所以然。 +- 如何无监督的学习句子表示 + - 我说 Self-Attention + - 让我把公式写出来,因为写的不清楚,让我写原始的 Attention + - 然后问怎么训练,损失函数是什么(没说出来,除了词向量我基本没碰过无监督任务,而且我认为词向量也算不上无监督...) +- 如何无监督的学习一个短视频的特征表示 + - 抽取关键帧,然后通过 ResNet 等模型对每一帧转化为特征表示,然后对各帧的特征向量做拼接或者直接保存为二维特征(瞎说的,别说视频,我连图像都没做过) + ## 今日头条-算法工程师-实习 > [6.14今日头条算法工程师实习生](https://www.nowcoder.com/discuss/84462?type=2&order=0&pos=11&page=1)_笔经面经_牛客网 diff --git "a/\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\213\233\350\241\214-180830.md" "b/\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\213\233\350\241\214-180830.md" index 3ea1f4f7..25521ab2 100644 --- "a/\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\213\233\350\241\214-180830.md" +++ "b/\347\254\224\350\257\225\351\235\242\347\273\217/\347\254\224\350\257\225-\346\213\233\350\241\214-180830.md" @@ -23,6 +23,7 @@ Index ## 推倒吧骨牌 > LeetCode 原题:https://leetcode-cn.com/problems/push-dominoes/description/ +>> **题解**:../算法/LeetCode题解/[838. 推多米诺](../算法/题解-LeetCode.md#838-推多米诺)
diff --git "a/\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204.md" index 4a446d5e..7da4ac8e 100644 --- "a/\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204.md" +++ "b/\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -1,13 +1,17 @@ 专题-数据结构 === -数据结构相关基本是**现场面试**中出现频率最高的问题。因为现场面试的时间限制,更难的问题需要大量的思考时间,所以一般只要求需要阐述思路;而数据结构相关的问题,因为有很强的先验知识,通常要求**手写代码**。 +- 数据结构相关基本是**现场面试**中出现频率最高的问题。因为现场面试的时间限制,更难的问题需要大量的思考时间,所以一般只要求需要阐述思路;而数据结构相关的问题,因为有很强的先验知识,通常要求**手写代码**。 +- 本专题只收录**纯数据结构问题**,不包括数据结构的应用。 Index --- - [二叉树](#二叉树) + - [二叉树的深度](#二叉树的深度) + - [二叉树中的最长路径](#二叉树中的最长路径) + - [判断平衡二叉树](#判断平衡二叉树) - [判断树 B 是否为树 A 的子结构 TODO](#判断树-b-是否为树-a-的子结构-todo) - [利用前序和中序重建二叉树](#利用前序和中序重建二叉树) - [二叉树的序列化与反序列化](#二叉树的序列化与反序列化) @@ -23,8 +27,8 @@ Index - [链表排序](#链表排序) - [插入排序 TODO](#插入排序-todo) - [链表快排](#链表快排) -- [数组](#数组) - - [打印二维数组 TODO](#打印二维数组-todo) +- [二维数组](#二维数组) + - [打印二维数组](#打印二维数组) - [回形打印](#回形打印) - [蛇形打印](#蛇形打印) - [堆](#堆) @@ -34,9 +38,36 @@ Index -# 二叉树 +## 二叉树 -## 判断树 B 是否为树 A 的子结构 TODO +### 二叉树的深度 +> [二叉树的深度](https://www.nowcoder.com/practice/435fb86331474282a3499955f0a41e8b?tpId=13&tqId=11191&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - 牛客 + +**C++** +```C++ +class Solution { +public: + int TreeDepth(TreeNode* root) { + if (root == NULL) return 0; + + return max(TreeDepth(root->left), TreeDepth(root->right)) + 1; + } +}; +``` + +### 二叉树中的最长路径 + +**思路** +- 基于[二叉树的深度](#二叉树的深度) +- 对任一子树而言,则经过该节点的一条最长路径为其`左子树的深度 + 右子树的深度 + 1` +- 遍历树中每个节点的最长路径,其中最大的即为整个树的最长路径 + > 为什么最长路径不一定是经过根节点的那条路径? + > + +### 判断平衡二叉树 + + +### 判断树 B 是否为树 A 的子结构 TODO > [树的子结构](https://www.nowcoder.com/practice/6e196c44c7004d15b1610b9afca8bd88?tpId=13&tqId=11170&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - 牛客 **题目描述** @@ -78,7 +109,7 @@ public: ``` -## 利用前序和中序重建二叉树 +### 利用前序和中序重建二叉树 > [重建二叉树](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - 牛客 **题目描述** @@ -139,7 +170,7 @@ class Solution: ``` -## 二叉树的序列化与反序列化 +### 二叉树的序列化与反序列化 > [序列化二叉树](https://www.nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&tqId=11214&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - NowCoder **题目描述** @@ -212,7 +243,7 @@ public: }; ``` -## 最近公共祖先 +### 最近公共祖先 > 《剑指 Offer》 7.2 案例二 **问题描述** @@ -220,15 +251,15 @@ public: 给定一棵树的根节点 root,和其中的两个节点 p1 和 p2,求它们的最小公共父节点。 ``` -### 如果树是二叉搜索树 +#### 如果树是二叉搜索树 - 找到第一个满足 `p1 < root < p2` 的根节点,即为它们的最小公共父节点; - 如果寻找的过程中,没有这样的 `root`,那么 `p1` 和 `p2` 的最小公共父节点必是它们之一,此时遍历到 `p1` 或 `p2` 就返回。 -### 如果树的节点中保存有指向父节点的指针 +#### 如果树的节点中保存有指向父节点的指针 - 问题等价于求两个链表的**第一个公共节点** > [两个链表的第一个公共节点](#两个链表的第一个公共节点) -### 如果只是普通的二叉树 +#### 如果只是普通的二叉树 > [236. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/description/) - LeetCode - 利用两个辅助链表/数组,保存分别到 `p1` 和 `p2` 的路径; @@ -286,7 +317,7 @@ public: ``` -## 获取节点的路径 +### 获取节点的路径 **二叉树** ```C++ // 未测试 @@ -345,9 +376,9 @@ bool getPath(const TreeNode* root, const TreeNode* p, deque& pa ``` -# 链表 +## 链表 -## 反转链表 +### 反转链表 > [反转链表](https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca?tpId=13&tqId=11168&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - 牛客 **题目描述** @@ -399,7 +430,7 @@ public: }; ``` -## 合并排序链表 +### 合并排序链表 > [合并两个排序的链表](https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - 牛客 **问题描述** @@ -465,7 +496,7 @@ public: }; ``` -## 两个链表的第一个公共节点 +### 两个链表的第一个公共节点 **思路 1** - 先求出两个链表的长度 `l1` 和 `l2`,然后让长的链表先走 `|l1-l2|` 步,此时两个指针距离第一个公共节点的距离相同,再走相同的步数即可在第一个公共节点相遇 @@ -529,13 +560,13 @@ public: return p1; ``` -## 链表排序 +### 链表排序 > [链表排序(冒泡、选择、插入、快排、归并、希尔、堆排序)](https://www.cnblogs.com/TenosDoIt/p/3666585.html) - tenos - 博客园 -### 插入排序 TODO +#### 插入排序 TODO > [147. 对链表进行插入排序](https://leetcode-cn.com/problems/insertion-sort-list/description/) - LeetCode -### 链表快排 +#### 链表快排 > [148. 排序链表](https://leetcode-cn.com/problems/sort-list/description/) - LeetCode **思路** @@ -591,23 +622,25 @@ public: **要求交换节点** TODO -# 数组 +## 二维数组 +> 绝大多数问题都是用数组模拟,所以这里不再记录 +>> ./LeetCode/[数组](./题解-LeetCode.md#数组) -## 打印二维数组 TODO +### 打印二维数组 -### 回形打印 +#### 回形打印 -### 蛇形打印 +#### 蛇形打印 -# 堆 +## 堆 -## 堆的调整(自上而下) +### 堆的调整(自上而下) -# 栈 +## 栈 -## 用两个栈模拟队列 +### 用两个栈模拟队列 > [用两个栈实现队列](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) - 牛客 **题目描述** diff --git "a/\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204_\345\255\227\347\254\246\344\270\262.md" "b/\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204_\345\255\227\347\254\246\344\270\262.md" index 9ed58b2a..0ba572d4 100644 --- "a/\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204_\345\255\227\347\254\246\344\270\262.md" +++ "b/\347\256\227\346\263\225/\344\270\223\351\242\230-A-\346\225\260\346\215\256\347\273\223\346\236\204_\345\255\227\347\254\246\344\270\262.md" @@ -3,6 +3,157 @@ Index --- + + +- [进制转换](#进制转换) + - [10进制转任意进制](#10进制转任意进制) + - [任意进制转10进制](#任意进制转10进制) + - [长地址转短地址](#长地址转短地址) +- [功能函数 atoi()](#功能函数-atoi) +- [表达式转化(中缀,后缀,前缀)](#表达式转化中缀后缀前缀) + - [中缀转后缀](#中缀转后缀) + + + +## 进制转换 + +### 10进制转任意进制 +```python +from string import digits, ascii_uppercase, ascii_lowercase + +Alphabet = digits + ascii_lowercase + ascii_uppercase +print(Alphabet) # "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +``` + +**递归方法** +```python +def ten2any(n, b=62): + """""" + assert b <= 62 + + n, index = divmod(n, b) # n = n // b, index = n % b + if n > 0: + return ten2any(n, b) + Alphabet[index] + else: + return Alphabet[index] +``` + +**迭代方法** +```python +def ten2any_2(n, b=62): + """""" + ret = "" + while n > 0: + n, index = divmod(n, b) + ret = Alphabet[index] + ret + + return ret +``` + +### 任意进制转10进制 + +**迭代方法** +```python +from string import digits, ascii_uppercase, ascii_lowercase + +Alphabet = digits + ascii_lowercase + ascii_uppercase +print(Alphabet) # "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +def any2ten(s, base=62): + """""" + n = 0 + for i, c in enumerate(reversed(s)): # reversed(s) + index = Alphabet.index(c) + n += index * pow(base, i) + + return n +``` + +### 长地址转短地址 +> [短 URL 系统是怎么设计的?](https://www.zhihu.com/question/29270034/answer/46446911) - 知乎 + +**基本思路** +- 发号策略:给每一个收录的长地址,分配一个自增索引 +- 将分配的索引转换为一个 62 进制数(10数字+26大写字母+26小写字母) +- **注意**,发号机制不会判断是否重复 + +**重复长地址怎么处理?** +- 如果要求相同的长地址要对应唯一的短地址,那么唯一的方法就是维护一个映射表 +- 但是维护一个映射表反而比忽略重复需要更多的空间 +- 一个**折衷**的方法是——维护一个“最近”的长对短映射,比如采用一小时过期的机制来实现 LRU 淘汰 + - 当一个地址被频繁使用,那么它会一直在这个 key-value 表中,总能返回当初生成那个短地址 + - 如果它使用并不频繁,那么长对短的 key 会过期,LRU 机制就会自动淘汰掉它 + +**如何保证发号器的大并发高可用?** +- 如果做成分布式的,那么多节点要保持同步加 1,多点同时写入,以 CAP 理论看,很难做到 +- 一个简单的处理方式是,使用多个发号器,以**奇偶**/**尾数**区分 + - 比如一个发单号,一个发双号; + - 或者实现 1000 个发号器,分别发尾号为 0 到 999 的号;每发一个号,每个发号器加 1000,而不是加 1 + + +## 功能函数 atoi() +**功能简述** +- 将字符串(C风格)转换成整型; +- 会跳过前面的空格字符,直到遇上数字或正负号才开始转换; +- 如果遇到的第一个字符不是数字,则返回 0,并结束转化; +- 当遇到**非数字**或**结束符**('\0') 时结束转化,并将结果返回(整型) +- 如果发生溢出,则输出 INT_MAX 或 INT_MIN; +- 内置 atoi 不会处理 NULL 指针 + +**合法样例**: +``` +"123" -> 123 +"+123" -> 123 +"-123" -> -123 +"123abc" -> 123 +" 123abc" -> 123 +"a123" -> 0 +``` + +**核心代码(C++)** +```C++ +while (*p >= '0' && *p <= '9') { + ret = ret * 10 + (*p - '0'); + p++; +} +``` +- 除了核心代码,更重要的是**异常处理**和**溢出判断** + +**完整代码** +```C++ +int atoi_my(const char* const cs) { + if (cs == nullptr) return 0; + + int ret = 0; + auto *p = cs; // cs 为常指针 + + // 跳过前面的空格 + while (isspace(*p)) p++; + + // 判断正负 + int sign = 1; // 默认正数 + if (*p == '-') sign = -1; + if (*p == '-' || *p == '+') p++; + + // 核心代码:循环转换整数(加入溢出判断) + int tmp; // 保存临时结果,用于溢出判断 + while (*p >= '0' && *p <= '9') { + tmp = ret * 10 + (*p - '0'); + if (tmp / 10 != ret) { // 溢出判断 + return sign > 0 ? INT_MAX : INT_MIN; + } + ret = tmp; + p++; + } + // 核心代码(无溢出判断) + //while (*p >= '0' && *p <= '9') { + // ret = ret * 10 + (*p - '0'); + // p++; + //} + + return sign * ret; +} +``` ## 表达式转化(中缀,后缀,前缀) @@ -32,15 +183,127 @@ Index -+a*bc+de ``` - **计算机方法**: - - 后缀:从左到右遍历后缀表达式,遇到操作数,放进栈,遇到操作符,栈顶两个数出栈,进行运算,运算结果放进栈,直到读完后缀表达式。 - - 前缀:从左到右遍历前缀表达式,遇到操作符,放进栈,遇到操作数,查看栈顶,栈顶为操作符,放进栈,栈顶为操作数,取出栈顶操作数和操作符,进行运算,运算后继续判断栈顶的情况。 + - [中缀转后缀](#中缀转后缀) ### 中缀转后缀 **思路** -- 从左到右遍历中缀表达式,遇到操作数则输出;遇到操作符,若当前操作符的优先级**大于**栈顶操作符优先级,进栈;否则,弹出栈顶操作符,当前操作符进栈。 -- 括号的处理: -- 操作符优先级 - - 1: `()` - - 2: `* /` - - 3: `+ -` +- 从左到右遍历中缀表达式,遇到**操作数**则输出;遇到操作符,若当前操作符的**优先级大于**栈顶操作符优先级,进栈;否则,弹出栈顶操作符,当前操作符进栈。(这只是一段比较**粗糙**的描述,更多细节请参考链接或下面的源码) + > [中、前、后缀表达式](https://blog.csdn.net/lin74love/article/details/65631935) - CSDN博客 + +**C++** +- 只测试了部分样例,如果有**未通过的样例**请告诉我 +- 以下代码中有一些**循环**可能是多余的;如果字符串合法,应该只需要判断一次,而不需要循环处理(未验证) +```C++ +#include +#include +#include +#include +#include +#include +using namespace std; + +//set l1{ '+', '-' }; +//set l2{ '*', '/' }; +// +//vector> l{ l1, l2 }; + +int get_level(char c) { + switch (c) { + case '+': + case '-': + return 1; + case '*': + case '/': + return 2; + //case '(': + // return 3; + default: + return -1; + } +} + +string infix2suffix(const string& s) { + stack tmp; // 符号栈 + queue ans; // 必须使用 string 队列,因为可能存在多位数字 + //stringstream ret; // 用字符流模拟队列 + + bool num_flag = false; // 用于判断数字的末尾 + //初始设为 false 是为了避免第一个字符是括号 + //int v = 0; // 保存数值 + string v{ "" }; // 用字符串保存更好,这样还能支持字母形式的表达式 + for (auto c : s) { + // 处理数字 + if (isalnum(c)) { // 处理多位数字 + v.append(string(1, c)); // 注意,char 字符不能直接转 string + num_flag = true; + } + else { + if (num_flag) { // 因为可能存在多位数字,所以数字需要等遇到第一个非数字字符才入队 + ans.push(v); + //ret << v << ' '; + v.clear(); + num_flag = false; + } + + // 处理运算符的过程 + if (c == ')') { // 如果遇到右括号,则依次弹出栈顶符号,直到遇到**第一个**左括号并弹出(坑点 1:可能存在连续的左括号) + while (!tmp.empty()) { + if (tmp.top() == '(') { + tmp.pop(); + break; + } + ans.push(string(1, tmp.top())); + //ret << tmp.top() << ' '; + tmp.pop(); + } + } // 注意这两个判断的顺序(坑点 2:右括号是始终不用入栈的,所以应该先处理右括号) + else if (tmp.empty() || tmp.top() == '(' || c == '(') { // 如果符号栈为空,或栈顶为 ')',或遇到左括号 + tmp.push(c); // 则将该运算符入栈 + } + else { + while (!tmp.empty() && get_level(tmp.top()) >= get_level(c)) { // 如果栈顶元素的优先级大于等于当前运算符,则弹出 + if (tmp.top() == '(') // (坑点 3:左括号的优先级是大于普通运算符的,但它不应该在这里弹出) + break; + ans.push(string(1, tmp.top())); + //ret << tmp.top() << ' '; + tmp.pop(); + } + tmp.push(c); + } + } + } + + if (num_flag) { // 表达式的最后一个数字入栈 + ans.push(v); + //ret << v << ' '; + } + + while (!tmp.empty()) { // 字符串处理完后,依次弹出栈中的运算符 + if (tmp.top() == '(') // 这个判断好像是多余的 + tmp.pop(); + ans.push(string(1, tmp.top())); + //ret << tmp.top() << ' '; + tmp.pop(); + } + + //return ret.str(); + + stringstream ret; + while (!ans.empty()) { + ret << ans.front() << ' '; + ans.pop(); + } + return ret.str(); +} + +void solve() { + // 只测试了以下样例,如果有反例请告诉我 + + cout << infix2suffix("12+(((23)+3)*4)-5") << endl; // 12 23 3 + 4 * + 5 - + cout << infix2suffix("1+1+1") << endl; // 1 1 + 1 + + cout << infix2suffix("(1+1+1)") << endl; // 1 1 + 1 + + cout << infix2suffix("1+(2-3)*4+10/5") << endl; // 1 2 3 - 4 * + 10 5 / + + cout << infix2suffix("az-(b+c/d)*e") << endl; // az b c d / + e * - +} +``` diff --git "a/\347\256\227\346\263\225/\344\270\223\351\242\230-A-\347\273\217\345\205\270\347\256\227\346\263\225.md" "b/\347\256\227\346\263\225/\344\270\223\351\242\230-A-\347\273\217\345\205\270\347\256\227\346\263\225.md" new file mode 100644 index 00000000..e69de29b diff --git "a/\347\256\227\346\263\225/\344\270\223\351\242\230-A-\345\212\250\346\200\201\350\247\204\345\210\222.md" "b/\347\256\227\346\263\225/\344\270\223\351\242\230-B-\345\212\250\346\200\201\350\247\204\345\210\222.md" similarity index 100% rename from "\347\256\227\346\263\225/\344\270\223\351\242\230-A-\345\212\250\346\200\201\350\247\204\345\210\222.md" rename to "\347\256\227\346\263\225/\344\270\223\351\242\230-B-\345\212\250\346\200\201\350\247\204\345\210\222.md" diff --git "a/\347\256\227\346\263\225/\344\270\223\351\242\230-\346\216\222\345\210\227\344\270\216\347\273\204\345\220\210.md" "b/\347\256\227\346\263\225/\344\270\223\351\242\230-\346\216\222\345\210\227\344\270\216\347\273\204\345\220\210.md" new file mode 100644 index 00000000..646b3983 --- /dev/null +++ "b/\347\256\227\346\263\225/\344\270\223\351\242\230-\346\216\222\345\210\227\344\270\216\347\273\204\345\220\210.md" @@ -0,0 +1,12 @@ +专题-排列与组合 +=== + +Index +--- + + +- [](#) + + + +## \ No newline at end of file diff --git "a/\347\256\227\346\263\225/\344\270\223\351\242\230-\346\264\227\347\211\214\343\200\201\351\207\207\346\240\267\343\200\201\351\232\217\346\234\272\346\225\260.md" "b/\347\256\227\346\263\225/\344\270\223\351\242\230-\346\264\227\347\211\214\343\200\201\351\207\207\346\240\267\343\200\201\351\232\217\346\234\272\346\225\260.md" index ae8e37b7..00cf6003 100644 --- "a/\347\256\227\346\263\225/\344\270\223\351\242\230-\346\264\227\347\211\214\343\200\201\351\207\207\346\240\267\343\200\201\351\232\217\346\234\272\346\225\260.md" +++ "b/\347\256\227\346\263\225/\344\270\223\351\242\230-\346\264\227\347\211\214\343\200\201\351\207\207\346\240\267\343\200\201\351\232\217\346\234\272\346\225\260.md" @@ -16,7 +16,7 @@ Index - [采样(等概率)](#采样等概率) - [无放回思路](#无放回思路) - [有放回思路](#有放回思路) - - [蓄水池采样/抽样(有放回)](#蓄水池采样抽样有放回) + - [蓄水池采样](#蓄水池采样) - [采样(不等概率)](#采样不等概率) - [查表法(有放回)](#查表法有放回) - [如何采样,使 `n-1` 被采样 n 次?](#如何采样使-n-1-被采样-n-次) @@ -121,7 +121,8 @@ Index ### Inside-Out Shuffle 无穷版 -- 所谓无限,指的是 `n=len(src)` 未知的情况,参考 `->` [蓄水池采样/抽样](#蓄水池采样抽样) +- 所谓无限,指的是 `n=len(src)` 未知的情况 + > [蓄水池采样](#蓄水池采样) - Python 实现 ```Python def Inside_Out_shuffle_inf(src): @@ -188,12 +189,9 @@ Index ret = [None] * m for i in range(m): j = random.randrange(n - i) # 0 <= j < n-i - ret[i] = src[j] # 把 src[n - i - 1] 移动到 src[j] 保证每个值都可能被抽到 - src[j] = src[n - i - 1] - - # 原 src[j] 实际上被舍弃了,因为只是原数据的副本 - # 如果是在原数据上操作,那么应该交换 (src[j], src[n - i - 1]) - # src[j], src[n - i - 1] = src[n - i - 1], src[j] + ret[i] = src[j] + src[j], src[n - i - 1] = src[n - i - 1], src[j] # 保证每个值都可能被抽到 + # src[j] = src[n - i - 1] # 因为使用的是副本,所以直接覆盖也可以 return ret ``` @@ -219,10 +217,11 @@ Index return ret ``` - 如果 `n < 4**ceil(log(3*m, 4))` 时采用这个策略,可能会产生类似 Hash 冲突的问题,导致调用随机数方法的次数过多 - > 求解调用`rangd()`的次数是一个期望问题 > [蓄水池抽样及实现](https://www.cnblogs.com/hrlnw/archive/2012/11/27/2777337.html) - handspeaker - 博客园 + > 求解调用`rangd()`的次数是一个期望问题 + >> [蓄水池抽样及实现](https://www.cnblogs.com/hrlnw/archive/2012/11/27/2777337.html) - handspeaker - 博客园 -### 蓄水池采样/抽样(有放回) +### 蓄水池采样 **问题描述** ``` diff --git "a/\347\256\227\346\263\225/\344\270\223\351\242\230-\346\265\267\351\207\217\346\225\260\346\215\256\345\244\204\347\220\206.md" "b/\347\256\227\346\263\225/\344\270\223\351\242\230-\346\265\267\351\207\217\346\225\260\346\215\256\345\244\204\347\220\206.md" index 3814318b..110d8de7 100644 --- "a/\347\256\227\346\263\225/\344\270\223\351\242\230-\346\265\267\351\207\217\346\225\260\346\215\256\345\244\204\347\220\206.md" +++ "b/\347\256\227\346\263\225/\344\270\223\351\242\230-\346\265\267\351\207\217\346\225\260\346\215\256\345\244\204\347\220\206.md" @@ -1,7 +1,65 @@ 专题-海量数据处理 === -Reference +备忘 --- +- `1 GB`: 十亿个字节(Byte) + > `1(B) * 10*10^8 / 1024 / 1024 ≈ 953.67(MB) ≈ 1000(MB) ≈ 1(GB)` +- `400 MB`: 一亿个 4 字节(Byte) int 整型占用的内存 + > `4(B) * 10^8 / 1024 / 1024 ≈ 381.57(MB) ≈ 382(MB) ≈ 400(MB)` + - 10 亿个整型 -> `400(MB) * 10 = 4(GB)` + - 40 亿个整型 -> `4(GB) * 4 = 16(GB)` +- `12 MB`: 一亿个比特(bit)占用的内存(相比于 int 型,节省了 32 倍内存) + > `1(b) * 10^8 / 8 / 1024 / 1024 ≈ 11.92(MB) ≈ 12(MB)` + - 10 亿个比特 -> `12(MB) * 10 = 120(MB) ≈ 4(GB)/32 = 128(MB)` + - 40 亿个整型 -> `120(MB) * 4 = 480(MB) ≈ 16(GB)/32 = 500(MB)` + + +Index +--- + + +- [判断一个整数是否在给定的 40 亿个(不重复)整数中出现过](#判断一个整数是否在给定的-40-亿个不重复整数中出现过) +- [Reference](#reference) + + + + +## 判断一个整数是否在给定的 40 亿个(不重复)整数中出现过 +> [腾讯面试题:给定 40 亿个不重复的...](https://blog.csdn.net/wenqiang1208/article/details/69669084) - CSDN博客 + +**思路 1** +- BitMap + +**思路 2** +- Hash 分桶 + 排序 + 二分查找 + +**思路 3** +- 外部排序 + 结构存储 + 二分查找 + - int 型整数的范围是 `2^32 ≈ 42亿`,那么对于 40 亿个整数,必然存在大量连续的范围 + - 排序后,必然存在大量以下情况: + ``` + 1 2 3 4 7 8 9 ... + ``` + - 对于这种形式的序列,可以构造如下结构 + ``` + struct { + start; // 记录连续序列的开头 + n_continue; // 连续字段的长度 + } + ``` + - 则上述示例,可以存储为 + ``` + (1, 4), (7, 3), ... + ``` +- 复杂度分析 + - 这样最差情况存在 `2(=42-40)` 亿个断点,即 `2` 亿个结构体,每个结构体占 `8` 个字节,共 `400(MB) * 4 = 1.6(GB)` + - 每次查找的时间复杂度为 `O(logN)` + +**思路 4** +- 多机分布式 + + +## Reference - [海量数据处理面试题集锦](https://blog.csdn.net/v_july_v/article/details/6685962) - CSDN博客 - [十道海量数据处理面试题与十个方法大总结](https://blog.csdn.net/v_JULY_v/article/details/6279498) - CSDN博客 \ No newline at end of file diff --git "a/\347\256\227\346\263\225/\351\242\230\350\247\243-LeetCode.md" "b/\347\256\227\346\263\225/\351\242\230\350\247\243-LeetCode.md" index badf7590..690b1aef 100644 --- "a/\347\256\227\346\263\225/\351\242\230\350\247\243-LeetCode.md" +++ "b/\347\256\227\346\263\225/\351\242\230\350\247\243-LeetCode.md" @@ -1,22 +1,242 @@ 题解-LeetCode === +RoadMap +--- +- [二叉树](#二叉树) + - [DFS](#dfs) +- [数组](#数组) + - [双指针](#双指针) + - [多级排序](#多级排序) + - [其他](#其他) +- [暴力搜索](#暴力搜索) + - [DFS](#dfs) + - [BFS](#bfs) + + Index --- +- [二叉树](#二叉树) + - [124. 二叉树中的最大路径和(DFS)](#124-二叉树中的最大路径和dfs) - [数组](#数组) + - [560. 和为K的子数组(前缀和 + Map)](#560-和为k的子数组前缀和--map) + - [838. 推多米诺(双指针)](#838-推多米诺双指针) - [15. 三数之和(双指针)](#15-三数之和双指针) - [16. 最接近的三数之和(双指针)](#16-最接近的三数之和双指针) - - [26. 删除排序数组中的重复项(迭代)](#26-删除排序数组中的重复项迭代) - [729. 我的日程安排表 I(多级排序)](#729-我的日程安排表-i多级排序) + - [26. 删除排序数组中的重复项](#26-删除排序数组中的重复项) - [暴力搜索](#暴力搜索) - [200. 岛屿的个数(DFS | BFS)](#200-岛屿的个数dfs--bfs) +## 二叉树 + +### 124. 二叉树中的最大路径和(DFS) +> https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/description/ + +**题目描述** +``` +给定一个非空二叉树,返回其最大路径和。 + +本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。 +该路径至少包含一个节点,且不一定经过根节点。 + +输入: [1,2,3] + + 1 + / \ + 2 3 + +输出: 6 +``` + +**暴力求解的思路** +- 利用一个子函数,求出每个节点**最大深度路径和**(做法类似求**树的深度**) + - 注意,因为节点中的值可能为负数,所以最大深度路径和不一定都会到达叶子 + - 同样,最大深度路径和也可能为负数,此时应该返回 0 +- 接着对每个节点,**经过该节点**的最大路径和为 + ``` + 该节点的值 + 左子树的最大深度路径和 + 右子树的最大深度路径和 + ``` +- **空树的最大路径和**应该为负无穷(作为递归基);但实际用例中没有空树的情况 + +**C++** +- 初始版本(AC) + ```C++ + class Solution { + const int inf = 0x3f3f3f3f; + + int maxDeep(TreeNode* root) { + if (!root) return 0; + + // 避免负数的情况 + return max(0, root->val + max({ 0, maxDeep(root->left), maxDeep(root->right) })); + } + public: + int maxPathSum(TreeNode* root) { + if (root == nullptr) return -inf; // 空树返回负无穷 + + int path_sum = root->val + maxDeep(root->right) + maxDeep(root->left); + + return max({ path_sum, maxPathSum(root->left), maxPathSum(root->right) }); + } + }; + ``` +- **改进** + - 使用一个变量保存中间结果 + ``` + class Solution { + // C++11 支持 就地 初始化 + const int inf = 0x3f3f3f3f; + int ret = -inf; + + int maxDeepSum(TreeNode* node) { + if (node == nullptr) + return 0; + + int l_sum = max(0, maxDeepSum(node->left)); + int r_sum = max(0, maxDeepSum(node->right)); + + ret = max(ret, node->val + l_sum + r_sum); + return node->val + max(l_sum, r_sum); + } + public: + int maxPathSum(TreeNode* root) { + maxDeepSum(root); + return ret; + } + }; + ``` + +**优化方案** +- 记忆化搜索(树DP);简单来说,就是保存中间结果 + + ## 数组 +### 560. 和为K的子数组(前缀和 + Map) +> https://leetcode-cn.com/problems/subarray-sum-equals-k/description/ + +**问题描述** +``` +给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。 + +示例 1 : + +输入:nums = [1,1,1], k = 2 +输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。 +``` + +**思路** +- 前缀和 + map +- 难点在于重复的情况也要记录 + +**C++** +```C++ +class Solution { +public: + int subarraySum(vector& nums, int k) { + + int sum = 0; + int cnt = 0; + map bok; + bok[0] = 1; + for (int i=0; i https://leetcode-cn.com/problems/push-dominoes/description/ + +**问题描述** +``` +一行中有 N 张多米诺骨牌,我们将每张多米诺骨牌垂直竖立。 + +在开始时,我们同时把一些多米诺骨牌向左或向右推。 + +每过一秒,倒向左边的多米诺骨牌会推动其左侧相邻的多米诺骨牌。 + +同样地,倒向右边的多米诺骨牌也会推动竖立在其右侧的相邻多米诺骨牌。 + +如果同时有多米诺骨牌落在一张垂直竖立的多米诺骨牌的两边,由于受力平衡, 该骨牌仍然保持不变。 + +就这个问题而言,我们会认为正在下降的多米诺骨牌不会对其它正在下降或已经下降的多米诺骨牌施加额外的力。 + +给定表示初始状态的字符串 "S" 。如果第 i 张多米诺骨牌被推向左边,则 S[i] = 'L';如果第 i 张多米诺骨牌被推向右边,则 S[i] = 'R';如果第 i 张多米诺骨牌没有被推动,则 S[i] = '.'。 + +返回表示最终状态的字符串。 + +示例 1: + 输入:".L.R...LR..L.." + 输出:"LL.RR.LLRRLL.." + +示例 2: + 输入:"RR.L" + 输出:"RR.L" + 说明:第一张多米诺骨牌没有给第二张施加额外的力。 + +提示: + 0 <= N <= 10^5 + 表示多米诺骨牌状态的字符串只含有 'L','R'; 以及 '.'; +``` + +**思路** +- 如果给原始输入左右分别加上一个 "L" 和 "R",那么共有以下 4 种可能 + ``` + 'R......R' => 'RRRRRRRR' + 'L......L' => 'LLLLLLLL' + 'L......R' => 'L......R' + 'R......L' => 'RRRRLLLL' or 'RRRR.LLLL' + ``` + > [[C++/Java/Python] Two Pointers](https://leetcode.com/problems/push-dominoes/discuss/132332/C++JavaPython-Two-Pointers) - LeetCode + +**C++** +```C++ +class Solution { +public: + string pushDominoes(string d) { + string s = "L" + d + "R"; + string ret = ""; + + int lo = 0, hi = 1; + for (; hi < s.length(); hi++) { + if (s[hi] == '.') + continue; + + if (lo > 0) // 注意这一步操作 + ret += s[lo]; + + int delta = hi - lo - 1; + if (s[lo] == s[hi]) + ret += string(delta, s[lo]); // string 的一种构造函数,以 s[lo] 为每个字符,生成长度为 h_l 的字符串 + else if (s[lo] == 'L' && s[hi] == 'R') + ret += string(delta, '.'); + else if (s[lo] == 'R' && s[hi] == 'L') + ret += string(delta / 2, 'R') + string(delta & 1, '.') + string(delta / 2, 'L'); + + lo = hi; + } + + return ret; + } +}; +``` + ### 15. 三数之和(双指针) > https://leetcode-cn.com/problems/3sum/description/ @@ -113,37 +333,6 @@ public: }; ``` -### 26. 删除排序数组中的重复项(迭代) -> https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/description/ - -**题目描述** -``` -给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 - -不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 -``` - -**C++** -```C++ -class Solution { -public: - int removeDuplicates(vector& nums) { - if (nums.size() <= 1) return nums.size(); - - int lo = 0; - int hi = lo + 1; - - int n = nums.size(); - while (hi < n) { - while (hi < n && nums[hi] == nums[lo]) hi++; - nums[++lo] = nums[hi]; - } - - return lo; - } -}; -``` - ### 729. 我的日程安排表 I(多级排序) > https://leetcode-cn.com/problems/my-calendar-i/description/ @@ -241,6 +430,37 @@ MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 }; ``` +### 26. 删除排序数组中的重复项 +> https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/description/ + +**题目描述** +``` +给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 + +不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 +``` + +**C++** +```C++ +class Solution { +public: + int removeDuplicates(vector& nums) { + if (nums.size() <= 1) return nums.size(); + + int lo = 0; + int hi = lo + 1; + + int n = nums.size(); + while (hi < n) { + while (hi < n && nums[hi] == nums[lo]) hi++; + nums[++lo] = nums[hi]; + } + + return lo; + } +}; +``` + ## 暴力搜索 ### 200. 岛屿的个数(DFS | BFS) @@ -361,4 +581,4 @@ public: } } }; -``` \ No newline at end of file +``` diff --git "a/\347\256\227\346\263\225/\351\242\230\350\247\243-\345\211\221\346\214\207Offer.md" "b/\347\256\227\346\263\225/\351\242\230\350\247\243-\345\211\221\346\214\207Offer.md" index a8a15134..01b74a50 100644 --- "a/\347\256\227\346\263\225/\351\242\230\350\247\243-\345\211\221\346\214\207Offer.md" +++ "b/\347\256\227\346\263\225/\351\242\230\350\247\243-\345\211\221\346\214\207Offer.md" @@ -305,7 +305,7 @@ public: **思路** - 栈 -- 头插法 +- 头插法(双端队列或数组) **Code** ```C++ @@ -338,6 +338,28 @@ public: **思路** - 涉及二叉树的问题,应该条件反射般的使用**递归**(无优化要求时) - 前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为左子树的中序遍历结果,右部分为右子树的中序遍历的结果。 +- **示例** + ``` + 前序 + 1,2,4,7,3,5,6,8 + 中序 + 4,7,2,1,5,3,8,6 + + 第一层 + 根节点 1 + 根据根节点的值(不重复),划分中序: + {4,7,2} 和 {5,3,8,6} + 根据左右子树的长度,划分前序: + {2,4,7} 和 {3,5,6,8} + 从而得到左右子树的前序和中序 + 左子树的前序和中序:{2,4,7}、{4,7,2} + 右子树的前序和中序:{3,5,6,8}、{5,3,8,6} + + 第二层 + 左子树的根节点 2 + 右子树的根节点 3 + ... + ``` **Code - 无优化** ```C++ @@ -458,8 +480,9 @@ public: **Code** ```C++ -class Solution -{ +class Solution { + stack stack_in; + stack stack_out; public: void push(int node) { stack_in.push(node); @@ -478,10 +501,6 @@ public: stack_out.pop(); return ret; } - -private: - stack stack_in; - stack stack_out; }; ``` @@ -1521,25 +1540,25 @@ public: - 要求:不使用额外空间 **思路** -- 可以辅助图示思考 +- 辅助图示思考 **Code - 迭代** ```C++ class Solution { public: - ListNode * ReverseList(ListNode* pHead) { - if (pHead == NULL) - return NULL; + ListNode * ReverseList(ListNode* head) { + if (head == nullptr) + return nullptr; - ListNode* cur = pHead; - ListNode* pre = NULL; - ListNode* nxt = cur->next; - cur->next = NULL; // 断开当前节点及下一个节点 - while (nxt) { - pre = cur; - cur = nxt; - nxt = nxt->next; - cur->next = pre; + ListNode* cur = head; // 当前节点 + ListNode* pre = nullptr; // 前一个节点 + ListNode* nxt = cur->next; // 下一个节点 + cur->next = nullptr; // 断开当前节点及下一个节点(容易忽略的一步) + while (nxt != nullptr) { + pre = cur; // 把前一个节点指向当前节点 + cur = nxt; // 当前节点向后移动 + nxt = nxt->next; // 下一个节点向后移动 + cur->next = pre; // 当前节点的下一个节点指向前一个节点 } return cur; } @@ -1550,15 +1569,15 @@ public: ```C++ class Solution { public: - ListNode * ReverseList(ListNode* pHead) { - if (pHead == nullptr || pHead->next == nullptr) - return pHead; + ListNode * ReverseList(ListNode* head) { + if (head == nullptr || head->next == nullptr) + return head; - auto nxt = pHead->next; - pHead->next = nullptr; // 断开当前节点及下一个节点 - auto newHead = ReverseList(nxt); - nxt->next = pHead; - return newHead; + auto nxt = head->next; + head->next = nullptr; // 断开当前节点及下一个节点 + auto new_head = ReverseList(nxt); + nxt->next = head; + return new_head; } }; ``` @@ -1572,44 +1591,63 @@ public: 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 ``` -**Code - 迭代** +**思路** +- 迭代 +- 递归 + +**Code**(**迭代**) ```C++ class Solution { public: - ListNode * Merge(ListNode* pHead1, ListNode* pHead2) { - ListNode head{-1}; - ListNode *cur = &head; - while (pHead1 && pHead2) { - if (pHead1->val <= pHead2->val) { - cur->next = pHead1; - pHead1 = pHead1->next; + ListNode* Merge(ListNode* p1, ListNode* p2) { + if (p1 == nullptr) return p2; + if (p2 == nullptr) return p1; + + // 选择头节点 + ListNode* head = nullptr; + if (p1->val <= p2->val) { + head = p1; + p1 = p1->next; + } else { + head = p2; + p2 = p2->next; + } + + auto cur = head; + while (p1 && p2) { + if (p1->val <= p2->val) { + cur->next = p1; + p1 = p1->next; } else { - cur->next = pHead2; - pHead2 = pHead2->next; + cur->next = p2; + p2 = p2->next; } cur = cur->next; } - if (pHead1) cur->next = pHead1; - if (pHead2) cur->next = pHead2; - return head.next; + // 别忘了拼接剩余部分 + if (p1) cur->next = p1; + if (p2) cur->next = p2; + + return head; } }; ``` -**Code - 递归** +**Code**(**递归**) ```C++ class Solution { public: - ListNode* Merge(ListNode* pHead1, ListNode* pHead2) { - if (!pHead1) return pHead2; - if (!pHead2) return pHead1; - if(pHead1->val <= pHead2->val){ - pHead1->next = Merge(pHead1->next, pHead2); - return pHead1; + ListNode* Merge(ListNode* p1, ListNode* p2){ + if (!p1) return p2; + if (!p2) return p1; + + if (p1->val <= p2->val) { + p1->next = Merge(p1->next, p2); + return p1; } else { - pHead2->next = Merge(pHead1, pHead2->next); - return pHead2; + p2->next = Merge(p1, p2->next); + return p2; } } }; @@ -2130,7 +2168,7 @@ public: ``` **思路** -- 注意,必须要从根节点到叶子节点,才叫一条路径,中间结果都不算路径 +- 注意:必须要从根节点到叶子节点,才叫一条路径,中间结果都不算路径,这样的话问题的难度一下子降低了很多 **Code** ```C++ diff --git "a/\347\256\227\346\263\225/\351\242\230\350\247\243-\351\235\242\350\257\225\347\234\237\351\242\230.md" "b/\347\256\227\346\263\225/\351\242\230\350\247\243-\351\235\242\350\257\225\347\234\237\351\242\230.md" new file mode 100644 index 00000000..dd238fc7 --- /dev/null +++ "b/\347\256\227\346\263\225/\351\242\230\350\247\243-\351\235\242\350\257\225\347\234\237\351\242\230.md" @@ -0,0 +1,39 @@ +题解-面试真题 +=== +- 记录一些暂时没找到原型的面试真题 + +Index +--- + + +- [给定 `n` 个`[0,n)`区间内的数,统计每个数出现的次数,不使用额外空间](#给定-n-个0n区间内的数统计每个数出现的次数不使用额外空间) + + + +## 给定 `n` 个`[0,n)`区间内的数,统计每个数出现的次数,不使用额外空间 +> 头条 + +**思路**: +- 基于两个基本运算: + ```tex + 若 i ∈ [0, n),则有 + (t*n + i) % n = i + (t*n + i) / n = t + ``` +- 顺序遍历每个数 i,i 每出现一次,则 nums[i] += n +- 遍历结束后,i 出现的次数,即 `nums[i] / n`,同时利用 `nums[i] % n` 可以还原之前 `nums[i]` 上的数。 + +**C++**(未测试) +```C++ +vector nums; + +void init(vector& nums) { + for (int i = 0; i < nums.size(); i++) { + nums[nums[i]] += n; + } +} + +int cnt(int k) { + return nums[k] / n; +} +``` \ No newline at end of file