[{"content":"欢迎来到曙光磁铁的 Codeforces 题解区。\n这里记录竞赛题解、算法思路和常用代码模板。题目主要来自 Codeforces，常用语言为 Rust、C++ 和 Go。\n本站使用 Hugo 与 Blowfish 构建。\n","date":"2026年2月5日","externalUrl":null,"permalink":"/codeforces/","section":"Codeforces 题解","summary":"","title":"Codeforces 题解","type":"codeforces"},{"content":"","date":"2026年9月14日","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"2026年9月14日","externalUrl":null,"permalink":"/tags/codex/","section":"Tags","summary":"","title":"Codex","type":"tags"},{"content":"","date":"2026年9月14日","externalUrl":null,"permalink":"/tags/microsoft-store/","section":"Tags","summary":"","title":"Microsoft Store","type":"tags"},{"content":"","date":"2026年9月14日","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"2026年9月14日","externalUrl":null,"permalink":"/tags/powershell/","section":"Tags","summary":"","title":"PowerShell","type":"tags"},{"content":"","date":"2026年9月14日","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"2026年9月14日","externalUrl":null,"permalink":"/categories/windows/","section":"Categories","summary":"","title":"Windows","type":"categories"},{"content":"","date":"2026年9月14日","externalUrl":null,"permalink":"/tags/winget/","section":"Tags","summary":"","title":"Winget","type":"tags"},{"content":"在没有管理员权限的 Windows 上安装 Codex，麻烦经常发生在下载之前。OpenAI 的 Windows 文档给出的命令很短。\nwinget install --id 9PLM9XGG6VKS -s msstore 这条命令能正常运行时，直接用它就好。有些受管理的电脑缺少可用的 winget，msstore 源也可能无法工作。用户知道 Codex 就在 Microsoft Store 里，却没有权限修复 App Installer，也不能把整台机器的策略改掉。\n我写 wingets，就是为了补上这一小段路。它用 Codex 的 Store 产品 ID 找到官方包，把文件下载到用户目录，再通过 Add-AppxPackage 为当前用户注册。整个过程不需要管理员权限，也不会安装 PowerShell 模块。\n先把 Codex 装上 # 打开 DawnMagnet 的 wingets Gist 可以先查看完整源码。下面的命令把脚本放进当前用户的 LocalAppData，不会写入系统目录。\n$wingetsPath = Join-Path $env:LOCALAPPDATA \u0026#39;wingets\\wingets.ps1\u0026#39; $wingetsDir = Split-Path $wingetsPath New-Item -ItemType Directory -Force -Path $wingetsDir | Out-Null Invoke-WebRequest ` -Uri \u0026#39;https://gist.githubusercontent.com/DawnMagnet/3b05c689a849ba0cfc48cbaf55969d32/raw/wingets.ps1\u0026#39; ` -OutFile $wingetsPath 随后安装 Codex。\npowershell.exe -NoProfile -ExecutionPolicy Bypass ` -File $wingetsPath install 9PLM9XGG6VKS -ExecutionPolicy Bypass 只作用于这次启动的 PowerShell 进程，不会永久修改本机或当前用户的执行策略。脚本默认选择 x64。Windows on Arm 设备需要明确指定 arm64。\npowershell.exe -NoProfile -ExecutionPolicy Bypass ` -File $wingetsPath install 9PLM9XGG6VKS --architecture arm64 下载文件默认保存在 %USERPROFILE%\\Downloads\\wingets。安装完成后，可以删除脚本和下载目录，已经注册的 Codex 不受影响。第一次安装不要加 --force，这个选项会先卸载当前用户已有的包，应用数据和设置可能随之删除。\nWingets 补的是哪一块 # 官方命令依赖 Windows Package Manager、msstore 源和当前机器上的 App Installer 注册状态。管理员可以修复或重新部署这些组件。普通用户遇到同样的问题时，经常只能停在这里。\nwingets 跳过损坏或不可用的 winget 客户端，继续使用 Microsoft 的服务和 Windows 自带的包注册能力。它不会提升权限，也不会绕过组织策略。这个区别很要紧。\n脚本执行的流程很短。\nCodex Store product ID 9PLM9XGG6VKS │ ▼ Microsoft Store catalog returns WuCategoryId │ ▼ Windows Update resolves current Retail packages and dependencies │ ▼ Microsoft CDN download with digest verification │ ▼ Add-AppxPackage registers packages for the current user Microsoft Store 的产品元数据给出 PackageFamilyName 和 WuCategoryId。前者用于检查 Codex 是否已经安装，后者交给 Windows Update 查询当前可用的 MSIX、AppX 及依赖包。脚本按 FileDigest 关联元数据与下载地址，避免把列表顺序当成固定接口。\n下载只接受 *.delivery.mp.microsoft.com。文件落盘后，脚本会核对 Windows Update 经 HTTPS 返回的 SHA-1 摘要，安装阶段再由 Windows 验证包签名。SHA-1 在这里用于检查传输内容，发布者身份仍由 Windows 的签名验证负责。\n为什么删掉 WinStoreRip # 早期版本依赖 WinStoreRip。继续追查以后，我发现这会要求目标电脑访问 PowerShell Gallery，还会把包链接查询交给第三方网页。受限电脑本来就容易卡在网络和组件状态上，再临时安装一个模块只会增加失败点。\n现在的 wingets.ps1 是单文件脚本。它直接访问 Microsoft Store、Windows Update 和 Microsoft CDN，不需要 WinStoreRip，也不请求第三方包链接服务。\n无管理员权限仍然有边界 # Wingets 只为当前用户注册 Store 包，不支持 --scope machine。组织如果通过 AppLocker、WDAC 或 AppX 策略明确禁止这个包，脚本也会失败，它不会规避这些控制。\n网络至少要能访问 storeedgefd.dsx.mp.microsoft.com、fe3.delivery.mp.microsoft.com 和 *.delivery.mp.microsoft.com。GitHub 只用于取得脚本，无法访问 Gist 时，可以在另一台机器上下载 wingets.ps1 再复制过来。\n脚本只支持当前 Retail 通道，不负责安装任意历史版本。它也不是完整的 winget 替代品，不处理社区仓库里的 MSI、EXE、portable 或压缩包。这里的目标一直很窄，就是在普通用户权限下取得并安装 Microsoft Store 里的 Codex。\n我实际测试了什么 # 发布版本分别在 Windows PowerShell 5.1 和 PowerShell 7 下跑过 26 项测试，覆盖中英文界面、长短参数、参数顺序、错误边界、Store 只读查询、包摘要、CDN 域名限制和一字节 Range 请求。\n2026 年 9 月 14 日的实时查询返回 OpenAI.Codex 26.908.4834.0，文件大小为 774,918,112 字节。Microsoft CDN 对 Range 请求返回 206 Partial Content。最后一轮验证没有重新下载完整的 739 MiB 包，也没有强制卸载已经安装的 Codex，避免无意义地消耗带宽或改动工作站。\n如果官方 winget 命令能用，Wingets 不会带来额外好处。它适合的情况很明确，你能运行普通 PowerShell，也能访问 Microsoft 的 Store 和更新服务，但没有管理员权限去修复已经坏掉的安装入口。\n参考资料 # OpenAI 的 Windows 桌面应用说明 Microsoft Windows Update Services Protocol 中的 GetCookie Microsoft Windows Update Services Protocol 中的 SyncUpdates Microsoft Learn 上的 Add-AppxPackage Microsoft Learn 上的 PowerShell 执行策略 ","date":"2026年9月14日","externalUrl":null,"permalink":"/posts/wingets/","section":"Posts","summary":"","title":"没有管理员权限，怎样在 Windows 上安装 Codex","type":"posts"},{"content":"","date":"2026年9月14日","externalUrl":null,"permalink":"/","section":"曙光磁铁的小站","summary":"","title":"曙光磁铁的小站","type":"page"},{"content":"","date":"2026年2月24日","externalUrl":null,"permalink":"/tags/cryptography/","section":"Tags","summary":"","title":"Cryptography","type":"tags"},{"content":"","date":"2026年2月24日","externalUrl":null,"permalink":"/ctf/","section":"CTF","summary":"","title":"CTF","type":"ctf"},{"content":"","date":"2026年2月24日","externalUrl":null,"permalink":"/tags/ctf/","section":"Tags","summary":"","title":"CTF","type":"tags"},{"content":"","date":"2026年2月24日","externalUrl":null,"permalink":"/categories/cybersecurity/","section":"Categories","summary":"","title":"Cybersecurity","type":"categories"},{"content":"","date":"2026年2月24日","externalUrl":null,"permalink":"/tags/rust/","section":"Tags","summary":"","title":"Rust","type":"tags"},{"content":" 仿射密码(affine-break)解析工具详解 # 项目信息 # 本文介绍的 affine-break 工具是 ctf-tools-rs 项目的一个组件，该项目是一个专为CTF竞赛优化的工具集合。\n仓库链接 # 完整项目：https://github.com/DawnMagnet/ctf-tools-rs.git\n包含多个CTF相关工具（密码破解、图像隐写、数据提取等） Rust生态的最佳实践参考 工作区管理和跨平台构建示例 单个工具：https://github.com/DawnMagnet/affine-break\n仅包含affine-break源代码 适合独立使用或二次开发 更轻量级的依赖关系 许可证 # 许可证类型：MIT 作者：DawnMagnet 当前版本：1.5.0 前言 # 在CTF（Capture The Flag）竞赛和密码学研究中，我们经常会遇到各种经典密码。其中，仿射密码是一种既简单又优雅的加密方式，但同时它也存在明显的安全漏洞。本文将带你深入了解什么是仿射密码，以及如何使用Rust编写一个高效的仿射密码破解工具。\n仿射密码是什么？ # 基本概念 # 仿射密码（Affine Cipher）是一种单字母替代密码（Monoalphabetic Substitution Cipher），它使用一个仿射函数来进行加密。\n加密公式 # 对于明文字母位置 x（A=0, B=1, \u0026hellip;, Z=25），加密过程为：\n$$E(x) = (ax + b) \\bmod 26$$其中：\na 是倍增密钥，必须满足 \\(\\gcd(a, 26) = 1\\)（与26互质） b 是平移密钥，可以是0-25的任意整数 26 是英文字母总数 解密公式 # 解密过程需要用到 a 的模乘法逆元 \\(a^{-1}\\)：\n$$D(y) = a^{-1}(y - b) \\bmod 26$$ 实例演示 # 假设我们选择 \\(a = 5, b = 8\\)：\n字母 \u0026lsquo;A\u0026rsquo; (0) → \\((5×0 + 8) \\bmod 26 = 8\\) → \u0026lsquo;I\u0026rsquo; 字母 \u0026lsquo;B\u0026rsquo; (1) → \\((5×1 + 8) \\bmod 26 = 13\\) → \u0026lsquo;N\u0026rsquo; 字母 \u0026lsquo;C\u0026rsquo; (2) → \\((5×2 + 8) \\bmod 26 = 18\\) → \u0026lsquo;S\u0026rsquo; 仿射密码的安全性问题 # 仿射密码看起来提供了两个密钥参数，但实际上安全性远不如现代密码：\n密钥空间有限：\na 的有效值只有 \\(\\phi(26) = 12\\) 个（与26互质的数） b 的有效值有 26 个 总共只有 \\(12 × 26 = 312\\) 种密钥组合 已知明文攻击脆弱：只需知道2对明文-密文字符对应关系，就能完全恢复密钥\n频率分析可行：作为单字母替代密码，它容易受到频率分析攻击\n正因为这些根本性的缺陷，仿射密码在现实中只有教学和CTF竞赛的价值。\naffine-break工具介绍 # 工具功能 # affine-break 是一个用Rust编写的轻量级工具，它通过已知明文攻击方式快速破解仿射密码：\n✅ 只需提供2对明文-密文字符对应关系即可破解 ✅ 自动计算密钥参数 a 和 b ✅ 快速解密整个密文 ✅ 支持大小写字母混合 ✅ 保留非字符内容（数字、符号、空格等） 使用方法 # affine-break \u0026lt;密文\u0026gt; \u0026lt;密文字符1\u0026gt; \u0026lt;明文字符1\u0026gt; \u0026lt;密文字符2\u0026gt; \u0026lt;明文字符2\u0026gt; 参数说明 # 参数 说明 密文 需要解密的密文文本 密文字符1 密文中已知的第一个字符 明文字符1 对应的明文字符 密文字符2 密文中已知的第二个字符 明文字符2 对应的明文字符 使用示例 # 假设我们有一个密文：\u0026quot;prwy{w14wf3p5-fo6w-11gf-w02o-88g9pg5197wo}\u0026quot;\n我们知道这个密文对应的明文应该是某个flag格式，通常会以 flag{ 开头。\naffine-break \u0026#34;prwy{w14wf3p5-fo6w-11gf-w02o-88g9pg5197wo}\u0026#34; p f r l 解释：\n密文第一个字符 \u0026lsquo;p\u0026rsquo; → 明文字符 \u0026lsquo;f\u0026rsquo; 密文第二个字符 \u0026lsquo;r\u0026rsquo; → 明文字符 \u0026rsquo;l\u0026rsquo; 输出结果：\nflag{a14ab3f5-bc6a-11eb-a02c-88e9fe5197ac} 技术实现细节 # 核心算法 # 工具的破解过程分为以下几步：\n1. 字符到数字的转换 # const fn char_to_num(c: char) -\u0026gt; i32 { if c.is_ascii_lowercase() { c as i32 - \u0026#39;a\u0026#39; as i32 } else { c as i32 - \u0026#39;A\u0026#39; as i32 } } 将字母转换为对应的数字（A/a → 0, B/b → 1, \u0026hellip;, Z/z → 25）。\n2. 密钥计算 - 求解线性方程组 # 给定两对明文-密文对应关系：\\((x_1, c_1)\\) 和 \\((x_2, c_2)\\)\n我们有方程组： $$ax_1 + b \\equiv c_1 \\pmod{26}$$ $$ax_2 + b \\equiv c_2 \\pmod{26}$$两式相减消去 b： $$a(x_1 - x_2) \\equiv (c_1 - c_2) \\pmod{26}$$const fn solve_a_and_b(c1: char, x1: char, c2: char, x2: char) -\u0026gt; Option\u0026lt;(i32, i32)\u0026gt; { let diff_c = char_to_num(c1) - char_to_num(c2); let diff_x = char_to_num(x1) - char_to_num(x2); let mut a = 0; while a \u0026lt; 26 { if mod_space(diff_x * a) == mod_space(diff_c) { let b = mod_space(char_to_num(c1) - (a * char_to_num(x1))); return Some((a, b)); } a += 1; } None } 算法步骤：\n暴力遍历所有可能的 a 值（0-25） 对每个 a，检查是否满足方程 \\(\\mathrm{diff}_x \\times a \\equiv \\mathrm{diff}_c \\pmod{26}\\) 当找到合适的 a 后，通过第一个方程求出 b：\\(b = c_1 - ax_1 \\pmod{26}\\) 验证成功则返回 (a, b) 对 时间复杂度：\\(O(1)\\)（最多只需遍历26次）\n3. 模运算辅助函数 # const fn mod_space(x: i32) -\u0026gt; i32 { x.rem_euclid(26) } 使用 rem_euclid 而不是 % 来处理负数，确保结果总是在 0-25 范围内。这在模运算中至关重要。\n为什么重要：\n% 在Rust中可能返回负数 rem_euclid 确保返回值总是非负的 4. 解密过程 # fn affine_decrypt(info: \u0026amp;str, a: i32, b: i32) -\u0026gt; String { let mut inverse_map = [0; 26]; // 构建逆映射表 for i in 0..26 { inverse_map[mod_space(a * i + b) as usize] = i; } let mut result = String::new(); // 遍历每个字符进行解密 for x in info.chars() { if x.is_ascii_alphabetic() { let num = inverse_map[char_to_num(x) as usize]; if x.is_ascii_lowercase() { result.push((b\u0026#39;a\u0026#39; + num as u8) as char); } else { result.push((b\u0026#39;A\u0026#39; + num as u8) as char); } } else { result.push(x); // 保留非字母字符 } } result } 解密思路：\n构建一个逆映射表：加密后的位置 → 原始位置 遍历密文中的每个字符 对于字母，通过逆映射表查找原始位置（O(1)查询） 对于非字母字符，原样保留 性能优化：\n使用数组而非HashMap实现映射，访问速度更快 一次构建映射表，重复查询效率高 编程语言选择：为什么用Rust？ # 性能：编译到机器码，零开销抽象，接近C++的速度 安全： 强类型系统预防类型相关的漏洞 所有权系统消除了内存问题 const fn 支持编译期计算，节省运行时开销 易用性： clap 库便捷的CLI参数解析 简洁的语法和强大的模式匹配 优秀的错误信息和编译器提示 Rust架构与跨平台设计 # Rust的可移植性优势 # affine-break采用纯Rust编写，具有卓越的跨平台特性：\n1. 编译目标多元化 # Rust通过LLVM后端支持70+个编译目标，包括：\n目标平台 架构 说明 x86_64-unknown-linux-gnu Linux x86-64 最常见的服务器平台 x86_64-pc-windows-msvc Windows x86-64 使用MSVC工具链 x86_64-apple-darwin macOS Intel Intel芯片Mac aarch64-apple-darwin macOS Silicon Apple Silicon美N1/M2 aarch64-unknown-linux-gnu Linux ARM64 树莓派、移动设备 armv7-unknown-linux-gnueabihf Linux ARM32 嵌入式系统 x86_64-pc-windows-gnu Windows MinGW 无MSVC依赖 wasm32-unknown-unknown WebAssembly 浏览器环境 2. 依赖极简化 # [dependencies] clap = { version = \u0026#34;4.5.34\u0026#34;, features = [\u0026#34;derive\u0026#34;] } affine-break仅依赖一个外部库 clap（用于CLI参数解析），且无其他间接依赖。这意味着：\n✅ 编译时间短（几秒内） ✅ 可执行文件小（仅数MB） ✅ 无动态库依赖（全静态链接） ✅ 减少安全风险面 3. 零运行时依赖 # 与Python、Node.js等语言不同，Rust编译的二进制：\n无需安装运行时环境 无需管理依赖包 直接在目标机器上执行 即插即用 使用Zig编译工具链实现无缝跨平台编译 # Zig编译器的优势 # Zig 是一门为系统编程设计的新语言，但其最强大的特性是通用C/C++编译工具链。对Rust开发者的帮助：\nZig = C/C++ 编译器 + 链接器 + 跨平台工具链 为什么选择Zig而不是传统工具链？ # 工具 Linux Windows macOS ARM 优点 缺点 GCC ✅ ⚠️ ❌ ✅ 广泛支持 交叉编译复杂 MSVC ❌ ✅ ❌ ❌ Windows原生 仅Windows Clang ✅ ✅ ✅ ✅ 通用强大 需要多个工具链 Zig编译器 ✅ ✅ ✅ ✅ 一个工具链全搞定 新兴项目 Zig编译工具链的关键特性 # 1. 单一通用工具链 # # 一个zig命令搞定所有平台编译 zig cc -target x86_64-linux-gnu main.c -o main-linux zig cc -target x86_64-windows-gnu main.c -o main.exe zig cc -target aarch64-macos main.c -o main-macos Rust与Zig的集成（添加到.cargo/config.toml）：\n[build] # 使用Zig作为C链接器 linker = \u0026#34;zig\u0026#34; rustflags = [\u0026#34;-C\u0026#34;, \u0026#34;link-arg=cc\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=-target\u0026#34;] [target.x86_64-unknown-linux-gnu] linker = \u0026#34;zig\u0026#34; rustflags = [\u0026#34;-C\u0026#34;, \u0026#34;link-arg=cc\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=-target\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=x86_64-linux-gnu\u0026#34;] [target.x86_64-pc-windows-gnu] linker = \u0026#34;zig\u0026#34; rustflags = [\u0026#34;-C\u0026#34;, \u0026#34;link-arg=cc\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=-target\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=x86_64-windows-gnu\u0026#34;] [target.aarch64-apple-darwin] linker = \u0026#34;zig\u0026#34; rustflags = [\u0026#34;-C\u0026#34;, \u0026#34;link-arg=cc\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=-target\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=aarch64-macos\u0026#34;] 2. 无缝跨平台库支持 # Zig内置完整的C标准库，无需为不同平台配置不同的libc：\n// Rust代码可以安全跨平台 use std::fs; use std::process::Command; // 这些都能在任何平台上工作 fn main() { let content = fs::read_to_string(\u0026#34;test.txt\u0026#34;).unwrap(); let output = Command::new(\u0026#34;echo\u0026#34;).arg(\u0026amp;content).output().unwrap(); } 3. 静态链接 # # 编译为完全静态的可执行文件 RUSTFLAGS=\u0026#34;-C target-feature=+crt-static\u0026#34; \\ cargo build --target x86_64-unknown-linux-musl --release 结果是一个完全独立的可执行文件：\n不依赖系统libc版本 可直接在任何Linux发行版上运行 即使在最小化Docker镜像中也能执行 实际跨平台编译示例 # 场景：在Linux开发机上为Windows和macOS编译 # # 环境要求：仅需安装Zig编译器和Rust curl https://ziglang.org/builds/zig-linux-x86_64-latest.tar.xz | tar xJ # Linux x86-64 编译 cargo build --release --target x86_64-unknown-linux-gnu # Windows x86-64 编译（在Linux上） cargo build --release --target x86_64-pc-windows-gnu # macOS Intel 编译（在Linux上，需要macOS SDK） cargo build --release --target x86_64-apple-darwin # macOS ARM64编译（在Linux上，需要macOS SDK） cargo build --release --target aarch64-apple-darwin # ARM Linux编译（树莓派等） cargo build --release --target aarch64-unknown-linux-gnu 关键点：一切在一个开发机上完成，无需虚拟机或远程构建服务。\n无外部依赖的架构设计 # affine-break的最小化设计 # 项目采用极简主义架构，核心功能完全自实现：\n// 所有密码学操作都是自己实现 const fn mod_space(x: i32) -\u0026gt; i32 const fn char_to_num(c: char) -\u0026gt; i32 const fn solve_a_and_b(...) fn affine_decrypt(...) 为什么不用外部密码库？\n体积：仅需几百字节，使用crypto库会增加数MB 依赖链：密码库通常引入20+个间接依赖 简洁：仿射密码的数学太简单，自实现更清晰 学习价值：展示密码学原理，而非黑盒调用 CLI框架的取舍 # 唯一外部依赖是 clap（命令行参数解析）：\n#[derive(Parser)] #[command( author = \u0026#34;DawnMagnet\u0026#34;, version, about = \u0026#34;...\u0026#34; )] struct Args { ciphertext: String, c1: char, x1: char, c2: char, x2: char, } 为什么使用clap而不是手写？\n对比代码量：\n// 用clap：5行代码 #[derive(Parser)] struct Args { ... } // 手写参数解析：50+行代码 fn main() { let args: Vec\u0026lt;String\u0026gt; = std::env::args().collect(); if args.len() \u0026lt; 6 { eprintln!(\u0026#34;Usage: ...\u0026#34;); return; } let ciphertext = \u0026amp;args[1]; let c1 = args[2].chars().next().unwrap(); // ... } clap的优势值得这1KB的额外依赖。\n完整的静态编译指南 # 1. 安装必要工具 # # 安装Zig编译器 curl -L https://ziglang.org/download/latest-dev/zig-linux-x86_64.tar.xz | tar xJ export PATH=$PWD/zig-linux-x86_64:$PATH # 验证 zig version rustc --version 2. Cargo配置（可选，用于自动化） # 创建 .cargo/config.toml：\n[build] # 使用zig cc作为C链接器 cc = \u0026#34;zig cc\u0026#34; [target.x86_64-unknown-linux-gnu] linker = \u0026#34;zig\u0026#34; rustflags = [\u0026#34;-C\u0026#34;, \u0026#34;link-arg=cc\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=-target\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=x86_64-linux\u0026#34;] [target.x86_64-unknown-linux-musl] linker = \u0026#34;zig\u0026#34; rustflags = [\u0026#34;-C\u0026#34;, \u0026#34;link-arg=cc\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=-target\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=x86_64-linux-musl\u0026#34;] [target.aarch64-unknown-linux-gnu] linker = \u0026#34;zig\u0026#34; rustflags = [\u0026#34;-C\u0026#34;, \u0026#34;link-arg=cc\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=-target\u0026#34;, \u0026#34;-C\u0026#34;, \u0026#34;link-arg=aarch64-linux\u0026#34;] 3. 快速编译脚本 # 创建 build.sh：\n#!/bin/bash set -e PROJECT_NAME=\u0026#34;affine-break\u0026#34; TARGETS=( \u0026#34;x86_64-unknown-linux-gnu\u0026#34; \u0026#34;aarch64-unknown-linux-gnu\u0026#34; \u0026#34;x86_64-pc-windows-gnu\u0026#34; \u0026#34;x86_64-apple-darwin\u0026#34; \u0026#34;aarch64-apple-darwin\u0026#34; ) mkdir -p releases for target in \u0026#34;${TARGETS[@]}\u0026#34;; do echo \u0026#34;Compiling for $target...\u0026#34; cargo build -p \u0026#34;$PROJECT_NAME\u0026#34; --release --target \u0026#34;$target\u0026#34; # 确定可执行文件名 if [[ $target == *\u0026#34;windows\u0026#34;* ]]; then exe_name=\u0026#34;$PROJECT_NAME.exe\u0026#34; else exe_name=\u0026#34;$PROJECT_NAME\u0026#34; fi # 复制到releases目录 cp \u0026#34;target/$target/release/$exe_name\u0026#34; \\ \u0026#34;releases/$PROJECT_NAME-$target-${exe_name:-.exe}\u0026#34; done echo \u0026#34;✅ Build complete! Binaries in releases/\u0026#34; ls -lah releases/ 4. GitHub Actions自动化跨平台发布 # 创建 .github/workflows/release.yml：\nname: Cross-Platform Build on: push: tags: - \u0026#34;v*\u0026#34; jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu - os: ubuntu-latest target: aarch64-unknown-linux-gnu - os: windows-latest target: x86_64-pc-windows-msvc - os: macos-latest target: x86_64-apple-darwin - os: macos-latest target: aarch64-apple-darwin steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: toolchain: stable target: ${{ matrix.target }} - name: Build run: cargo build -p affine-break --release --target ${{ matrix.target }} - name: Upload artifacts uses: actions/upload-artifact@v3 with: name: affine-break-${{ matrix.target }} path: target/${{ matrix.target }}/release/ 运行此工作流后，GitHub会自动：\n在多个平台编译 生成平台特定的二进制文件 上传为artifacts供下载 发布和分发 # 用户视角的安装体验 # # 一行命令安装（来自crates.io） cargo install affine-break # 或直接下载预编译二进制 wget https://github.com/DawnMagnet/ctf-tools-rs/releases/download/v1.5.0/affine-break-linux-x86_64 chmod +x affine-break-linux-x86_64 ./affine-break-linux-x86_64 \u0026#34;密文\u0026#34; c1 x1 c2 x2 优势对比 # 分发方式 下载大小 不依赖 启动速度 Python脚本 10KB 需要Python+库 1-2秒 Node.js工具 50KB 需要Node.js 0.5秒 Java JAR 5MB 需要JRE 2-5秒 Rust二进制 3-5MB 无 \u0026lt;50ms Docker镜像 100MB+ 需要Docker 数秒 性能基准测试 # 在现代硬件上的实际性能（针对100KB密文）：\nLinux x86-64: 12ms macOS ARM64: 15ms Windows AMD64: 13ms ARM Linux: 18ms 启动时间（包括参数解析）：\n\u0026lt; 1ms（本地SSD） \u0026lt; 2ms（网络存储） 这与用C或Go编写的工具性能相当，远优于脚本语言。\n实际应用场景 # 1. CTF竞赛中的仿射密码 # 在CTF竞赛的密码学类题目中，经常出现仿射密码：\n题目：破解以下密文 Ciphertext: \u0026#34;uryyb jbeyq\u0026#34; Known plaintext: \u0026#34;h\u0026#34; → \u0026#34;u\u0026#34; and \u0026#34;w\u0026#34; → \u0026#34;j\u0026#34; 使用工具一键解决：\naffine-break \u0026#34;uryyb jbeyq\u0026#34; u h j w 2. 密码学教学 # 演示单字母替代密码的原理 展示已知明文攻击的威力 理解线性方程在密码学中的应用 3. 历史密码文献研究 # 应用于破解历史密码体系中的仿射变换。\n进阶话题 # 如何增强仿射密码的安全性？ # 多表替代：使用不同的密钥加密不同位置的字母 添加噪声：混入虚假的标点符号和数字 更大的字母表：包含数字、符号等，扩大密钥空间 相关的其他密码 # 凯撒密码（Caesar Cipher）：仿射密码的特例（a=1） 扩展欧几里得算法：求模乘法逆元的标准方法 替代密码（Substitution Cipher）：仿射密码的推广 总结 # affine-break 是一个简洁而强大的工具，展示了现代系统编程的最佳实践：\n技术亮点 # 密码数学：线性同余方程、模运算 密码分析：已知明文攻击的威力 软件设计：高效的算法和数据结构 Rust编程：const fn、模式匹配、内存安全 架构优势 # ┌─────────────────────────────────────┐ │ 纯Rust实现 + Zig工具链 │ ├─────────────────────────────────────┤ │ ✅ 零外部运行时依赖 │ │ ✅ 支持70+编译目标（一行命令跨平台） │ │ ✅ 完全静态链接（任意Linux/Windows） │ │ ✅ 编译文件小（3-5MB） │ │ ✅ 启动秒杀Python/Node.js(\u0026lt;1ms) │ │ ✅ 内存安全（无缓冲区溢出风险） │ └─────────────────────────────────────┘ 为什么这个组合是未来方向 # 容器友好：无需基础镜像预装运行时 云原生：快速冷启动，适合Serverless 边缘计算：ARM设备原生支持，无额外依赖 安全性：一个二进制=一个可审计的攻击面 可维护性：单一语言栈，减少工具链复杂度 对于CTF爱好者和密码学初学者，这个工具既是实用的破解脚手架，也是学习密码学原理的优秀参考实现。\n实战部署指南 # 快速上手 # 方式一：使用Cargo（推荐） # # 安装Rust（首次） curl --proto \u0026#39;=https\u0026#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh # 安装affine-break cargo install affine-break # 使用 affine-break \u0026#34;密文\u0026#34; 密文字符1 明文字符1 密文字符2 明文字符2 优点：一键安装，自动选择本机最优编译目标\n方式二：从源码编译（自定义） # # 克隆项目 git clone https://github.com/DawnMagnet/ctf-tools-rs.git cd ctf-tools-rs/affine-break # 编译 cargo build --release # 运行 ./target/release/affine-break \u0026#34;密文\u0026#34; c1 x1 c2 x2 适用场景：修改源码、学习实现、裁剪依赖\n方式三：预编译二进制（最快） # 从GitHub Releases直接下载：\n# Linux wget https://github.com/DawnMagnet/ctf-tools-rs/releases/download/v1.5.0/affine-break-linux-x86_64 chmod +x affine-break-linux-x86_64 ./affine-break-linux-x86_64 \u0026#34;密文\u0026#34; c1 x1 c2 x2 # macOS (Intel) wget https://github.com/DawnMagnet/ctf-tools-rs/releases/download/v1.5.0/affine-break-macos-x86_64 # macOS (Apple Silicon) wget https://github.com/DawnMagnet/ctf-tools-rs/releases/download/v1.5.0/affine-break-macos-aarch64 # Windows # 下载 affine-break-windows-x86_64.exe 优点：秒级启动，无需编译，可离线使用\nDocker部署（可选） # 如果要在容器中使用：\n# Dockerfile FROM scratch COPY --from=rust:1.75-alpine AS builder WORKDIR /build COPY . . RUN cargo build --release FROM scratch COPY --from=builder /build/target/release/affine-break /affine-break ENTRYPOINT [\u0026#34;/affine-break\u0026#34;] 构建和运行：\ndocker build -t affine-break . docker run --rm affine-break \u0026#34;密文\u0026#34; c1 x1 c2 x2 镜像大小：仅5-8MB（使用scratch基础镜像）\nCI/CD集成 # 在自动化工具中集成affine-break：\n在脚本中使用 # #!/bin/bash CIPHERTEXT=\u0026#34;prwy{w14wf3p5-fo6w-11gf-w02o-88g9pg5197wo}\u0026#34; PLAINTEXT=$(affine-break \u0026#34;$CIPHERTEXT\u0026#34; p f r l) echo \u0026#34;Decrypted: $PLAINTEXT\u0026#34; 在Python中调用 # import subprocess def break_affine(ciphertext, c1, x1, c2, x2): result = subprocess.run( [\u0026#39;affine-break\u0026#39;, ciphertext, c1, x1, c2, x2], capture_output=True, text=True ) return result.stdout.strip() plaintext = break_affine(\u0026#34;prwy{w14wf3p5-fo6w-11gf-w02o-88g9pg5197wo}\u0026#34;, \u0026#39;p\u0026#39;, \u0026#39;f\u0026#39;, \u0026#39;r\u0026#39;, \u0026#39;l\u0026#39;) print(plaintext) # flag{a14ab3f5-bc6a-11eb-a02c-88e9fe5197ac} MacBook M系列集成（iOS Shortcut） # 可以通过Homebrew安装后集成到Shortcut应用中，实现\u0026quot;一键破解\u0026quot;的移动端体验。\n性能优化建议 # 批量破解优化 # 如果需要尝试多对明文-密文字符组合：\n#!/bin/bash CIPHERTEXT=\u0026#34;prwy{w14wf3p5-fo6w-11gf-w02o-88g9pg5197wo}\u0026#34; # 尝试常见的词对 pairs=( \u0026#34;p f r l\u0026#34; # p-\u0026gt;f, r-\u0026gt;l \u0026#34;w f o l\u0026#34; # w-\u0026gt;f, o-\u0026gt;l \u0026#34;y a w t\u0026#34; # y-\u0026gt;a, w-\u0026gt;t ) for pair in \u0026#34;${pairs[@]}\u0026#34;; do echo \u0026#34;Trying: $pair\u0026#34; affine-break \u0026#34;$CIPHERTEXT\u0026#34; $pair \u0026amp;\u0026amp; break done 并行处理（大量密文） # # 对多个密文并行破解 cat ciphertexts.txt | xargs -I {} -P 4 \\ affine-break {} p f r l -P 4: 使用4个并行进程（可根据CPU核心数调整）\n常见问题排查 # Q: \u0026ldquo;No solution found\u0026rdquo; 错误 # 原因：提供的明文-密文对不匹配\n解决：\n检查输入字符是否正确 尝试其他已知的字符对 检查密文是否真的使用仿射密码加密 # 如果不确定，尝试bruteforce验证 # 生成所有可能的 a,b 组合并尝试解密 for a in {1..25}; do for b in {0..25}; do echo \u0026#34;Testing a=$a, b=$b\u0026#34; # 这部分需要自己实现或修改工具 done done Q: 跨平台编译失败 # 症状：error: could not compile dependency\n解决方案：\n# 清理旧的构建缓存 cargo clean # 更新工具链 rustup update # 指定特定目标后重试 cargo build --release --target x86_64-unknown-linux-gnu Q: 如何验证解密结果？ # # 手动验证密钥 plaintext=\u0026#34;flag\u0026#34; ciphertext=\u0026#34;prwy\u0026#34; # 计算加密参数（如果已知） echo \u0026#34;若 ${plaintext[0]} (${plaintext:0:1}) -\u0026gt; ${ciphertext[0]} 与已知密钥相符，则正确\u0026#34; 高级用法 # 修改源码支持自定义字母表 # 如果要支持中文、日文或其他字符集：\n// 修改 const fn char_to_num 以支持更大的字母表 const fn char_to_num_extended(c: char) -\u0026gt; i32 { // 支持 0-65535 范围的Unicode字符 c as i32 } // 修改 mod_space 来处理更大的模数 const fn mod_space_extended(x: i32, modulus: i32) -\u0026gt; i32 { x.rem_euclid(modulus) } 性能微调 # // 使用inline宏减少函数调用开销 #[inline(always)] const fn mod_space(x: i32) -\u0026gt; i32 { x.rem_euclid(26) } // 在热路径中使用SIMD（针对大文本） // 当前实现已足够快，但可深入优化 相关资源 # Rust官方文档 Zig编译器 Cargo包管理 仿射密码详解 CTF密码学资源 跨平台Rust应用 免责声明 # 本工具仅供学习和授权的安全测试使用。未经授权对他人的信息进行解密是违法的。请确保在有适当权限和同意的情况下使用此工具。\n","date":"2026年2月24日","externalUrl":null,"permalink":"/ctf/crypto/affine-break/","section":"CTF","summary":"","title":"仿射密码(affine-break)解析工具详解","type":"ctf"},{"content":"","date":"2026年2月24日","externalUrl":null,"permalink":"/ctf/crypto/","section":"CTF","summary":"","title":"密码学","type":"ctf"},{"content":"","date":"2026年2月5日","externalUrl":null,"permalink":"/tags/codeforces/","section":"Tags","summary":"","title":"Codeforces","type":"tags"},{"content":" Rust 代码模板 # use std::collections::HashMap; pub use __cargo_equip::prelude::*; #[allow(unused_imports)] use proconio::fastout; use proconio::input; // #[fastout] fn solve() { // write your solution here! } fn main() { input! { t: usize } for _ in 0..t { solve(); } } // The following code was expanded by `cargo-equip`. #[cfg_attr(any(), rustfmt::skip)] #[allow(unused)] mod __cargo_equip { pub(crate) mod crates { pub mod __algo_0_1_0 {pub use crate::__cargo_equip::macros::__algo_0_1_0::*;pub mod math{pub mod primes{use super::Integer;pub trait PrimeExt:Integer{fn is_prime(\u0026amp;self)-\u0026gt;bool{if*self\u0026lt;Self::ZERO{panic!(\u0026#34;failed to check whether integer \u0026#39;{:?}\u0026#39; is a prime number, because it is smaller than zero for which this function is undefined\u0026#34;,self);}if*self==Self::ZERO||*self==Self::ONE{return false;}let mut cur=Self::from_i64(2);while cur*cur\u0026lt;=*self{if*self%cur==Self::ZERO{return false;}cur=cur+Self::ONE;}true}fn factorize(\u0026amp;self)-\u0026gt;Vec\u0026lt;Self\u0026gt;{if*self\u0026lt;Self::ZERO{panic!(\u0026#34;failed to find prime factors of the integer \u0026#39;{:?}\u0026#39;, because it is smaller than zero for which this function is undefined\u0026#34;,self);}let mut result=vec![];let mut k=Self::from_i64(2);let mut n=*self;while k*k\u0026lt;=*self{while n%k==Self::ZERO{result.push(k);n=n/k;}k=k+Self::ONE;}if n\u0026gt;Self::ONE{result.push(n);}result}}impl\u0026lt;T:Integer\u0026gt;PrimeExt for T{}}pub mod integer{use std::{ops::{Mul,Div,Rem,Add,Sub},fmt::{Display,Debug}};pub trait Integer:Sized+Mul\u0026lt;Self,Output=Self\u0026gt;+Div\u0026lt;Self,Output=Self\u0026gt;+Rem\u0026lt;Self,Output=Self\u0026gt;+Add\u0026lt;Self,Output=Self\u0026gt;+Sub\u0026lt;Self,Output=Self\u0026gt;+PartialEq+Eq+Ord+PartialOrd+Copy+Clone+Display+Debug{const ONE:Self;const ZERO:Self;fn from_i64(val:i64)-\u0026gt;Self;}macro_rules!impl_integer{($t:ty)=\u0026gt;{impl Integer for$t{const ONE:Self=1;const ZERO:Self=0;fn from_i64(val:i64)-\u0026gt;Self{val as$t}}};}impl_integer!(usize);impl_integer!(isize);impl_integer!(i32);impl_integer!(i64);impl_integer!(u32);impl_integer!(u64);}pub use integer::Integer;pub mod modular{use std::{ops::{Add,Mul,Sub,Div,AddAssign,MulAssign,SubAssign,DivAssign},fmt::Display};#[derive(Copy,Clone,Default,Debug,PartialEq,PartialOrd,Eq,Ord)]pub struct MInt\u0026lt;const MODULUS:u64\u0026gt;{pub value:u64}impl\u0026lt;const M:u64\u0026gt;Display for MInt\u0026lt;M\u0026gt;{fn fmt(\u0026amp;self,f:\u0026amp;mut std::fmt::Formatter\u0026lt;\u0026#39;_\u0026gt;)-\u0026gt;std::fmt::Result{write!(f,\u0026#34;{}\u0026#34;,self.value)}}impl\u0026lt;const MODULUS:u64\u0026gt;MInt\u0026lt;MODULUS\u0026gt;{pub fn one()-\u0026gt;Self{Self{value:1}}pub fn zero()-\u0026gt;Self{Self{value:0}}pub fn new(value:u64)-\u0026gt;Self{Self{value:value%MODULUS}}pub fn pow(self,n:u64)-\u0026gt;MInt\u0026lt;MODULUS\u0026gt;{let mut result=Self::one();let mut base=self;let mut exponent=n;while exponent\u0026gt;0{if exponent%2==1{result=result*base;}base=base*base;exponent/=2;}result}fn extended_gcd(a:i64,b:i64)-\u0026gt;(i64,i64,i64){if a==0{(b,0,1)}else{let(g,x,y)=Self::extended_gcd(b%a,a);(g,y-(b/a)*x,x)}}pub fn inv(self)-\u0026gt;Self{let(_,x,_)=Self::extended_gcd(self.value as i64,MODULUS as i64);Self::new((x+MODULUS as i64)as u64)}}impl\u0026lt;const T:u64\u0026gt;Add\u0026lt;MInt\u0026lt;T\u0026gt;\u0026gt;for MInt\u0026lt;T\u0026gt;{type Output=MInt\u0026lt;T\u0026gt;;fn add(self,rhs:MInt\u0026lt;T\u0026gt;)-\u0026gt;Self::Output{let mut value=self.value+rhs.value;if value\u0026gt;=T{value-=T;}Self{value}}}impl\u0026lt;const T:u64\u0026gt;AddAssign\u0026lt;MInt\u0026lt;T\u0026gt;\u0026gt;for MInt\u0026lt;T\u0026gt;{fn add_assign(\u0026amp;mut self,rhs:MInt\u0026lt;T\u0026gt;){*self=*self+rhs;}}impl\u0026lt;const T:u64\u0026gt;Mul\u0026lt;MInt\u0026lt;T\u0026gt;\u0026gt;for MInt\u0026lt;T\u0026gt;{type Output=MInt\u0026lt;T\u0026gt;;fn mul(self,rhs:MInt\u0026lt;T\u0026gt;)-\u0026gt;Self::Output{Self{value:(self.value*rhs.value)%T}}}impl\u0026lt;const T:u64\u0026gt;MulAssign\u0026lt;MInt\u0026lt;T\u0026gt;\u0026gt;for MInt\u0026lt;T\u0026gt;{fn mul_assign(\u0026amp;mut self,rhs:MInt\u0026lt;T\u0026gt;){self.value=(self.value*rhs.value)%T;}}impl\u0026lt;const T:u64\u0026gt;Sub\u0026lt;MInt\u0026lt;T\u0026gt;\u0026gt;for MInt\u0026lt;T\u0026gt;{type Output=MInt\u0026lt;T\u0026gt;;fn sub(self,rhs:MInt\u0026lt;T\u0026gt;)-\u0026gt;Self::Output{let mut value=self.value+T-rhs.value;if value\u0026gt;=T{value-=T;}Self{value}}}impl\u0026lt;const T:u64\u0026gt;SubAssign\u0026lt;MInt\u0026lt;T\u0026gt;\u0026gt;for MInt\u0026lt;T\u0026gt;{fn sub_assign(\u0026amp;mut self,rhs:MInt\u0026lt;T\u0026gt;){*self=*self-rhs;}}impl\u0026lt;const T:u64\u0026gt;Div\u0026lt;MInt\u0026lt;T\u0026gt;\u0026gt;for MInt\u0026lt;T\u0026gt;{type Output=MInt\u0026lt;T\u0026gt;;fn div(self,rhs:MInt\u0026lt;T\u0026gt;)-\u0026gt;Self::Output{self*rhs.inv()}}impl\u0026lt;const T:u64\u0026gt;DivAssign\u0026lt;MInt\u0026lt;T\u0026gt;\u0026gt;for MInt\u0026lt;T\u0026gt;{fn div_assign(\u0026amp;mut self,rhs:MInt\u0026lt;T\u0026gt;){*self=*self/rhs;}}impl\u0026lt;const T:u64\u0026gt;Into\u0026lt;MInt\u0026lt;T\u0026gt;\u0026gt;for u64{fn into(self)-\u0026gt;MInt\u0026lt;T\u0026gt;{MInt::new(self)}}}pub fn gcd\u0026lt;T:Integer\u0026gt;(mut a:T,mut b:T)-\u0026gt;T{if a\u0026gt;b{std::mem::swap(\u0026amp;mut a,\u0026amp;mut b);}while a!=T::ZERO{b=b%a;std::mem::swap(\u0026amp;mut a,\u0026amp;mut b);}b}pub fn lcm\u0026lt;T:Integer\u0026gt;(a:T,b:T)-\u0026gt;T{a/gcd(a,b)*b}}pub mod collections{pub mod segment_tree{use std::ops::{Range,RangeBounds};pub struct SegmentTree\u0026lt;T,F\u0026gt;{n:usize,data:Vec\u0026lt;T\u0026gt;,neutral:T,f:F,}impl\u0026lt;T,F\u0026gt;SegmentTree\u0026lt;T,F\u0026gt;where T:Clone,F:Fn(\u0026amp;T,\u0026amp;T)-\u0026gt;T,{pub fn new(n:usize,neutral:T,f:F)-\u0026gt;Self{let mut size=1;while size\u0026lt;n{size*=2;}Self{n:size,data:vec![neutral.clone();2*size],neutral,f,}}pub fn new_from_iter\u0026lt;I\u0026gt;(iter:I,neutral:T,f:F)-\u0026gt;Self where I:IntoIterator\u0026lt;Item=T\u0026gt;,{let v:Vec\u0026lt;_\u0026gt; =iter.into_iter().collect();let mut size=1;while size\u0026lt;v.len(){size\u0026lt;\u0026lt;=1;}let mut data=vec![neutral.clone();size\u0026lt;\u0026lt;1];for i in 0..v.len(){data[size+i]=v[i].clone();}for i in(1..size).rev(){data[i]=(f)(\u0026amp;data[i\u0026lt;\u0026lt;1],\u0026amp;data[(i\u0026lt;\u0026lt;1)+1]);}Self{n:size,data,neutral,f,}}pub fn update(\u0026amp;mut self,i:usize,value:T){let mut i=i+self.n;self.data[i]=value;while i\u0026gt;1{i\u0026gt;\u0026gt;=1;self.data[i]=(self.f)(\u0026amp;self.data[i\u0026lt;\u0026lt;1],\u0026amp;self.data[(i\u0026lt;\u0026lt;1)+1]);}}pub fn get(\u0026amp;self,i:usize)-\u0026gt;\u0026amp;T{\u0026amp;self.data[i+self.n]}pub fn query(\u0026amp;self,range:impl RangeBounds\u0026lt;usize\u0026gt;)-\u0026gt;T{self.query_inner(range,1,..)}fn to_range(\u0026amp;self,range:impl RangeBounds\u0026lt;usize\u0026gt;)-\u0026gt;Range\u0026lt;usize\u0026gt;{let start=match range.start_bound(){std::ops::Bound::Included(\u0026amp;p)=\u0026gt;p,std::ops::Bound::Excluded(\u0026amp;p)=\u0026gt;p+1,std::ops::Bound::Unbounded=\u0026gt;0,};let end=match range.end_bound(){std::ops::Bound::Included(\u0026amp;p)=\u0026gt;p+1,std::ops::Bound::Excluded(\u0026amp;p)=\u0026gt;p,std::ops::Bound::Unbounded=\u0026gt;self.n,};start..end}fn query_inner(\u0026amp;self,range:impl RangeBounds\u0026lt;usize\u0026gt;,i:usize,inner_range:impl RangeBounds\u0026lt;usize\u0026gt;)-\u0026gt;T{let range=self.to_range(range);let inner_range=self.to_range(inner_range);if range.start\u0026gt;=range.end{return self.neutral.clone();}if range.start==inner_range.start\u0026amp;\u0026amp;range.end==inner_range.end{return self.data[i].clone();}let mid=(inner_range.start+inner_range.end)\u0026gt;\u0026gt;1;let left=self.query_inner(range.start..mid.min(range.end),i\u0026lt;\u0026lt;1,inner_range.start..mid);let right=self.query_inner(mid.max(range.start)..range.end,(i\u0026lt;\u0026lt;1)+1,mid..inner_range.end);(self.f)(\u0026amp;left,\u0026amp;right)}pub fn data(\u0026amp;self)-\u0026gt;\u0026amp;[T]{\u0026amp;self.data[self.n..]}}pub struct SegmentTreeRU\u0026lt;T,F,G,H\u0026gt;{n:usize,data:Vec\u0026lt;T\u0026gt;,lazy:Vec\u0026lt;Option\u0026lt;T\u0026gt;\u0026gt;,neutral:T,f:F,g:G,h:H}impl\u0026lt;T,F,G,H\u0026gt;SegmentTreeRU\u0026lt;T,F,G,H\u0026gt;where T:Clone+Copy,F:Fn(\u0026amp;T,\u0026amp;T)-\u0026gt;T,G:Fn(\u0026amp;T,\u0026amp;T,usize)-\u0026gt;T,H:Fn(\u0026amp;T,\u0026amp;T)-\u0026gt;T{pub fn new(n:usize,neutral:T,f:F,g:G,h:H)-\u0026gt;Self{let mut size=1;while size\u0026lt;n{size*=2;}Self{n:size,data:vec![neutral.clone();size*2],lazy:vec![None;size*2],neutral,f,g,h}}pub fn new_from_iter\u0026lt;I\u0026gt;(iter:I,neutral:T,f:F,g:G,h:H)-\u0026gt;Self where I:IntoIterator\u0026lt;Item=T\u0026gt;{let v:Vec\u0026lt;_\u0026gt; =iter.into_iter().collect();let mut size=1;while size\u0026lt;v.len(){size*=2;}let mut data=vec![neutral.clone();2*size];for(i,val)in v.into_iter().enumerate(){data[size+i]=val;}for i in(1..size).rev(){data[i]=(f)(\u0026amp;data[2*i],\u0026amp;data[2*i+1]);}Self{n:size,data,lazy:vec![None;2*size],neutral,f,g,h,}}pub fn query(\u0026amp;mut self,range:impl RangeBounds\u0026lt;usize\u0026gt;)-\u0026gt;T{self.query_inner(self.to_range(range),1,self.to_range(..))}fn propagate(\u0026amp;mut self,i:usize,inner_range:Range\u0026lt;usize\u0026gt;){let Some(lazy)=self.lazy[i].clone()else{return;};self.data[i]=(self.g)(\u0026amp;self.data[i],\u0026amp;lazy,inner_range.end-inner_range.start);if inner_range.end-inner_range.start!=1{if let Some(child_lazy)=\u0026amp;self.lazy[i*2]{self.lazy[i*2]=Some((self.h)(child_lazy,\u0026amp;lazy));}else{self.lazy[i*2]=Some(lazy.clone());}if let Some(child_lazy)=\u0026amp;self.lazy[i*2+1]{self.lazy[i*2+1]=Some((self.h)(child_lazy,\u0026amp;lazy));}else{self.lazy[i*2+1]=Some(lazy.clone());}}self.lazy[i]=None;}fn query_inner(\u0026amp;mut self,range:Range\u0026lt;usize\u0026gt;,i:usize,inner_range:Range\u0026lt;usize\u0026gt;)-\u0026gt;T{self.propagate(i,inner_range.clone());if range.start\u0026gt;=inner_range.end||inner_range.start\u0026gt;=range.end{return self.neutral.clone();}if range.start\u0026lt;=inner_range.start\u0026amp;\u0026amp;inner_range.end\u0026lt;=range.end{return self.data[i];}let mid=(inner_range.start+inner_range.end)/2;let left=self.query_inner(range.clone(),i*2,inner_range.start..mid);let right=self.query_inner(range,i*2+1,mid..inner_range.end);(self.f)(\u0026amp;left,\u0026amp;right)}pub fn update(\u0026amp;mut self,range:impl RangeBounds\u0026lt;usize\u0026gt;,val:T){self.update_inner(self.to_range(range),val,1,self.to_range(..));}fn update_inner(\u0026amp;mut self,range:Range\u0026lt;usize\u0026gt;,val:T,i:usize,inner_range:Range\u0026lt;usize\u0026gt;){self.propagate(i,inner_range.clone());if range.end\u0026lt;=inner_range.start||inner_range.end\u0026lt;=range.start{return;}if range.start\u0026lt;=inner_range.start\u0026amp;\u0026amp;inner_range.end\u0026lt;=range.end{self.lazy[i]=Some(val.clone());self.propagate(i,inner_range);return;}let mid=(inner_range.start+inner_range.end)/2;self.update_inner(range.clone(),val,i*2,inner_range.start..mid);self.update_inner(range,val,i*2+1,mid..inner_range.end);self.data[i]=(self.f)(\u0026amp;self.data[i*2],\u0026amp;self.data[i*2+1]);}fn to_range(\u0026amp;self,range:impl RangeBounds\u0026lt;usize\u0026gt;)-\u0026gt;Range\u0026lt;usize\u0026gt;{let start=match range.start_bound(){std::ops::Bound::Included(\u0026amp;p)=\u0026gt;p,std::ops::Bound::Excluded(\u0026amp;p)=\u0026gt;p+1,std::ops::Bound::Unbounded=\u0026gt;0,};let end=match range.end_bound(){std::ops::Bound::Included(\u0026amp;p)=\u0026gt;p+1,std::ops::Bound::Excluded(\u0026amp;p)=\u0026gt;p,std::ops::Bound::Unbounded=\u0026gt;self.n,};start..end}}}pub mod treap{use std::mem;use super::super::misc::random::rand_u64;pub enum TreapNodeCont\u0026lt;T:PartialOrd\u0026gt;{Some(Box\u0026lt;TreapNode\u0026lt;T\u0026gt;\u0026gt;),Empty}impl\u0026lt;T:PartialOrd\u0026gt;Default for TreapNodeCont\u0026lt;T\u0026gt;{fn default()-\u0026gt;Self{TreapNodeCont::Empty}}pub struct Treap\u0026lt;T:PartialOrd\u0026gt;{root:TreapNodeCont\u0026lt;T\u0026gt;,}impl\u0026lt;T:Clone+PartialOrd\u0026gt;Treap\u0026lt;T\u0026gt;{pub fn new()-\u0026gt;Self{Self{root:TreapNodeCont::Empty,}}pub fn from_root(root:TreapNodeCont\u0026lt;T\u0026gt;)-\u0026gt;Self{Self{root,}}pub fn split(self,x:T)-\u0026gt;(Treap\u0026lt;T\u0026gt;,Treap\u0026lt;T\u0026gt;){let(left_root,right_root)=self.root.split(x);(Treap::from_root(left_root),Treap::from_root(right_root))}pub fn insert(\u0026amp;mut self,key:T){let root=mem::take(\u0026amp;mut self.root);self.root=root.insert(TreapNode::from_key(key));}pub fn merge(self,other:Treap\u0026lt;T\u0026gt;)-\u0026gt;Treap\u0026lt;T\u0026gt;{let new_root=self.root.merge(other.root);Self{root:new_root}}pub fn erase(\u0026amp;mut self,key:\u0026amp;T){let root=mem::take(\u0026amp;mut self.root);self.root=root.erase(key);}pub fn contains(\u0026amp;self,key:\u0026amp;T)-\u0026gt;bool{self.root.contains(key)}pub fn len(\u0026amp;self)-\u0026gt;usize{self.root.len()}}pub struct TreapNode\u0026lt;T:PartialOrd\u0026gt;{key:T,left:TreapNodeCont\u0026lt;T\u0026gt;,right:TreapNodeCont\u0026lt;T\u0026gt;,priority:u64,len:usize}impl\u0026lt;T:PartialOrd+Clone\u0026gt;TreapNode\u0026lt;T\u0026gt;{pub fn from_key(key:T)-\u0026gt;Self{Self{key,left:TreapNodeCont::Empty,right:TreapNodeCont::Empty,priority:rand_u64(),len:1}}fn recompute(\u0026amp;mut self){self.len=self.left.len()+self.right.len()+1;}}impl\u0026lt;T:PartialOrd+Clone\u0026gt;TreapNodeCont\u0026lt;T\u0026gt;{fn len(\u0026amp;self)-\u0026gt;usize{if let TreapNodeCont::Some(root)=self{root.len}else{0}}fn split(self,x:T)-\u0026gt;(TreapNodeCont\u0026lt;T\u0026gt;,TreapNodeCont\u0026lt;T\u0026gt;){let Self::Some(mut root)=self else{return(Self::Empty,Self::Empty);};let(left_res,right_res);if root.key\u0026gt;x{(left_res,root.left)=root.left.split(x);root.len=root.left.len()+root.right.len();root.recompute();right_res=TreapNodeCont::Some(root);}else{(root.right,right_res)=root.right.split(x);root.recompute();left_res=TreapNodeCont::Some(root);}(left_res,right_res)}fn insert(self,mut single_node:TreapNode\u0026lt;T\u0026gt;)-\u0026gt;TreapNodeCont\u0026lt;T\u0026gt;{let TreapNodeCont::Some(mut root)=self else{return TreapNodeCont::Some(Box::new(single_node));};if single_node.priority\u0026gt;=root.priority{(single_node.left,single_node.right)=TreapNodeCont::Some(root).split(single_node.key.clone());single_node.recompute();TreapNodeCont::Some(Box::new(single_node))}else{if single_node.key\u0026lt;=root.key{root.left=root.left.insert(single_node);}else{root.right=root.right.insert(single_node);}root.recompute();TreapNodeCont::Some(root)}}fn contains(\u0026amp;self,key:\u0026amp;T)-\u0026gt;bool{let TreapNodeCont::Some(root)=self else{return false;};match key.partial_cmp(\u0026amp;root.key).unwrap(){std::cmp::Ordering::Less=\u0026gt;{root.left.contains(key)},std::cmp::Ordering::Greater=\u0026gt;{root.right.contains(key)},std::cmp::Ordering::Equal=\u0026gt;{true},}}fn erase(self,key:\u0026amp;T)-\u0026gt;TreapNodeCont\u0026lt;T\u0026gt;{let TreapNodeCont::Some(mut root)=self else{return TreapNodeCont::Empty;};match key.partial_cmp(\u0026amp;root.key).unwrap(){std::cmp::Ordering::Less=\u0026gt;{root.left=root.left.erase(key);root.recompute();TreapNodeCont::Some(root)},std::cmp::Ordering::Greater=\u0026gt;{root.right=root.right.erase(key);root.recompute();TreapNodeCont::Some(root)},std::cmp::Ordering::Equal=\u0026gt;{let left_subtree=mem::take(\u0026amp;mut root.left);let right_subtree=mem::take(\u0026amp;mut root.right);left_subtree.merge(right_subtree)},}}fn merge(self,other:TreapNodeCont\u0026lt;T\u0026gt;)-\u0026gt;TreapNodeCont\u0026lt;T\u0026gt;{let TreapNodeCont::Some(mut left_root)=self else{return other;};let TreapNodeCont::Some(mut right_root)=other else{return TreapNodeCont::Some(left_root);};if left_root.priority\u0026gt;=right_root.priority{let(left_child,right_child)=(mem::take(\u0026amp;mut left_root.left),mem::take(\u0026amp;mut left_root.right));left_root.left=left_child.merge(right_child);left_root.right=TreapNodeCont::Some(right_root);left_root.recompute();TreapNodeCont::Some(left_root)}else{let(left_child,right_child)=(mem::take(\u0026amp;mut right_root.left),mem::take(\u0026amp;mut right_root.right));right_root.right=left_child.merge(right_child);right_root.left=TreapNodeCont::Some(left_root);right_root.recompute();TreapNodeCont::Some(right_root)}}}}pub mod disjoint_set{pub struct DisjointSet{data:Vec\u0026lt;usize\u0026gt;,size:Vec\u0026lt;usize\u0026gt;}impl DisjointSet{pub fn new(n:usize)-\u0026gt;Self{Self{data:(0..n).collect(),size:vec![1;n]}}pub fn find(\u0026amp;mut self,mut x:usize)-\u0026gt;usize{if self.data[x]!=x{x=self.find(self.data[x]);self.data[x]=x;}x}pub fn join(\u0026amp;mut self,mut x:usize,mut y:usize)-\u0026gt;bool{x=self.find(x);y=self.find(y);if x==y{return false;}if self.size[x]\u0026lt;self.size[y]{std::mem::swap(\u0026amp;mut x,\u0026amp;mut y);}self.size[x]+=self.size[y];self.data[y]=x;true}pub fn size(\u0026amp;mut self,x:usize)-\u0026gt;usize{let x=self.find(x);self.size[x]}pub fn len(\u0026amp;self)-\u0026gt;usize{self.data.len()}}}}pub mod misc{pub mod random{use std::time::SystemTime;pub static mut RNG:Option\u0026lt;XorShift\u0026gt; =None;pub fn rand_u64()-\u0026gt;u64{let rng=unsafe{RNG.get_or_insert(XorShift::new((SystemTime::UNIX_EPOCH.elapsed().unwrap().as_nanos()\u0026amp;0xFFFFFFFFFFFFFFFF)as u64))};rng.next()}pub struct XorShift{x:u64,y:u64,z:u64,w:u64,}impl XorShift{pub fn new(seed:u64)-\u0026gt;Self{XorShift{x:9018237498,y:1982731389,z:1894712904,w:seed,}}pub fn next(\u0026amp;mut self)-\u0026gt;u64{let t=self.x^(self.x\u0026lt;\u0026lt;11);self.x=self.y;self.y=self.z;self.z=self.w;self.w=self.w^(self.w\u0026gt;\u0026gt;19)^t^(t\u0026gt;\u0026gt;8);self.w}}}pub mod stdout{#[macro_export]macro_rules!__cargo_equip_macro_def___algo_0_1_0_println_iter{($iter:expr)=\u0026gt;{for val in$iter{print!(\u0026#34;{} \u0026#34;,val);}println!();};}macro_rules!println_iter{($($tt:tt)*)=\u0026gt;(crate::__cargo_equip_macro_def___algo_0_1_0_println_iter!{$($tt)*})}}pub mod mim{#[macro_export]macro_rules!__cargo_equip_macro_def___algo_0_1_0_maxim{($a:expr,$b:expr)=\u0026gt;{if$b\u0026gt;$a{$a=$b;}};}macro_rules!maxim{($($tt:tt)*)=\u0026gt;(crate::__cargo_equip_macro_def___algo_0_1_0_maxim!{$($tt)*})}#[macro_export]macro_rules!__cargo_equip_macro_def___algo_0_1_0_minim{($a:expr,$b:expr)=\u0026gt;{if$b\u0026lt;$a{$a=$b;}};}macro_rules!minim{($($tt:tt)*)=\u0026gt;(crate::__cargo_equip_macro_def___algo_0_1_0_minim!{$($tt)*})}}}} pub mod codeforces {} pub mod proconio {#![allow(clippy::needless_doctest_main,clippy::print_literal)]use crate::__cargo_equip::preludes::proconio::*;pub use crate::__cargo_equip::macros::proconio::*;pub use proconio_derive::*;pub mod marker{use crate::__cargo_equip::preludes::proconio::*;use crate::__cargo_equip::crates::proconio::source::{Readable,Source};use std::io::BufRead;pub enum Chars{}impl Readable for Chars{type Output=Vec\u0026lt;char\u0026gt;;fn read\u0026lt;R:BufRead,S:Source\u0026lt;R\u0026gt;\u0026gt;(source:\u0026amp;mut S)-\u0026gt;Vec\u0026lt;char\u0026gt;{source.next_token_unwrap().chars().collect()}}pub enum Bytes{}impl Readable for Bytes{type Output=Vec\u0026lt;u8\u0026gt;;fn read\u0026lt;R:BufRead,S:Source\u0026lt;R\u0026gt;\u0026gt;(source:\u0026amp;mut S)-\u0026gt;Vec\u0026lt;u8\u0026gt;{source.next_token_unwrap().bytes().collect()}}pub enum Usize1{}impl Readable for Usize1{type Output=usize;fn read\u0026lt;R:BufRead,S:Source\u0026lt;R\u0026gt;\u0026gt;(source:\u0026amp;mut S)-\u0026gt;usize{usize::read(source).checked_sub(1).expect(\u0026#34;attempted to read the value 0 as a Usize1\u0026#34;)}}pub enum Isize1{}impl Readable for Isize1{type Output=isize;fn read\u0026lt;R:BufRead,S:Source\u0026lt;R\u0026gt;\u0026gt;(source:\u0026amp;mut S)-\u0026gt;isize{isize::read(source).checked_sub(1).unwrap_or_else(||{panic!(concat!(\u0026#34;attempted to read the value {} as a Isize1:\u0026#34;,\u0026#34; the value is isize::MIN and cannot be decremented\u0026#34;),std::isize::MIN,)})}}}pub mod source{use crate::__cargo_equip::preludes::proconio::*;use std::any::type_name;use std::fmt::Debug;use std::io::BufRead;use std::str::FromStr;pub mod line{use crate::__cargo_equip::preludes::proconio::*;use super::Source;use crate::__cargo_equip::crates::proconio::source::tokens::Tokens;use std::io::BufRead;pub struct LineSource\u0026lt;R:BufRead\u0026gt;{tokens:Tokens,reader:R,}impl\u0026lt;R:BufRead\u0026gt;LineSource\u0026lt;R\u0026gt;{pub fn new(reader:R)-\u0026gt;LineSource\u0026lt;R\u0026gt;{LineSource{tokens:\u0026#34;\u0026#34;.to_owned().into(),reader,}}fn prepare(\u0026amp;mut self){while self.tokens.is_empty(){let mut line=String::new();let num_bytes=self.reader.read_line(\u0026amp;mut line).expect(\u0026#34;failed to get linel maybe an IO error.\u0026#34;);if num_bytes==0{return;}self.tokens=line.into();}}}impl\u0026lt;R:BufRead\u0026gt;Source\u0026lt;R\u0026gt;for LineSource\u0026lt;R\u0026gt;{fn next_token(\u0026amp;mut self)-\u0026gt;Option\u0026lt;\u0026amp;str\u0026gt;{self.prepare();self.tokens.next_token()}fn is_empty(\u0026amp;mut self)-\u0026gt;bool{self.prepare();self.tokens.is_empty()}}use std::io::BufReader;impl\u0026lt;\u0026#39;a\u0026gt;From\u0026lt;\u0026amp;\u0026#39;a str\u0026gt;for LineSource\u0026lt;BufReader\u0026lt;\u0026amp;\u0026#39;a[u8]\u0026gt;\u0026gt;{fn from(s:\u0026amp;\u0026#39;a str)-\u0026gt;LineSource\u0026lt;BufReader\u0026lt;\u0026amp;\u0026#39;a[u8]\u0026gt;\u0026gt;{LineSource::new(BufReader::new(s.as_bytes()))}}}pub mod once{use crate::__cargo_equip::preludes::proconio::*;use super::Source;use crate::__cargo_equip::crates::proconio::source::tokens::Tokens;use std::io::BufRead;use std::marker::PhantomData;pub struct OnceSource\u0026lt;R:BufRead\u0026gt;{tokens:Tokens,_read:PhantomData\u0026lt;R\u0026gt;,}impl\u0026lt;R:BufRead\u0026gt;OnceSource\u0026lt;R\u0026gt;{pub fn new(mut source:R)-\u0026gt;OnceSource\u0026lt;R\u0026gt;{let mut context=String::new();source.read_to_string(\u0026amp;mut context).expect(\u0026#34;failed to read from source; maybe an IO error.\u0026#34;);OnceSource{tokens:context.into(),_read:PhantomData,}}}impl\u0026lt;R:BufRead\u0026gt;Source\u0026lt;R\u0026gt;for OnceSource\u0026lt;R\u0026gt;{fn next_token(\u0026amp;mut self)-\u0026gt;Option\u0026lt;\u0026amp;str\u0026gt;{self.tokens.next_token()}fn is_empty(\u0026amp;mut self)-\u0026gt;bool{self.tokens.is_empty()}}use std::io::BufReader;impl\u0026lt;\u0026#39;a\u0026gt;From\u0026lt;\u0026amp;\u0026#39;a str\u0026gt;for OnceSource\u0026lt;BufReader\u0026lt;\u0026amp;\u0026#39;a[u8]\u0026gt;\u0026gt;{fn from(s:\u0026amp;\u0026#39;a str)-\u0026gt;OnceSource\u0026lt;BufReader\u0026lt;\u0026amp;\u0026#39;a[u8]\u0026gt;\u0026gt;{OnceSource::new(BufReader::new(s.as_bytes()))}}}mod tokens{use crate::__cargo_equip::preludes::proconio::*;use std::{iter::Peekable,ptr::NonNull,str::SplitWhitespace};pub(super)struct Tokens{tokens:Peekable\u0026lt;SplitWhitespace\u0026lt;\u0026#39;static\u0026gt;\u0026gt;,_current_context:CurrentContext,}impl Tokens{pub(super)fn next_token(\u0026amp;mut self)-\u0026gt;Option\u0026lt;\u0026amp;str\u0026gt;{self.tokens.next()}pub(super)fn is_empty(\u0026amp;mut self)-\u0026gt;bool{self.tokens.peek().is_none()}}impl From\u0026lt;String\u0026gt;for Tokens{fn from(current_context:String)-\u0026gt;Self{let current_context=CurrentContext::from(current_context);unsafe{let tokens=current_context.0.as_ref().split_whitespace().peekable();Self{tokens,_current_context:current_context,}}}}unsafe impl Send for Tokens{}unsafe impl Sync for Tokens{}struct CurrentContext(NonNull\u0026lt;str\u0026gt;);impl From\u0026lt;String\u0026gt;for CurrentContext{fn from(s:String)-\u0026gt;Self{let s=s.into_boxed_str();Self(NonNull::new(Box::leak(s)).unwrap())}}impl Drop for CurrentContext{fn drop(\u0026amp;mut self){unsafe{Box::from_raw(self.0.as_mut())};}}}pub mod auto{use crate::__cargo_equip::preludes::proconio::*;#[cfg(debug_assertions)]pub use super::line::LineSource as AutoSource;#[cfg(not(debug_assertions))]pub use super::once::OnceSource as AutoSource;}pub trait Source\u0026lt;R:BufRead\u0026gt;{fn next_token(\u0026amp;mut self)-\u0026gt;Option\u0026lt;\u0026amp;str\u0026gt;;#[allow(clippy::wrong_self_convention)]fn is_empty(\u0026amp;mut self)-\u0026gt;bool;fn next_token_unwrap(\u0026amp;mut self)-\u0026gt;\u0026amp;str{self.next_token().expect(concat!(\u0026#34;failed to get the next token; \u0026#34;,\u0026#34;maybe reader reached an end of input. \u0026#34;,\u0026#34;ensure that arguments for `input!` macro is correctly \u0026#34;,\u0026#34;specified to match the problem input.\u0026#34;))}}impl\u0026lt;R:BufRead,S:Source\u0026lt;R\u0026gt;\u0026gt;Source\u0026lt;R\u0026gt;for\u0026amp;\u0026#39;_ mut S{fn next_token(\u0026amp;mut self)-\u0026gt;Option\u0026lt;\u0026amp;str\u0026gt;{(*self).next_token()}fn is_empty(\u0026amp;mut self)-\u0026gt;bool{(*self).is_empty()}}pub trait Readable{type Output;fn read\u0026lt;R:BufRead,S:Source\u0026lt;R\u0026gt;\u0026gt;(source:\u0026amp;mut S)-\u0026gt;Self::Output;}impl\u0026lt;T:FromStr\u0026gt;Readable for T where T::Err:Debug,{type Output=T;fn read\u0026lt;R:BufRead,S:Source\u0026lt;R\u0026gt;\u0026gt;(source:\u0026amp;mut S)-\u0026gt;T{let token=source.next_token_unwrap();match token.parse(){Ok(v)=\u0026gt;v,Err(e)=\u0026gt;panic!(concat!(\u0026#34;failed to parse the input `{input}` \u0026#34;,\u0026#34;to the value of type `{ty}`: {err:?}; \u0026#34;,\u0026#34;ensure that the input format is collectly specified \u0026#34;,\u0026#34;and that the input value must handle specified type.\u0026#34;,),input=token,ty=type_name::\u0026lt;T\u0026gt;(),err=e,),}}}}use crate::__cargo_equip::crates::proconio::source::{auto::AutoSource,line::LineSource};use std::io::{BufReader,Stdin};use std::sync::OnceLock;use std::{io::{self,BufRead},sync::Mutex,};pub use crate::__cargo_equip::crates::proconio::source::Readable as __Readable;pub enum StdinSource\u0026lt;R:BufRead\u0026gt;{Normal(AutoSource\u0026lt;R\u0026gt;),Interactive(LineSource\u0026lt;R\u0026gt;),Unknown(LineSource\u0026lt;R\u0026gt;),}impl\u0026lt;R:BufRead\u0026gt;source::Source\u0026lt;R\u0026gt;for StdinSource\u0026lt;R\u0026gt;{fn next_token(\u0026amp;mut self)-\u0026gt;Option\u0026lt;\u0026amp;str\u0026gt;{match self{StdinSource::Normal(source)=\u0026gt;source.next_token(),StdinSource::Interactive(source)=\u0026gt;source.next_token(),StdinSource::Unknown(source)=\u0026gt;source.next_token(),}}fn is_empty(\u0026amp;mut self)-\u0026gt;bool{match self{StdinSource::Normal(source)=\u0026gt;source.is_empty(),StdinSource::Interactive(source)=\u0026gt;source.is_empty(),StdinSource::Unknown(source)=\u0026gt;source.is_empty(),}}}pub static STDIN_SOURCE:OnceLock\u0026lt;Mutex\u0026lt;StdinSource\u0026lt;BufReader\u0026lt;Stdin\u0026gt;\u0026gt;\u0026gt;\u0026gt; =OnceLock::new();#[macro_export]macro_rules!__cargo_equip_macro_def_proconio_input{(@from[$source:expr]@rest)=\u0026gt;{};(@from[$source:expr]@rest mut$($rest:tt)*)=\u0026gt;{$crate::__cargo_equip::crates::proconio::input!{@from[$source]@mut[mut]@rest$($rest)*}};(@from[$source:expr]@rest$($rest:tt)*)=\u0026gt;{$crate::__cargo_equip::crates::proconio::input!{@from[$source]@mut[]@rest$($rest)*}};(@from[$source:expr]@mut[$($mut:tt)?]@rest$var:tt:$($rest:tt)*)=\u0026gt;{$crate::__cargo_equip::crates::proconio::input!{@from[$source]@mut[$($mut)*]@var$var@kind[]@rest$($rest)*}};(@from[$source:expr]@mut[$($mut:tt)?]@var$var:tt@kind[$($kind:tt)*]@rest)=\u0026gt;{let$($mut)*$var=$crate::__cargo_equip::crates::proconio::read_value!(@source[$source]@kind[$($kind)*]);};(@from[$source:expr]@mut[$($mut:tt)?]@var$var:tt@kind[$($kind:tt)*]@rest,$($rest:tt)*)=\u0026gt;{$crate::__cargo_equip::crates::proconio::input!(@from[$source]@mut[$($mut)*]@var$var@kind[$($kind)*]@rest);$crate::__cargo_equip::crates::proconio::input!(@from[$source]@rest$($rest)*);};(@from[$source:expr]@mut[$($mut:tt)?]@var$var:tt@kind[$($kind:tt)*]@rest$tt:tt$($rest:tt)*)=\u0026gt;{$crate::__cargo_equip::crates::proconio::input!(@from[$source]@mut[$($mut)*]@var$var@kind[$($kind)*$tt]@rest$($rest)*);};(from$source:expr,$($rest:tt)*)=\u0026gt;{#[allow(unused_variables,unused_mut)]let mut s=$source;$crate::__cargo_equip::crates::proconio::input!{@from[\u0026amp;mut s]@rest$($rest)*}};($($rest:tt)*)=\u0026gt;{let mut locked_stdin=$crate::__cargo_equip::crates::proconio::STDIN_SOURCE.get_or_init(||{std::sync::Mutex::new($crate::__cargo_equip::crates::proconio::StdinSource::Normal($crate::__cargo_equip::crates::proconio::source::auto::AutoSource::new(std::io::BufReader::new(std::io::stdin())),))}).lock().expect(concat!(\u0026#34;failed to lock the stdin; please re-run this program. \u0026#34;,\u0026#34;If this issue repeatedly occur, this is a bug in `proconio`. \u0026#34;,\u0026#34;Please report this issue from \u0026#34;,\u0026#34;\u0026lt;https://github.com/statiolake/proconio-rs/issues\u0026gt;.\u0026#34;));$crate::__cargo_equip::crates::proconio::input!{@from[\u0026amp;mut*locked_stdin]@rest$($rest)*}drop(locked_stdin);};}macro_rules!input{($($tt:tt)*)=\u0026gt;(crate::__cargo_equip_macro_def_proconio_input!{$($tt)*})}#[macro_export]macro_rules!__cargo_equip_macro_def_proconio_input_interactive{($($rest:tt)*)=\u0026gt;{let mut locked_stdin=$crate::__cargo_equip::crates::proconio::STDIN_SOURCE.get_or_init(||{std::sync::Mutex::new($crate::__cargo_equip::crates::proconio::StdinSource::Interactive($crate::__cargo_equip::crates::proconio::source::line::LineSource::new(std::io::BufReader::new(std::io::stdin())),))}).lock().expect(concat!(\u0026#34;failed to lock the stdin; please re-run this program. \u0026#34;,\u0026#34;If this issue repeatedly occur, this is a bug in `proconio`. \u0026#34;,\u0026#34;Please report this issue from \u0026#34;,\u0026#34;\u0026lt;https://github.com/statiolake/proconio-rs/issues\u0026gt;.\u0026#34;));$crate::__cargo_equip::crates::proconio::input!{from\u0026amp;mut*locked_stdin,$($rest)*}drop(locked_stdin);};}macro_rules!input_interactive{($($tt:tt)*)=\u0026gt;(crate::__cargo_equip_macro_def_proconio_input_interactive!{$($tt)*})}#[macro_export]macro_rules!__cargo_equip_macro_def_proconio_read_value{(@source[$source:expr]@kind[[$($kind:tt)*]])=\u0026gt;{$crate::__cargo_equip::crates::proconio::read_value!(@array@source[$source]@kind[]@rest$($kind)*)};(@array@source[$source:expr]@kind[$($kind:tt)*]@rest)=\u0026gt;{{let len=\u0026lt;usize as$crate::__cargo_equip::crates::proconio::__Readable\u0026gt;::read($source);$crate::__cargo_equip::crates::proconio::read_value!(@source[$source]@kind[[$($kind)*;len]])}};(@array@source[$source:expr]@kind[$($kind:tt)*]@rest;$($rest:tt)*)=\u0026gt;{$crate::__cargo_equip::crates::proconio::read_value!(@array@source[$source]@kind[$($kind)*]@len[$($rest)*])};(@array@source[$source:expr]@kind[$($kind:tt)*]@rest$tt:tt$($rest:tt)*)=\u0026gt;{$crate::__cargo_equip::crates::proconio::read_value!(@array@source[$source]@kind[$($kind)*$tt]@rest$($rest)*)};(@array@source[$source:expr]@kind[$($kind:tt)*]@len[$($len:tt)*])=\u0026gt;{{let len=$($len)*;(0..len).map(|_|$crate::__cargo_equip::crates::proconio::read_value!(@source[$source]@kind[$($kind)*])).collect::\u0026lt;Vec\u0026lt;_\u0026gt;\u0026gt;()}};(@source[$source:expr]@kind[($($kinds:tt)*)])=\u0026gt;{$crate::__cargo_equip::crates::proconio::read_value!(@tuple@source[$source]@kinds[]@current[]@rest$($kinds)*)};(@tuple@source[$source:expr]@kinds[$([$($kind:tt)*])*]@current[]@rest)=\u0026gt;{($($crate::__cargo_equip::crates::proconio::read_value!(@source[$source]@kind[$($kind)*]),)*)};(@tuple@source[$source:expr]@kinds[$($kinds:tt)*]@current[$($curr:tt)*]@rest)=\u0026gt;{$crate::__cargo_equip::crates::proconio::read_value!(@tuple@source[$source]@kinds[$($kinds)*[$($curr)*]]@current[]@rest)};(@tuple@source[$source:expr]@kinds[$($kinds:tt)*]@current[$($curr:tt)*]@rest,$($rest:tt)*)=\u0026gt;{$crate::__cargo_equip::crates::proconio::read_value!(@tuple@source[$source]@kinds[$($kinds)*[$($curr)*]]@current[]@rest$($rest)*)};(@tuple@source[$source:expr]@kinds[$($kinds:tt)*]@current[$($curr:tt)*]@rest$tt:tt$($rest:tt)*)=\u0026gt;{$crate::__cargo_equip::crates::proconio::read_value!(@tuple@source[$source]@kinds[$($kinds)*]@current[$($curr)*$tt]@rest$($rest)*)};(@source[$source:expr]@kind[])=\u0026gt;{compile_error!(concat!(\u0026#34;Reached unreachable statement while parsing macro input. \u0026#34;,\u0026#34;This is a bug in `proconio`. \u0026#34;,\u0026#34;Please report this issue from \u0026#34;,\u0026#34;\u0026lt;https://github.com/statiolake/proconio-rs/issues\u0026gt;.\u0026#34;));};(@source[$source:expr]@kind[$kind:ty])=\u0026gt;{\u0026lt;$kind as$crate::__cargo_equip::crates::proconio::__Readable\u0026gt;::read($source)}}macro_rules!read_value{($($tt:tt)*)=\u0026gt;(crate::__cargo_equip_macro_def_proconio_read_value!{$($tt)*})}pub fn is_stdin_empty()-\u0026gt;bool{use source::Source;let mut lock=STDIN_SOURCE.get_or_init(||{Mutex::new(StdinSource::Unknown(LineSource::new(BufReader::new(io::stdin(),))))}).lock().expect(concat!(\u0026#34;failed to lock the stdin; please re-run this program. \u0026#34;,\u0026#34;If this issue repeatedly occur, this is a bug in `proconio`. \u0026#34;,\u0026#34;Please report this issue from \u0026#34;,\u0026#34;\u0026lt;https://github.com/statiolake/proconio-rs/issues\u0026gt;.\u0026#34;));lock.is_empty()}} pub mod __proconio_derive_0_2_1 {pub use crate::__cargo_equip::macros::__proconio_derive_0_2_1::*;#[macro_export]macro_rules!__cargo_equip_macro_def___proconio_derive_0_2_1_derive_readable{($(_:tt)*)=\u0026gt;(::std::compile_error!(\u0026#34;`derive_readable` from `proconio-derive 0.2.1` should have been expanded\u0026#34;);)}#[macro_export]macro_rules!__cargo_equip_macro_def___proconio_derive_0_2_1_fastout{($(_:tt)*)=\u0026gt;(::std::compile_error!(\u0026#34;`fastout` from `proconio-derive 0.2.1` should have been expanded\u0026#34;);)}} } pub(crate) mod macros { pub mod __algo_0_1_0 {pub use crate::{__cargo_equip_macro_def___algo_0_1_0_maxim as maxim,__cargo_equip_macro_def___algo_0_1_0_minim as minim,__cargo_equip_macro_def___algo_0_1_0_println_iter as println_iter};} pub mod codeforces {} pub mod proconio {pub use crate::{__cargo_equip_macro_def_proconio_input as input,__cargo_equip_macro_def_proconio_input_interactive as input_interactive,__cargo_equip_macro_def_proconio_read_value as read_value};} pub mod __proconio_derive_0_2_1 {pub use crate::{__cargo_equip_macro_def___proconio_derive_0_2_1_derive_readable as derive_readable,__cargo_equip_macro_def___proconio_derive_0_2_1_fastout as fastout};} } pub(crate) mod prelude {pub use crate::__cargo_equip::crates::*;} mod preludes { pub mod __algo_0_1_0 {} pub mod codeforces {pub(in crate::__cargo_equip)use crate::__cargo_equip::crates::{__algo_0_1_0 as algo,proconio};} pub mod proconio {pub(in crate::__cargo_equip)use crate::__cargo_equip::crates::__proconio_derive_0_2_1 as proconio_derive;} pub mod __proconio_derive_0_2_1 {} } } ","date":"2026年2月5日","externalUrl":null,"permalink":"/codeforces/template/","section":"Codeforces 题解","summary":"","title":"Codeforces Rust 代码模板","type":"codeforces"},{"content":"","date":"2026年2月5日","externalUrl":null,"permalink":"/categories/competitive-programming/","section":"Categories","summary":"","title":"Competitive Programming","type":"categories"},{"content":"","date":"2026年2月4日","externalUrl":null,"permalink":"/tags/algorithm/","section":"Tags","summary":"","title":"Algorithm","type":"tags"},{"content":"","date":"2026年2月4日","externalUrl":null,"permalink":"/categories/algorithms/","section":"Categories","summary":"","title":"Algorithms","type":"categories"},{"content":"","date":"2026年2月4日","externalUrl":null,"permalink":"/tags/leetcode/","section":"Tags","summary":"","title":"LeetCode","type":"tags"},{"content":"这是今天 LeetCode 每日一题的题解。\n","date":"2026年2月4日","externalUrl":null,"permalink":"/leetcode/2026/02/04/example/","section":"LeetCode 题解","summary":"","title":"LeetCode 每日一题: 示例题目","type":"leetcode"},{"content":" LeetCode 题解主页 # 欢迎来到曙光磁铁 (DawnMagnet) 的算法小站 - 力扣题解部分！\n本书为个人在刷题过程中的心得和体会。 主要题目来源为 力扣-中国站 - LeetCode-cn 主要刷题语言为 Rust / C++ / Golang 欢迎加微信 axccjqh 交流，备注 leetcode-solution 和必要的个人信息。\n感谢 Blowfish，本站构建在其之上。 如果觉得本项目好，可以点个 Star。请点击右上角的 GitHub 图标进入本项目的 GitHub 页面。 ","date":"2026年2月4日","externalUrl":null,"permalink":"/leetcode/","section":"LeetCode 题解","summary":"","title":"LeetCode 题解","type":"leetcode"},{"content":"","date":"2024年12月25日","externalUrl":null,"permalink":"/tags/file-forensics/","section":"Tags","summary":"","title":"File Forensics","type":"tags"},{"content":"","date":"2024年12月25日","externalUrl":null,"permalink":"/tags/png/","section":"Tags","summary":"","title":"PNG","type":"tags"},{"content":" PNG CRC32 修复工具 # 这是一个用于修复 PNG 图片 CRC32 校验的工具，主要通过调整图片的宽度和高度来实现。\n功能特性 # CRC32 检测\n自动检测 PNG 文件的 IHDR 块 CRC32 值 对比实际 CRC32 和期望值是否匹配 自动修复\n支持三种修复模式： 仅修复高度 仅修复宽度 同时修复高度和宽度 自动生成修复后的文件（文件名后缀为 _fix.png） 可视化输出\n使用彩色输出显示处理过程 显示当前和修复后的图片尺寸 显示详细的 CRC32 信息 使用方法 # python break.py \u0026lt;png_file_path\u0026gt; 例如：\npython break.py test.png 输出说明 # 绿色: 成功信息 红色: 错误信息 黄色: 预期值 青色: 信息提示 技术细节 # IHDR 类\n处理 PNG 文件的 IHDR 块 提供宽度/高度的读取和修改方法 计算 CRC32 校验值 修复策略\n优先尝试仅修改高度（最快） 其次尝试仅修改宽度 最后尝试同时修改高度和宽度（最慢） 文件处理\n支持二进制文件读写 自动备份原始文件 错误处理和异常捕获 注意事项 # 输入文件必须是有效的 PNG 文件 工具会在原文件同目录下创建修复后的文件 修复过程可能需要一定时间，特别是在尝试同时修复宽度和高度时 # -*- coding: utf-8 -*- import binascii import struct import sys from pathlib import Path from typing import Optional, Tuple # ANSI escape codes for colors class Colors: RED = \u0026#34;\\033[91m\u0026#34; GREEN = \u0026#34;\\033[92m\u0026#34; YELLOW = \u0026#34;\\033[93m\u0026#34; BLUE = \u0026#34;\\033[94m\u0026#34; CYAN = \u0026#34;\\033[96m\u0026#34; RESET = \u0026#34;\\033[0m\u0026#34; class IHDR: def __init__(self, data: bytes): self.raw_data = data self._header = data[:4] # IHDR chunk type self._height_bytes = data[8:12] self._width_bytes = data[4:8] self._ends = data[12:] # bit depth, color type, etc @property def width(self) -\u0026gt; int: return int.from_bytes(self._width_bytes, \u0026#34;big\u0026#34;) @property def height(self) -\u0026gt; int: return int.from_bytes(self._height_bytes, \u0026#34;big\u0026#34;) def get_data(self, width=None, height=None) -\u0026gt; bytes: return ( bytes(self._header) + (bytes(self._width_bytes) if width is None else struct.pack(\u0026#34;\u0026gt;i\u0026#34;, width)) + ( bytes(self._height_bytes) if height is None else struct.pack(\u0026#34;\u0026gt;i\u0026#34;, height) ) + bytes(self._ends) ) def crc32(self, width=None, height=None) -\u0026gt; int: return binascii.crc32(self.get_data(width, height)) \u0026amp; 0xFFFFFFFF @staticmethod def from_png(png_data: bytes) -\u0026gt; Tuple[\u0026#34;IHDR\u0026#34;, int]: \u0026#34;\u0026#34;\u0026#34;Extract IHDR from PNG file data and return (IHDR, expected_crc32)\u0026#34;\u0026#34;\u0026#34; ihdr_data = png_data[12:29] expected_crc32 = int.from_bytes(png_data[29:33], \u0026#34;big\u0026#34;) return IHDR(ihdr_data), expected_crc32 def try_fix_height(ihdr: IHDR, expected_crc32: int) -\u0026gt; Optional[int]: for height in range(0, 65535): if ihdr.crc32(height=height) == expected_crc32: return height return None def try_fix_width(ihdr: IHDR, expected_crc32: int) -\u0026gt; Optional[int]: for width in range(0, 65535): if ihdr.crc32(width=width) == expected_crc32: return width return None def try_fix_both( ihdr: IHDR, expected_crc32: int ) -\u0026gt; Tuple[Optional[int], Optional[int]]: for height in range(0, 65535): for width in range(0, 65535): if ihdr.crc32(width=width, height=height) == expected_crc32: return height, width return None, None def save_fixed_png( original_data: bytes, width: Optional[int] = None, height: Optional[int] = None, output_path: Path | None = None, ) -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;Save a new PNG with fixed dimensions\u0026#34;\u0026#34;\u0026#34; if output_path is None: output_path = Path(str(png_path).replace(\u0026#34;.png\u0026#34;, \u0026#34;_fix.png\u0026#34;)) # Only modify if we have new dimensions if width is None and height is None: return # Copy original data new_data = bytearray(original_data) # Update width if provided (at offset 16) if width is not None: new_data[16:20] = struct.pack(\u0026#34;\u0026gt;i\u0026#34;, width) # Update height if provided (at offset 20) if height is not None: new_data[20:24] = struct.pack(\u0026#34;\u0026gt;i\u0026#34;, height) # Calculate new CRC32 for IHDR chunk new_crc = binascii.crc32(new_data[12:29]) \u0026amp; 0xFFFFFFFF new_data[29:33] = struct.pack(\u0026#34;\u0026gt;I\u0026#34;, new_crc) # Save new file with open(output_path, \u0026#34;wb\u0026#34;) as f: f.write(new_data) print(f\u0026#34;{Colors.GREEN}Saved fixed PNG to: {output_path}{Colors.RESET}\u0026#34;) def try_fix_crc32(img_data: bytes, png_path: Path) -\u0026gt; None: ihdr, expected_crc32 = IHDR.from_png(img_data) real_crc32 = ihdr.crc32() if expected_crc32 == real_crc32: print(f\u0026#34;{Colors.GREEN}CRC32 is correct!{Colors.RESET}\u0026#34;) return print(f\u0026#34;{Colors.CYAN}IHDR data:{Colors.RESET} {ihdr.get_data()}\u0026#34;) print(f\u0026#34;{Colors.YELLOW}Expected CRC32: 0x{expected_crc32:X}\u0026#34;) print(f\u0026#34;{Colors.RED}Current CRC32: 0x{real_crc32:X}{Colors.RESET}\u0026#34;) print(f\u0026#34;{Colors.CYAN}Current dimensions: {ihdr.width}x{ihdr.height}{Colors.RESET}\u0026#34;) # Try fixing height height = try_fix_height(ihdr, expected_crc32) if height is not None: print(f\u0026#34;{Colors.GREEN}Found height fix: {height} (0x{height:X})\u0026#34;) print(f\u0026#34;Real dimensions: {ihdr.width}x{height}{Colors.RESET}\u0026#34;) save_fixed_png( img_data, height=height, output_path=png_path.parent / f\u0026#34;{png_path.stem}_fix.png\u0026#34;, ) return # Try fixing width width = try_fix_width(ihdr, expected_crc32) if width is not None: print(f\u0026#34;{Colors.GREEN}Found width fix: {width} (0x{width:X})\u0026#34;) print(f\u0026#34;Real dimensions: {width}x{ihdr.height}{Colors.RESET}\u0026#34;) save_fixed_png( img_data, width=width, output_path=png_path.parent / f\u0026#34;{png_path.stem}_fix.png\u0026#34;, ) return # Try fixing both height, width = try_fix_both(ihdr, expected_crc32) if height is not None and width is not None: print( f\u0026#34;{Colors.GREEN}Found height/width fix: {height}x{width} (0x{height:X}x0x{width:X}){Colors.RESET}\u0026#34; ) save_fixed_png( img_data, width=width, height=height, output_path=png_path.parent / f\u0026#34;{png_path.stem}_fix.png\u0026#34;, ) return print(f\u0026#34;{Colors.RED}No fix found{Colors.RESET}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: if len(sys.argv) != 2: print(f\u0026#34;{Colors.RED}Usage: python {sys.argv[0]} \u0026lt;png_file_path\u0026gt;{Colors.RESET}\u0026#34;) sys.exit(1) png_path = Path(sys.argv[1]) if not png_path.exists(): print(f\u0026#34;{Colors.RED}Error: File {png_path} does not exist{Colors.RESET}\u0026#34;) sys.exit(1) if not png_path.is_file(): print(f\u0026#34;{Colors.RED}Error: {png_path} is not a file{Colors.RESET}\u0026#34;) sys.exit(1) try: with open(png_path, \u0026#34;rb\u0026#34;) as f: img_data = f.read() try_fix_crc32(img_data, png_path) except Exception as e: print(f\u0026#34;{Colors.RED}Error: {str(e)}{Colors.RESET}\u0026#34;) sys.exit(1) f ","date":"2024年12月25日","externalUrl":null,"permalink":"/ctf/forensics/png_width_height/","section":"CTF","summary":"","title":"PNG CRC32 修复工具","type":"ctf"},{"content":"","date":"2024年12月25日","externalUrl":null,"permalink":"/ctf/forensics/","section":"CTF","summary":"","title":"文件取证","type":"ctf"},{"content":" A 题 # fn solve() { input! { n: usz, a: [usz; n] } let mut cur = 0; for i in 0..n { cur += 1; if a[i] == cur { cur += 1; } } println!(\u0026#34;{}\u0026#34;, cur); } fn main() { input! { t: usize } for _ in 0..t { solve(); } } B 题 # 对于每一个位贪心的求取不到该二进制位时，剩余所有集合的最大并集\nfn solve() { input! { n: usize, } let mut mask_k = vec![]; let mut hm = HashMap::new(); let mut cnt: u64 = 0; for _ in 0..n { input! { x: usize, p: [usize; x] } let mut mask = 0; for item in p { if !hm.contains_key(\u0026amp;item) { hm.insert(item, cnt); cnt += 1; } mask |= 1 \u0026lt;\u0026lt; hm[\u0026amp;item]; } mask_k.push(mask); } let mut res = 0; for i in 0..cnt { let mut cur: u64 = 0; for \u0026amp;mask in \u0026amp;mask_k { if ((mask \u0026gt;\u0026gt; i) \u0026amp; 1) == 0 { cur |= mask; } } res = res.max(cur.count_ones()); } println!(\u0026#34;{}\u0026#34;, res); } fn main() { input! { t: usize } for _ in 0..t { solve(); } } C 题 # fn solve() { input! { n: usize, cards: [i64; n] } // 0 select odd // 1 skip odd // 2 select even // 3 skip even let (mut a,mut b,mut c) = (cards[0], cards[0].max(0), i64::MIN); for i in 1..n { let na = a.max(c) + cards[i]; let nb = a.max(c) + cards[i].max(0); let nc = b.max(c); a = na; b = nb; c = nc; } println!(\u0026#34;{}\u0026#34;, a.max(b).max(c)); } fn main() { input! { t: usize } dbg!(t); for _ in 0..t { solve(); } } D 题 # #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using ll = long long; const int maxn = 200004; int n; struct Node { ll size, val, dp1, dp2; }; map\u0026lt;Node *, vector\u0026lt;Node *\u0026gt;\u0026gt; e; Node ns[maxn]; void dfs1(Node *u, Node *f) { u-\u0026gt;size = 1; for (auto v: e[u]) { if (v != f) { dfs1(v, u); u-\u0026gt;size += v-\u0026gt;size; u-\u0026gt;dp1 += v-\u0026gt;dp1 + v-\u0026gt;size * (u-\u0026gt;val ^ v-\u0026gt;val); } } } void dfs2(Node *u, Node *f) { for (auto \u0026amp;v: e[u]) { if (v != f) { ll w = u-\u0026gt;dp2 + u-\u0026gt;dp1 - v-\u0026gt;dp1 - v-\u0026gt;size * (u-\u0026gt;val ^ v-\u0026gt;val); v-\u0026gt;dp2 = w + (n - v-\u0026gt;size) * (u-\u0026gt;val ^ v-\u0026gt;val); dfs2(v, u); } } } void solve() { cin \u0026gt;\u0026gt; n; e.clear(); for (int i = 1; i \u0026lt;= n; i++) { cin \u0026gt;\u0026gt; ns[i].val; ns[i].dp1 = ns[i].dp2 = ns[i].size = 0; } for (int i = 1; i \u0026lt; n; i++) { int u, v; cin \u0026gt;\u0026gt; u \u0026gt;\u0026gt; v; e[ns + u].push_back(ns + v); e[ns + v].push_back(ns + u); } dfs1(ns + 1, nullptr); dfs2(ns + 1, nullptr); for (int i = 1; i \u0026lt;= n; i++) { printf(\u0026#34;%lld \u0026#34;, ns[i].dp1 + ns[i].dp2); } putchar(\u0026#39;\\n\u0026#39;); } int main() { cin.tie(0)-\u0026gt;sync_with_stdio(0); int T; cin \u0026gt;\u0026gt; T; while (T--) { solve(); } } ","date":"2023年9月25日","externalUrl":null,"permalink":"/codeforces/1882/","section":"Codeforces 题解","summary":"","title":"Div.2 899 - ID: 1882","type":"codeforces"},{"content":"参考链接\n文件头 (04 03 4B 50) 中央目录文件头 (02 01 4B 50) 中央目录结束标记 (06 05 4B 50) ","date":"2023年1月13日","externalUrl":null,"permalink":"/ctf/forensics/zip/","section":"CTF","summary":"","title":"ZIP","type":"ctf"},{"content":"","date":"2023年1月13日","externalUrl":null,"permalink":"/tags/zip/","section":"Tags","summary":"","title":"ZIP","type":"tags"},{"content":"","date":"2023年1月12日","externalUrl":null,"permalink":"/ctf/forensics/png/","section":"CTF","summary":"","title":"PNG","type":"ctf"},{"content":"","date":"2023年1月11日","externalUrl":null,"permalink":"/tags/file-format/","section":"Tags","summary":"","title":"File Format","type":"tags"},{"content":"","date":"2023年1月11日","externalUrl":null,"permalink":"/ctf/forensics/jpg/","section":"CTF","summary":"","title":"JPG","type":"ctf"},{"content":" 可能会修改的地方 # struct BITMAPFILEHEADER bmfh \u0026gt; DWORD bfOffBits 默认值 54 struct BITMAPINFOHEADER bmih \u0026gt; DOWRD biSize 默认值 40 ","date":"2023年1月10日","externalUrl":null,"permalink":"/ctf/forensics/bmp/","section":"CTF","summary":"","title":"BMP","type":"ctf"},{"content":"","date":"2023年1月10日","externalUrl":null,"permalink":"/tags/bmp/","section":"Tags","summary":"","title":"BMP","type":"tags"},{"content":"原文地址：https://home.dawnmagnet.date/leetcode/2022/06/11/\n如果一个二进制字符串，是以一些 0（可能没有 0）后面跟着一些 1（也可能没有 1）的形式组成的，那么该字符串是 单调递增 的。\n给你一个二进制字符串 s，你可以将任何 0 翻转为 1 或者将 1 翻转为 0 。\n返回使 s 单调递增的最小翻转次数。\n示例 1：\n输入：s = \u0026ldquo;00110\u0026rdquo; 输出：1 解释：翻转最后一位得到 00111.\n示例 2：\n输入：s = \u0026ldquo;010110\u0026rdquo; 输出：2 解释：翻转得到 011111，或者是 000111。\n示例 3：\n输入：s = \u0026ldquo;00011000\u0026rdquo; 输出：2 解释：翻转得到 00000000。 提示：\n1 \u0026lt;= s.length \u0026lt;= 105 s[i] 为 \u0026lsquo;0\u0026rsquo; 或 \u0026lsquo;1\u0026rsquo; 来源：力扣（LeetCode） 链接：https://leetcode.cn/problems/flip-string-to-monotone-increasing 著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。\n思路分析 - 1 # 首先理解一下题目，主要的难点是翻转次数。其实有一个关键的要点，只需在字符串中选择一个确切的位置，作为0和1的分界线，那么就可以很快地计算得到整个字符串变成有序的翻转次数。选定了一个分界线之后，将其之前的“1”全部变成“0”，将其之后的“0”全部变成“1”。就可以很快地得到翻转所需的次数。\n所以问题变成了，如何快速地求一个特定的分界线，前面的“1”的个数和后面的“0”的个数呢？我首先想到的是动态规划，使用两个数组，分别从数组的两个方向进行遍历，可以很轻松地得到每个特定位置前面的“1”的个数和后面的“0”的个数。最后遍历一遍，分别获得每个特定位置上前面的“1”的个数和后面的“0”的个数之和，并求那个位置是一个开销最小的位置（也就是前面的“1”的个数和后面的“0”的个数之和最小）。返回这个答案即可\nC++ 代码 - 1 # class Solution { public: int minFlipsMonoIncr(string s) { int n = s.size(); vector\u0026lt;int\u0026gt; v1(n + 1), v2(n + 1); for (int i = 0; i \u0026lt; n; ++i) v1[i + 1] = v1[i] + (s[i] == \u0026#39;1\u0026#39;); for (int i = n - 1; i \u0026gt;= 0; --i) v2[i] = v2[i + 1] + (s[i] == \u0026#39;0\u0026#39;); int res = n; for (int i = 0; i \u0026lt;= n; ++i) res = min(res, v1[i] + v2[i]); return res; } }; 思路分析 2 # 上面的这种思路还有一些可以优化的地方。因为使用了过量的空间，开了两个数组。在这里可以使用一种新的方法，从而能在仅占用 O(1)空间的情况下做出这道题。\n换一个思路进行思考，动态规划的思路。如果我们已知一个字符串，它的最小翻转次数是 n。那么在其之后新加一个字符，能否计算这个新的字符串的最小翻转次数呢？\n如果新加的字符是1，那么它前面是0或者1都是合理的，不需要新添加翻转次数。\n如果新加的字符是0，那么它前面必须是字符0，也不需要增加翻转次数。\n因为考虑到每次翻转之后，跟前一个字符串的最后一个字符有很大的关系，所以必须分开讨论\n首先假设 dp[i][j]是一个动态规划数组，它的含义是对于 s的子字符串 s[0:i]，如果在翻转之后最后一位是 j(j是0或1)的最小翻转次数是多少。\n首先可以假设以下推导式\n如果新添加的字符是 0，那么 dp[i][0]=dp[i-1][0] 如果新添加的字符是 1，那么 dp[i][1]=min(dp[i-1][0], dp[i-1][1]) 以上两种情况是比较好想到的，但如果新添加的字符也是需要翻转的呢？那就需要多加一个翻转次数 1\n如果新添加的字符是 0，那么 dp[i][1]=min(dp[i-1][0], dp[i-1][1])+1 如果新添加的字符是 1，那么 dp[i][0]=dp[i-1][0]+1 以上就完成了一个逻辑自洽的动态规划状态转移方程。\nC++代码 - 2 # class Solution { public: int minFlipsMonoIncr(string s) { int a = 0, b = 0; for (auto \u0026amp; ch : s) { b = min(a, b); if (ch == \u0026#39;1\u0026#39;) a++; else b++; } return min(a, b); } }; ","date":"2022年6月11日","externalUrl":null,"permalink":"/leetcode/2022/06/11/","section":"LeetCode 题解","summary":"","title":"11 日 No.926 将字符串翻转到单调递增","type":"leetcode"},{"content":"","date":"2022年6月11日","externalUrl":null,"permalink":"/tags/greedy/","section":"Tags","summary":"","title":"Greedy","type":"tags"},{"content":"原文地址：https://home.dawnmagnet.date/leetcode/2022/01/19/\n给你一个整数数组 nums 和一个整数 k ，判断数组中是否存在两个 不同的索引 i 和 j ，满足 nums[i] == nums[j] 且 abs(i - j) \u0026lt;= k 。如果存在，返回 true ；否则，返回 false 。\n示例 1：\n输入：nums = [1,2,3,1], k = 3 输出：true 示例 2：\n输入：nums = [1,0,1,1], k = 1 输出：true 示例 3：\n输入：nums = [1,2,3,1,2,3], k = 2 输出：false 提示：\n1 \u0026lt;= nums.length \u0026lt;= 10^5 -10^9 \u0026lt;= nums[i] \u0026lt;= 10^9 0 \u0026lt;= k \u0026lt;= 10^5 来源：力扣（LeetCode） 链接：https://leetcode-cn.com/problems/contains-duplicate-ii 著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。\n思路分析 # 本题用一个通俗易懂的话讲解一下，就是在一个数组中找出下标相距小于一个特定值的两个相同值。而如何更高效地判断呢？\n我们可以使用滑动窗口高效的解决这一个题目，因为按照题目要求，可能配对的两个值一定是在总长度为k+1的下标区间中，所以我们可以维护一个长度为k+1的滑动窗口，另外使用一个哈希表来判断是否有重复的值，从而只在这个区间内判断是否存在合法的答案\nRust 代码 # impl Solution { pub fn contains_nearby_duplicate(mut nums: Vec\u0026lt;i32\u0026gt;, k: i32) -\u0026gt; bool { let k = k as usize; let mut map = std::collections::HashMap::new(); for i in 0..nums.len() { if i \u0026gt; k { *map.entry(nums[i - k - 1]).or_insert(0) -= 1; } *map.entry(nums[i]).or_insert(0) += 1; if *map.entry(nums[i]).or_insert(0) \u0026gt; 1 { return true; } } return false; } } ","date":"2022年1月19日","externalUrl":null,"permalink":"/leetcode/2022/01/19/","section":"LeetCode 题解","summary":"","title":"19 日 No.219 存在重复元素 II","type":"leetcode"},{"content":"","date":"2022年1月19日","externalUrl":null,"permalink":"/tags/hash-table/","section":"Tags","summary":"","title":"Hash Table","type":"tags"},{"content":"原文地址：https://home.dawnmagnet.date/leetcode/2022/01/18/\n给定一个 24 小时制（小时:分钟 \u0026ldquo;HH:MM\u0026rdquo;）的时间列表，找出列表中任意两个时间的最小时间差并以分钟数表示。\n示例 1：\n输入：timePoints = [\u0026quot;23:59\u0026quot;,\u0026quot;00:00\u0026quot;] 输出：1 示例 2：\n输入：timePoints = [\u0026quot;00:00\u0026quot;,\u0026quot;23:59\u0026quot;,\u0026quot;00:00\u0026quot;] 输出：0 提示：\n2 \u0026lt;= timePoints \u0026lt;= 2 * 104 timePoints[i] 格式为 \u0026ldquo;HH:MM\u0026rdquo; 来源：力扣（LeetCode）\n链接：https://leetcode-cn.com/problems/minimum-time-difference\n著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。\n思路分析 # 首先使用一个函数将日期转化成指定分钟的数字，然后将这个数字对应到一个1440(24*60)长度的布尔数组中，然后去枚举布尔数组哪里有值。本来也可以使用一个数组去排序，但数据量太小了(1440)，排序也没什么必要。\n放到数组里面之后，因为有两种可能，一种是两个有效值之间的间隔，一种是第一个值和最后一个值的间隔。所以在判断的时候需要多加注意\nRust代码 # pub fn parse(s: \u0026amp;str) -\u0026gt; usize { let mut res = 0; let b: Vec\u0026lt;usize\u0026gt; = s.as_bytes().iter().map(|\u0026amp;x| x as usize - 48).collect(); (b[0] * 10 + b[1]) * 60 + b[3] * 10 + b[4] } impl Solution { pub fn find_min_difference(time_points: Vec\u0026lt;String\u0026gt;) -\u0026gt; i32 { let mut c = [false; 1440]; for item in \u0026amp;time_points { let pos = parse(item); if c[pos] { return 0; } else { c[parse(item)] = true; } } let mut cur = usize::MAX; let mut res = 10000; let mut first = 0; for time in 0..1440 { if c[time] { if cur == usize::MAX { cur = time; first = time + 1440; } else { res = res.min(time - cur); cur = time; } } } res = res.min(first - cur); res as i32 } } ","date":"2022年1月18日","externalUrl":null,"permalink":"/leetcode/2022/01/18/","section":"LeetCode 题解","summary":"","title":"18日 No.539 最小时间差","type":"leetcode"},{"content":"","date":"2022年1月18日","externalUrl":null,"permalink":"/tags/sorting/","section":"Tags","summary":"","title":"Sorting","type":"tags"},{"content":"原文地址：https://home.dawnmagnet.date/leetcode/2022/01/17/\n给你一个整数 n，请你帮忙统计一下我们可以按下述规则形成多少个长度为 n 的字符串：\n字符串中的每个字符都应当是小写元音字母（\u0026lsquo;a\u0026rsquo;, \u0026rsquo;e\u0026rsquo;, \u0026lsquo;i\u0026rsquo;, \u0026lsquo;o\u0026rsquo;, \u0026lsquo;u\u0026rsquo;）\n每个元音 \u0026lsquo;a\u0026rsquo; 后面都只能跟着 \u0026rsquo;e\u0026rsquo; 每个元音 \u0026rsquo;e\u0026rsquo; 后面只能跟着 \u0026lsquo;a\u0026rsquo; 或者是 \u0026lsquo;i\u0026rsquo; 每个元音 \u0026lsquo;i\u0026rsquo; 后面 不能 再跟着另一个 \u0026lsquo;i\u0026rsquo; 每个元音 \u0026lsquo;o\u0026rsquo; 后面只能跟着 \u0026lsquo;i\u0026rsquo; 或者是 \u0026lsquo;u\u0026rsquo; 每个元音 \u0026lsquo;u\u0026rsquo; 后面只能跟着 \u0026lsquo;a\u0026rsquo; 由于答案可能会很大，所以请你返回 模 10^9 + 7 之后的结果。 示例 1：\n输入：n = 1 输出：5 解释：所有可能的字符串分别是：\u0026quot;a\u0026quot;, \u0026quot;e\u0026quot;, \u0026quot;i\u0026quot; , \u0026quot;o\u0026quot; 和 \u0026quot;u\u0026quot;。 示例 2：\n输入：n = 2 输出：10 解释：所有可能的字符串分别是：\u0026quot;ae\u0026quot;, \u0026quot;ea\u0026quot;, \u0026quot;ei\u0026quot;, \u0026quot;ia\u0026quot;, \u0026quot;ie\u0026quot;, \u0026quot;io\u0026quot;, \u0026quot;iu\u0026quot;, \u0026quot;oi\u0026quot;, \u0026quot;ou\u0026quot; 和 \u0026quot;ua\u0026quot;。 示例 3：\n输入：n = 5 输出：68 提示：\n1 \u0026lt;= n \u0026lt;= 2 * 10^4 来源：力扣（LeetCode）\n链接：https://leetcode-cn.com/problems/count-vowels-permutation\n著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。\n思路分析 # 本道题其实就是一个找规律分析题，科学一点的讲法叫做动态规划，但真的挺简单的。\n根据题目中的意思，我们可以看出，题目所谓的“元音字母序列”实际上就是只跟最后一位字母有关系，也就是说，对于一个序列，我们压根没必要知道它前面是啥，只要告诉我们最后一位字母是啥，就可以继续往下进行扩展了。在这种条件下，我们可以只使用一个数组，保存序列末尾分别对应aeiou时的序列数量，就能大大简化计算。\n然后根据题目中的条件，自行推导一下，可以发现\na前面可以是eiu e前面可以是ai i前面可以是eo o前面可以是i u前面可以是io 根据这个条件，就可以实现递推式\n在计算出来指定的那一步aeiou结尾的序列分别有多少个之后，再对其进行一个简单的相加就可以获得答案。\nRust代码 # const m: i64 = 1_000_000_007; impl Solution { pub fn count_vowel_permutation(n: i32) -\u0026gt; i32 { let mut c = [1i64, 1, 1, 1, 1]; for i in 1..n { c = [ (c[1] + c[2] + c[4]) % m, (c[0] + c[2]) % m, (c[1] + c[3]) % m, c[2], (c[2] + c[3]) % m, ]; } (c.iter().sum::\u0026lt;i64\u0026gt;() % m) as i32 } } ","date":"2022年1月17日","externalUrl":null,"permalink":"/leetcode/2022/01/17/","section":"LeetCode 题解","summary":"","title":"17 日 No.1220 统计元音字母序列的数目","type":"leetcode"},{"content":"","date":"2022年1月17日","externalUrl":null,"permalink":"/tags/dynamic-programming/","section":"Tags","summary":"","title":"Dynamic Programming","type":"tags"},{"content":" 原文地址：https://home.dawnmagnet.date/leetcode/2022/01/16/\n给你一个单链表，随机选择链表的一个节点，并返回相应的节点值。每个节点 被选中的概率一样 。\n实现 Solution 类：\nSolution(ListNode head) 使用整数数组初始化对象。 int getRandom() 从链表中随机选择一个节点并返回该节点的值。链表中所有节点被选中的概率相等。 示例：\n输入 [\u0026quot;Solution\u0026quot;, \u0026quot;getRandom\u0026quot;, \u0026quot;getRandom\u0026quot;, \u0026quot;getRandom\u0026quot;, \u0026quot;getRandom\u0026quot;, \u0026quot;getRandom\u0026quot;] [[[1, 2, 3]], [], [], [], [], []] 输出 [null, 1, 3, 2, 2, 3] 解释 Solution solution = new Solution([1, 2, 3]); solution.getRandom(); // 返回 1 solution.getRandom(); // 返回 3 solution.getRandom(); // 返回 2 solution.getRandom(); // 返回 2 solution.getRandom(); // 返回 3 // getRandom() 方法应随机返回 1、2、3中的一个，每个元素被返回的概率相等。 提示：\n链表中的节点数在范围 [1, \\(10^4\\)] 内 \\(-10^4\\) \u0026lt;= Node.val \u0026lt;= \\(10^4\\) 至多调用 getRandom 方法 \\(10^4\\) 次 进阶：\n如果链表非常大且长度未知，该怎么处理？ 你能否在不使用额外空间的情况下解决此问题？ 来源：力扣（LeetCode） 链接：https://leetcode-cn.com/problems/linked-list-random-node 著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。\n思路分析 # 这种题目一般都是表面简单，做出来也很简单，但要是完成题目中所有要求的进阶任务还是很有挑战性的。\n在本题中，我们手里与其说有一个链表，倒不如说有一个未知长度的数据流。实际上本题真正想要挑战我们的是如何在一个未知长度的数据流中去取随机数（根据进阶中的要求可以知道），还要求不能使用常数空间，如果这个数据流很长但算法需要的比常数空间大（如\\(O(n)\\)），那将会是一个非常可怕的算法。\n那么本题就要隆重介绍一个算法，叫做蓄水池抽样。不懂也很正常，这种东西就是一回生二回熟，多看看就知道了。\n记住一件事，蓄水池抽样这个算法就是用来做在未知长度的数据流中获取随机数的\n假设我们已经处理了\\(n\\)个数字，获得了前\\(n\\)个数字中的一个随机数结果，那么这个结果对于前\\(n\\)个数的概率一定是相同的，也就是每个数字有\\(\\frac1n\\)的概率取到。在这种背景下，如果我们加入一个新的数字，也就是第\\(n+1\\)个数字，这种时候我们肯定希望，所有数字被取到的概率都是\\(\\frac1{n+1}\\)，当然也包含这个新加进来的数字。\n那么如何完成这个概率转变的过程呢？答案就是确保第\\(n+1\\)个数字被取到的概率是\\(\\frac1{n+1}\\)。因为在确保了这个事情之后，因为取到前\\(n\\)个数字的概率是均等的，如果在新加入的第\\(n+1\\)个数字保持\\(\\frac1{n+1}\\)概率，那么我们可以保证前\\(n\\)个数字也是这个概率。\n以上是一个对蓄水池抽样算法的简单说明和证明。有兴趣的同学可以在网上自行查阅更多资料。\n讲完了这么一些个步骤，算法干的什么事也就很明了了。对于第\\(n\\)个数字，我们首先取一个随机数，如果概率小于\\(\\frac1n\\)，我们就将结果改为第\\(n\\)个数字，否则就不动，（数据流）继续往下走。\n在LeetCode平台上，Rust支持第三方的随机数库，名字叫做rand，具体的文档可以在https://docs.rs/rand/latest/rand/进行查询。\nRust 代码 # use rand::prelude::*; struct Solution { node: Option\u0026lt;Box\u0026lt;ListNode\u0026gt;\u0026gt; } impl Solution { fn new(head: Option\u0026lt;Box\u0026lt;ListNode\u0026gt;\u0026gt;) -\u0026gt; Self { Solution { node: head } } fn get_random(\u0026amp;self) -\u0026gt; i32 { let mut node = self.node.clone(); let mut res = 0; let mut factor = 1; let mut rng = rand::thread_rng(); while let Some(node_some) = node { let rnd: i32 = rng.gen(); if rnd % factor == 0 { res = node_some.val; } factor += 1; node = node_some.next; } res } } ","date":"2022年1月16日","externalUrl":null,"permalink":"/leetcode/2022/01/16/","section":"LeetCode 题解","summary":"","title":"16 日 No.382 链表随机节点","type":"leetcode"},{"content":"","date":"2022年1月16日","externalUrl":null,"permalink":"/tags/randomized/","section":"Tags","summary":"","title":"Randomized","type":"tags"},{"content":" 原文地址：https://home.dawnmagnet.date/leetcode/2022/01/15/\nHercy 想要为购买第一辆车存钱。他 每天 都往力扣银行里存钱。\n最开始，他在周一的时候存入 1 块钱。从周二到周日，他每天都比前一天多存入 1 块钱。在接下来每一个周一，他都会比 前一个周一 多存入 1 块钱。\n给你 n ，请你返回在第 n 天结束的时候他在力扣银行总共存了多少块钱。\n示例 1：\n输入：n = 4 输出：10 解释：第 4 天后，总额为 1 + 2 + 3 + 4 = 10 。 示例 2：\n输入：n = 10 输出：37 解释：第 10 天后，总额为 (1 + 2 + 3 + 4 + 5 + 6 + 7) + (2 + 3 + 4) = 37 。注意到第二个星期一，Hercy 存入 2 块钱。 示例 3：\n输入：n = 20 输出：96 解释：第 20 天后，总额为 (1 + 2 + 3 + 4 + 5 + 6 + 7) + (2 + 3 + 4 + 5 + 6 + 7 + 8) + (3 + 4 + 5 + 6 + 7 + 8) = 96 。 提示：\n1 \u0026lt;= n \u0026lt;= 1000 来源：力扣（LeetCode） 链接：https://leetcode-cn.com/problems/calculate-money-in-leetcode-bank 著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。\n思路分析 # 这道题其实主要是一道数学题，首先我们要完成一个函数，函数在数学上就是指给一些输入，返回一些输出，这样的形式就叫函数，而在这道题中，我们要计算一个由输入n确定的一个数字序列的和，并且作为返回值，那么我们来分析一下这个数字序列的特征。\n拿题目中的示例来看\n\\((1 + 2 + 3 + 4 + 5 + 6 + 7) + (2 + 3 + 4 + 5 + 6 + 7 + 8) + (3 + 4 + 5 + 6 + 7 + 8) = 96\\)\n在这个示例中，存在明眼人都能看出来的两个部分：\n一部分是\\((1 + 2 + 3 + 4 + 5 + 6 + 7) + (2 + 3 + 4 + 5 + 6 + 7 + 8)\\)这些组成的，一组七个，非常规整 还有一部分是\\((3 + 4 + 5 + 6 + 7 + 8)\\)这样的，没有完整的七个，这一部分只有可能出现在序列的末尾 根据题意我们能够知道，完整的7个是一个星期，不完整的7个只有可能出现在序列的末尾 那么我们将其分解成两个部分来进行运算：\n对于第一部分，我们将其整体进行求和，也就是七个一组我们先算一下，发现很有规律，计算的结果为\\(28,28+7,28+14,28+21,...\\) 对于第二部分，我们也可以发现它是一个等差序列，但其中第一个数字需要用n来计算得到。我们假设\\(n=k*7+b\\)，也就是k组完整的周加上b个单独的末尾天数。那么第二个等差序列可以用如下字母来表示：\\(k+1,k+2,k+3,...,k+b\\) 可以看出，前一个序列和后一个序列都是等差数列，那么可以使用等差数列的公式进行快速的计算，不需要循环。\nPS：不会等差数列公式的同学可以自学一下初中数学\n那么我们将其计算一下：\n第一个等差数列的首项是\\(28\\)，末项是\\(21+7*k\\)，项数是\\(k\\) 第二个等差数列的首项是\\(k+1\\)，末项是\\(k+b\\)，项数是\\(b\\) 带入公式即可得到最后的答案表达式为\\((49 + k * 7) * k / 2 + (2 * k + b + 1) * b / 2\\)，有的时候你也不用非得化简，比如不需要把\\(k\\)乘进去，因为这样计算机反倒多算几步\nRust代码 # impl Solution { pub fn total_money(n: i32) -\u0026gt; i32 { let k = n / 7; let b = n % 7; (49 + k * 7) * k / 2 + (2 * k + b + 1) * b / 2 } } ","date":"2022年1月15日","externalUrl":null,"permalink":"/leetcode/2022/01/15/","section":"LeetCode 题解","summary":"","title":"15日 No.1716 计算力扣银行的钱","type":"leetcode"},{"content":"","date":"2022年1月15日","externalUrl":null,"permalink":"/tags/math/","section":"Tags","summary":"","title":"Math","type":"tags"},{"content":" 原文地址：https://home.dawnmagnet.date/leetcode/2022/01/14/\n给定两个以 升序排列 的整数数组 nums1 和 nums2,以及一个整数 k。\n定义一对值(u,v)，其中第一个元素来自nums1，第二个元素来自 nums2。\n请找到和最小的 k个数对(u1,v1), (u2,v2) \u0026hellip; (uk,vk)。\n示例 1:\n输入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3 输出: [1,2],[1,4],[1,6] 解释: 返回序列中的前 3 对数： [1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6] 示例 2:\n输入: nums1 = [1,1,2], nums2 = [1,2,3], k = 2 输出: [1,1],[1,1] 解释: 返回序列中的前 2 对数： [1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3] 示例 3:\n输入: nums1 = [1,2], nums2 = [3], k = 3 输出: [1,3],[2,3] 解释: 也可能序列中所有的数对都被返回:[1,3],[2,3] 提示:\n1 \u0026lt;= nums1.length, nums2.length \u0026lt;= 10E5 -10E9 \u0026lt;= nums1[i], nums2[i] \u0026lt;= 10E9 nums1 和 nums2 均为升序排列 1 \u0026lt;= k \u0026lt;= 1000 来源：力扣（LeetCode） 链接：https://leetcode-cn.com/problems/find-k-pairs-with-smallest-sums 著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。\n思路分析 # 本题主要思路就是一个找最小k个，一般找最小k个或者最大k个都会用到一个算法叫做堆，这个堆一般也有很多叫法，有叫最小堆、最大堆的，也有叫优先队列的，但本质都是维护一个堆。这个堆有一些性质，最大堆的每个父节点都比子节点大，最小堆每个父节点都比子节点小。在维护这个性质的同时，我们就可以对其进行入堆或者出堆操作，并用一个非常短的时间\\(O(lg(n))\\)来维护这个堆的性质。这样就能做到快速获得几个最大或者最小的元素。\n在这里，因为我们总体要来查询的一共有\\(m*n\\)个元素，也就是两个数组两两相加的数字都有可能是结果的一部分。\n所以我们维护一个最大堆，在这里我们就叫它heap，这个heap存在的元素个数始终不会超过k个，k也就是最终答案里所要求的答案个数，首先我们来两层循环，也就是两两相加，往这个heap里放置元素。因为我们最终要保留k个，所以一旦heap里元素的个数大于k个，就将其pop掉。在这种情况下我们仍然可以保证一定可以取得到最小的k个元素。\n以上就是主要的思路，接下来讲一些可以优化的点（不优化可能会超时）：\n因为我们来看到题目的数据范围，nums1和nums2中都有可能存在至多\\(10^5\\)个元素，而我们最终的答案最多最多也就1000个，都判断肯定没有必要，所以首先可以限制循环的变量变化的范围，原来是不设限制，现在可以改成最大到k（因为最终的答案一定是在nums1中前k个数字和nums2中前k个数字两两配对的结果中的，稍微仔细想一想就能明白吧） 第二个优化的地方就是在循环过程中，因为我们是按顺序从前向后遍历的，如果当前遍历到的数字大于等于堆中的最大元素时，那么对于当前的某些循环已经没有遍历的必要了，就可以进行break跳过。 Rust代码 # use std::collections::BinaryHeap; impl Solution { pub fn k_smallest_pairs(nums1: Vec\u0026lt;i32\u0026gt;, nums2: Vec\u0026lt;i32\u0026gt;, k: i32) -\u0026gt; Vec\u0026lt;Vec\u0026lt;i32\u0026gt;\u0026gt; { let mut res = vec![]; let mut heap = BinaryHeap::new(); for i in 0..(nums1.len()).min(k as usize) { for j in 0..(nums2.len()).min(k as usize) { heap.push((nums1[i] + nums2[j], nums1[i], nums2[j])); if heap.len() \u0026gt; k as usize { if let Some((a, b, c)) = heap.pop() { if b == nums1[i] \u0026amp;\u0026amp; c == nums2[j] { break; } } } } } while heap.len() \u0026gt; 0 { if let Some((a, b, c)) = heap.pop() { res.push(vec![b, c]); } } res } } ","date":"2022年1月14日","externalUrl":null,"permalink":"/leetcode/2022/01/14/","section":"LeetCode 题解","summary":"","title":"14日 No.373 查找和最小的 K 对数字","type":"leetcode"},{"content":"","date":"2022年1月14日","externalUrl":null,"permalink":"/tags/heap/","section":"Tags","summary":"","title":"Heap","type":"tags"},{"content":"给你一个整数数组 nums ，其中总是存在 唯一的 一个最大整数 。\n请你找出数组中的最大元素并检查它是否 至少是数组中每个其他数字的两倍 。如果是，则返回 最大元素的下标 ，否则返回 -1 。\n示例 1：\n输入：nums = [3,6,1,0] 输出：1 解释：6 是最大的整数，对于数组中的其他整数，6 大于数组中其他元素的两倍。6 的下标是 1 ，所以返回 1 。 示例 2：\n输入：nums = [1,2,3,4] 输出：-1 解释：4 没有超过 3 的两倍大，所以返回 -1 。 示例 3：\n输入：nums = [1] 输出：0 解释：因为不存在其他数字，所以认为现有数字 1 至少是其他数字的两倍。 提示：\n1 \u0026lt;= nums.length \u0026lt;= 50 0 \u0026lt;= nums[i] \u0026lt;= 100 nums 中的最大元素是唯一的 来源：力扣（LeetCode） 链接：https://leetcode-cn.com/problems/largest-number-at-least-twice-of-others 著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。\n思路分析 # 本题的思路非常简单，因为我们首先要找到最大整数，然后去判断这个最大整数是否是所有其他数字的两倍，因为一旦找到了最大整数，我们最需要去判断的也就是第二大的整数与最大整数的关系，因为一旦第二大的整数的两倍小于等于最大整数，说明剩余的所有数字也都满足这个条件。\n那么我们遍历一遍即可同时获得最大整数与第二大整数的值，具体的遍历方法是使用两个变量first和second来表示最大数字与第二大数字的下标（因为我们最后要返回的是下标），在遍历的过程中，做以下几个事情\n首先去判断一个数字是否大于first下标对应的数字，如果更大那么说明第一大的数字需要更换为当前的数字 如果大于second下标对应的数字且小于first下标对应的数字，那么就说明第二大的值可以替换成当前的数字 如果都不大于，那么就无事发生 在这种情况下，遍历一遍就可以同时获得第一大数字与第二大数字的下标，然后我们再将其进行比较，也就是比较第一大数字与第二大数字的两倍哪个更大从而获得答案。\n当然在此处也要考虑一些特殊情况，如数组长度只有1，和下标的初始值如何取。这种东西都可以在实践中积累经验。在此处，我使用usize::MAX也就是usize类型的最大值来标记未初始化的数字。所以我也在相对应的判断条件中加入了first == usize::MAX等条件，如果first没有取到有效值，我们就优先给其赋值\nRust代码 # impl Solution { pub fn dominant_index(nums: Vec\u0026lt;i32\u0026gt;) -\u0026gt; i32 { let mut first = usize::MAX; let mut second = usize::MAX; for i in 0..nums.len() { if first == usize::MAX || nums[i] \u0026gt; nums[first] { second = first; first = i; } else if second == usize::MAX || nums[i] \u0026gt; nums[second] \u0026amp;\u0026amp; i != first { second = i; } } if second == usize::MAX || nums[first] \u0026gt;= nums[second] * 2 {first as i32} else {-1} } } ","date":"2022年1月13日","externalUrl":null,"permalink":"/leetcode/2022/01/13/","section":"LeetCode 题解","summary":"","title":"13日 No.747 至少是其他数字两倍的最大数","type":"leetcode"},{"content":"","date":"2022年1月13日","externalUrl":null,"permalink":"/tags/array/","section":"Tags","summary":"","title":"Array","type":"tags"},{"content":" 原文地址：https://home.dawnmagnet.date/leetcode/2022/01/12/\n给你一个整数数组 nums ，判断这个数组中是否存在长度为 3 的递增子序列。\n如果存在这样的三元组下标 (i, j, k) 且满足 i \u0026amp;lt; j \u0026amp;lt; k ，使得 nums[i] \u0026amp;lt; nums[j] \u0026amp;lt; nums[k] ，返回 true ；否则，返回 false 。\n示例 1：\n**输入：**nums = [1,2,3,4,5] **输出：**true **解释：**任何 i \u0026lt; j \u0026lt; k 的三元组都满足题意 示例 2：\n**输入：**nums = [5,4,3,2,1] **输出：**false **解释：**不存在满足题意的三元组 示例 3：\n**输入：**nums = [2,1,5,0,4,6] **输出：**true **解释：**三元组 (3, 4, 5) 满足题意，因为 nums[3] == 0 \u0026lt; nums[4] == 4 \u0026lt; nums[5] == 6 提示：\n1 \u0026amp;lt;= nums.length \u0026amp;lt;= 5 * 10\u0026lt;sup\u0026gt;5\u0026lt;/sup\u0026gt; -2\u0026lt;sup\u0026gt;31\u0026lt;/sup\u0026gt; \u0026amp;lt;= nums[i] \u0026amp;lt;= 2\u0026lt;sup\u0026gt;31\u0026lt;/sup\u0026gt; - 1 **进阶：**你能实现时间复杂度为 O(n) ，空间复杂度为 O(1) 的解决方案吗？\n思路分析 # 在本题中，可以看出，找一个符合题目条件的三元组即可，这个三元组，要是我们好好找，再怎么也得\\(O(n^3)\\)的时间，这样肯定是不可以的，然后来分析一下题目给了我们关于三元组的哪些条件\n三元组只有两个条件：下标递增和递增，下标递增这个东西好办，因为题目限制我们是\\(O(n)\\)，那么我们就只用遍历一次，而且是从前向后，那么既然只遍历一次，我们很显然要在遍历的过程中去记录三元组中前一个或者前两个数字，这样我们才能凑成三元组。\n那么在遍历的时候，势必存在两个变量，我们先称其为first和second，这两个变量记录了能够和当前元素形成三元组的前两个元素（因为我们是从前向后遍历的），那么因为只能遍历一次，我们要用什么办法来尽可能凑三元组，那么大家都知道，要是出现一个很小的数，其在组成三元组的时候是很有优势的，那么一旦出现小的数字，我们就可以考虑将其与first或者second进行替换，这样就能更好地组成三元组。\n这个替换是有条件的，也就是说，我们需要对当前的数字进行判断，有几个区间（因为我们记录了两个数字）。\n当前数字小于first时，我们可以将其替换为first 当前数字小于second时，我们可以将其替换为second 当前数字大于second时，就成了呀！就形成三元组了，就返回true了呀！还算啥 所以说，只要我们按照当上述的步骤对这个数组进行一次遍历就可以获得答案，如果没有找到答案，就在最后返回false就可以了~\n这样满足了题目的进阶要求中\\(O(n)\\)的时间复杂度和\\(O(1)\\)的空间复杂度（因为只使用了常数个变量）\nRust代码 # impl Solution { pub fn increasing_triplet(nums: Vec\u0026lt;i32\u0026gt;) -\u0026gt; bool { let mut first = nums[0]; let mut second = i32::MAX; for num in \u0026amp;nums[1..] { if num \u0026gt; \u0026amp;second { return true; } else if num \u0026gt; \u0026amp;first { second = *num; } else { first = *num; } } false } } ","date":"2022年1月12日","externalUrl":null,"permalink":"/leetcode/2022/01/12/","section":"LeetCode 题解","summary":"","title":"12日 No.334 递增的三元子序列","type":"leetcode"},{"content":"在社交媒体网站上有 n 个用户。给你一个整数数组 ages ，其中 ages[i] 是第 i 个用户的年龄。\n如果下述任意一个条件为真，那么用户 x 将不会向用户 y（x != y）发送好友请求：\nage[y] \u0026lt;= 0.5 * age[x] + 7 age[y] \u0026gt; age[x] age[y] \u0026gt; 100 \u0026amp;\u0026amp; age[x] \u0026lt; 100 否则，x 将会向 y 发送一条好友请求。\n注意，如果 x 向 y 发送一条好友请求，y 不必也向 x 发送一条好友请求。另外，用户不会向自己发送好友请求。\n返回在该社交媒体网站上产生的好友请求总数。\n\u0026nbsp;\n示例 1：\n输入：ages = [16,16] 输出：2 解释：2 人互发好友请求。 示例 2：\n输入：ages = [16,17,18] 输出：2 解释：产生的好友请求为 17 -\u0026gt; 16 ，18 -\u0026gt; 17 。 示例 3：\n输入：ages = [20,30,100,110,120] 输出：3 解释：产生的好友请求为 110 -\u0026gt; 100 ，120 -\u0026gt; 110 ，120 -\u0026gt; 100 。 \u0026nbsp;\n提示：\nn == ages.length 1 \u0026lt;= n \u0026lt;= 2 * 104 1 \u0026lt;= ages[i] \u0026lt;= 120 思路分析 # 这道题一拿到手肯定觉得简单，但又没那么简单，这种给条件判断的一般可以直接暴力判断，两两比较，也就是O(n^2)的复杂度。但在这道题中，很明显不能这么做。\n既然不能不看条件楞做，我们就来看一下题目给了什么可以利用的条件。一共有三个，但在这里必须擦亮眼睛，因为他这里的意思是只要有一个为真，就不发送请求，那么发送请求的条件又是什么呢？ 就是三个任意一个都不为真。我们看到第一个条件，用中文翻译一下就是y要大于0.5x+7，第二个条件就是y要比x小（或者等），第三个条件就是（y大于100和x小于100）不能同时成立。\n那么我们就可以不用考虑输入的顺序了，既然这样，我们就用一个数组去统计每一个年龄的有多少人，再进行后续操作。\n接下来我们采用双指针的方法。首先这个数据在经过我们的整理之后变成了有序的，而且我们可以看出，对于某一个具体年龄的请求发送者来说，请求接收者也位于某一个具体的年龄段之间，而且存在互相交叠的情况。所以我们选择使用前缀和的方法来进行计算。\nimpl Solution { pub fn num_friend_requests(ages: Vec\u0026lt;i32\u0026gt;) -\u0026gt; i32 { let (mut cnt, mut cs, mut res) = ([0; 121], [0; 121], 0); for age in ages { cnt[age as usize] += 1; } for i in 1..=120 { cs[i] = cs[i - 1] + cnt[i]; } for i in 15..=120 { res += cnt[i] * (cs[i] - cs[i / 2 + 7] - 1); } res } } 可以看到程序的第四行，是一个统计到数组里的操作。第五行是计算前缀和的操作。第六行是计算最终结果的操作。可以看出，在年龄段位于15-120岁之间的发送者，若年龄为i，那么他可以给年龄段位于(i~i/2+7)之内的y发送好友请求\n","date":"2021年12月27日","externalUrl":null,"permalink":"/leetcode/2021/12/27/","section":"LeetCode 题解","summary":"","title":"27日 No.825 适龄的朋友","type":"leetcode"},{"content":"","date":"2021年12月27日","externalUrl":null,"permalink":"/tags/two-pointers/","section":"Tags","summary":"","title":"Two Pointers","type":"tags"},{"content":"给出第一个词\u0026nbsp;first 和第二个词\u0026nbsp;second，考虑在某些文本\u0026nbsp;text\u0026nbsp;中可能以 \"first second third\" 形式出现的情况，其中\u0026nbsp;second\u0026nbsp;紧随\u0026nbsp;first\u0026nbsp;出现，third\u0026nbsp;紧随\u0026nbsp;second\u0026nbsp;出现。\n对于每种这样的情况，将第三个词 \"third\" 添加到答案中，并返回答案。\n\u0026nbsp;\n示例 1：\n输入：text = \"alice is a good girl she is a good student\", first = \"a\", second = \"good\" 输出：[\"girl\",\"student\"] 示例 2：\n输入：text = \"we will we will rock you\", first = \"we\", second = \"will\" 输出：[\"we\",\"rock\"] \u0026nbsp;\n提示：\n1 \u0026lt;= text.length \u0026lt;= 1000 text\u0026nbsp;由小写英文字母和空格组成 text 中的所有单词之间都由 单个空格字符 分隔 1 \u0026lt;= first.length, second.length \u0026lt;= 10 first 和\u0026nbsp;second\u0026nbsp;由小写英文字母组成 思路分析 # 这道题就只需在遍历数组的同时判断一下是否满足题目所给的条件即可\nRust 代码 # impl Solution { pub fn find_ocurrences(text: String, first: String, second: String) -\u0026gt; Vec\u0026lt;String\u0026gt; { let mut is_one = false; let mut is_two = false; let mut res = vec![]; let mut pre1 = \u0026#34;\u0026#34;; let mut pre2 = \u0026#34;\u0026#34;; for word in text.split(\u0026#34; \u0026#34;) { if pre1 == first \u0026amp;\u0026amp; pre2 == second { res.push(word.to_string()); } pre1 = pre2; pre2 = word; } res } } ","date":"2021年12月26日","externalUrl":null,"permalink":"/leetcode/2021/12/26/","section":"LeetCode 题解","summary":"","title":"26日 No.1078 Bigram分词","type":"leetcode"},{"content":"","date":"2021年12月26日","externalUrl":null,"permalink":"/tags/string/","section":"Tags","summary":"","title":"String","type":"tags"},{"content":"有一棵特殊的苹果树，一连 n 天，每天都可以长出若干个苹果。在第 i 天，树上会长出 apples[i] 个苹果，这些苹果将会在 days[i] 天后（也就是说，第 i + days[i] 天时）腐烂，变得无法食用。也可能有那么几天，树上不会长出新的苹果，此时用 apples[i] == 0 且 days[i] == 0 表示。\n你打算每天 最多 吃一个苹果来保证营养均衡。注意，你可以在这 n 天之后继续吃苹果。\n给你两个长度为 n 的整数数组 days 和 apples ，返回你可以吃掉的苹果的最大数目。\n\u0026nbsp;\n示例 1：\n输入：apples = [1,2,3,5,2], days = [3,2,1,4,2] 输出：7 解释：你可以吃掉 7 个苹果： - 第一天，你吃掉第一天长出来的苹果。 - 第二天，你吃掉一个第二天长出来的苹果。 - 第三天，你吃掉一个第二天长出来的苹果。过了这一天，第三天长出来的苹果就已经腐烂了。 - 第四天到第七天，你吃的都是第四天长出来的苹果。 示例 2：\n输入：apples = [3,0,0,0,0,2], days = [3,0,0,0,0,2] 输出：5 解释：你可以吃掉 5 个苹果： - 第一天到第三天，你吃的都是第一天长出来的苹果。 - 第四天和第五天不吃苹果。 - 第六天和第七天，你吃的都是第六天长出来的苹果。 \u0026nbsp;\n提示：\napples.length == n days.length == n 1 \u0026lt;= n \u0026lt;= 2 * 104 0 \u0026lt;= apples[i], days[i] \u0026lt;= 2 * 104 只有在 apples[i] = 0 时，days[i] = 0 才成立 思路分析 # 首先看到吃苹果的数量最多只能一天一个，我们可以得出结论，每天都应该吃今天之后最早过期的那个苹果，这种策略在生活中也非常常见，大家总是喜欢先吃（自己拥有的）快到期的食物，这样可以尽可能多的减少浪费（也就是多吃）。\n在这种策略的驱使下，我们可以使用一个数组rots作为苹果过期的标识，然后再使用一个eatId作为当前可以吃的最早腐烂的苹果腐烂时的天数。在这种情况下，我们在每一天收获新的苹果的时候只需要做如下几件事：（假设当前是第i天，数组下标均默认从1开始，与题目描述中的相同）\n首先确保eatId，也就是当前能吃的苹果的最小天数一定小于当前的天数 然后再看看今天收货的苹果，假设今天获得a个苹果，n天后腐烂，那么我们就将其添加到rots中，也就是使得rots[i + n - 1] += a，因为在第i+n天我们就不能吃这批苹果了，所以能吃这批苹果的最后一天就是i+n-1的时候。 最后开始选择能吃的苹果，如果eatId大于今天新到的一批苹果腐烂的天数，那么我们就将eatId设置为今天新到的腐烂时间，也就是说，如果今天新来苹果烂的早，我们就先吃它 在选择了能吃的苹果之后(在这个地方，如果rots[eatId]等于0，说明当前选择的天数已经没有待吃的苹果了，我们就必须要让eatId加到有苹果的地方，因为我们假设的是eatId是距离今天最近的腐烂苹果的天数，所以只能向后选择)，就使答案加一，并且使得rots[eatId] -= 1。如果没有剩余的苹果，就啥都不吃 Rust代码 # impl Solution { pub fn eaten_apples(apples: Vec\u0026lt;i32\u0026gt;, days: Vec\u0026lt;i32\u0026gt;) -\u0026gt; i32 { let mut rots = [0; 40005]; let mut eatId = 0; let mut res = 0; for index in 1..40003 { eatId = eatId.max(index); if index \u0026lt;= apples.len() \u0026amp;\u0026amp; apples[index - 1] != 0 { rots[index - 1 + days[index - 1] as usize] += apples[index - 1]; eatId = eatId.min(index - 1 + days[index - 1] as usize); } while rots[eatId] == 0 \u0026amp;\u0026amp; eatId \u0026lt; 40002 { eatId += 1; } if eatId != 40002 { rots[eatId] -= 1; res += 1; } } res } } ","date":"2021年12月24日","externalUrl":null,"permalink":"/leetcode/2021/12/24/","section":"LeetCode 题解","summary":"","title":"24日 No.1705 吃苹果的最大数目","type":"leetcode"},{"content":" 给定两个字符串\u0026nbsp;a 和 b，寻找重复叠加字符串 a 的最小次数，使得字符串 b 成为叠加后的字符串 a 的子串，如果不存在则返回 -1。 注意：字符串 \"abc\"\u0026nbsp;重复叠加 0 次是 \"\"，重复叠加 1 次是\u0026nbsp;\"abc\"，重复叠加 2 次是\u0026nbsp;\"abcabc\"。 \u0026nbsp;\n示例 1：\n输入：a = \"abcd\", b = \"cdabcdab\" 输出：3 解释：a 重复叠加三遍后为 \"abcdabcdabcd\", 此时 b 是其子串。 示例 2：\n输入：a = \"a\", b = \"aa\" 输出：2 示例 3：\n输入：a = \"a\", b = \"a\" 输出：1 示例 4：\n输入：a = \"abc\", b = \"wxyz\" 输出：-1 \u0026nbsp;\n提示：\n1 \u0026lt;= a.length \u0026lt;= 104 1 \u0026lt;= b.length \u0026lt;= 104 a 和 b 由小写英文字母组成 思路分析 # 首先获取两个串的长度a, b，根据题意可知，只有两种情况，假设n为(b/a)向上取整，那么只有可能b是n倍a的子串，或者b是(n+1)倍a的子串，或者都不是，这样输出-1\nRust代码 # impl Solution { pub fn repeated_string_match(a: String, b: String) -\u0026gt; i32 { let (lb, la) = (b.len(), a.len()); let times = ((lb as f64) / (la as f64)).ceil() as usize; if a.repeat(times).contains(\u0026amp;b) { times as i32 } else if a.repeat(times + 1).contains(\u0026amp;b) { times as i32 + 1 } else { -1 } } } ","date":"2021年12月22日","externalUrl":null,"permalink":"/leetcode/2021/12/22/","section":"LeetCode 题解","summary":"","title":"22日 No.686 重复叠加字符串匹配","type":"leetcode"},{"content":" 给你一个字符串\u0026nbsp;date ，按 YYYY-MM-DD 格式表示一个 现行公元纪年法 日期。请你计算并返回该日期是当年的第几天。 通常情况下，我们认为 1 月 1 日是每年的第 1 天，1 月 2 日是每年的第 2 天，依此类推。每个月的天数与现行公元纪年法（格里高利历）一致。 \u0026nbsp;\n示例 1：\n输入：date = \"2019-01-09\" 输出：9 示例 2：\n输入：date = \"2019-02-10\" 输出：41 示例 3：\n输入：date = \"2003-03-01\" 输出：60 示例 4：\n输入：date = \"2004-03-01\" 输出：61 \u0026nbsp;\n提示：\ndate.length == 10 date[4] == date[7] == '-'，其他的\u0026nbsp;date[i]\u0026nbsp;都是数字 date 表示的范围从 1900 年 1 月 1 日至 2019 年 12 月 31 日 思路分析 # 先拆分，再判断闰年，最后求和即可\n代码(Rust) # impl Solution { pub fn day_of_year(date: String) -\u0026gt; i32 { let v: Vec\u0026lt;i32\u0026gt; = date.split(\u0026#39;-\u0026#39;).map(|x| x.parse().unwrap()).collect(); let mut days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; if v[1] \u0026gt; 2 \u0026amp;\u0026amp; v[0] % 4 == 0 \u0026amp;\u0026amp; (v[0] % 100 != 0 || v[0] % 400 == 0) { days[1] += 1; } \u0026amp;days[0..(v[1] as usize - 1)].iter().sum() + v[2] } } 代码(Golang) # func dayOfYear(date string) int { t, _ := time.Parse(\u0026#34;2006-01-02\u0026#34;, date) return t.YearDay() } ","date":"2021年12月21日","externalUrl":null,"permalink":"/leetcode/2021/12/21/","section":"LeetCode 题解","summary":"","title":"21日 No.1154 一年中的第几天","type":"leetcode"},{"content":"","date":"2021年12月21日","externalUrl":null,"permalink":"/tags/date-and-time/","section":"Tags","summary":"","title":"Date and Time","type":"tags"},{"content":"","date":"2021年12月20日","externalUrl":null,"permalink":"/tags/binary-search/","section":"Tags","summary":"","title":"Binary Search","type":"tags"},{"content":" 冬季已经来临。\u0026nbsp;你的任务是设计一个有固定加热半径的供暖器向所有房屋供暖。 在加热器的加热半径范围内的每个房屋都可以获得供暖。\n现在，给出位于一条水平线上的房屋\u0026nbsp;houses 和供暖器\u0026nbsp;heaters 的位置，请你找出并返回可以覆盖所有房屋的最小加热半径。 说明：所有供暖器都遵循你的半径标准，加热的半径也一样。 \u0026nbsp;\n示例 1:\n输入: houses = [1,2,3], heaters = [2] 输出: 1 解释: 仅在位置2上有一个供暖器。如果我们将加热半径设为1，那么所有房屋就都能得到供暖。 示例 2:\n输入: houses = [1,2,3,4], heaters = [1,4] 输出: 1 解释: 在位置1, 4上有两个供暖器。我们需要将加热半径设为1，这样所有房屋就都能得到供暖。 示例 3：\n输入：houses = [1,5], heaters = [2] 输出：3 \u0026nbsp;\n提示：\n1 \u0026lt;= houses.length, heaters.length \u0026lt;= 3 * 104 1 \u0026lt;= houses[i], heaters[i] \u0026lt;= 109 题解 # impl Solution { pub fn find_radius(mut houses: Vec\u0026lt;i32\u0026gt;, mut heaters: Vec\u0026lt;i32\u0026gt;) -\u0026gt; i32 { houses.sort_unstable(); heaters.sort_unstable(); let mut res = 0; let mut cur = 0; for house in houses { while cur \u0026lt; heaters.len() - 1 \u0026amp;\u0026amp; (heaters[cur + 1] - house).abs() \u0026lt;= (heaters[cur] - house).abs() { cur += 1; } res = res.max((heaters[cur] - house).abs()) } res } } ","date":"2021年12月20日","externalUrl":null,"permalink":"/leetcode/2021/12/20/","section":"LeetCode 题解","summary":"","title":"No.475 供暖器","type":"leetcode"},{"content":"统计字符串中的单词个数，这里的单词指的是连续的不是空格的字符。\n请注意，你可以假定字符串里不包括任何不可打印的字符。\n示例:\n输入: \"Hello, my name is John\" 输出: 5 解释: 这里的单词是指连续的不是空格的字符，所以 \"Hello,\" 算作 1 个单词。 思路分析 # 这题考察的有点像状态机。因为需要维护一个前一个字符是不是字母的状态。如果前一个是空格，后一个是字母，就说明开始了一个新单词。\nRust代码 # # struct Solution; impl Solution { pub fn count_segments(s: String) -\u0026gt; i32 { let mut state = 0; let mut res = 0; for ch in s.chars() { if ch == \u0026#39; \u0026#39; { state = 0; } else { if state == 0 { res += 1; } state = 1; } } res } } ","date":"2021年10月7日","externalUrl":null,"permalink":"/leetcode/2021/10/07/","section":"LeetCode 题解","summary":"","title":"No.434 字符串中的单词数","type":"leetcode"},{"content":"给你一个非空数组，返回此数组中 第三大的数 。如果不存在，则返回数组中最大的数。\n\u0026nbsp;\n示例 1：\n输入：[3, 2, 1] 输出：1 解释：第三大的数是 1 。 示例 2：\n输入：[1, 2] 输出：2 解释：第三大的数不存在, 所以返回最大的数 2 。 示例 3：\n输入：[2, 2, 3, 1] 输出：1 解释：注意，要求返回第三大的数，是指在所有不同数字中排第三大的数。 此例中存在两个值为 2 的数，它们都排第二。在所有不同数字中排第三大的数为 1 。 \u0026nbsp;\n提示：\n1 \u0026lt;= nums.length \u0026lt;= 104 -231 \u0026lt;= nums[i] \u0026lt;= 231 - 1 \u0026nbsp;\n进阶：你能设计一个时间复杂度 O(n) 的解决方案吗？\n思路分析 # 这种题一般就套一个有序数组就行。\nO(n)的话就维护一下有序数组，使得数组中元素的个数不会大于三个。因为大于三个也用不到。\nRust代码 # # struct Solution; use std::collections::BTreeSet; impl Solution { pub fn third_max(mut nums: Vec\u0026lt;i32\u0026gt;) -\u0026gt; i32 { let mut set = BTreeSet::new(); for item in nums { set.insert(item); } *set.iter().rev().nth(2).unwrap_or(set.iter().last().unwrap()) } } ","date":"2021年10月6日","externalUrl":null,"permalink":"/leetcode/2021/10/06/","section":"LeetCode 题解","summary":"","title":"No.414 第三大的数","type":"leetcode"},{"content":"","date":"2021年10月5日","externalUrl":null,"permalink":"/tags/iterator/","section":"Tags","summary":"","title":"Iterator","type":"tags"},{"content":"请你设计一个迭代器，除了支持 hasNext 和 next 操作外，还支持 peek 操作。\n实现 PeekingIterator 类：\nPeekingIterator(int[] nums) 使用指定整数数组 nums 初始化迭代器。 int next() 返回数组中的下一个元素，并将指针移动到下个元素处。 bool hasNext() 如果数组中存在下一个元素，返回 true ；否则，返回 false 。 int peek() 返回数组中的下一个元素，但 不 移动指针。 \u0026nbsp;\n示例：\n输入： [\"PeekingIterator\", \"next\", \"peek\", \"next\", \"next\", \"hasNext\"] [[[1, 2, 3]], [], [], [], [], []] 输出： [null, 1, 2, 2, 3, false] 解释： PeekingIterator peekingIterator = new PeekingIterator([1, 2, 3]); // [1,2,3] peekingIterator.next(); // 返回 1 ，指针移动到下一个元素 [1,2,3] peekingIterator.peek(); // 返回 2 ，指针未发生移动 [1,2,3] peekingIterator.next(); // 返回 2 ，指针移动到下一个元素 [1,2,3] peekingIterator.next(); // 返回 3 ，指针移动到下一个元素 [1,2,3] peekingIterator.hasNext(); // 返回 False \u0026nbsp;\n提示：\n1 \u0026lt;= nums.length \u0026lt;= 1000 1 \u0026lt;= nums[i] \u0026lt;= 1000 对 next 和 peek 的调用均有效 next、hasNext 和 peek 最多调用\u0026nbsp; 1000 次 \u0026nbsp;\n进阶：你将如何拓展你的设计？使之变得通用化，从而适应所有的类型，而不只是整数型？\n思路分析 # 本题的难点主要在于，本来原题给我们提供的数据结构Iterator只提供了以下两个接口：\nint next() bool hasNext() 而我们要实现的数据结构要在其基础上再实现一个int peek()。\n这里就会出现一个问题，也就是每次调用next()无疑都会使得迭代器向后移动一个位置，但是每次调用peek()却不会。\n其实很好提出一个解决方法，就是在每次调用next()的时候拿一个数字存一下，作为下次调用peek()时候的返回值。但这样会出现问题。\n观察一下示例1，可以发现，在调用peek()时，指针不发生移动，但是确确实实地，我们获取到了下一个变量的值。这题的难点就在于，我们期望能够在获取下一个元素的值的时候不移动指针。但next()一定会移动指针。\n思路可以这样：先用一个变量一直存住下一个元素的值，如果遇到了peek()就返回这个预先存的值，如果遇到next()也返回这个预先存的值并且更新预先存的值为下一个。\nC++ 代码 # class PeekingIterator : public Iterator { public: PeekingIterator(const vector\u0026lt;int\u0026gt;\u0026amp; nums) : Iterator(nums) { flag = Iterator::hasNext(); if (flag) { nextElement = Iterator::next(); } } int peek() { return nextElement; } int next() { int ret = nextElement; flag = Iterator::hasNext(); if (flag) { nextElement = Iterator::next(); } return ret; } bool hasNext() const { return flag; } private: int nextElement; bool flag; }; ","date":"2021年10月5日","externalUrl":null,"permalink":"/leetcode/2021/10/05/","section":"LeetCode 题解","summary":"","title":"No.284 窥探迭代器","type":"leetcode"},{"content":"有一个密钥字符串 S ，只包含字母，数字以及 '-'（破折号）。其中， N 个 '-' 将字符串分成了 N+1 组。\n给你一个数字 K，请你重新格式化字符串，使每个分组恰好包含 K 个字符。特别地，第一个分组包含的字符个数必须小于等于 K，但至少要包含 1 个字符。两个分组之间需要用 '-'（破折号）隔开，并且将所有的小写字母转换为大写字母。\n给定非空字符串 S 和数字 K，按照上面描述的规则进行格式化。\n\u0026nbsp;\n示例 1：\n输入：S = \"5F3Z-2e-9-w\", K = 4 输出：\"5F3Z-2E9W\" 解释：字符串 S 被分成了两个部分，每部分 4 个字符； \u0026nbsp; 注意，两个额外的破折号需要删掉。 示例 2：\n输入：S = \"2-5g-3-J\", K = 2 输出：\"2-5G-3J\" 解释：字符串 S 被分成了 3 个部分，按照前面的规则描述，第一部分的字符可以少于给定的数量，其余部分皆为 2 个字符。 \u0026nbsp;\n提示:\nS 的长度可能很长，请按需分配大小。K 为正整数。 S 只包含字母数字（a-z，A-Z，0-9）以及破折号'-' S 非空 \u0026nbsp;\n思路分析 # 根据题目的思路可知，原字符串中的下划线对本题没什么用，所以统计的时候去掉。\n首先计算每个部分可以分到多少个字符，也就是parts变量。然后计算第一个区块中有多少个字符，也就是remains。如果可以整除，第一个区块中就有k个字符，如果不能整除，第一个区块中的个数会小于k个，也就是valid(合法字符) / k。\n在计算完成后，就可以将原字符串中的字符填充进去。注意：在填充完成remains个之后就填入一个下划线，并且将remains重置成k，因为往后的每一个区块之内都有k个字符。\nRust代码 # # struct Solution; impl Solution { pub fn license_key_formatting(s: String, k: i32) -\u0026gt; String { let k = k as usize; let n = s.len(); let count = s.chars().fold(0, |acc, x| acc + (x == \u0026#39;-\u0026#39;) as usize); let valid = n - count; let parts = valid / k + (valid % k \u0026gt; 0) as usize; let mut remains = if valid % k == 0 {k} else {valid % k}; let mut res = \u0026#34;\u0026#34;.to_string(); for ch in s.chars() { if ch == \u0026#39;-\u0026#39; { continue; } if remains == 0 { res.push(\u0026#39;-\u0026#39;); remains = k; } res.push(ch.to_ascii_uppercase()); remains -= 1; } res } } ","date":"2021年10月4日","externalUrl":null,"permalink":"/leetcode/2021/10/04/","section":"LeetCode 题解","summary":"","title":"No.482 密钥格式化","type":"leetcode"},{"content":"给定两个整数，分别表示分数的分子\u0026nbsp;numerator 和分母 denominator，以 字符串形式返回小数 。\n如果小数部分为循环小数，则将循环的部分括在括号内。\n如果存在多个答案，只需返回 任意一个 。\n对于所有给定的输入，保证 答案字符串的长度小于 104 。\n\u0026nbsp;\n示例 1：\n输入：numerator = 1, denominator = 2 输出：\"0.5\" 示例 2：\n输入：numerator = 2, denominator = 1 输出：\"2\" 示例 3：\n输入：numerator = 2, denominator = 3 输出：\"0.(6)\" 示例 4：\n输入：numerator = 4, denominator = 333 输出：\"0.(012)\" 示例 5：\n输入：numerator = 1, denominator = 5 输出：\"0.2\" \u0026nbsp;\n提示：\n-231 \u0026lt;=\u0026nbsp;numerator, denominator \u0026lt;= 231 - 1 denominator != 0 思路分析 # 这道题本质上就是要让我们用除法的思维去做题。\n在我们做除法的时候，怎么判断重复循环的小数呢？\n首先当然是进行预处理。包括以下几个步骤。\n处理符号，将负号统一处理一下。因为两个正数相除显然比较符合人类的习惯。 将所有32位数字输入处理成64位，因为可能会超限。 处理整数部分，因为整数部分一般不参与到循环小数的范围内。 其他特殊情况。 在处理完上述步骤之后，我们得到了还是题目中的两个数字numerator和denominator。这两个数字仍然是被除数与除数的关系，只不过经过减去整数部分的处理之后，我们能确保numerator一定小于denominator。 如果没有思路可以看一下上面这张图。这张图基本上概括了本题所需的循环小数部分的数学知识。 可以看出，在除法的过程中，遇到除不尽的数字，我们一般是采用余数的方式，并且继续拿余数乘10之后再除。可以确定的是，只有在余数出现相同的情况下，余数之后的情况也会一模一样，也就是说，第一次出现相同的余数就是循环节的位置。\n在此处，我们使用一个哈希表来存之前出现过的所有余数，并且在第二次出现余数的时候，标注循环节的位置，结束程序。\nRust代码 # # struct Solution; use std::collections::HashMap; impl Solution { pub fn fraction_to_decimal(numerator: i32, denominator: i32) -\u0026gt; String { let mut ret = String::new(); if (numerator as i64 * denominator as i64) \u0026lt; 0 { ret.push(\u0026#39;-\u0026#39;); } let mut denominator = (denominator as i64).abs(); let mut numerator = (numerator as i64).abs(); let pre = numerator / denominator; numerator %= denominator; let mut map: HashMap\u0026lt;i64, i32\u0026gt; = HashMap::new(); ret.push_str(\u0026amp;pre.to_string()); if numerator == 0 { return ret; } ret.push(\u0026#39;.\u0026#39;); let mut ws = ret.len() as i32; loop { if numerator == 0 { break; } if map.contains_key(\u0026amp;numerator) { ret.insert(*map.get(\u0026amp;numerator).unwrap() as usize, \u0026#39;(\u0026#39;); ret.push(\u0026#39;)\u0026#39;); break; } map.insert(numerator, ws); numerator *= 10; ret.push(((\u0026#39;0\u0026#39; as u8) + (numerator / denominator) as u8) as char); numerator %= denominator; ws += 1; } ret } } ","date":"2021年10月3日","externalUrl":null,"permalink":"/leetcode/2021/10/03/","section":"LeetCode 题解","summary":"","title":"No.166 分数到小数","type":"leetcode"},{"content":"","date":"2021年10月2日","externalUrl":null,"permalink":"/tags/bit-manipulation/","section":"Tags","summary":"","title":"Bit Manipulation","type":"tags"},{"content":"给定一个整数，编写一个算法将这个数转换为十六进制数。对于负整数，我们通常使用\u0026nbsp;补码运算\u0026nbsp;方法。\n注意:\n十六进制中所有字母(a-f)都必须是小写。 十六进制字符串中不能包含多余的前导零。如果要转化的数为0，那么以单个字符'0'来表示；对于其他情况，十六进制字符串中的第一个字符将不会是0字符。\u0026nbsp; 给定的数确保在32位有符号整数范围内。 不能使用任何由库提供的将数字直接转换或格式化为十六进制的方法。 示例 1：\n输入: 26 输出: \"1a\" 示例 2：\n输入: -1 输出: \"ffffffff\" 思路分析 # 本质上就是一个二进制转十六进制的操作。最好使用二进制运算来做，能够更高效地利用计算机二进制的架构。\n一位十六进制对应到四位二进制。\nRust代码 # impl Solution { pub fn to_hex(num: i32) -\u0026gt; String { let turned: Vec\u0026lt;char\u0026gt; = \u0026#34;0123456789abcdef\u0026#34;.chars().collect(); let mut num = num as u32; if num == 0 { return \u0026#34;0\u0026#34;.to_string(); } let mut res = vec![]; while num != 0 { let mut tmp = 0; for i in 0..4 { tmp += (num \u0026amp; 1) * (1 \u0026lt;\u0026lt; i); num \u0026gt;\u0026gt;= 1; } res.push(turned[tmp as usize]); } res.reverse(); res.iter().collect() } } C++代码 # static const char * turned = \u0026#34;0123456789abcdef\u0026#34;; class Solution { public: string toHex(unsigned int num) { string res; do { int tmp = 0; for (int i = 0; i \u0026lt; 4; ++i) { tmp += (num \u0026amp; 1) * (1 \u0026lt;\u0026lt; i); num \u0026gt;\u0026gt;= 1; } res.push_back(turned[tmp]); } while (num != 0); reverse(res.begin(), res.end()); return res; } }; ","date":"2021年10月2日","externalUrl":null,"permalink":"/leetcode/2021/10/02/","section":"LeetCode 题解","summary":"","title":"No.405 数字转换为十六进制数","type":"leetcode"},{"content":"给你一份旅游线路图，该线路图中的旅行线路用数组 paths 表示，其中 paths[i] = [cityAi, cityBi] 表示该线路将会从 cityAi 直接前往 cityBi 。请你找出这次旅行的终点站，即没有任何可以通往其他城市的线路的城市。\n题目数据保证线路图会形成一条不存在循环的线路，因此恰有一个旅行终点站。\n\u0026nbsp;\n示例 1：\n输入：paths = [[\"London\",\"New York\"],[\"New York\",\"Lima\"],[\"Lima\",\"Sao Paulo\"]] 输出：\"Sao Paulo\" 解释：从 \"London\" 出发，最后抵达终点站 \"Sao Paulo\" 。本次旅行的路线是 \"London\" -\u0026gt; \"New York\" -\u0026gt; \"Lima\" -\u0026gt; \"Sao Paulo\" 。 示例 2：\n输入：paths = [[\"B\",\"C\"],[\"D\",\"B\"],[\"C\",\"A\"]] 输出：\"A\" 解释：所有可能的线路是： \"D\" -\u0026gt; \"B\" -\u0026gt; \"C\" -\u0026gt; \"A\".\u0026nbsp; \"B\" -\u0026gt; \"C\" -\u0026gt; \"A\".\u0026nbsp; \"C\" -\u0026gt; \"A\".\u0026nbsp; \"A\".\u0026nbsp; 显然，旅行终点站是 \"A\" 。 示例 3：\n输入：paths = [[\"A\",\"Z\"]] 输出：\"Z\" \u0026nbsp;\n提示：\n1 \u0026lt;= paths.length \u0026lt;= 100 paths[i].length == 2 1 \u0026lt;=\u0026nbsp;cityAi.length,\u0026nbsp;cityBi.length \u0026lt;= 10 cityAi\u0026nbsp;!=\u0026nbsp;cityBi 所有字符串均由大小写英文字母和空格字符组成。 思路分析 # 如题要求一个站点没有下一个到达点，也就是说该城市不会位于(A, B)中的A位置，只位于B位置，因此我们只需要将所有的A用哈希表存储起来，然后遍历所有B，查看是否存在于表中，某个城市不存在直接返回答案（该城市）。\nJava代码 # class Solution { public String destCity(List\u0026lt;List\u0026lt;String\u0026gt;\u0026gt; paths) { int len = paths.size(); Set\u0026lt;String\u0026gt; set = new HashSet\u0026lt;\u0026gt;(); paths.forEach((path) -\u0026gt; { set.add(path.get(0)); }); for (int i = 0; i \u0026lt; len; i++) { List\u0026lt;String\u0026gt; list = paths.get(i); if (!set.contains(list.get(1))) { return list.get(1); } } return null; } } ","date":"2021年10月1日","externalUrl":null,"permalink":"/leetcode/2021/10/01/","section":"LeetCode 题解","summary":"","title":"No.1436 旅行终点站","type":"leetcode"},{"content":"假设有 n\u0026nbsp;台超级洗衣机放在同一排上。开始的时候，每台洗衣机内可能有一定量的衣服，也可能是空的。\n在每一步操作中，你可以选择任意 m (1 \u0026lt;= m \u0026lt;= n) 台洗衣机，与此同时将每台洗衣机的一件衣服送到相邻的一台洗衣机。\n给定一个整数数组\u0026nbsp;machines 代表从左至右每台洗衣机中的衣物数量，请给出能让所有洗衣机中剩下的衣物的数量相等的 最少的操作步数 。如果不能使每台洗衣机中衣物的数量相等，则返回 -1 。\n\u0026nbsp;\n示例 1：\n输入：machines = [1,0,5] 输出：3 解释： 第一步: 1 0 \u0026lt;-- 5 =\u0026gt; 1 1 4 第二步: 1 \u0026lt;-- 1 \u0026lt;-- 4 =\u0026gt; 2 1 3 第三步: 2 1 \u0026lt;-- 3 =\u0026gt; 2 2 2 示例 2：\n输入：machines = [0,3,0] 输出：2 解释： 第一步: 0 \u0026lt;-- 3 0 =\u0026gt; 1 2 0 第二步: 1 2 --\u0026gt; 0 =\u0026gt; 1 1 1 示例 3：\n输入：machines = [0,2,0] 输出：-1 解释： 不可能让所有三个洗衣机同时剩下相同数量的衣物。 \u0026nbsp;\n提示：\nn == machines.length 1 \u0026lt;= n \u0026lt;= 104 0 \u0026lt;= machines[i] \u0026lt;= 105 思路分析 # 本题主要的思路就是遍历。 先遍历一遍，求得是否有可行解以及每个位置上的平均衣服的件数avg是多少。 这里肯定大家都想得到。那么下面怎么办呢？ 我们知道，如果衣服多，假设一个位置上有K件衣服，而K\u0026gt;avg,那么我们最终的答案一定不会小于K-avg，因为对于该堆衣服来说，不考虑其他洗衣机的操作，仅对于本洗衣机而言，一定要花费K-avg的时间将里面多出来的衣服分出去。\n对于本题我们还知道，可以在遍历数组的时候维护一个cur。假设有一只无形的大手在遍历数组的同时将所有洗衣机里衣服的数量变成了avg，多余的衣服就认为是余量，缺少的就认为是缺口。这样有一个好处，就是能够各司其职。下面的例子能够加强理解。\n示例： [5, 0, 7], avg = 12 / 3 = 4 在遍历时的`cur`值：[1, -3, 0] 这样可以发现，对于本数组而言，cur的最大绝对值就是需要的总时间。因为我们如果单看一个1号位，也就是中间的那一位，我们会发现，一定要4次填充才能将其填到avg。但是这四次填充有一次是来自左边，三次是来自右边，这两段时间是可以重叠的。而cur值就很好的对于缺口和余量的体现。当遍历到这一位，缺口是k时，说明之前的所有洗衣机就算内部匀一匀也还缺k个，必须从cur的右方去移动过来，这个k就是最小移动时间。而当余量是k时，也是一样的道理，说明在cur及cur的左边怎么匀都会多出来k个，必须转移到cur的右边，而转移必须通过cur，也就是说这k个转移是没有叠加的余地的，必须在最短为k的时间内完成。\n所以cur可以作为一个全局的转移次数的把控。但还存在一些局限。看下面一个例子\n示例： [0, 3, 0], avg = 3 / 3 = 1 在遍历时的`cur`值：[-1, 1, 0] 这个示例来看，cur忽略了一个非常关键的点，也就是对于3而言，必须花费2次的时间才能将其完全转移出去。但对于cur来说，它并不知道自己所遍历到的-1和1是没法叠加的，都是从3上面分出来的。那么这时候就需要同考虑cur和K-avg对于最终结果的作用了。\nRust代码 # # struct Solution; impl Solution { pub fn find_min_moves(machines: Vec\u0026lt;i32\u0026gt;) -\u0026gt; i32 { let sum: i32 = machines.iter().sum(); let n = machines.len() as i32; if sum % n != 0 { return -1; } let avg = sum / n; let mut res = 0; let mut cur = 0; for mut m in machines { m -= avg; cur += m; res = res.max(cur.abs()).max(m); } res } } C++代码 # class Solution { public: int findMinMoves(vector\u0026lt;int\u0026gt;\u0026amp; m) { int n = m.size(); int sum = 0; for (auto \u0026amp; t : m) sum += t; if (sum % n != 0) return -1; int per = sum / n; sum = 0; int res = 0; for (auto \u0026amp; t : m) { t -= per; sum += t; res = max(res, abs(sum)); res = max(res, t); } return res; } }; ","date":"2021年9月29日","externalUrl":null,"permalink":"/leetcode/2021/09/29/","section":"LeetCode 题解","summary":"","title":"No.517 超级洗衣机","type":"leetcode"},{"content":"一条包含字母\u0026nbsp;A-Z 的消息通过以下的方式进行了编码：\n'A' -\u0026gt; 1 'B' -\u0026gt; 2 ... 'Z' -\u0026gt; 26 要 解码 一条已编码的消息，所有的数字都必须分组，然后按原来的编码方案反向映射回字母（可能存在多种方式）。例如，\"11106\" 可以映射为：\n\"AAJF\" 对应分组 (1 1 10 6) \"KJF\" 对应分组 (11 10 6) 注意，像 (1 11 06) 这样的分组是无效的，因为 \"06\" 不可以映射为 'F' ，因为 \"6\" 与 \"06\" 不同。\n除了 上面描述的数字字母映射方案，编码消息中可能包含 '*' 字符，可以表示从 '1' 到 '9' 的任一数字（不包括 '0'）。例如，编码字符串 \"1*\" 可以表示 \"11\"、\"12\"、\"13\"、\"14\"、\"15\"、\"16\"、\"17\"、\"18\" 或 \"19\" 中的任意一条消息。对 \"1*\" 进行解码，相当于解码该字符串可以表示的任何编码消息。\n给你一个字符串 s ，由数字和 '*' 字符组成，返回 解码 该字符串的方法 数目 。\n由于答案数目可能非常大，返回对 109 + 7 取余 的结果。\n\u0026nbsp;\n示例 1：\n输入：s = \"*\" 输出：9 解释：这一条编码消息可以表示 \"1\"、\"2\"、\"3\"、\"4\"、\"5\"、\"6\"、\"7\"、\"8\" 或 \"9\" 中的任意一条。 可以分别解码成字符串 \"A\"、\"B\"、\"C\"、\"D\"、\"E\"、\"F\"、\"G\"、\"H\" 和 \"I\" 。 因此，\"*\" 总共有 9 种解码方法。 示例 2：\n输入：s = \"1*\" 输出：18 解释：这一条编码消息可以表示 \"11\"、\"12\"、\"13\"、\"14\"、\"15\"、\"16\"、\"17\"、\"18\" 或 \"19\" 中的任意一条。 每种消息都可以由 2 种方法解码（例如，\"11\" 可以解码成 \"AA\" 或 \"K\"）。 因此，\"1*\" 共有 9 * 2 = 18 种解码方法。 示例 3：\n输入：s = \"2*\" 输出：15 解释：这一条编码消息可以表示 \"21\"、\"22\"、\"23\"、\"24\"、\"25\"、\"26\"、\"27\"、\"28\" 或 \"29\" 中的任意一条。 \"21\"、\"22\"、\"23\"、\"24\"、\"25\" 和 \"26\" 由 2 种解码方法，但 \"27\"、\"28\" 和 \"29\" 仅有 1 种解码方法。 因此，\"2*\" 共有 (6 * 2) + (3 * 1) = 12 + 3 = 15 种解码方法。 \u0026nbsp;\n提示：\n1 \u0026lt;= s.length \u0026lt;= 105 s[i] 是 0 - 9 中的一位数字或字符 '*' 思路分析 # 这一题考察的主要就是思路严密。\n通过题目所给信息不难判断这是一个一维的动态规划。\n思路也是非常动态规划，dp[i]为使用了s的前i个字符所有的方案数。\n如果s[i] 不是 \u0026lsquo;0\u0026rsquo;，那么就说明可以单独作为一个方案。也就是 dp[i + 1] += dp[i] 如果s[i - 1]和s[i]可以组成大于等于10（此处应为大坑，因为不能出现\u0026quot;01\u0026quot;）且小于等于26的数字，那么我们就认为可以将这两位数作为一个方案。dp[i + 1] += dp[i - 1] 思路非常简单，但是实现比较复杂，是因为我们总是会忽略各种各样的情况，比如\u0026quot;*\u0026ldquo;只能代表1到9，\u0026ldquo;01\u0026quot;不能作为合法的数之类的。\n主要的思路就是分开来写，对于s[i]和s[i-1]是不是\u0026rdquo;*\u0026ldquo;分四种情况讨论，这样就不会漏了。\nC++代码 # using ll = int64_t; class Solution { public: int numDecodings(string s) { int n = s.size(); ll dp[n + 1]; memset(dp, 0, sizeof dp); dp[0] = 1; for (int i = 0; i \u0026lt; n; ++i) { if (s[i] == \u0026#39;*\u0026#39;) dp[i + 1] += dp[i] * 9; else if (s[i] != \u0026#39;0\u0026#39;) dp[i + 1] += dp[i]; if (i \u0026gt; 0) { // cout \u0026lt;\u0026lt; \u0026#34;Good\u0026#34;; if (s[i - 1] == \u0026#39;*\u0026#39; \u0026amp;\u0026amp; s[i] != \u0026#39;*\u0026#39;){ if (s[i] \u0026lt;= \u0026#39;6\u0026#39;) dp[i + 1] += 2 * dp[i - 1]; else dp[i + 1] += dp[i - 1]; } else if (s[i - 1] == \u0026#39;*\u0026#39; \u0026amp;\u0026amp; s[i] == \u0026#39;*\u0026#39;) { // cout \u0026lt;\u0026lt; \u0026#34;Good\u0026#34;; dp[i + 1] += dp[i - 1] * 15; } else if (s[i - 1] != \u0026#39;*\u0026#39; \u0026amp;\u0026amp; s[i] == \u0026#39;*\u0026#39;) { if (s[i - 1] == \u0026#39;1\u0026#39;) dp[i + 1] += dp[i - 1] * 9; else if (s[i - 1] == \u0026#39;2\u0026#39;) dp[i + 1] += dp[i - 1] * 6; } else { if (s[i - 1] == \u0026#39;1\u0026#39; || (s[i - 1] == \u0026#39;2\u0026#39; \u0026amp;\u0026amp; s[i] \u0026lt;= \u0026#39;6\u0026#39;)) dp[i + 1] += dp[i - 1]; } } dp[i + 1] %= ll(1e9 + 7); } return dp[n]; } }; ","date":"2021年9月27日","externalUrl":null,"permalink":"/leetcode/2021/09/27/","section":"LeetCode 题解","summary":"","title":"No.639 解码方法 II","type":"leetcode"},{"content":"给你两个整数 a 和 b ，不使用 运算符\u0026nbsp;+ 和\u0026nbsp;-\u0026nbsp;​​​​​​​，计算并返回两整数之和。\n\u0026nbsp;\n示例 1：\n输入：a = 1, b = 2 输出：3 示例 2：\n输入：a = 2, b = 3 输出：5 \u0026nbsp;\n提示：\n-1000 \u0026lt;= a, b \u0026lt;= 1000 思路分析 # 先给大家讲一下什么是加法器。 加法器，顾名思义，就是做加法运算的工具。一个二输入的二进制加法器，应该有两个输出，一个是原原，一个是进位。 下面给大家打个表看看。\n数1 数2 答案 原位 进位 0 0 0 0 0 1 0 1 1 0 0 1 1 1 0 1 1 10 0 1 观察上表，我们不难看出，由原位和进位组成了最终的答案。 而原位操作对于二进制来说就是一个很明显的异或(XOR)操作，而进位操作也就是与(AND)操作。那么我们可以得到一个等式：\na + b = a ^ b + (a \u0026amp; b) * 2 也就是 a + b = a ^ b + (a \u0026amp; b) \u0026laquo; 1 从一个加号变成了另一个加号！好像没啥用。 但遇到这种情况，我们可以考虑，是否可以递归。 观察可得，a \u0026amp; b一定是一个越来越小的数字，它最后一定会趋向于0。一旦b趋向于0，那么答案就是a，我们就有了递归的基础。\nRust代码 # # struct Solution; impl Solution { pub fn get_sum(a: i32, b: i32) -\u0026gt; i32 { if b == 0 { a } else { Solution::get_sum(a ^ b, (a \u0026amp; b) \u0026lt;\u0026lt; 1) } } } C++代码 # class Solution { public: unsigned getSum(unsigned a, unsigned b) { if (b == 0) return a; return getSum(a ^ b, (a \u0026amp; b) \u0026lt;\u0026lt; 1); } }; ","date":"2021年9月26日","externalUrl":null,"permalink":"/leetcode/2021/09/26/","section":"LeetCode 题解","summary":"","title":"No.371 两整数之和","type":"leetcode"},{"content":" 写在前面 # 今年发挥还算挺不错的。以后再接再励！\n我 30 分钟写完了 2、4 两道题，队友一小时多写完了 1、3 两道题，然后就是啥都不会的几个小时。。。\n争取以后不会出现这种情况。\n第一题 开幕式焰火 # 1.1 题目 # 「力扣挑战赛」开幕式开始了，空中绽放了一颗二叉树形的巨型焰火。 给定一棵二叉树 root 代表焰火，节点值表示巨型焰火这一位置的颜色种类。请帮小扣计算巨型焰火有多少种不同的颜色。 示例 1： 输入：root = [1,3,2,1,null,2] 输出：3 解释：焰火中有 3 个不同的颜色，值分别为 1、2、3\n示例 2： 输入：root = [3,3,3] 输出：1 解释：焰火中仅出现 1 个颜色，值为 3\n提示： 1 \u0026lt;= 节点个数 \u0026lt;= 1000 1 \u0026lt;= Node.val \u0026lt;= 1000 1.2 思路分析 # 数组 dp 存一下状态就行了，没有出现过就加一次。\n1.3 代码 # /** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: bool dp[1005]; int count = 0; void deal(TreeNode * root) { if (!root) return ; if (!dp[root-\u0026gt;val]) { count++; dp[root-\u0026gt;val] = true; } deal(root-\u0026gt;left); deal(root-\u0026gt;right); } int numColor(TreeNode* root) { deal(root); return count; } }; 第二题 自行车炫技赛场 # 2.1 题目 # 「力扣挑战赛」中 N*M 大小的自行车炫技赛场的场地由一片连绵起伏的上下坡组成，场地的高度值记录于二维数组 terrain 中，场地的减速值记录于二维数组 obstacle 中。 若选手骑着自行车从高度为 h1 且减速值为 o1 的位置到高度为 h2 且减速值为 o2 的相邻位置（上下左右四个方向），速度变化值为 h1-h2-o2 （负值减速，正值增速）。 选手初始位于坐标 position 处且初始速度为 1，请问选手可以刚好到其他哪些位置时速度依旧为 1。请以二维数组形式返回这些位置。若有多个位置则按行坐标升序排列，若有多个位置行坐标相同则按列坐标升序排列。 注意： 骑行过程中速度不能为零或负值 示例 1： 输入\b： position = [0,0], terrain = [[0,0],[0,0]], obstacle = [[0,0],[0,0]] 输出：[[0,1],[1,0],[1,1]] 解释： 由于当前场地属于平地，根据上面的规则，选手从[0,0] 的位置出发都能刚好在其他处的位置速度为 1。 示例 2： 输入\b： position = [1,1], terrain = [[5,0],[0,6]], obstacle = [[0,6],[7,0]] 输出：[[0,1]] 解释： 选手从 [1,1] 处的位置出发，到 [0,1] 处的位置时恰好速度为 1。 提示： n == terrain.length == obstacle.length m == terrain[i].length == obstacle[i].length 1 \u0026lt;= n \u0026lt;= 100 1 \u0026lt;= m \u0026lt;= 100 0 \u0026lt;= terrain[i][j], obstacle[i][j] \u0026lt;= 100 position.length == 2 0 \u0026lt;= position[0] \u0026lt; n 0 \u0026lt;= position[1] \u0026lt; m 2.2 思路分析 # 这道题是一个比较基础的 dfs 思路。 可能会要使用记忆化的技术去重。 仔细读题后发现，自行车到达一个地方，并非一定会有相同的速度，也就是说，不同的路径到达一个地方，速度可能并不相同。 在这种情况下，我们必须得记录一下，如果两次到达同一个地方，具有相同的速度，我们就可以考虑不再继续。如果不去判断这一点，就会导致循环的出现，也就是示例 1 中出现的情况，如果我们不判断重复，一定会导致死循环。\n所以总结一下，思路就是 dfs 加上记忆化去重（剪枝）。只有两次到达同一个地方并且具有相同的速度，才会是剪枝条件。\n我的代码中主要分成了两块，一块是主函数，一块是 dfs，dfs 函数接受三个参数，也就是当前的 x，y 坐标和 speed 速度。 当passed[x][y][speed]为true时候，就说明之前以相同的速度遍历过相同的地方，而且无需再遍历一次。res[x][y]为true的时候说明该坐标是答案的一个候选坐标。接下来就是非常常规的往四个方向进行遍历。速度小于 0 就截断之类的基操。\n主函数中主要是处理了一下输入输出的逻辑，存了一些全局变量，对输出的答案格式做了一个简单的操作，包括将 res 变成答案所需的形式。\n2.3 C++代码 # using VI = vector\u0026lt;int\u0026gt;; using VVI = vector\u0026lt;VI\u0026gt;; const VI direc = {0, 1, 0, -1, 0}; class Solution { public: VVI t, o; bool res[100][100] = {}; bool passed[100][100][105] = {}; int n, m; void dfs(int x, int y, int speed) { if (speed == 1) res[x][y] = true; if (speed \u0026lt; 1) return; if (passed[x][y][speed]) { return; } passed[x][y][speed] = true; for (int i = 0; i \u0026lt; 4; ++i) { int xt = x + direc[i]; int yt = y + direc[i + 1]; if (xt \u0026gt;= 0 \u0026amp;\u0026amp; yt \u0026gt;= 0 \u0026amp;\u0026amp; xt \u0026lt; n \u0026amp;\u0026amp; yt \u0026lt; m) dfs(xt, yt, speed + t[x][y] - t[xt][yt] - o[xt][yt]); } } VVI bicycleYard(VI\u0026amp; p, VVI\u0026amp; terrain, VVI\u0026amp; obstacle) { t = terrain; o = obstacle; n = t.size(); m = t[0].size(); int x = p[0], y = p[1]; dfs(x, y, 1); VVI res_v; res[x][y] = false; for (int i = 0; i \u0026lt; 100; ++i) for (int j = 0; j \u0026lt; 100; ++j) if (res[i][j]) res_v.push_back({i, j}); return res_v; } }; 第三题 志愿者调配 # 3.1 题目 # 「力扣挑战赛」有 n 个比赛场馆（场馆编号从 0 开始），场馆之间的通道分布情况记录于二维数组 edges 中， edges[i]= [x, y] 表示第 i 条通道连接场馆 x 和场馆 y (即两个场馆相邻)。初始每个场馆中都有一定人数的志愿者（不同场馆人数可能不同），后续 m 天每天均会根据赛事热度进行志愿者人数调配。调配方案分为如下三种： 将编号为 idx 的场馆内的志愿者人数减半； 将编号为 idx 的场馆相邻的场馆的志愿者人数都加上编号为 idx 的场馆的志愿者人数； 将编号为 idx 的场馆相邻的场馆的志愿者人数都减去编号为 idx 的场馆的志愿者人数。 所有的调配信息记录于数组 plans 中， plans[i] = [num,idx] 表示第 i 天对编号 idx 的场馆执行了第 num 种调配方案。 在比赛结束后对调配方案进行复盘时，不慎将第 0 个场馆的 最终志愿者人数丢失，只保留了初始 所有场馆的志愿者总人数 totalNum ，以及记录了第 1 ~ n-1 个场馆的最终志愿者人数的一维数组 finalCnt 。请你根据现有的信息求出初始每个场馆的志愿者人数，并按场馆编号顺序返回志愿者人数列表。 注意： 测试数据保证当某场馆进行第一种调配时，该场馆的志愿者人数一定为偶数； 测试数据保证当某场馆进行第三种调配时，该场馆的相邻场馆志愿者人数不为负数； 测试数据保证比赛开始时每个场馆的志愿者人数都不超过 10^9； 测试数据保证给定的场馆间的道路分布情况中不会出现自环、重边的情况。 示例 1： 输入： finalCnt = [1,16], totalNum = 21, edges = [[0,1],[1,2]], plans = [[2,1],[1,0],[3,0]] 输出：[5,7,9] 解释： 示例 2 ： 输入： finalCnt = [4,13,4,3,8], totalNum = 54, edges = [[0,3],[1,3],[4,3],[2,3],[2,5]], plans = [[1,1],[3,3],[2,5],[1,0]] 输出：[10,16,9,4,7,8] 提示： 2 \u0026lt;= n \u0026lt;= 5*10^4 1 \u0026lt;= edges.length \u0026lt;= min((n * (n - 1)) / 2, 5*10^4) 0 \u0026lt;= edges[i][0], edges[i][1] \u0026lt; n 1 \u0026lt;= plans.length \u0026lt;= 10 1 \u0026lt;= plans[i][0] \u0026lt;=3 0 \u0026lt;= plans[i][1] \u0026lt; n finalCnt.length = n-1 0 \u0026lt;= finalCnt[i] \u0026lt; 10^9 0 \u0026lt;= totalNum \u0026lt; 5*10^13 3.2 思路分析 # 我们只有一个值不知道，就是当前（最后时刻）第 0 号位的人数。 通过观察题目不难得出，三个操作都是线性操作，而且我们准确地知道每一步的操作，也就是说，只要知道知道了最后一个时刻的第 0 号位的人数，我们就可以严丝合缝地将结果递推到第一步。并且，在此时，第一步的总和应当严格等于totalNum。\n既然如此，我们不妨设一个变量 x，作为最后时刻第 0 号位的人数。逆推来完成最终的推理，推理到第一步之后，每一个场馆的人数都应该是一个关于变量 x 的一次函数，我们将其求和之后仍然是一个一次函数，这个一次函数最终等于 totalNum ，代入就可以求得 x，代入每个场馆的表达式中即可获得每个场馆在第一步时的人数。\n在具体代码中，设置两个数组 x_param 和 c_param ，分别代表指定场馆一次函数中的 x 系数项和常数项。最终递推回第一步，求得 x_param_sum 与 c_param_sum 。求解 x 的方程为:\n$$ \\sum^{n}_{i=1}{x_{param}[i]} * x + \\sum^{n}_{i=1}{c_{param}[i]} = totalNum $$简单变换后得到:\n$$ x = \\frac{(totalNum - \\sum^{n}_{i=1}{c_{param}[i]})}{\\sum^{n}_{i=1}{x_{param}[i]}} $$最后代入到表达式中即可完成计算。\n3.3 C++代码 # using VI = vector\u0026lt;int\u0026gt;; using VVI = vector\u0026lt;VI\u0026gt;; using ll = long long; class Solution { public: VI volunteerDeployment(VI\u0026amp; finalCnt, ll totalNum, VVI\u0026amp; edges, VVI\u0026amp; plans) { VI m[50005]; int n = finalCnt.size() + 1; // x系数和常数 int x_param[50005] = {}, c_param[50005] = {}; for (auto \u0026amp; edge : edges) { m[edge[0]].push_back(edge[1]); m[edge[1]].push_back(edge[0]); } x_param[0] = 1; for (int i = 0; i \u0026lt; n - 1; ++i) c_param[i + 1] = finalCnt[i]; while (plans.size()) { int kind = plans.back()[0]; int place = plans.back()[1]; plans.pop_back(); if (kind == 1) { x_param[place] *= 2; c_param[place] *= 2; } else if (kind == 2) { for (auto \u0026amp; nxt : m[place]) { x_param[nxt] -= x_param[place]; c_param[nxt] -= c_param[place]; } } else { for (auto \u0026amp; nxt : m[place]) { x_param[nxt] += x_param[place]; c_param[nxt] += c_param[place]; } } } ll x_param_sum = 0, c_param_sum = 0; for (int i = 0; i \u0026lt; n; ++i) { x_param_sum += x_param[i]; c_param_sum += c_param[i]; } // 方程: x_param_sum * x + c_param_sum = totalNum int x = (totalNum - c_param_sum) / x_param_sum; vector\u0026lt;int\u0026gt; res(n); for (int i = 0; i \u0026lt; n; ++i) res[i] = x_param[i] * x + c_param[i]; return res; } }; 第四题 入场安检 # 4.1 题目 # 「力扣挑战赛」 的入场仪式马上就要开始了，由于安保工作的需要，设置了可容纳人数总和为 M 的 N 个安检室，capacities[i] 记录第 i 个安检室可容纳人数。安检室拥有两种类型： 先进先出：在安检室中的所有观众中，最早进入安检室的观众最先离开 后进先出：在安检室中的所有观众中，最晚进入安检室的观众最先离开 恰好 M+1 位入场的观众（编号从 0 开始）需要排队 依次入场安检， 入场安检的规则如下： 观众需要先进入编号 0 的安检室 当观众将进入编号 i 的安检室时（0 \u0026lt;= i \u0026lt; N )， 若安检室未到达可容纳人数上限，该观众可直接进入； 若安检室已到达可容纳人数上限，在该观众进入安检室之前需根据当前安检室类型选择一位观众离开后才能进入； 当观众离开编号 i 的安检室时 （0 \u0026lt;= i \u0026lt; N-1 )，将进入编号 i+1 的安检室接受安检。 若可以任意设定每个安检室的类型，请问有多少种设定安检室类型的方案可以使得编号 k 的观众第一个通过最后一个安检室入场。 注意： 观众不可主动离开安检室，只有当安检室容纳人数达到上限，且又有新观众需要进入时，才可根据安检室的类型选择一位观众离开； 由于方案数可能过大，请将答案对 1000000007 取模后返回。 示例 1： 输入：capacities = [2,2,3], k = 2 输出：2 解释： 存在两种设定的 2 种方案： 方案 1：将编号为 0 、1 的实验室设置为 后进先出 的类型，编号为 2 的实验室设置为 先进先出 的类型； 方案 2：将编号为 0 、1 的实验室设置为 先进先出 的类型，编号为 2 的实验室设置为 后进先出 的类型。 以下是方案 1 的示意图： 示例 2： 输入：capacities = [3,3], k = 3 输出：0 示例 3： 输入：capacities = [4,3,2,2], k = 6 输出：2 提示: 1 \u0026lt;= capacities.length \u0026lt;= 200 1 \u0026lt;= capacities[i] \u0026lt;= 200 0 \u0026lt;= k \u0026lt;= sum(capacities) 4.2 思路分析 # 本题注意观察题目即可发现，先进先出对应的数据结构为队列，后进先出对应的数据结构为栈。 而注意看第二个 gif，我们发现，当一个容器（实验室，我们在此统称为容器）为队列时，第一个进入的第一个出队列，也就是说，一个队列对于改变流程中的第一个人没任何影响。对比着来看栈，我们发现，一个长度为 2 的栈必须先填充 1 个元素，这一个元素就相当于固定在此处，没法移动，也就是说，对于流程中的第一个人，一个长度为c的栈能够拦截c-1个人。\n那么就很简单了。因为我们想要让第k个人达到对首，我们必须使用栈来拦截前k个人。\n所以我们把这道题翻译成一个我们喜闻乐见的形式：\n有`N`个硬币，每个的金额都在`cap`数组中给出（需要一个减 1 操作）。我们从前往后选，求最终金额为`k`的方案数。 是不是一下就简单了呢！\n最终的做法就是一个非常简单的 dp。因为一个硬币只能用一次，所以要控制 dp 的方向。\n4.3 C++代码 # const int md = 1000000007; class Solution { public: int securityCheck(vector\u0026lt;int\u0026gt;\u0026amp; cap, int k) { for (auto \u0026amp; c : cap) c--; int sum = accumulate(cap.begin(), cap.end(), 0); if (sum \u0026lt; k) return 0; int dp[40005] = {1}; for (auto \u0026amp; c : cap) { for (int i = k - c; i \u0026gt;= 0; --i) { (dp[i + c] += dp[i]) %= md; } } return dp[k]; } }; ","date":"2021年9月25日","externalUrl":null,"permalink":"/leetcode/special/leetcode-cup-2021-autumn-group/","section":"LeetCode 题解","summary":"","title":"2021 秋季力扣杯 - 战队赛复盘","type":"leetcode"},{"content":"","date":"2021年9月25日","externalUrl":null,"permalink":"/tags/contest/","section":"Tags","summary":"","title":"Contest","type":"tags"},{"content":"给定两个单词\u0026nbsp;word1\u0026nbsp;和\u0026nbsp;word2，找到使得\u0026nbsp;word1\u0026nbsp;和\u0026nbsp;word2\u0026nbsp;相同所需的最小步数，每步可以删除任意一个字符串中的一个字符。\n\u0026nbsp;\n示例：\n输入: \"sea\", \"eat\" 输出: 2 解释: 第一步将\"sea\"变为\"ea\"，第二步将\"eat\"变为\"ea\" \u0026nbsp;\n提示：\n给定单词的长度不超过500。 给定单词中的字符只含有小写字母。 思路分析 # 两个字符串求这种类似编辑距离的题基本上用二维动态规划都比较简单。 这道题有两种思路（均为动态规划），一种正着想，一种倒着想。\n正向思维 # dp[i][j]表示答案，也就是word1中前i个字符和word2中前j个字符所组成的字符串的删除操作的最短步数。\n那么边界条件首先是dp[i][0]=i(i \u0026lt;= word1.length)与dp[0][i]=i(i \u0026lt;= word2.length)。因为一个空字符串与一个长度为i的字符串，总是要把长度为i的字符串删空才能相等，所以边界条件就此确定。 动态规划的转移方程为两个\ndp[i + 1][j + 1] = dp[i][j] (a[i] == b[j]) dp[i + 1][j + 1] = min(dp[i + 1][j], dp[i][j + 1]) + 1 (a[i] != b[i]) 第一行，相等的时候，因为当出现两个相等字符的时候，删除的距离一定不增加。 第二行也很好理解，如果没法做到相同的字符，那么就只能在删掉一位的基础上再进行比较，那么会在取最小值之后再加上1。\n逆向思维 # 既然是删除字符串中的某些字符，那么留下来的公共部分我们怎么称呼呢？仔细思考，便发现，这不就是最长公共子序列吗！\n那么我们就可以先求出两个字符串最长公共子序列的长度，然后再用两个字符串的长度减去最长公共子序列两倍的长度，即可获得每边字符串需要减少的字符数量之和。 边界条件dp[i][0]=0与dp[0][i]=0\n动态规划的转移方程\ndp[i + 1][j + 1] = dp[i][j] + 1 (a[i] == b[j]) dp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1]) (a[i] != b[i]) 也比较好理解。\nC++代码（正向） # #define REP(i, j) for (int i = 0; i \u0026lt; j; ++i) class Solution { public: int minDistance(string a, string b) { int m = a.size(), n = b.size(); int dp[m + 1][n + 1]; memset(dp, 0, sizeof dp); REP(i, m) REP(j, n) if (a[i] == b[j]) dp[i + 1][j + 1] = dp[i][j] + 1; else dp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1]); return m + n - 2 * dp[m][n]; } }; C++代码（逆向） # #define REP(i, j) for (int i = 0; i \u0026lt; j; ++i) class Solution { public: int minDistance(string a, string b) { /*这破烂玩意不用dp做我把力扣炸了*/ int m = a.size(), n = b.size(); int dp[m + 1][n + 1]; memset(dp, 0, sizeof dp); REP(i, m) REP(j, n) if (a[i] == b[j]) dp[i + 1][j + 1] = dp[i][j] + 1; else dp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1]); return m + n - 2 * dp[m][n]; } }; ","date":"2021年9月25日","externalUrl":null,"permalink":"/leetcode/2021/09/25/","section":"LeetCode 题解","summary":"","title":"No.583 两个字符串的删除操作","type":"leetcode"},{"content":"给定一个整数，写一个函数来判断它是否是 3\u0026nbsp;的幂次方。如果是，返回 true ；否则，返回 false 。\n整数 n 是 3 的幂次方需满足：存在整数 x 使得 n == 3x\n\u0026nbsp;\n示例 1：\n输入：n = 27 输出：true 示例 2：\n输入：n = 0 输出：false 示例 3：\n输入：n = 9 输出：true 示例 4：\n输入：n = 45 输出：false \u0026nbsp;\n提示：\n-231 \u0026lt;= n \u0026lt;= 231 - 1 \u0026nbsp;\n进阶：\n你能不使用循环或者递归来完成本题吗？ 思路分析 # 这题其实很简单，不管递归与否\n递归就是常规想法，一直除3，如果除得只剩下1，那么就是3的幂次，如果还剩点别的就不是。\n非递归也是常规想法，幂次有一个特性，就是它的因子包含比它更小（或者相等）的3的所有幂次。所以我们只需要选出来一个可能出现的最大的3的幂次N（在32位整数条件下为1162261467），判断给入的数字是否是N的因子，如果是，那么一定是3的幂次。\nRust代码 # # struct Solution; impl Solution { pub fn is_power_of_three(n: i32) -\u0026gt; bool { if n % 3 == 0 \u0026amp;\u0026amp; n != 0 { Self::is_power_of_three(n / 3) } else { n == 1 } } } C++代码 # class Solution { public: bool isPowerOfThree(int n) { return n \u0026gt; 0 \u0026amp;\u0026amp; 1162261467 % n == 0; } } ","date":"2021年9月23日","externalUrl":null,"permalink":"/leetcode/2021/09/23/","section":"LeetCode 题解","summary":"","title":"No.326 3的幂","type":"leetcode"},{"content":"给你一份 n 位朋友的亲近程度列表，其中 n 总是 偶数 。\n对每位朋友 i，preferences[i] 包含一份 按亲近程度从高到低排列 的朋友列表。换句话说，排在列表前面的朋友与 i 的亲近程度比排在列表后面的朋友更高。每个列表中的朋友均以 0 到 n-1 之间的整数表示。\n所有的朋友被分成几对，配对情况以列表 pairs 给出，其中 pairs[i] = [xi, yi] 表示 xi 与 yi 配对，且 yi 与 xi 配对。\n但是，这样的配对情况可能会是其中部分朋友感到不开心。在 x 与 y 配对且 u 与 v 配对的情况下，如果同时满足下述两个条件，x 就会不开心：\nx 与 u 的亲近程度胜过 x 与 y，且 u 与 x 的亲近程度胜过 u 与 v 返回 不开心的朋友的数目 。\n\u0026nbsp;\n示例 1：\n输入：n = 4, preferences = [[1, 2, 3], [3, 2, 0], [3, 1, 0], [1, 2, 0]], pairs = [[0, 1], [2, 3]] 输出：2 解释： 朋友 1 不开心，因为： - 1 与 0 配对，但 1 与 3 的亲近程度比 1 与 0 高，且 - 3 与 1 的亲近程度比 3 与 2 高。 朋友 3 不开心，因为： - 3 与 2 配对，但 3 与 1 的亲近程度比 3 与 2 高，且 - 1 与 3 的亲近程度比 1 与 0 高。 朋友 0 和 2 都是开心的。 示例 2：\n输入：n = 2, preferences = [[1], [0]], pairs = [[1, 0]] 输出：0 解释：朋友 0 和 1 都开心。 示例 3：\n输入：n = 4, preferences = [[1, 3, 2], [2, 3, 0], [1, 3, 0], [0, 2, 1]], pairs = [[1, 3], [0, 2]] 输出：4 \u0026nbsp;\n提示：\n2 \u0026lt;= n \u0026lt;= 500 n 是偶数 preferences.length\u0026nbsp;== n preferences[i].length\u0026nbsp;== n - 1 0 \u0026lt;= preferences[i][j] \u0026lt;= n - 1 preferences[i] 不包含 i preferences[i] 中的所有值都是独一无二的 pairs.length\u0026nbsp;== n/2 pairs[i].length\u0026nbsp;== 2 xi != yi 0 \u0026lt;= xi, yi\u0026nbsp;\u0026lt;= n - 1 每位朋友都 恰好 被包含在一对中 思路分析 # 首先我们要对给入的数据做一个预处理，因为我们在查询亲近程度的时候一定是希望给进两个人，给出一个亲近程度的指数。而题目给我们的数据非常不直观。所以我们可以拿一个二维数组来重新装一下。\n# let n = 10; # let preferences = vec![vec![3]]; let n = n as usize; let mut pre = vec![vec![0; n]; n]; for i in 0..n { for j in 0..(n - 1) { pre[i][preferences[i][j] as usize] = n - j - 1; } } 以上代码将preferences重新整合至pre。这样我们就能很方便的通过pre[i][j]的方式来查询了。\n接下来我们仔细观察不开心的示例并且将其改写为代码\npre[x][u] \u0026gt; pre[x][y] \u0026amp;\u0026amp; pre[u][x] \u0026gt; pre[u][v]\n这个式子非常有意思，不难看出，在满足这个式子的时候，不仅x不满意，u也不满意。\n再进一步观察又能发现，这个式子通过x和u是可以完全确定的。所以对于x,y,u,v四个变量来说，这样的两两组合一共有四对。\n# let (a, b, c, d) = (0, 0, 0, 0); # let pre = vec![vec![3]]; # let mut set = std::collections::HashSet::new(); if pre[a][c] \u0026gt; pre[a][b] \u0026amp;\u0026amp; pre[c][a] \u0026gt; pre[c][d] { set.insert(a); set.insert(c); } if pre[b][c] \u0026gt; pre[b][a] \u0026amp;\u0026amp; pre[c][b] \u0026gt; pre[c][d] { set.insert(b); set.insert(c); } if pre[b][d] \u0026gt; pre[b][a] \u0026amp;\u0026amp; pre[d][b] \u0026gt; pre[d][c] { set.insert(b); set.insert(d); } if pre[d][a] \u0026gt; pre[d][c] \u0026amp;\u0026amp; pre[a][d] \u0026gt; pre[a][b] { set.insert(d); set.insert(a); } 其中set是为了不重复而设置的哈希集合。（x,y,u,v为了方便写成了a,b,c,d） 然后再对哈希表进行遍历并输出即可。\nRust代码 # # struct Solution; impl Solution { pub fn unhappy_friends(n: i32, preferences: Vec\u0026lt;Vec\u0026lt;i32\u0026gt;\u0026gt;, pairs: Vec\u0026lt;Vec\u0026lt;i32\u0026gt;\u0026gt;) -\u0026gt; i32 { let n = n as usize; let mut pre = vec![vec![0; n]; n]; for i in 0..n { for j in 0..(n - 1) { pre[i][preferences[i][j] as usize] = n - j - 1; } } // println!(\u0026#34;{:?}\u0026#34;, pre); use std::collections::*; let mut set = HashSet::new(); for i in 0..pairs.len() { for j in 0..pairs.len() { let a = pairs[i][0] as usize; let b = pairs[i][1] as usize; let c = pairs[j][0] as usize; let d = pairs[j][1] as usize; if pre[a][c] \u0026gt; pre[a][b] \u0026amp;\u0026amp; pre[c][a] \u0026gt; pre[c][d] { set.insert(a); set.insert(c); } if pre[b][c] \u0026gt; pre[b][a] \u0026amp;\u0026amp; pre[c][b] \u0026gt; pre[c][d] { set.insert(b); set.insert(c); } if pre[b][d] \u0026gt; pre[b][a] \u0026amp;\u0026amp; pre[d][b] \u0026gt; pre[d][c] { set.insert(b); set.insert(d); } if pre[d][a] \u0026gt; pre[d][c] \u0026amp;\u0026amp; pre[a][d] \u0026gt; pre[a][b] { set.insert(d); set.insert(a); } } } set.len() as i32 } } ","date":"2021年8月14日","externalUrl":null,"permalink":"/leetcode/2021/08/14/","section":"LeetCode 题解","summary":"","title":"No.1583 统计不开心的朋友","type":"leetcode"},{"content":"","date":"2021年8月14日","externalUrl":null,"permalink":"/tags/simulation/","section":"Tags","summary":"","title":"Simulation","type":"tags"},{"content":"给定一个整数 n，计算所有小于等于 n 的非负整数中数字 1 出现的个数。\n\u0026nbsp;\n示例 1：\n输入：n = 13 输出：6 示例 2：\n输入：n = 0 输出：0 \u0026nbsp;\n提示：\n0 \u0026lt;= n \u0026lt;= 2 * 109 思路分析 # 令a=n/i,b=n%i; 举例分析，假设n=12345，i=100; 此时a=123,b=45： 1）当a的个位大于1时（也就是n的百位），百位为1的数总共出现了(a/10+1)*100次 2）当a为1的时候，百位为1的数总共出现了(a/10)*100+(b+1); 3）当a为0的时候，百位为1的数出现了(a/10)*100次； 因此可以根据a的个位是否为1分成2中情况计算，可以使用一个表达式合并以上三个式子。 a. 9=\u0026gt;(a个位)\u0026gt;=2和0=\u0026lt;(a个位\u0026lt;=1)时的(a/10+1),(a/10)表达式与(a+8)/10等价； b. （a\u0026gt;=2时（a+8）/10的结果与a/10+1相同，a==1或a==0时（a+8）/10的结果等价a/10。 c. 当a个位为1时需要加上（b+1）与(a%10==1)*(b+1)等价。因此合并后百位为1的数的个数为 (a+8)/10*i+(a%10==1)*(b+1); 然后令i从个位到最高位遍历即可计算每一位含1的个数； Rust代码 # # struct Solution; impl Solution { pub fn count_digit_one(n: i32) -\u0026gt; i32 { let mut cnt = 0; let mut i = 1; while i \u0026lt;= n { let a = n / i; let b = n % i; cnt += (a + 8) / 10 * i + if a % 10 == 1 {b + 1} else {0}; i *= 10; } cnt } } ","date":"2021年8月13日","externalUrl":null,"permalink":"/leetcode/2021/08/13/","section":"LeetCode 题解","summary":"","title":"No.233 数字1的个数","type":"leetcode"},{"content":"给你一个字符串 s ，找出其中最长的回文子序列，并返回该序列的长度。\n子序列定义为：不改变剩余字符顺序的情况下，删除某些字符或者不删除任何字符形成的一个序列。\n\u0026nbsp;\n示例 1：\n输入：s = \"bbbab\" 输出：4 解释：一个可能的最长回文子序列为 \"bbbb\" 。 示例 2：\n输入：s = \"cbbd\" 输出：2 解释：一个可能的最长回文子序列为 \"bb\" 。 \u0026nbsp;\n提示：\n1 \u0026lt;= s.length \u0026lt;= 1000 s 仅由小写英文字母组成 思路分析 # 其实有一个非常关键的思路。就是\n字符串s的最长回文子序列 等价于 字符串s和s的倒序s\u0026rsquo;的最长公共子序列\n简单的证明，s与s倒序的最长公共子序列一定是回文的。正着有，反着有，且相同，这就是回文的定义。\n也不会有比s与s倒序的最长公共子序列更长的回文子序列了。如果有，那么s与s倒序的最长公共子序列也会变得更长。\n所以我们成功的将题目转化为求s与s倒序的最长公共子序列的长度。\n既然是这样，就非常方便了。直接使用二维动态规划即可轻松求解。 首先设置动态规划变量dp[n+1][n+1](预留一个空位方便计算)。\n再说dp[i][j]的含义，就是s中前i位与s\u0026rsquo;中前j位的最长公共子序列。\n动态规划的递推式也非常好思考。只要遇到了两个相同的字符，dp[i][j] = dp[i - 1][j - 1] + 1\n否则，dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])\nRust代码 # # struct Solution; impl Solution { pub fn longest_palindrome_subseq(s: String) -\u0026gt; i32 { let s = s.into_bytes(); let n = s.len(); let mut res = 0; let mut dp = vec![vec![0; n + 1]; n + 1]; for i in 1..=n { for j in 1..=n { if s[n - i] == s[j - 1] { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = dp[i - 1][j].max(dp[i][j - 1]); } } } dp[n][n] } } ","date":"2021年8月12日","externalUrl":null,"permalink":"/leetcode/2021/08/12/","section":"LeetCode 题解","summary":"","title":"No.516 最长回文子序列","type":"leetcode"},{"content":"给你一个整数数组 nums ，返回 nums 中所有 等差子序列 的数目。\n如果一个序列中 至少有三个元素 ，并且任意两个相邻元素之差相同，则称该序列为等差序列。\n例如，[1, 3, 5, 7, 9]、[7, 7, 7, 7] 和 [3, -1, -5, -9] 都是等差序列。 再例如，[1, 1, 2, 5, 7] 不是等差序列。 数组中的子序列是从数组中删除一些元素（也可能不删除）得到的一个序列。\n例如，[2,5,10] 是 [1,2,1,2,4,1,5,10] 的一个子序列。 题目数据保证答案是一个 32-bit 整数。\n\u0026nbsp;\n示例 1：\n输入：nums = [2,4,6,8,10] 输出：7 解释：所有的等差子序列为： [2,4,6] [4,6,8] [6,8,10] [2,4,6,8] [4,6,8,10] [2,4,6,8,10] [2,6,10] 示例 2：\n输入：nums = [7,7,7,7,7] 输出：16 解释：数组中的任意子序列都是等差子序列。 \u0026nbsp;\n提示：\n1\u0026nbsp; \u0026lt;= nums.length \u0026lt;= 1000 -231 \u0026lt;= nums[i] \u0026lt;= 231 - 1 思路分析 # 本题与昨天的题目几乎完全一致。唯一有区别的地方就是将子数组改成了子序列。\n但这个改动可不是小小的改动，我们之前的思考在这里几乎不会有什么用处。\n子序列是没有连续性的，也就是说，对于任意一些有先后顺序但是没有邻接顺序的数字，只要他们满足差值是一个恒定的数，他们就是合法的等差子序列。\n首先要理解题目，才有可能做出来。\n那么对于子序列，我们能想到的除了暴力还有别的办法吗？\n其实答案很简单，我们只需要去提炼一下规律，一个可以总结的规律。\n比如在这一题中，如果我们知道[.....,2,4,6]是一个等差数列，那么4前面的东西有多少位与我们并无关系，也就是说，对于后面的数字来说，我们并不关心2前面有几位，因为都是差为2的等差数列。\n那么整个数据结构的雏形就出来了，首先外层是一个从前向后的遍历，每一步都会对自己及之前的东西做一个记录，方便后续使用。\n记录的这个东西，肯定是基于等差数列的差来记录的，我们就使用一个HashMap\u0026lt;i32,?\u0026gt;来记录，那么记录的内容是什么呢？\n为了后续使用，我们肯定要想记录数量，也就是合法的等差子序列的数量，但是这里又出现了一个问题，长度问题。\n长度是一个很关键的点，如果我们记录的是长度为2的，就有可能多算；我们也没有办法只记录长度大于等于3的，因为这必定是基于长度为2的才能推得\n在这种情况下，我们使用了HashMap\u0026lt;i32, (i32, i32)\u0026gt;的数据结构。这样可以用前面的i32来记录长度大于等于3的，后面的来记录长度等于2的。\n但这里还有一个坑点需要注意，我相信大家也都没发现：-231 \u0026lt;= nums[i] \u0026lt;= 231 - 1 这个条件直接导致我们在计算两位之差的时候需要使用比i32更大的数据，否则会溢出。所以我们改用了i64。\n然后我们写出了第一版本的代码\nimpl Solution { pub fn number_of_arithmetic_slices(nums: Vec\u0026lt;i32\u0026gt;) -\u0026gt; i32 { use std::collections::*; let mut dp: Vec\u0026lt;HashMap\u0026lt;i64, (i32, i32)\u0026gt;\u0026gt; = vec![]; let n = nums.len(); let mut res = 0; for i in 0..n { let mut nxt = HashMap::new(); for j in 0..i { let diff = nums[i] as i64 - nums[j] as i64; if let Some(value) = dp[j].get(\u0026amp;diff) { nxt.entry(diff).or_insert((0, 0)).0 += value.0 + value.1; res += value.0 + value.1; } nxt.entry(diff).or_insert((0, 0)).1 += 1; } dp.push(nxt); } res } } dp是一个包含每一个位所有信息的一个总和。 在对nums进行遍历的同时，记录所有数据。\nnxt.entry(diff).or_insert((0, 0)).1 += 1;\n这句话的意思就是对于所有在i前面的nums[j]都可以与nums[i]组成一个长度为2的组合。所以加到了后面的位。 nxt.entry(diff).or_insert((0, 0)).0 += value.0 + value.1;\n这句话的意思是如果j位置之前出现过长度大于3或者长度等于2的，再与nums[i]进行搭配，一定会得到一个长度大于等于3的子序列，那么我们就将其加到前面的位上，并且加到答案上。\n这段代码是可以通过的，但是我们发现一个要点。在我们使用(i32, i32)时，总是使用它们的和。代码中出现的只有value[0] + value[1]。那么我们是否可以考虑将其合并？\n其实也是可以的。合并之后的含义就变化了，HashMap\u0026lt;i64, i32\u0026gt;表示的就是一个特定的diff下长度大于等于2的所有子序列数量。而最令人震惊的是这好像不影响计算。\nRust代码 # impl Solution { pub fn number_of_arithmetic_slices(nums: Vec\u0026lt;i32\u0026gt;) -\u0026gt; i32 { use std::collections::*; let mut dp: Vec\u0026lt;HashMap\u0026lt;i64, i32\u0026gt;\u0026gt; = vec![]; let n = nums.len(); let mut res = 0; for i in 0..n { let mut nxt = HashMap::new(); for j in 0..i { let diff = nums[i] as i64 - nums[j] as i64; if let Some(value) = dp[j].get(\u0026amp;diff) { *nxt.entry(diff).or_insert(0) += value; res += value; } *nxt.entry(diff).or_insert(0) += 1; } dp.push(nxt); } res } } 看到了吗？其实答案就是我们在读取前一位的长度为2的子序列的时候，加上这一位一定大于等于3了，所以我们多虑了！\n但你要是不试你永远也不知道自己多虑了。 😂\n","date":"2021年8月11日","externalUrl":null,"permalink":"/leetcode/2021/08/11/","section":"LeetCode 题解","summary":"","title":"No.446 等差数列划分 II - 子序列","type":"leetcode"},{"content":"如果一个数列 至少有三个元素 ，并且任意两个相邻元素之差相同，则称该数列为等差数列。\n例如，[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。 给你一个整数数组 nums ，返回数组 nums 中所有为等差数组的 子数组 个数。\n子数组 是数组中的一个连续序列。\n\u0026nbsp;\n示例 1：\n输入：nums = [1,2,3,4] 输出：3 解释：nums 中有三个子等差数组：[1, 2, 3]、[2, 3, 4] 和 [1,2,3,4] 自身。 示例 2：\n输入：nums = [1] 输出：0 \u0026nbsp;\n提示：\n1 \u0026lt;= nums.length \u0026lt;= 5000 -1000 \u0026lt;= nums[i] \u0026lt;= 1000 思路分析 # 子数组一定是在原数组中连续的，所以我们关心的只有原数组中的两两之差。所以可以预处理一下，开一个新的数组存相邻数字的差。\n先来思考一个问题，什么样的才能构成等差数列。\n答案：字面意思，差相等。那么如果出现连读多个差相等的情况该怎么办呢\n根据示例可见，如果出现多个连续的差相等的情况，其实是比较类似排列组合的一个选择，比如[1,2,3,4]和[1,2,3]、[2,3,4]都是合法答案。 如果连续四个数（也就是连续三个缝隙）差相等，就存在三种情况。如果连续n个数字呢？\n1.先假设存在n个可以构成等差数列的数字 例如：1,2,3,4,5,6,7,.....,n（刚好n个） 2.因为最短需要长度为3的子数组，所以长度为3的有n - 2个 3.长度为t的子数组（t\u0026lt;n）有n - t + 1个 4.总共有(n-2)+(n-3)+....+(1)个 还是4.所以总共有(n-2)*(n-1)/2个 但对于我们来说，计算哪几个数在同一个等差数列很困难，而且存在交叉公用的情况，比如[1,2,3,2,1]中的3，就可能是两个子数组公用的数。\n所以我们用缝隙来处理，先计算出两两之差（即缝隙），再比较。\n将[1,2,3,2,1]处理成[1,1,-1,-1]这样，只要出现两个连续的缝隙，我们就认为可以组成子数组。\n这样也很巧妙地处理了公用数字的情况。\n那么思路就非常清晰了，我们只需要遍历一遍数组，只关心缝隙，在缝隙连续出现的时候统计，最多连续出现n个缝隙，然后再将答案加上n*(n-1)即可\nRust代码 # # struct Solution; impl Solution { pub fn number_of_arithmetic_slices(nums: Vec\u0026lt;i32\u0026gt;) -\u0026gt; i32 { let n = nums.len(); let mut res = 0; let mut pre = i32::MAX; let mut count = 0; for i in 1..n { let item = nums[i] - nums[i - 1]; if pre != item { if count \u0026gt; 1 { res += count * (count - 1) / 2; } pre = item; count = 1; } else { count += 1; } } if count \u0026gt; 1 { res += count * (count - 1) / 2; } res } } ","date":"2021年8月10日","externalUrl":null,"permalink":"/leetcode/2021/08/10/","section":"LeetCode 题解","summary":"","title":"No.413 等差数列划分","type":"leetcode"},{"content":" 超级丑数 是一个正整数，并满足其所有质因数都出现在质数数组 primes 中。\n给你一个整数 n 和一个整数数组 primes ，返回第 n 个 超级丑数 。\n题目数据保证第 n 个 超级丑数 在 32-bit 带符号整数范围内。\n\u0026nbsp;\n示例 1：\n输入：n = 12, primes = [2,7,13,19] 输出：32 解释：给定长度为 4 的质数数组 primes = [2,7,13,19]，前 12 个超级丑数序列为：[1,2,4,7,8,13,14,16,19,26,28,32] 。 示例 2：\n输入：n = 1, primes = [2,3,5] 输出：1 解释：1 不含质因数，因此它的所有质因数都在质数数组 primes = [2,3,5] 中。 提示：\n1 \u0026lt;= n \u0026lt;= 106 1 \u0026lt;= primes.length \u0026lt;= 100 2 \u0026lt;= primes[i] \u0026lt;= 1000 题目数据 保证 primes[i] 是一个质数 primes 中的所有值都 互不相同 ，且按 递增顺序 排列 思路分析 # 题目的意思还是比较好理解的，我们需要列出来一个列表（的前n项），这个列表里包括了所有的超级丑数，而我们关心的就是只有第n个超级丑数。 列这个表有许多种思路。比较常见的是优先队列的思路。\n优先对列是一个特殊的数据结构，它能保证在其中的所有数字都是有序的。 既然如此，我们可以维护一个优先队列，原始值为[1]，并对它进行n次如下操作:\n1.在第n次的时候，从优先队列中取出一个最小的数字t 2.t即为第n个超级丑数 3.将t * primes[0], t * primes[1], .....等数字放入优先队列中。 4.这样就能保证所有超级丑数都曾经被加入过该优先队列，而且因为我们是按照从小到大的顺序取的，所以第二条成立 按照这个思路，我们可以在\\(O(n * primes.length * log_2(n * primes.length))\\)的复杂度内计算出第n个超级丑数。\nRust代码 # use std::collections::*; # struct Solution; impl Solution { pub fn nth_super_ugly_number(mut n: i32, primes: Vec\u0026lt;i32\u0026gt;) -\u0026gt; i32 { let mut set = BTreeSet::new(); set.insert(1); while set.len() \u0026gt; 0 { let first = *set.iter().next().unwrap(); set.remove(\u0026amp;first); for \u0026amp;prime in \u0026amp;primes { if (first as i64) * (prime as i64) \u0026lt; (i32::MAX as i64) { set.insert(first * prime); } } n -= 1; if n == 0 { return first; } } 0 } } 进阶方法 # 思路 # 不难看出，超级丑数数列中的所有项都可以表示为 primes中的一个数和另一个在其之前出现的超级丑数的乘积。\n看懂了上面的那个方法之后，也可以看出，我们再每一步将一个超级丑数取出来，乘了一个primes中的数字，再放回到超级丑数的序列中作为候选。\n但这样会出现许多冗余和不必要的计算。\n比如超级丑数的数列过长，而我们需要的可能只是前几项。\n这促使我们找到一个新的方法来计算\n因此，我们设立一个新的数组pointer，其中的每一项和primes中的每一项都对应，且初值均为0。在这种情况下，我们也需要将丑数记录在一个名为dp的数列里\n在每一次循环中做如下的工作\n1.计算出当前最小候选超级丑数的值 2.具体的流程为求 dp[pointer[i]] * primes[i] 的最小值(i的取值范围就是primes的长度m) 3.该数即为下一个超级丑数 dp_nxt 4.对于所有项 dp[pointer[i]] * primes[i] == dp_nxt 的i，均使 pointer[i] 增加1 再理一遍思路，所有超级丑数都是由之前出现过的一个超级丑数乘上primes中的一个数可以获得的。而现在pointer指示的就是之前出现过的超级丑数的位置。\n可能出现重复的情况，比如2*9和6*3都会获得18，也就是说为什么对于所有符合最小值的项均需在pointer加1。\n按照这种遍历方式，可以从小到大完全遍历所有可能出现的超级丑数，并且省去了很多无效计算。\n按照这个思路，我们可以在O(n * primes.length)的时间内完成所有的计算。\nuse std::collections::*; # struct Solution; impl Solution { pub fn nth_super_ugly_number(n: i32, primes: Vec\u0026lt;i32\u0026gt;) -\u0026gt; i32 { let mut dp = vec![1]; let n = n as usize; let m = primes.len(); let mut pointer = vec![0; m]; while dp.len() \u0026lt; n { let mut min_pos = 0; for i in 1..m { if dp[pointer[i]] * primes[i] \u0026lt; dp[pointer[min_pos]] * primes[min_pos] { min_pos = i; } } let tmp = dp[pointer[min_pos]] * primes[min_pos]; dp.push(tmp); for i in 0..m { if dp[pointer[i]] * primes[i] == tmp { pointer[i] += 1; } } } println!(\u0026#34;{:?}\u0026#34;, dp); dp[n - 1] } } ","date":"2021年8月9日","externalUrl":null,"permalink":"/leetcode/2021/08/09/","section":"LeetCode 题解","summary":"","title":"No.313 超级丑数","type":"leetcode"},{"content":"泰波那契序列\u0026nbsp;Tn\u0026nbsp;定义如下：\u0026nbsp;\nT0 = 0, T1 = 1, T2 = 1, 且在 n \u0026gt;= 0\u0026nbsp;的条件下 Tn+3 = Tn + Tn+1 + Tn+2\n给你整数\u0026nbsp;n，请返回第 n 个泰波那契数\u0026nbsp;Tn 的值。\n\u0026nbsp;\n示例 1：\n输入：n = 4 输出：4 解释： T_3 = 0 + 1 + 1 = 2 T_4 = 1 + 1 + 2 = 4 示例 2：\n输入：n = 25 输出：1389537 \u0026nbsp;\n提示：\n0 \u0026lt;= n \u0026lt;= 37 答案保证是一个 32 位整数，即\u0026nbsp;answer \u0026lt;= 2^31 - 1。 思路分析 # 通过观察，发现题目要求的是一个数列的和。而且这个数列可以通过前面的3项来计算得到后面的一项。因此我们可以用最简单的暴力算法来做。 也就是我们新建一个数组，里面有固定的三项[0,1,1]。这是题目中给我们的条件，然后我们根据给入的n去计算到我们需要的地方，就可以获得第 N 个泰波那契数了\nRust代码 # # struct Solution; impl Solution { pub fn tribonacci(n: i32) -\u0026gt; i32 { let n = n as usize; let mut v = vec![0, 1, 1]; let mut cur_len = 3; while cur_len \u0026lt;= n { v.push(v[cur_len - 1] + v[cur_len - 2] + v[cur_len - 3]); cur_len += 1; } v[n] } } 运行效果 # # fn main() { # pub fn tribonacci(n: i32) -\u0026gt; i32 { # let n = n as usize; # let mut v = vec![0, 1, 1]; # let mut cur_len = 3; # while cur_len \u0026lt;= n { # v.push(v[cur_len - 1] + v[cur_len - 2] + v[cur_len - 3]); # cur_len += 1; # } # v[n] # } println!(\u0026#34;{:?}\u0026#34;, tribonacci(2)); # } ","date":"2021年8月8日","externalUrl":null,"permalink":"/leetcode/2021/08/08/","section":"LeetCode 题解","summary":"","title":"No.1137 第N个泰波那契数","type":"leetcode"},{"content":"","date":"2021年8月1日","externalUrl":null,"permalink":"/tags/archive/","section":"Tags","summary":"","title":"Archive","type":"tags"},{"content":"","date":"2021年8月1日","externalUrl":null,"permalink":"/categories/site/","section":"Categories","summary":"","title":"Site","type":"categories"},{"content":"之前是在蚂蚁笔记上进行写作。但因为诸多问题，将其迁移到GitHub Pages上来进行维护。 如需阅览之前的笔记，请点击下载(共有93篇)\n可能会比较乱，因为没有按照时间加标签，但根据题号还是可以找到指定的题目的。 ","date":"2021年8月1日","externalUrl":null,"permalink":"/leetcode/leanote/","section":"LeetCode 题解","summary":"","title":"关于从蚂蚁笔记迁移而来的说明","type":"leetcode"},{"content":"","date":"2020年8月14日","externalUrl":null,"permalink":"/tags/hugo/","section":"Tags","summary":"","title":"Hugo","type":"tags"},{"content":"","date":"2020年8月14日","externalUrl":null,"permalink":"/tags/website/","section":"Tags","summary":"","title":"Website","type":"tags"},{"content":" 欢迎来到曙光磁铁的小站 # 欢迎来到曙光磁铁的小站\n","date":"2020年8月14日","externalUrl":null,"permalink":"/about/startup/","section":"重要说明","summary":"","title":"曙光磁铁的小站开站文档","type":"about"},{"content":"","date":"2020年8月14日","externalUrl":null,"permalink":"/about/","section":"重要说明","summary":"","title":"重要说明","type":"about"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]