news 2026/10/9 1:22:25

近红外光谱回归分析全流程:预处理、模型选型与PyTorch实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
近红外光谱回归分析全流程:预处理、模型选型与PyTorch实现

简介:这是一份基于深度学习的近红外光谱(NIR)数据回归分析项目源码,聚焦利用非破坏性光谱技术预测物质成分或性质,适用于计算机类毕业设计、课程作业及光谱数据分析方向的学习者。项目结合Python与C++,通过TensorFlow、PyTorch等框架完成模型搭建与训练,并利用C++处理数据预处理等计算密集部分以提升效率。代码中包含卷积网络、Transformer和视觉Transformer等多个回归模型,如ConvNet、SpectFormer、DeepViT等,并提供对比迁移学习实现,覆盖数据清洗、特征工程、模型调优、RMSE评估与可视化在内的完整流程。压缩包共9个文件,其中8个Python脚本构成核心源码,另附1个Markdown说明文档,整体仅27KB,结构紧凑、便于运行与二次开发。目前已有235人学习,适合需要参考完整深度学习回归项目以完成毕设或课程作业的学生快速上手。

1. 拿到近红外光谱回归分析模型工程,先别急着调参

打开《毕设&课程作业_基于深度学习的近红外光谱数据回归分析模型.zip》这类压缩包,大多数情况是同一件事:用近红外光谱预测样品里某个成分的含量。近红外光谱本身是一堆重叠的宽峰,肉眼基本看不出特征,真正的价值在于建模型,把吸光度和化学实测值之间的隐藏映射挖出来。这套工程里最关键的并不是“深度学习”四个字,而是那条完整的回归分析链路——光谱预处理、模型选型、训练验证、误差评估,每一步都直接影响最终能不能把决定系数做到 0.9 以上。这篇笔记就按这条链路拆开讲,适合正在做毕设或课程设计、手头有光谱数据与配套化学值的同学照着走一遍。

2. 数据清洗与光谱预处理:近红外建模的第一道分水岭

近红外光谱数据拿到手,先别想着上模型,数据质量不过关,后面所有工作都是给垃圾喂垃圾。光谱行数通常只有几十到几百条,波长变量却有几百到几千个,这种“宽数据”最容易藏问题,也最容易让模型在错误的信号上学得头头是道。

2.1 先看数据形态:光谱矩阵与化学值的对齐

典型的近红外数据集是一个二维矩阵,行是样本,列是波长或波数,每个样本还对应一个化学实测值,比如水分含量、蛋白质含量、某成分浓度。第一步永远是确认行列含义,再把光谱和化学值对齐到同一个索引上。

import pandas as pd import numpy as np # 光谱数据:每行一个样本,列是波长 spectra = pd.read_csv("data/spectra.csv") # shape: (n_samples, n_wavelengths) # 化学值:每行一个样本,只有一列 y = pd.read_csv("data/chem_values.csv", header=None) y.columns = ["target"] # 按样本编号对齐,防止顺序错位 sample_id = spectra.iloc[:, 0] # 假设第一列是样本编号 X = spectra.iloc[:, 1:] # 去掉编号列,剩下纯光谱 y = y.loc[y.index] # 如果化学值和光谱行序不一致,这里用 sample_id 做 merge

这里最容易翻车的点在于:读入 DataFrame 后,行顺序未必和化学值文件的顺序一致。我一般会先看两个文件的样本编号,列出来比对一遍,再用编号做合并而不是直接y = pd.read_csv()一刀切。很多同学在答辩前才发现光谱第二行对的是化学值第三行,这种问题一旦发生,整个模型的可信度直接归零。

对齐之后还要做一次基础统计:每个样本的光谱均值、方差,化学值的分布范围。化学值分布如果严重偏态,比如大部分集中在 50~60 之间,只有两个样本在 75,这个数据集后面会很难训,要考虑是否做对数变换。

2.2 异常样本剔除:先画 PCA 散点图再决定

近红外的异常样本常来自三类原因:样品本身有问题(比如颗粒度不均匀)、光谱采集时表面状态不一致(压实密度不同)、化学值测定出错。用模型去拟合异常样本,会让回归模型为了照顾它们而扭曲主规律。

常见做法是先做一遍 PCA,把光谱压缩到前两个主成分上,画散点图看有没有离群点。离群点通常远离主体样本簇,在得分图上一眼就能看出来。

from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA import matplotlib.pyplot as plt scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 先标准化再做 PCA pca = PCA(n_components=2) score = pca.fit_transform(X_scaled) # 粗略定位离群点:主成分得分距离均值超过 k 倍标准差 mean_score = score.mean(axis=0) std_score = score.std(axis=0) outlier_idx = np.where(((score - mean_score) / std_score).abs().max(axis=1) > 3)[0] plt.figure(figsize=(8, 6)) plt.scatter(score[:, 0], score[:, 1], s=30) plt.scatter(score[outlier_idx, 0], score[outlier_idx, 1], c="red", s=50, label="outlier") plt.xlabel("PC1") plt.ylabel("PC2") plt.legend() plt.show()

这段代码里的阈值 3 是经验值,不代表所有数据集都适用。样本本身批次差异大的,PC1 上分布很宽,沿用固定阈值会误杀正常样本。我会先用 PCA 散点图观察整体形态,再决定阈值放 2.5 还是 3.5,而不是一上来就删。PCA 只在这里当探路工具用,后续建模不一定用它降维。

2.3 光谱预处理三板斧:SNV、MSC 与一阶导数

近红外光谱受物理散射影响很重,同一种样品压实密度不同,光谱基线就会上下漂移,颗粒大小不同则会让整体吸光度浮动。预处理的目的就是把这部分与化学成分无关的物理干扰压掉。

常见做法有三种:标准正态变量变换(SNV)消除颗粒度和光程差异;多元散射校正(MSC)以平均光谱为基准修正散射;一阶导数配合 Savitzky-Golay 平滑则用来去除基线漂移并突出峰的细微变化。

def snv(x): return (x - x.mean(axis=1, keepdims=True)) / x.std(axis=1, keepdims=True) def msc(spectra, reference=None): if reference is None: reference = spectra.mean(axis=0) n_spectra = spectra.shape[0] corrected = np.zeros_like(spectra) for i in range(n_spectra): # 对每条光谱与参考光谱做线性回归,拟合系数 a, b a, b = np.polyfit(reference, spectra[i], 1) corrected[i] = (spectra[i] - a) / b return corrected # 一阶导数 + SG 平滑 from scipy.signal import savgol_filter deriv1 = savgol_filter(X, window_length=11, polyorder=2, deriv=1)

每个预处理函数都有前提。SNV 要求同一组数据内基线偏移近似均匀,MSC 要求样品间散射规律一致,导数处理放大了高频噪声,必须先平滑再加。实际项目中我习惯做三份数据:SNV 后的、MSC 后的、一阶导后的,分别跑一遍模型选效果最好的,而不是赌单一预处理。

2.4 样本划分的讲究:训练、验证、测试一定要严格分离

近红外数据样本量不大,常见就几十到几百条,划分方式直接决定模型评估的公平性。常见做法是 Kennard-Stone 算法,它按光谱空间距离挑选最有代表性的样本进训练集,其余进测试集,比随机抽样稳定得多。

def kennard_stone(X, test_ratio=0.2, random_state=None): n = X.shape[0] n_test = int(n * test_ratio) # 初始化:选离均值最远的样本作为第一个训练点 X_center = X.mean(axis=0) dist_to_center = ((X - X_center) ** 2).sum(axis=1) train_idx = [int(np.argmax(dist_to_center))] remaining_idx = list(range(n)) remaining_idx.remove(train_idx[0]) while len(train_idx) < (n - n_test): # 计算剩余样本到已选训练样本的最小距离 min_dist = np.inf chosen = None for i in remaining_idx: d = np.min(np.sqrt(((X[train_idx] - X[i]) ** 2).sum(axis=1))) if d < min_dist: min_dist = d chosen = i train_idx.append(chosen) remaining_idx.remove(chosen) test_idx = remaining_idx return train_idx, test_idx

这里是按纯光谱特征划分的,化学值不参与距离计算。这样保证测试集尽量覆盖不同光谱形态,逻辑上更接近真实抽样。务必先划分再预处理,原则是:对训练集 fit 预处理参数,把同一参数应用到验证集与测试集。

3. 回归模型选型:从 PLSR 到 CNN,先跑基准线再上深度网络

近红外光谱数据的特征是高维共线性,几十个样本配上几百个波长,直接把原始光谱丢进深度网络容易在训练集上表现优秀、在测试集上原形毕露。常规路线是先跑偏最小二乘回归作为基准线,再逐步尝试浅层机器学习和深度网络。

3.1 偏最小二乘回归是绕不开的基准线

偏最小二乘回归(PLSR)是近红外定量分析领域用得最多的传统方法,它直接在 X 与 y 之间找主成分,兼顾光谱变化与浓度变化。对样本量小、多重共线性的数据格外友好,而且模型通过载荷还能反推哪些波段贡献大,可解释性强。

from sklearn.cross_decomposition import PLSRegression from sklearn.model_selection import train_test_split, cross_val_score from sklearn.metrics import r2_score, mean_squared_error # 用前面的 KS 划分结果 train_idx, test_idx = kennard_stone(X_scaled, test_ratio=0.2) X_train, X_test = X_scaled[train_idx], X_scaled[test_idx] y_train, y_test = y.iloc[train_idx], y.iloc[test_idx] # 网格搜索 PLS 成分数 best_n = 5 best_score = -np.inf for n_comp in range(2, 15): pls = PLSRegression(n_components=n_comp, scale=False) scores = cross_val_score(pls, X_train, y_train, cv=5, scoring="r2") if scores.mean() > best_score: best_score = scores.mean() best_n = n_comp pls = PLSRegression(n_components=best_n, scale=False) pls.fit(X_train, y_train) y_pred = pls.predict(X_test) print(f"PLSR R²: {r2_score(y_test, y_pred):.3f}") print(f"PLSR RMSE: {mean_squared_error(y_test, y_pred, squared=False):.3f}")

成分数从 2 试到 15,用 5 折交叉验证定最优值。注意scale=False是因为光谱已经做过标准化,再让 PLSR 内部执行标准化属于重复操作。这段代码跑完后你会得到一组“传统方法到底能做到什么程度”的数字,后面深度模型若不能显著超过它,那就得怀疑深度网络在这里的必要性。

3.2 浅层机器学习作为过渡参考

偏最小二乘之外,支持向量回归(SVR)和随机森林也能处理非线性关系,在中等样本量的光谱数据上通常比 PLSR 好一点,但调参成本高,且对异常值更敏感。SVR 的核心是核函数与惩罚系数 C,RBF 核在近红外上用得较多。

from sklearn.svm import SVR from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline svr_pipe = Pipeline([ ("scaler", StandardScaler()), ("svr", SVR(kernel="rbf", C=100, gamma=0.001, epsilon=0.1)) ]) svr_pipe.fit(X_train, y_train) y_pred_svr = svr_pipe.predict(X_test) print(f"SVR R²: {r2_score(y_test, y_pred_svr):.3f}")

C控制误差容忍度与模型复杂度,越大越容易过拟合;gamma控制 RBF 核的影响半径。这里只给了一组初值,实际要用 GridSearchCV 在C的对数网格与gamma的对数网格上搜索几十组。SVR 在训练集只有百来条样本时相当稳,不需要像后续神经网络那样调学习率。跑完这一档,就能得到一个“传统机器学习队的最高水平”。

3.3 深度模型选 MLP 还是 1D-CNN

深度学习在这个场景里可以承担两种角色,一是完全替代特征工程,直接用原始光谱或预处理谱输入网络,二是作为和 PLSR 对比的“先进方法”在毕业论文里作为一个章节。真正决定网络选型的,是光谱数据的样本量与信噪比。

多层感知机(MLP)实现简单、训练稳定,每个波长对应一个输入神经元,隐层用 ReLU,输出层只有一个神经元,损失函数选择均方误差。一维卷积网络(1D-CNN)在近红外上有天然优势,卷积核在波长方向滑动,能捕捉相邻波段之间的局部特征,但也带来更多超参数。样本量不足时,MLP 通常比 CNN 更不容易翻车,因为卷积核一旦设置不当,模型容量就白扔了一大块。

我的习惯是先跑 MLP 作为深度学习基线,再根据需要扩大到 1D-CNN,不会一上来就把 CNN 铺满。两者对应的损失函数、优化器和评估指标是一致的,切换成本主要在数据维度和模型定义上。L2 正则化是必选项,光谱数据高维且样本少,把权重衰减开到1e-4到1e-3区间,能明显抑制验证集上的过拟合。

4. 用 PyTorch 实现近红外光谱回归模型:最小可复现的训练流程

这一章给出一套能直接跑的 PyTorch 流程,以 1D-CNN 为例,把数据加载、模型定义、训练循环、评估指标写完整。你在毕设里提交的代码,逻辑清晰比性能再高一点更重要,答辩老师会逐个函数问细节。

4.1 把光谱数据包装成 PyTorch Dataset

import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import TensorDataset, DataLoader from sklearn.preprocessing import StandardScaler # 假设已经用前面的预处理得到 X_pre 和 y X_pre = snv(X) # 或你自己选定的预处理结果 y_arr = y["target"].values.astype(np.float32) # 训练集与测试集的标准化 scaler = StandardScaler() X_train_std = scaler.fit_transform(X_pre[train_idx]) X_test_std = scaler.transform(X_pre[test_idx]) # 转成 PyTorch Tensor,并增加通道维度 X_train_t = torch.tensor(X_train_std, dtype=torch.float32).unsqueeze(1) X_test_t = torch.tensor(X_test_std, dtype=torch.float32).unsqueeze(1) y_train_t = torch.tensor(y_arr[train_idx], dtype=torch.float32) y_test_t = torch.tensor(y_arr[test_idx], dtype=torch.float32) train_dataset = TensorDataset(X_train_t, y_train_t) test_dataset = TensorDataset(X_test_t, y_test_t) train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)

unsqueeze(1)是把形状从(batch, wavelength)变成(batch, channel, wavelength),因为Conv1d要求输入带通道维度。这里scaler只在训练集上fit,再应用到测试集,不是拿全部数据一起标准化。这是数据泄漏和“测试集看着好、实际泛化差”的最常见来源。

4.2 定义一个轻量 1D-CNN 回归模型

class SpectraCNN(nn.Module): def __init__(self, n_wavelengths, n_outputs=1): super(SpectraCNN, self).__init__() self.features = nn.Sequential( nn.Conv1d(1, 16, kernel_size=5, padding=2), nn.BatchNorm1d(16), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(16, 32, kernel_size=3, padding=1), nn.BatchNorm1d(32), nn.ReLU(), nn.AdaptiveAvgPool1d(1) ) self.fc = nn.Linear(32, n_outputs) def forward(self, x): x = self.features(x) x = x.flatten(1) return self.fc(x).squeeze(1)

第一层卷积核设 5,近红外光谱相邻几个波长的吸收带彼此影响,5 个点能形成局部感受野;第二层缩减到 3,提取更细的局部关系。AdaptiveAvgPool1d(1)把不同长度的特征图压成固定长度,保证全连接层的输入维度稳定。输出层不开激活函数,这是回归任务和分类任务最大的区别,直接用线性输出对应预测值。

这里的网络只有两个卷积块加一个全连接层,参数总量很小,在几十到几百个样本的光谱集上不容易过拟合。若换用更大的 ResNet 或 Transformer 版本,除非你有一两千个样本以上,否则基本是在给自己制造调参地狱。

4.3 训练循环:MSE 损失、Adam 优化器与早停

model = SpectraCNN(n_wavelengths=X_train_t.shape[2]) optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) criterion = nn.MSELoss() best_val_loss = np.inf patience = 30 counter = 0 n_epochs = 500 for epoch in range(n_epochs): model.train() train_loss = 0.0 for xb, yb in train_loader: optimizer.zero_grad() pred = model(xb) loss = criterion(pred, yb) loss.backward() optimizer.step() train_loss += loss.item() * xb.size(0) model.eval() val_loss = 0.0 with torch.no_grad(): for xb, yb in test_loader: pred = model(xb) loss = criterion(pred, yb) val_loss += loss.item() * xb.size(0) if val_loss < best_val_loss: best_val_loss = val_loss counter = 0 torch.save(model.state_dict(), "best_spectra_model.pth") else: counter += 1 if counter >= patience: print(f"early stop at epoch {epoch}") break if (epoch + 1) % 20 == 0: print(f"epoch {epoch+1}, train loss {train_loss:.4f}, val loss {val_loss:.4f}")

学习率 1e-3 是 Adam 常见的起点,loss 不降或剧烈振荡时再调 3e-4;weight_decay 是 L2 正则化的 PyTorch 写法,这里设为 1e-4。早停的 patience 设为 30,意即连续 30 轮验证集 loss 没有新低就停止,既防止过拟合,也节省时间。这里用的是最后的验证 loss 作为模型保存标准,保存的是“验证集上最好的那一版”。许多新手把最后一个 epoch 的参数当结果,这就错过了前期已经出现的更低验证 loss。

4.4 评估指标:R²、RMSE 与 RPD

model.eval() y_pred_all, y_true_all = [], [] with torch.no_grad(): for xb, yb in test_loader: pred = model(xb) y_pred_all.append(pred.numpy()) y_true_all.append(yb.numpy()) y_pred = np.concatenate(y_pred_all) y_true = np.concatenate(y_true_all) ss_res = np.sum((y_true - y_pred) ** 2) ss_tot = np.sum((y_true - y_true.mean()) ** 2) r2 = 1 - ss_res / ss_tot rmse = np.sqrt(mean_squared_error(y_true, y_pred)) rpd = np.std(y_true, ddof=1) / rmse print(f"Test R²={r2:.3f}, RMSE={rmse:.3f}, RPD={rpd:.2f}")

R² 与 RMSE 必须结合来看。R²=0.95 而 RMSE 很大,意味着测试集化学值本身波动很宽,趋势预测对了但绝对误差不可接受。在近红外定量分析中,RMSE 才是实际业务关心的量,因为它直接对应预测浓度的偏差。RPD 大于 3 通常认为模型可用于定量分析,2 到 3 之间只能做粗略筛查,小于 2 则模型没有应用价值。

5. 近红外光谱回归分析避坑清单:5 个常见翻车点

5.1 光谱与化学值行序错位,导致全链路失真

现象:模型训练时 R² 极高,一换测试集就崩溃,或者训练集 R² 几乎为 0。进一步检查发现代码里两个数据文件的行顺序并不一致。

原因:从不同来源收集的光谱与化学值,在整理成 Excel 或 CSV 时行顺序被无意打乱,又没有统一的样本编号作为连接键。

解决:在进入任何预处理前,先打印两个文件的前五行和末尾五行,核对样本编号。给每条样本一个唯一 ID,用pd.merge(spectra, chem_values, on="sample_id")做显式合并,而不是简单地在 DataFrame 上横向拼接。

5.2 数据泄漏:预处理在全数据集上完成

现象:验证集和测试集的 R² 高得离谱,比后期独立采样回测明显低,重建同一流程也复现不出好成绩。

原因:先对全部样本做了标准化或降维,再划分训练集与测试集。主成分、均值、标准差这些统计量混入了测试集信息,使得测试集不再“未见”。

解决:严格按“先划分,后预处理”的顺序执行。用Pipeline或手写代码,让fit只落在训练集上,transform应用在全部子集。这一条在 PLSR、SVR、MLP、CNN 中同等适用。

5.3 高维光谱直接硬塞进 MLP,造成过拟合

现象:训练集 loss 降到几乎为 0,RMSE 很小,但验证集的 R² 却是负值,预测曲线像噪声。

原因:波长变量成百上千,而样本只有几十条,模型自由度远高于样本量,网络把光谱上的随机噪声当作规律背了下来。

解决:先用 PLSR 或 PCA 估计有效维度,再把降维后的特征输入 MLP;或直接在 CNN 第一层加较大的池化。同时开启 weight_decay 和早停,两层隐藏层已经足够在百级样本上表达非线性关系。

5.4 BatchNorm 在训练与评估模式下表现不一致

现象:训练集 loss 一路下降,验证时输出几乎不变或变化幅度异常,尤其是在预测阶段。

原因:BatchNorm1d在训练时使用当前 batch 的均值与方差,在评估时使用积累的全局统计量。如果代码里没有正确切换model.train()和model.eval(),BatchNorm 的行为会前后不一致。样本数量不足时,batch 统计量抖动还很剧烈。

解决:训练循环开头写model.train(),验证和预测前必写model.eval()。如果 batch size 小且训练不稳定,去掉 BatchNorm 换成 LayerNorm,或者直接不要归一化层,只靠卷积加 ReLU。

5.5 只看 R² 交差,忽略了 RMSE 与建模目的

现象:答辩时被问“你的模型预测误差是多少”,答不上来或 RMSE 偏高被认定为模型不可用。

原因:不少课程作业把 R² 当作唯一指标,而近红外分析的实际考核标准往往是 RMSE。一个数据集本身浓度范围窄,R² 天然偏低,但 RMSE 或许已经足够小;反过来也是如此。

解决:统一报告 R²、RMSE、RPD 三个指标。理学和农学里还常常要求报告交叉验证均方根误差。将来写论文,最好把测试集的真实值-预测值散点图、残差分布图一起放进去,让误差信息可检视。

6. 让模型不只停在验收:外部验证与敏感波段分析

很多毕设做到测试集 R²=0.9 就足够了,但如果想让这项工作更有说服力,我建议做两件事:留一外部验证和敏感波段可视化。前者回答“模型到真正没见过的仪器和样本上还好使吗”,后者回答“模型到底在学什么”。

外部验证的做法是把某个月采集的样本或某个批次的样本整体作为验证集,完全不参与训练与调参。比随机切分的测试集更接近真实部署场景,数据量够时优先使用。敏感波段分析中,以训练好的模型为基础,计算每个波长对输出梯度的贡献值,或者用排列重要性逐一打乱波长、观察预测误差的变化,两种方式都能看出模型是否真的集中在化学吸收特征明显的区间。如果模型把重要权重落在 1800~2100 nm 的水吸收带上,那结论就有物理解释力了。

临近收尾,想起我最早做其中一个课程作业,只把光谱归一化后就直接丢进一个三层全连接网络,训练集 R² 一度到了 0.97,结果换了一批新样品全部预测成一条水平线。后来才明白,所谓“深度学习回归模型”不是把样本塞进网络就能万事大吉,它依赖的数据基础、对照基准和验证逻辑才是真正的功夫。后来我在每一个这类项目上都保留三件事:一份 PLSR 基准结果、一份严格先划分后预处理的 PyTorch 训练脚本、一套覆盖 R²/RMSE/RPD 的评估输出,任何新数据来了都先跑这三个流程。这一套思路,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 1:21:52

Docker入门与实战——端口映射与容器互联

端口映射与容器互联1、通过端口映射实现容器访问1.1、从外部访问容器应用1.2、映射所有端口地址1.3、映射到指定地址的指定端口1.4、映射到指定地址的任意端口1.5、查看映射端口配置2、通过互联机制实现便捷互访2.1、自定义容器命名2.2、容器互联在前几章的学习过程中&#xff…

作者头像 李华
网站建设 2026/10/9 1:19:42

用Python与PCA做异常检测:重构误差、KPCA与工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/9 1:16:44

题解:洛谷 AT_abc439_a [ABC439A] 2^n - 2*n

本文分享的必刷题目是从蓝桥云课、洛谷、AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。 欢迎大家订阅我的专栏:算法…

作者头像 李华