news 2026/7/26 5:18:38

NCM格式解密与音频转换:Python实现网易云音乐文件批量转MP3/FLAC

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NCM格式解密与音频转换:Python实现网易云音乐文件批量转MP3/FLAC

1. 项目概述与核心需求解析

最近在整理本地音乐库,发现从网易云音乐下载的VIP歌曲都是.ncm格式,这玩意儿在别的播放器上根本打不开,手机传歌也麻烦。这事儿估计不少人都遇到过,辛辛苦苦攒的歌单,换个设备或者想用第三方播放器就抓瞎了。所以,今天就来聊聊怎么把这些.ncm文件,无损地、批量地转换成通用的mp3或者flac格式。

简单说,这个教程就是帮你把网易云音乐的“专属加密格式”变成谁都能播的“通用格式”。核心需求就三点:第一,能解密,把网易云加的那层“锁”给打开;第二,能转换,把解密后的音频数据封装成mp3或flac;第三,最好能批量自动处理,谁也不想一首一首手动操作。适合所有手头有.ncm文件,想在更多设备上自由聆听这些歌曲的朋友,无论你是Python小白还是有点编程基础的,都能找到适合自己的方法。

2. 技术原理浅析:NCM格式与转换的本质

在动手之前,我们得先搞清楚.ncm到底是什么,以及我们要做的“转换”究竟是在转换什么。这能帮你理解后续每一步操作的意义,而不是机械地执行命令。

2.1 NCM格式的构成

网易云音乐的.ncm文件,并非一种全新的音频编码格式。你可以把它理解成一个“盒子”。这个盒子外面有一把专属的锁(网易云的加密算法),盒子里面装着两样东西:

  1. 核心音频数据:这部分通常是原始的、未压缩的PCM数据,或者已经是高质量的压缩格式(如FLAC或MP3)。它的音质理论上等同于甚至优于你在线听到的最高品质。
  2. 元数据(Metadata):包括歌曲名、歌手、专辑、封面图片等信息,也就是ID3标签。

网易云的加密,主要作用于这个“盒子”的整体封装和部分关键数据,目的是防止音频数据被直接提取出来在其他平台使用。因此,我们所谓的“ncm转mp3”,严格来说,是“ncm解密并重新封装为mp3”。音质损失与否,取决于我们转换时选择的参数和源文件的质量。

2.2 解密的关键:密钥

解密过程需要密钥。这个密钥并非固定不变,而是根据每个用户的账户ID和歌曲ID等信息动态生成的。不过,社区里已经有逆向工程师分析出了其生成算法。我们使用的Python脚本,其核心功能之一就是模拟这个算法,为每一个.ncm文件计算出正确的解密密钥。这是整个流程的技术核心,也是我们能实现转换的前提。

2.3 转换格式的选择:MP3 vs FLAC

解密后,我们会得到原始的音频数据。接下来就是封装:

  • 转换为FLAC:这是一种无损压缩格式。如果你的.ncm源文件本身是无损音质(如网易云的“无损”或“Hi-Res”品质),那么转换为FLAC可以近乎完美地保留所有音频信息,文件体积会比原始PCM小,但比MP3大。适合对音质有高要求、本地存储空间充足的用户。
  • 转换为MP3:这是一种有损压缩格式。它会通过心理声学模型舍弃一些人耳不太敏感的声音细节,从而大幅减小文件体积。转换时需要选择一个比特率(Bitrate),比如320kbps、256kbps等。比特率越高,保留的细节越多,音质越好,文件也越大。对于大多数流行音乐和普通收听设备(如手机、蓝牙耳机),320kbps的MP3已经非常接近无损听感,是体积与音质的优秀平衡点。

注意:从加密的.ncm转换到MP3/FLAC,其音质上限取决于你当初从网易云音乐下载时所选择的音质。如果你下载的是标准音质(128kbps左右的MP3),那么即使你转换成FLAC,也无法获得真正的无损效果,这只是将低品质数据封装进了无损容器,并无实际提升。

3. 环境与工具准备

工欲善其事,必先利其器。我们将主要使用Python方案,因为它灵活、可批量、可集成。此外,也会介绍一种更“傻瓜式”的图形界面工具作为备选。

3.1 Python方案核心工具链

  1. Python环境:这是基础。你需要安装Python 3.6或以上版本。可以去Python官网下载安装包,安装时务必勾选“Add Python to PATH”,这样才可以在命令行(CMD或终端)里直接使用pythonpip命令。
  2. 核心Python库
    • pycryptodome:一个功能强大的加密解密库,用于实现.ncm文件的解密算法。
    • mutagen:一个处理音频元数据(标签)的库,用于读写MP3、FLAC等文件的ID3信息。
    • tqdm(可选但推荐):用于在命令行中显示漂亮的进度条,处理大量文件时体验更好。
  3. 音频处理后端 - FFmpeg:这是整个流程的“引擎”。Python脚本负责解密和调用,实际的音频格式转换和编码工作由FFmpeg完成。FFmpeg是一个极其强大的开源音视频处理程序,我们需要将其安装到系统,并确保其路径被系统识别。

3.2 详细安装与配置步骤

步骤一:安装Python访问 python.org,下载最新稳定版的Windows安装程序。运行安装程序,在第一个界面最下方,一定要勾选“Add python.exe to PATH”,然后点击“Install Now”。安装完成后,打开命令提示符(Win+R,输入cmd),输入python --version,如果显示Python版本号,则说明安装和PATH配置成功。

步骤二:安装必要的Python库在命令提示符中,依次执行以下命令:

pip install pycryptodome mutagen tqdm

pip是Python的包管理工具,通常随Python一起安装。这些命令会从网络下载并安装我们需要的库。

步骤三:安装并配置FFmpeg这是关键一步,很多转换失败都源于FFmpeg没装好。

  1. 访问FFmpeg官网的下载页面,选择“Windows builds”。
  2. 下载一个“release”版本,比如由“gyan.dev”构建的版本,解压到一个你喜欢的目录,例如D:\Tools\ffmpeg
  3. 配置系统环境变量
    • 右键点击“此电脑” -> “属性” -> “高级系统设置” -> “环境变量”。
    • 在“系统变量”部分,找到并选中Path变量,点击“编辑”。
    • 点击“新建”,将FFmpeg的bin文件夹的完整路径添加进去(例如D:\Tools\ffmpeg\bin)。
    • 一路点击“确定”关闭所有窗口。
  4. 验证安装:打开一个新的命令提示符(重要,必须新开),输入ffmpeg -version。如果出现一大堆版本和配置信息,而不是“不是内部或外部命令”,则说明配置成功。

3.3 图形界面工具备选

如果你完全不想接触命令行,也有一些现成的图形化工具,例如“NCM转换器”等。这些工具通常将解密和转换功能打包成一个.exe文件,只需拖放文件即可。但需要注意:

  • 安全性:务必从可信来源下载,以防捆绑病毒或木马。
  • 更新:可能无法及时跟上网易云加密方式的更新。
  • 灵活性:通常无法自定义输出参数(如MP3比特率)。 本教程以更透明、可控的Python方案为主。

4. Python脚本解析与实操

我们将使用一个经过社区验证的Python脚本。你可以自己创建一个.py文件,将代码复制进去。这里我们不仅提供代码,还会逐段解释其作用,让你明白每一步在干什么。

4.1 脚本代码与注释

创建一个文本文件,命名为ncm_converter.py,用代码编辑器(如VSCode、Notepad++)或记事本打开,粘贴以下内容:

#!/usr/bin/env python3 """ 网易云音乐.ncm文件解密并转换为MP3/FLAC脚本 基于公开的解密算法实现 """ import os import json import struct import base64 from Crypto.Cipher import AES from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB from mutagen.flac import FLAC, Picture from mutagen.mp3 import MP3 import subprocess from tqdm import tqdm from pathlib import Path def dump(file_path): """核心解密函数""" with open(file_path, 'rb') as f: header = f.read(8) # 1. 验证文件魔数,确认是NCM文件 if header != b'CTENFDAM': print(f"文件 {file_path} 不是有效的NCM格式,跳过。") return None # 2. 跳过关键段长度信息 f.seek(2, 1) # 相对当前位置跳过2字节 key_length = struct.unpack('<I', f.read(4))[0] # 读取密钥长度(小端序) # 3. 读取加密的密钥数据并解密 key_data = f.read(key_length) key_data = bytearray([byte ^ 0x64 for byte in key_data]) # 异或解密 # 这里模拟了网易云的核心密钥解密算法 core_key = b'#14ljk_!\\]&0U<\'(' cipher = AES.new(core_key, AES.MODE_ECB) key_data = cipher.decrypt(key_data) key_data = key_data[:-key_data[-1]] # 去除PKCS#7填充 # 4. 读取元数据(歌曲信息、封面等) meta_length = struct.unpack('<I', f.read(4))[0] meta_data = f.read(meta_length) meta_data = bytearray([byte ^ 0x63 for byte in meta_data]) # 异或解密元数据 # 跳过“163 key(Don‘t modify):”这段固定字符串 meta_data = meta_data[22:] # 对元数据进行Base64解码并解析JSON meta_data = base64.b64decode(meta_data) meta_json = json.loads(meta_data.decode('utf-8')) # 5. 解密音频数据主体 # 获取用于解密音频的“盒密钥” box_key = bytearray([0x29, 0x23, 0x21, 0x5E]) for i in range(len(box_key)): box_key[i] ^= key_data[i] # 定位到音频数据开始位置 f.seek(9, 1) # 跳过9个未知字节 data_start = f.tell() # 读取剩余的整个文件作为加密的音频数据 cipher_data = f.read() # 6. 使用“盒密钥”解密音频数据 decrypted_data = bytearray() chunk_size = 0x8000 # 每次处理32KB for i in range(0, len(cipher_data), chunk_size): chunk = cipher_data[i:i + chunk_size] # 构建AES解密器,使用CBC模式,初始向量为全零 cipher = AES.new(bytes(box_key), AES.MODE_CBC, iv=b'\x00'*16) decrypted_chunk = cipher.decrypt(chunk) decrypted_data.extend(decrypted_chunk) # 7. 处理可能的填充字节 # 根据PKCS#7规则,最后一个字节的值代表填充长度 padding = decrypted_data[-1] if padding <= 16: decrypted_data = decrypted_data[:-padding] # 返回解密后的音频数据、元数据、和文件起始位置(用于后续计算) return decrypted_data, meta_json, data_start def save_audio(data, meta, output_path, format='mp3', bitrate='320k'): """保存音频文件并写入元数据""" # 先临时保存解密后的原始数据为.raw或.m4a文件(视格式而定) temp_file = output_path.with_suffix('.temp') with open(temp_file, 'wb') as f: f.write(data) # 使用FFmpeg进行格式转换 # -i 输入文件 -ab 指定比特率 -y 覆盖输出文件 cmd = ['ffmpeg', '-i', str(temp_file), '-ab', bitrate, '-y', str(output_path)] try: subprocess.run(cmd, check=True, capture_output=True) print(f"转换成功: {output_path}") except subprocess.CalledProcessError as e: print(f"FFmpeg转换失败: {e.stderr.decode()}") return False finally: # 无论成功与否,都删除临时文件 if temp_file.exists(): temp_file.unlink() # 写入元数据(ID3标签和封面) try: if format.lower() == 'mp3': audio = MP3(output_path) # 确保有ID3标签 if audio.tags is None: audio.add_tags() # 写入歌曲名、艺术家、专辑 audio.tags.add(TIT2(encoding=3, text=meta.get('musicName', ''))) audio.tags.add(TPE1(encoding=3, text=[meta.get('artist', [{}])[0].get('name', '')])) audio.tags.add(TALB(encoding=3, text=meta.get('album', ''))) # 处理封面图片 if 'albumPic' in meta: # 这里需要从网络获取封面,简化版可跳过或从本地加载 # 示例中我们跳过自动下载,你可以手动添加封面 pass audio.save() elif format.lower() == 'flac': audio = FLAC(output_path) audio['title'] = meta.get('musicName', '') audio['artist'] = meta.get('artist', [{}])[0].get('name', '') audio['album'] = meta.get('album', '') # FLAC封面添加略复杂,需要Picture对象,此处简化 audio.save() except Exception as e: print(f"写入元数据时出错(可忽略): {e}") return True def main(): import sys if len(sys.argv) < 2: print("用法: python ncm_converter.py <ncm文件或文件夹路径> [输出格式: mp3/flac] [比特率,如 320k]") print("示例: python ncm_converter.py ./music flac") print("示例: python ncm_converter.py song.ncm mp3 256k") return input_path = Path(sys.argv[1]) # 设置默认参数 output_format = sys.argv[2].lower() if len(sys.argv) > 2 else 'mp3' bitrate = sys.argv[3] if len(sys.argv) > 3 else '320k' if output_format not in ['mp3', 'flac']: print("错误:输出格式必须是 'mp3' 或 'flac'") return # 收集所有.ncm文件 ncm_files = [] if input_path.is_file() and input_path.suffix.lower() == '.ncm': ncm_files = [input_path] elif input_path.is_dir(): ncm_files = list(input_path.rglob('*.ncm')) + list(input_path.rglob('*.NCM')) else: print("错误:输入的路径不是文件也不是文件夹。") return if not ncm_files: print("在指定路径下未找到.ncm文件。") return print(f"找到 {len(ncm_files)} 个.ncm文件,开始转换...") # 使用tqdm创建进度条 for ncm_file in tqdm(ncm_files, desc="转换进度"): result = dump(ncm_file) if result is None: continue audio_data, meta, _ = result # 构建输出文件名和路径 output_dir = ncm_file.parent / 'converted' output_dir.mkdir(exist_ok=True) # 创建输出目录 output_filename = meta.get('musicName', ncm_file.stem) + f'.{output_format}' output_path = output_dir / output_filename # 防止文件名重复冲突 counter = 1 while output_path.exists(): output_path = output_dir / f"{meta.get('musicName', ncm_file.stem)}_{counter}.{output_format}" counter += 1 # 保存转换 save_audio(audio_data, meta, output_path, output_format, bitrate) print("所有文件处理完成!") if __name__ == '__main__': main()

4.2 脚本使用实操步骤

假设你的脚本和.ncm文件都准备好了,按以下步骤操作:

  1. 准备文件:将ncm_converter.py脚本放在一个方便的目录,比如D:\ncm_convert。把你的.ncm文件也放在这里,或者放在其子文件夹里。
  2. 打开命令行:在文件资源器中,进入D:\ncm_convert目录,在地址栏输入cmd并按回车,这样命令行就会直接在这个目录下打开。
  3. 执行转换
    • 转换单个文件:假设有一个song.ncm文件。
      python ncm_converter.py song.ncm mp3
      这会将song.ncm转换为320kbps的MP3。
    • 转换整个文件夹:假设所有.ncm文件都在downloads文件夹里。
      python ncm_converter.py downloads flac
      这会将downloads文件夹及其子文件夹下所有.ncm文件转换为无损FLAC格式。
    • 自定义比特率:如果你想指定MP3的比特率,比如192kbps。
      python ncm_converter.py song.ncm mp3 192k
  4. 查看结果:转换成功后,脚本会在.ncm文件所在目录下创建一个名为converted的文件夹,所有转换好的MP3或FLAC文件都会放在里面,并且会尝试写入歌曲名、歌手等标签信息。

实操心得:第一次运行时,可能会遇到一些库报错,通常是Crypto模块找不到。这是因为pycryptodome库的导入名有时是Crypto,有时是cryptodome。如果遇到ModuleNotFoundError: No module named 'Crypto',可以尝试在代码开头将from Crypto.Cipher import AES改为from Cryptodome.Cipher import AES。这是这个脚本最常见的一个小坑。

5. 高级技巧与自定义参数

掌握了基础用法后,你可以通过修改脚本或调整使用方式,来满足更个性化的需求。

5.1 批量处理与文件组织

脚本已经支持批量处理,但你可以进一步优化:

  • 按专辑/歌手分类:目前的脚本将所有输出文件放在一个converted文件夹。你可以修改save_audio函数中output_dir的生成逻辑。例如,使用元数据中的歌手名创建子文件夹:
    # 在save_audio函数或main函数中构建output_dir时 artist_name = meta.get('artist', [{}])[0].get('name', 'UnknownArtist') album_name = meta.get('album', 'UnknownAlbum') output_dir = ncm_file.parent / 'converted' / artist_name / album_name output_dir.mkdir(parents=True, exist_ok=True) # 递归创建目录
  • 处理文件名非法字符:有些歌曲名包含\/:*?"<>|等Windows文件名非法字符,直接保存会出错。可以在生成output_filename时进行过滤:
    import re music_name = meta.get('musicName', ncm_file.stem) # 替换非法字符为下划线 safe_name = re.sub(r'[\\/*?:"<>|]', '_', music_name) output_filename = safe_name + f'.{output_format}'

5.2 音质参数深度解析

脚本中通过FFmpeg的-ab参数指定比特率。对于MP3,比特率模式有三种:

  • CBR(恒定比特率)-ab 320k。全程恒定码率,文件大小可预测,兼容性最好。推荐使用。
  • VBR(可变比特率)-q:a 0(最高质量,范围0-9,值越小质量越高)。FFmpeg中使用-qscale:a-q:a。VBR会在简单段落用低码率,复杂段落用高码率,在同等文件体积下可能获得更好的听感,但某些老旧设备可能支持不佳。
  • ABR(平均比特率)-abr 192k。是VBR的一种,会努力使平均码率接近设定值。在FFmpeg中,通常用-b:a来指定,但MP3编码器默认就是ABR行为。

对于FLAC,它是无损压缩,没有比特率概念,音质是“无损的”。FFmpeg参数只需-c:a flac。你可以通过-compression_level参数(0-12,默认5)来控制压缩速度和文件大小的平衡,级别越高压缩率越高(文件越小),但编码时间越长。音质完全无损。

修改脚本以使用VBR:找到调用FFmpeg的cmd列表,将['ffmpeg', '-i', str(temp_file), '-ab', bitrate, '-y', str(output_path)]改为['ffmpeg', '-i', str(temp_file), '-q:a', '0', '-y', str(output_path)]即可使用最高质量的VBR编码。

5.3 封面图片的获取与嵌入

脚本中关于封面albumPic的部分被简化了,因为自动下载网络图片涉及网络请求和错误处理。一个实用的折中方案是:

  1. 让脚本运行,先完成音频转换。
  2. 手动从网易云音乐或其他渠道下载专辑封面,保存为cover.jpg放在converted文件夹。
  3. 使用另一个简单的脚本或工具(如MP3Tag、Kid3)批量将封面图片嵌入到已转换好的音频文件中。mutagen库本身也能做到,你可以编写一个后续处理的脚本。

6. 常见问题与故障排除实录

在实际操作中,你可能会遇到以下问题。这里记录了我踩过的坑和解决方案。

6.1 “不是有效的NCM格式”错误

  • 现象:脚本提示“文件 XXX.ncm 不是有效的NCM格式,跳过。”
  • 原因1:文件确实已损坏或不是.ncm文件。检查文件大小是否异常小(如几KB),或用十六进制编辑器查看文件开头是否是43 54 45 4E 46 44 41 4D(即CTENFDAM的ASCII码)。
  • 原因2:网易云音乐更新了加密格式。这是最可能的原因。社区逆向的算法可能暂时失效。
  • 解决方案
    1. 关注GitHub上相关的开源项目(如ncmdump),查看是否有更新。
    2. 临时使用其他图形化工具试试,看新版本是否支持。
    3. 等待脚本更新。你可以尝试搜索更新版的解密算法代码,替换dump函数中的核心解密部分(core_key和异或操作等)。

6.2 FFmpeg相关错误

  • 现象FFmpeg转换失败,并伴随一堆错误信息。
  • 原因1:FFmpeg未正确安装或环境变量未配置。这是最常见的原因。
    • 排查:在新的命令提示符窗口输入ffmpeg -version,看是否有输出。
    • 解决:重新检查FFmpeg的安装路径是否已添加到系统Path环境变量,并重启命令提示符。
  • 原因2:临时文件路径包含中文或特殊字符,导致FFmpeg读取失败。
    • 解决:将脚本和.ncm文件移动到纯英文路径下再试,例如D:\convert
  • 原因3:解密后的音频数据格式异常,FFmpeg无法识别。
    • 解决:这通常意味着解密过程出了问题。可以尝试用其他工具解密同一个文件,交叉验证。如果其他工具成功,则问题出在我们的脚本解密环节。

6.3 Python库导入错误

  • 现象ModuleNotFoundError: No module named 'Crypto'ModuleNotFoundError: No module named 'Crypto.Cipher'
  • 解决:首先确认已安装pycryptodome(pip install pycryptodome)。如果已安装仍报错,尝试在代码中将from Crypto.Cipher import AES改为from Cryptodome.Cipher import AES。这是因为安装的包名和内部模块名有时不一致。

6.4 转换后文件无声或音质极差

  • 现象:转换出来的MP3/FLAC能播放,但没声音,或者全是噪音。
  • 原因:几乎可以肯定是解密密钥计算错误,导致音频数据解密失败,FFmpeg将一堆乱码当成了音频数据编码。
  • 解决:同样,使用其他工具验证该.ncm文件是否能被正确转换。如果其他工具可以,则需要检查脚本的dump函数,特别是core_key和异或0x640x63这些值是否正确。这些是逆向工程得出的“魔法数字”,如果网易云更新,这些数字可能会变。

6.5 批量处理时内存占用过高

  • 现象:处理大量文件时,Python进程内存占用越来越大,甚至卡死。
  • 原因:脚本一次性将所有解密后的音频数据audio_data保存在内存中,对于大量文件或超长歌曲,可能导致内存不足。
  • 优化:可以修改save_audio函数,让解密和保存流式进行。即解密一部分,就写入临时文件一部分,而不是全部加载到内存。但这会显著增加代码复杂度。对于普通用户,更简单的办法是分批次处理文件,不要一次性处理成千上万个。

最后,关于版权和道德的提醒,虽然技术本身无罪,我们转换的是自己已经下载的、拥有收听权限的歌曲,用于个人在不同设备间同步聆听,这符合个人使用的合理范畴。但请务必尊重音乐人的劳动成果,不要将解密后的文件进行公开传播或用于商业用途,让技术帮助我们更好地享受音乐,而不是伤害创作生态。

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

Unity游戏配置管理新思路:Luban插件实现Excel到Json自动化流程

1. 项目概述&#xff1a;为什么我们需要新的配置管理思路&#xff1f;在Unity游戏开发中&#xff0c;配置管理是个老生常谈但又极其核心的话题。从早期的ScriptableObject&#xff0c;到直接读取CSV、XML&#xff0c;再到如今主流的Json&#xff0c;每个团队似乎都有一套自己的…

作者头像 李华
网站建设 2026/7/26 5:17:22

C++ string类模拟实现:从深拷贝到移动语义的底层原理与实践

1. 项目概述&#xff1a;为什么要亲手实现一个 string 类&#xff1f;在C的学习和面试路上&#xff0c;std::string几乎是每个开发者最早接触、最频繁使用的标准库组件之一。它封装了字符数组的复杂性&#xff0c;提供了便捷的拼接、查找、比较等操作&#xff0c;让我们能专注于…

作者头像 李华
网站建设 2026/7/26 5:16:24

CC2430 DMA控制器实战指南:从原理到嵌入式系统高效数据搬运

1. 项目概述在嵌入式系统开发&#xff0c;尤其是资源受限的无线传感网络节点设计中&#xff0c;如何平衡性能与功耗是一个永恒的课题。当你需要处理来自ADC的连续采样数据流&#xff0c;或者需要高速收发射频数据包时&#xff0c;如果让CPU亲自去搬运每一个字节&#xff0c;它很…

作者头像 李华
网站建设 2026/7/26 5:16:18

影刀RPA京东商品数据采集实战:价格库存评分批量监控

影刀RPA京东商品数据采集实战&#xff1a;价格库存评分批量监控 作者&#xff1a;林焱 京东是做电商竞品分析的核心数据源之一&#xff0c;价格体系透明、评分数据丰富。这篇把京东商品数据的采集方法完整梳理一遍。 一、能采集的数据类型 搜索结果列表&#xff1a;商品名称、…

作者头像 李华
网站建设 2026/7/26 5:14:47

C/C++ BFS算法面试实战:从核心原理到高频考点解析

1. 项目概述&#xff1a;一份面向求职者的C/C BFS算法实战与面试指南 最近在整理自己的技术笔记&#xff0c;翻到了几年前准备面试时刷过的那些BFS&#xff08;广度优先搜索&#xff09;题目。恰好看到网上有不少朋友在找“最全BFS例题合集”和“大厂面试总结”&#xff0c;但内…

作者头像 李华