[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fC08ZwBm80jA2AvH_ZlFgQ7e-7XvpJtkOstj33bVv8P4":3,"$fJU-4tot_gC5fDkujNeoE-cGsdMy5V_KcdUXLuAnTFgw":16,"$f4Wq3imfJ7jya_IPMsWdd_XgYJi1xLGep65wC3uaN4mQ":423},{"slug":4,"title":5,"description":6,"content":7,"content_html":8,"pub_date":9,"tags":10,"draft":15},"algorithm-sliding-window","滑动窗口算法：从暴力到 O(n) 的思维跃迁","系统讲解滑动窗口算法的核心模板、适用题型，配合三道经典 LeetCode 题目的完整 C++ 实现，彻底理解双指针收缩思路。","import Chart from '..\u002F..\u002Fcomponents\u002FChart.vue'\n\nexport const complexityData = {\n  labels: ['n=10', 'n=100', 'n=500', 'n=1000', 'n=5000'],\n  datasets: [\n    {\n      label: 'O(n) 滑动窗口',\n      data: [10, 100, 500, 1000, 5000],\n      borderColor: 'rgba(0,212,255,1)',\n      backgroundColor: 'rgba(0,212,255,0.15)',\n      tension: 0.4,\n      fill: true,\n      pointBackgroundColor: 'rgba(0,212,255,1)',\n      pointRadius: 4,\n    },\n    {\n      label: 'O(n²) 暴力枚举',\n      data: [100, 10000, 250000, 1000000, 25000000],\n      borderColor: 'rgba(255,0,170,1)',\n      backgroundColor: 'rgba(255,0,170,0.12)',\n      tension: 0.4,\n      fill: true,\n      pointBackgroundColor: 'rgba(255,0,170,1)',\n      pointRadius: 4,\n    }\n  ]\n}\n\nexport const complexityOptions = {\n  scales: {\n    x: {\n      ticks: { color: '#8888aa', font: { family: 'JetBrains Mono', size: 10 } },\n      grid: { color: 'rgba(30,30,48,0.8)' },\n      border: { display: false },\n    },\n    y: {\n      type: 'logarithmic',\n      ticks: { color: '#8888aa', font: { family: 'JetBrains Mono', size: 10 } },\n      grid: { color: 'rgba(30,30,48,0.8)' },\n      border: { display: false },\n    }\n  }\n}\n\n有一类题，暴力解法显而易见，O(n²) 甚至 O(n³)，但总觉得有更好的办法——答案往往是滑动窗口。\n\n滑动窗口的本质是一种**避免重复计算**的思维：通过维护一个可伸缩的区间 `[left, right]`，用 O(1) 的代价在每次移动时更新状态，把嵌套循环压缩成单次遍历。\n\n**直觉上的差距有多大？** 下图展示随输入规模增长，两种算法的操作次数对比（对数坐标）：\n\n\u003CChart client:only=\"vue\" type=\"line\" data={complexityData} options={complexityOptions} height={260} \u002F>\n\n当 n=5000 时，暴力枚举需要 2500 万次操作，而滑动窗口只需要 5000 次——差距达 **5000 倍**。\n\n---\n\n## 什么题型适合滑动窗口\n\n记住这几个关键词：\n\n- **连续子数组或子串**（不能跳跃，必须连续）\n- **求最长 \u002F 最短 \u002F 恰好满足某条件**\n- **条件可以随窗口扩缩而增量更新**（加一个元素、减一个元素，状态好维护）\n\n典型不适用场景：需要选不连续元素（用 DP）、全局排序后处理（用排序+双指针）。\n\n---\n\n## 双指针收缩模板\n\n所有滑动窗口题都能套进这个框架：\n\n```cpp\nint left = 0;\n\u002F\u002F window 是窗口的状态，可以是哈希表、计数器等\n\u002F\u002F 具体类型根据题目定\n\nfor (int right = 0; right \u003C n; right++) {\n    \u002F\u002F 1. 把 s[right] 加入窗口\n    window.add(s[right]);\n\n    \u002F\u002F 2. 判断是否需要收缩左边界\n    while (window 不满足条件) {\n        \u002F\u002F 把 s[left] 移出窗口\n        window.remove(s[left]);\n        left++;\n    }\n\n    \u002F\u002F 3. 此时窗口 [left, right] 满足条件，更新答案\n    ans = max(ans, right - left + 1);  \u002F\u002F 或其他逻辑\n}\n```\n\n关键逻辑：\n- `right` 只向右移动，负责**扩张**窗口\n- `left` 在需要时向右移动，负责**收缩**窗口\n- 每个元素最多进出窗口一次，总时间复杂度 O(n)\n\n---\n\n## 例题 1：最长无重复子串（LeetCode 3）\n\n> 给定字符串 s，找出其中不含重复字符的最长子串的长度。\n\n**分析**：窗口内不能有重复字符。当 `right` 指向的字符已经在窗口中时，收缩左边界直到重复消除。\n\n```cpp\n#include \u003Cstring>\n#include \u003Cunordered_map>\nusing namespace std;\n\nclass Solution {\npublic:\n    int lengthOfLongestSubstring(string s) {\n        unordered_map\u003Cchar, int> window;  \u002F\u002F 字符 → 出现次数\n        int left = 0, ans = 0;\n\n        for (int right = 0; right \u003C (int)s.size(); right++) {\n            char c = s[right];\n            window[c]++;  \u002F\u002F 扩张：加入右边界字符\n\n            \u002F\u002F 窗口内出现重复，收缩左边界\n            while (window[c] > 1) {\n                window[s[left]]--;\n                left++;\n            }\n\n            \u002F\u002F 此时 [left, right] 无重复字符\n            ans = max(ans, right - left + 1);\n        }\n\n        return ans;\n    }\n};\n```\n\n**走一遍示例**：`s = \"abcabcbb\"`\n\n```\nright=0, c='a': window={a:1}, 无重复, ans=1\nright=1, c='b': window={a:1,b:1}, 无重复, ans=2\nright=2, c='c': window={a:1,b:1,c:1}, 无重复, ans=3\nright=3, c='a': window={a:2,...}, 收缩: 移除s[0]='a', left=1, ans=3\nright=4, c='b': window={b:2,...}, 收缩: 移除s[1]='b', left=2, ans=3\n...\n最终 ans=3（\"abc\"）\n```\n\n时间复杂度 O(n)，空间 O(字符集大小)。\n\n---\n\n## 例题 2：长度最小的子数组（LeetCode 209）\n\n> 给定正整数数组 nums 和正整数 target，找出满足其和 ≥ target 的最小长度连续子数组。若不存在，返回 0。\n\n**分析**：求最短满足条件的窗口。当窗口和 ≥ target 时，尝试收缩左边界（看能不能更短）。\n\n```cpp\n#include \u003Cvector>\n#include \u003Cclimits>\nusing namespace std;\n\nclass Solution {\npublic:\n    int minSubArrayLen(int target, vector\u003Cint>& nums) {\n        int n = nums.size();\n        int left = 0, sum = 0;\n        int ans = INT_MAX;\n\n        for (int right = 0; right \u003C n; right++) {\n            sum += nums[right];  \u002F\u002F 扩张：加入右边界元素\n\n            \u002F\u002F 窗口和 >= target，尝试收缩\n            while (sum >= target) {\n                ans = min(ans, right - left + 1);  \u002F\u002F 更新最小长度\n                sum -= nums[left];  \u002F\u002F 收缩：移除左边界元素\n                left++;\n            }\n        }\n\n        return (ans == INT_MAX) ? 0 : ans;\n    }\n};\n```\n\n**注意与例题 1 的差异**：\n- 例题 1 是「窗口不满足条件时收缩」\n- 例题 2 是「窗口满足条件时收缩」（因为要找最小窗口）\n\n这是两种不同的收缩策略，根据题目要求选择：\n\n| 目标 | 收缩时机 | 更新答案时机 |\n|------|----------|--------------|\n| 最长满足条件 | 不满足时收缩 | 收缩后（满足时）更新 |\n| 最短满足条件 | 满足时收缩 | 收缩前（满足时）更新 |\n\n---\n\n## 例题 3：找所有字母异位词（LeetCode 438）\n\n> 给定字符串 s 和 p，找出 s 中所有 p 的异位词的起始索引。\n\n**分析**：固定窗口大小为 `p.size()`，窗口内字符频率与 p 完全一致时记录答案。\n\n```cpp\n#include \u003Cstring>\n#include \u003Cvector>\nusing namespace std;\n\nclass Solution {\npublic:\n    vector\u003Cint> findAnagrams(string s, string p) {\n        vector\u003Cint> ans;\n        if (s.size() \u003C p.size()) return ans;\n\n        \u002F\u002F need: p 中每个字符需要的数量\n        \u002F\u002F window: 当前窗口中每个字符的数量\n        vector\u003Cint> need(26, 0), window(26, 0);\n        for (char c : p) need[c - 'a']++;\n\n        int left = 0;\n        int valid = 0;  \u002F\u002F 窗口中满足 need 的字符种数\n\n        for (int right = 0; right \u003C (int)s.size(); right++) {\n            int c = s[right] - 'a';\n            window[c]++;\n            \u002F\u002F 如果这个字符恰好达到需要的数量，valid++\n            if (window[c] == need[c]) valid++;\n\n            \u002F\u002F 窗口大小超过 p，收缩左边界\n            if (right - left + 1 > (int)p.size()) {\n                int l = s[left] - 'a';\n                \u002F\u002F 收缩前检查 valid 是否会减少\n                if (window[l] == need[l]) valid--;\n                window[l]--;\n                left++;\n            }\n\n            \u002F\u002F 所有字符种数都满足\n            if (valid == 26) {\n                \u002F\u002F 实际上只需要 need 中非零的字符都满足\n                \u002F\u002F 这里用更简洁的向量比较\n            }\n        }\n\n        \u002F\u002F 更简洁的实现：直接比较向量\n        return findAnagramsClean(s, p);\n    }\n\n    vector\u003Cint> findAnagramsClean(string s, string p) {\n        vector\u003Cint> ans;\n        int sn = s.size(), pn = p.size();\n        if (sn \u003C pn) return ans;\n\n        vector\u003Cint> pCount(26, 0), sCount(26, 0);\n        for (char c : p) pCount[c - 'a']++;\n\n        \u002F\u002F 初始化第一个窗口\n        for (int i = 0; i \u003C pn; i++) sCount[s[i] - 'a']++;\n        if (sCount == pCount) ans.push_back(0);\n\n        \u002F\u002F 滑动窗口\n        for (int i = pn; i \u003C sn; i++) {\n            sCount[s[i] - 'a']++;           \u002F\u002F 加入右端新字符\n            sCount[s[i - pn] - 'a']--;      \u002F\u002F 移出左端旧字符\n            if (sCount == pCount) ans.push_back(i - pn + 1);\n        }\n\n        return ans;\n    }\n};\n```\n\n固定大小窗口更简单：每次右移一格，同时左端也移除一个元素，窗口大小始终为 `pn`。\n\n---\n\n## 滑动窗口 vs 前缀和 vs 双指针\n\n这三种技术经常让人困惑，核心区分：\n\n| 技术 | 适用场景 | 关键特征 |\n|------|----------|----------|\n| **滑动窗口** | 连续子数组，条件可增量维护 | 窗口大小可变，O(n) |\n| **前缀和** | 任意区间和的快速查询 | 预处理 O(n)，查询 O(1) |\n| **双指针（对撞）** | 有序数组，找两个数之和 | 一头一尾向中间逼近 |\n\n**判断用哪个**：\n\n- 需要查询任意区间 `[l, r]` 的和 → **前缀和**\n- 有序数组，找满足条件的两个元素 → **对撞双指针**\n- 连续区间，且状态能随窗口扩缩增量更新 → **滑动窗口**\n\n滑动窗口本质上也是双指针，只是两个指针都从左向右移动（而非对撞）。\n\n---\n\n## 小结\n\n滑动窗口的思维跃迁在于：\n\n**暴力**：枚举所有 `[i, j]` 区间，每次从头计算 → O(n²) 或更差  \n**优化**：维护一个窗口，扩张和收缩时增量更新状态 → O(n)\n\n关键是找到窗口状态的**增量维护方式**——能加一个元素、减一个元素，且每次 O(1)。一旦满足这个条件，滑动窗口就能用。\n\n遇到连续子数组\u002F子串类问题，先问自己：「如果我维护一个 `[left, right]` 的窗口，状态如何随右端扩张和左端收缩而更新？」答得出来，就能用滑动窗口。","\u003Cp>import Chart from ‘…\u002F…\u002Fcomponents\u002FChart.vue’\u003C\u002Fp>\n\u003Cp>export const complexityData = {\nlabels: [‘n=10’, ‘n=100’, ‘n=500’, ‘n=1000’, ‘n=5000’],\ndatasets: [\n{\nlabel: ‘O(n) 滑动窗口’,\ndata: [10, 100, 500, 1000, 5000],\nborderColor: ‘rgba(0,212,255,1)’,\nbackgroundColor: ‘rgba(0,212,255,0.15)’,\ntension: 0.4,\nfill: true,\npointBackgroundColor: ‘rgba(0,212,255,1)’,\npointRadius: 4,\n},\n{\nlabel: ‘O(n²) 暴力枚举’,\ndata: [100, 10000, 250000, 1000000, 25000000],\nborderColor: ‘rgba(255,0,170,1)’,\nbackgroundColor: ‘rgba(255,0,170,0.12)’,\ntension: 0.4,\nfill: true,\npointBackgroundColor: ‘rgba(255,0,170,1)’,\npointRadius: 4,\n}\n]\n}\u003C\u002Fp>\n\u003Cp>export const complexityOptions = {\nscales: {\nx: {\nticks: { color: ‘#8888aa’, font: { family: ‘JetBrains Mono’, size: 10 } },\ngrid: { color: ‘rgba(30,30,48,0.8)’ },\nborder: { display: false },\n},\ny: {\ntype: ‘logarithmic’,\nticks: { color: ‘#8888aa’, font: { family: ‘JetBrains Mono’, size: 10 } },\ngrid: { color: ‘rgba(30,30,48,0.8)’ },\nborder: { display: false },\n}\n}\n}\u003C\u002Fp>\n\u003Cp>有一类题，暴力解法显而易见，O(n²) 甚至 O(n³)，但总觉得有更好的办法——答案往往是滑动窗口。\u003C\u002Fp>\n\u003Cp>滑动窗口的本质是一种\u003Cstrong>避免重复计算\u003C\u002Fstrong>的思维：通过维护一个可伸缩的区间 \u003Ccode>[left, right]\u003C\u002Fcode>，用 O(1) 的代价在每次移动时更新状态，把嵌套循环压缩成单次遍历。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>直觉上的差距有多大？\u003C\u002Fstrong> 下图展示随输入规模增长，两种算法的操作次数对比（对数坐标）：\u003C\u002Fp>\n\u003CChart client:only=\"vue\" type=\"line\" data={complexityData} options={complexityOptions} height={260} \u002F>\n\u003Cp>当 n=5000 时，暴力枚举需要 2500 万次操作，而滑动窗口只需要 5000 次——差距达 \u003Cstrong>5000 倍\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Chr>\n\u003Ch2 id=\"什么题型适合滑动窗口\">什么题型适合滑动窗口\u003C\u002Fh2>\n\u003Cp>记住这几个关键词：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>连续子数组或子串\u003C\u002Fstrong>（不能跳跃，必须连续）\u003C\u002Fli>\n\u003Cli>\u003Cstrong>求最长 \u002F 最短 \u002F 恰好满足某条件\u003C\u002Fstrong>\u003C\u002Fli>\n\u003Cli>\u003Cstrong>条件可以随窗口扩缩而增量更新\u003C\u002Fstrong>（加一个元素、减一个元素，状态好维护）\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>典型不适用场景：需要选不连续元素（用 DP）、全局排序后处理（用排序+双指针）。\u003C\u002Fp>\n\u003Chr>\n\u003Ch2 id=\"双指针收缩模板\">双指针收缩模板\u003C\u002Fh2>\n\u003Cp>所有滑动窗口题都能套进这个框架：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">int left = 0;\n\u002F\u002F window 是窗口的状态，可以是哈希表、计数器等\n\u002F\u002F 具体类型根据题目定\n\nfor (int right = 0; right &lt; n; right++) {\n    \u002F\u002F 1. 把 s[right] 加入窗口\n    window.add(s[right]);\n\n    \u002F\u002F 2. 判断是否需要收缩左边界\n    while (window 不满足条件) {\n        \u002F\u002F 把 s[left] 移出窗口\n        window.remove(s[left]);\n        left++;\n    }\n\n    \u002F\u002F 3. 此时窗口 [left, right] 满足条件，更新答案\n    ans = max(ans, right - left + 1);  \u002F\u002F 或其他逻辑\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>关键逻辑：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>right\u003C\u002Fcode> 只向右移动，负责\u003Cstrong>扩张\u003C\u002Fstrong>窗口\u003C\u002Fli>\n\u003Cli>\u003Ccode>left\u003C\u002Fcode> 在需要时向右移动，负责\u003Cstrong>收缩\u003C\u002Fstrong>窗口\u003C\u002Fli>\n\u003Cli>每个元素最多进出窗口一次，总时间复杂度 O(n)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Chr>\n\u003Ch2 id=\"例题-1-最长无重复子串-leetcode-3\">例题 1：最长无重复子串（LeetCode 3）\u003C\u002Fh2>\n\u003Cblockquote>\n\u003Cp>给定字符串 s，找出其中不含重复字符的最长子串的长度。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>\u003Cstrong>分析\u003C\u002Fstrong>：窗口内不能有重复字符。当 \u003Ccode>right\u003C\u002Fcode> 指向的字符已经在窗口中时，收缩左边界直到重复消除。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">#include &lt;string&gt;\n#include &lt;unordered_map&gt;\nusing namespace std;\n\nclass Solution {\npublic:\n    int lengthOfLongestSubstring(string s) {\n        unordered_map&lt;char, int&gt; window;  \u002F\u002F 字符 → 出现次数\n        int left = 0, ans = 0;\n\n        for (int right = 0; right &lt; (int)s.size(); right++) {\n            char c = s[right];\n            window[c]++;  \u002F\u002F 扩张：加入右边界字符\n\n            \u002F\u002F 窗口内出现重复，收缩左边界\n            while (window[c] &gt; 1) {\n                window[s[left]]--;\n                left++;\n            }\n\n            \u002F\u002F 此时 [left, right] 无重复字符\n            ans = max(ans, right - left + 1);\n        }\n\n        return ans;\n    }\n};\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>走一遍示例\u003C\u002Fstrong>：\u003Ccode>s = &quot;abcabcbb&quot;\u003C\u002Fcode>\u003C\u002Fp>\n\u003Cpre>\u003Ccode>right=0, c='a': window={a:1}, 无重复, ans=1\nright=1, c='b': window={a:1,b:1}, 无重复, ans=2\nright=2, c='c': window={a:1,b:1,c:1}, 无重复, ans=3\nright=3, c='a': window={a:2,...}, 收缩: 移除s[0]='a', left=1, ans=3\nright=4, c='b': window={b:2,...}, 收缩: 移除s[1]='b', left=2, ans=3\n...\n最终 ans=3（&quot;abc&quot;）\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>时间复杂度 O(n)，空间 O(字符集大小)。\u003C\u002Fp>\n\u003Chr>\n\u003Ch2 id=\"例题-2-长度最小的子数组-leetcode-209\">例题 2：长度最小的子数组（LeetCode 209）\u003C\u002Fh2>\n\u003Cblockquote>\n\u003Cp>给定正整数数组 nums 和正整数 target，找出满足其和 ≥ target 的最小长度连续子数组。若不存在，返回 0。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>\u003Cstrong>分析\u003C\u002Fstrong>：求最短满足条件的窗口。当窗口和 ≥ target 时，尝试收缩左边界（看能不能更短）。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">#include &lt;vector&gt;\n#include &lt;climits&gt;\nusing namespace std;\n\nclass Solution {\npublic:\n    int minSubArrayLen(int target, vector&lt;int&gt;&amp; nums) {\n        int n = nums.size();\n        int left = 0, sum = 0;\n        int ans = INT_MAX;\n\n        for (int right = 0; right &lt; n; right++) {\n            sum += nums[right];  \u002F\u002F 扩张：加入右边界元素\n\n            \u002F\u002F 窗口和 &gt;= target，尝试收缩\n            while (sum &gt;= target) {\n                ans = min(ans, right - left + 1);  \u002F\u002F 更新最小长度\n                sum -= nums[left];  \u002F\u002F 收缩：移除左边界元素\n                left++;\n            }\n        }\n\n        return (ans == INT_MAX) ? 0 : ans;\n    }\n};\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>注意与例题 1 的差异\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>例题 1 是「窗口不满足条件时收缩」\u003C\u002Fli>\n\u003Cli>例题 2 是「窗口满足条件时收缩」（因为要找最小窗口）\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这是两种不同的收缩策略，根据题目要求选择：\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>目标\u003C\u002Fth>\n\u003Cth>收缩时机\u003C\u002Fth>\n\u003Cth>更新答案时机\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>最长满足条件\u003C\u002Ftd>\n\u003Ctd>不满足时收缩\u003C\u002Ftd>\n\u003Ctd>收缩后（满足时）更新\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>最短满足条件\u003C\u002Ftd>\n\u003Ctd>满足时收缩\u003C\u002Ftd>\n\u003Ctd>收缩前（满足时）更新\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Chr>\n\u003Ch2 id=\"例题-3-找所有字母异位词-leetcode-438\">例题 3：找所有字母异位词（LeetCode 438）\u003C\u002Fh2>\n\u003Cblockquote>\n\u003Cp>给定字符串 s 和 p，找出 s 中所有 p 的异位词的起始索引。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>\u003Cstrong>分析\u003C\u002Fstrong>：固定窗口大小为 \u003Ccode>p.size()\u003C\u002Fcode>，窗口内字符频率与 p 完全一致时记录答案。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">#include &lt;string&gt;\n#include &lt;vector&gt;\nusing namespace std;\n\nclass Solution {\npublic:\n    vector&lt;int&gt; findAnagrams(string s, string p) {\n        vector&lt;int&gt; ans;\n        if (s.size() &lt; p.size()) return ans;\n\n        \u002F\u002F need: p 中每个字符需要的数量\n        \u002F\u002F window: 当前窗口中每个字符的数量\n        vector&lt;int&gt; need(26, 0), window(26, 0);\n        for (char c : p) need[c - 'a']++;\n\n        int left = 0;\n        int valid = 0;  \u002F\u002F 窗口中满足 need 的字符种数\n\n        for (int right = 0; right &lt; (int)s.size(); right++) {\n            int c = s[right] - 'a';\n            window[c]++;\n            \u002F\u002F 如果这个字符恰好达到需要的数量，valid++\n            if (window[c] == need[c]) valid++;\n\n            \u002F\u002F 窗口大小超过 p，收缩左边界\n            if (right - left + 1 &gt; (int)p.size()) {\n                int l = s[left] - 'a';\n                \u002F\u002F 收缩前检查 valid 是否会减少\n                if (window[l] == need[l]) valid--;\n                window[l]--;\n                left++;\n            }\n\n            \u002F\u002F 所有字符种数都满足\n            if (valid == 26) {\n                \u002F\u002F 实际上只需要 need 中非零的字符都满足\n                \u002F\u002F 这里用更简洁的向量比较\n            }\n        }\n\n        \u002F\u002F 更简洁的实现：直接比较向量\n        return findAnagramsClean(s, p);\n    }\n\n    vector&lt;int&gt; findAnagramsClean(string s, string p) {\n        vector&lt;int&gt; ans;\n        int sn = s.size(), pn = p.size();\n        if (sn &lt; pn) return ans;\n\n        vector&lt;int&gt; pCount(26, 0), sCount(26, 0);\n        for (char c : p) pCount[c - 'a']++;\n\n        \u002F\u002F 初始化第一个窗口\n        for (int i = 0; i &lt; pn; i++) sCount[s[i] - 'a']++;\n        if (sCount == pCount) ans.push_back(0);\n\n        \u002F\u002F 滑动窗口\n        for (int i = pn; i &lt; sn; i++) {\n            sCount[s[i] - 'a']++;           \u002F\u002F 加入右端新字符\n            sCount[s[i - pn] - 'a']--;      \u002F\u002F 移出左端旧字符\n            if (sCount == pCount) ans.push_back(i - pn + 1);\n        }\n\n        return ans;\n    }\n};\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>固定大小窗口更简单：每次右移一格，同时左端也移除一个元素，窗口大小始终为 \u003Ccode>pn\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Chr>\n\u003Ch2 id=\"滑动窗口-vs-前缀和-vs-双指针\">滑动窗口 vs 前缀和 vs 双指针\u003C\u002Fh2>\n\u003Cp>这三种技术经常让人困惑，核心区分：\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>技术\u003C\u002Fth>\n\u003Cth>适用场景\u003C\u002Fth>\n\u003Cth>关键特征\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Cstrong>滑动窗口\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd>连续子数组，条件可增量维护\u003C\u002Ftd>\n\u003Ctd>窗口大小可变，O(n)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Cstrong>前缀和\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd>任意区间和的快速查询\u003C\u002Ftd>\n\u003Ctd>预处理 O(n)，查询 O(1)\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Cstrong>双指针（对撞）\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd>有序数组，找两个数之和\u003C\u002Ftd>\n\u003Ctd>一头一尾向中间逼近\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>\u003Cstrong>判断用哪个\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul>\n\u003Cli>需要查询任意区间 \u003Ccode>[l, r]\u003C\u002Fcode> 的和 → \u003Cstrong>前缀和\u003C\u002Fstrong>\u003C\u002Fli>\n\u003Cli>有序数组，找满足条件的两个元素 → \u003Cstrong>对撞双指针\u003C\u002Fstrong>\u003C\u002Fli>\n\u003Cli>连续区间，且状态能随窗口扩缩增量更新 → \u003Cstrong>滑动窗口\u003C\u002Fstrong>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>滑动窗口本质上也是双指针，只是两个指针都从左向右移动（而非对撞）。\u003C\u002Fp>\n\u003Chr>\n\u003Ch2 id=\"小结\">小结\u003C\u002Fh2>\n\u003Cp>滑动窗口的思维跃迁在于：\u003C\u002Fp>\n\u003Cp>\u003Cstrong>暴力\u003C\u002Fstrong>：枚举所有 \u003Ccode>[i, j]\u003C\u002Fcode> 区间，每次从头计算 → O(n²) 或更差\u003Cbr>\n\u003Cstrong>优化\u003C\u002Fstrong>：维护一个窗口，扩张和收缩时增量更新状态 → O(n)\u003C\u002Fp>\n\u003Cp>关键是找到窗口状态的\u003Cstrong>增量维护方式\u003C\u002Fstrong>——能加一个元素、减一个元素，且每次 O(1)。一旦满足这个条件，滑动窗口就能用。\u003C\u002Fp>\n\u003Cp>遇到连续子数组\u002F子串类问题，先问自己：「如果我维护一个 \u003Ccode>[left, right]\u003C\u002Fcode> 的窗口，状态如何随右端扩张和左端收缩而更新？」答得出来，就能用滑动窗口。\u003C\u002Fp>\n","2026-04-30",[11,12,13,14],"算法","滑动窗口","leetcode","cpp",false,[17,30,41,53,63,70,77,84,91,98,108,117,127,136,143,151,160,169,178,188,195,204,210,216,222,231,237,240,248,258,267,276,286,296,306,314,324,335,345,354,362,368,376,384,392,400,408,415],{"slug":18,"title":19,"description":20,"pub_date":21,"tags":22,"draft":15,"word_count":29},"ide-skills-guide","Agent Skills 完全指南：21 款第三方 Skill 深度评测与使用心得","全面评测 21 款第三方 Agent Skills，涵盖 Vue 生态、前端设计、构建工具、实用工具四大分类。从安装配置到实际使用场景，带你了解每个 Skill 的功能特点、最佳实践与使用心得。","2026-06-15",[23,24,25,26,27,28],"agent","skills","AI","效率工具","前端","Vue",4169,{"slug":31,"title":32,"description":33,"pub_date":34,"tags":35,"draft":15,"word_count":40},"linux-kernel-skeleton-struct-funcptr-container_of","Linux 内核骨架：struct、函数指针与 container_of","读懂 Linux 内核源码的三件套：巨大的 struct 组合代替继承、函数指针表实现虚派发、container_of 宏从嵌入成员找回完整对象。","2026-05-09",[36,37,38,39],"linux","kernel","C","container_of",1369,{"slug":42,"title":43,"description":44,"pub_date":45,"tags":46,"draft":15,"word_count":52},"astro-complete-guide-2025","Astro 5 深度剖析：Islands 架构原理、构建优化与 Cloudflare Workers 边缘部署","从编译器视角解析 Astro 5 的 Islands 架构实现原理，Content Layer API 的 Vite 插件机制，Server Islands 的流式渲染，以及如何在 Cloudflare Workers + D1 边缘环境下榨干性能。","2026-05-08",[47,48,49,50,51],"astro","frontend","cloudflare","performance","architecture",3663,{"slug":54,"title":55,"description":56,"pub_date":57,"tags":58,"draft":15,"word_count":62},"llm-prompt-engineering","Prompt Engineering 实战：让 LLM 真正听话的技巧","System prompt 怎么写、Few-shot 怎么设计、Chain-of-Thought 原理，以及常见失败模式和调试方法。","2026-05-03",[59,60,61],"ai","llm","工程实践",1723,{"slug":64,"title":65,"description":66,"pub_date":57,"tags":67,"draft":15,"word_count":69},"rag-system-design","RAG 系统设计：从 naive 到 production-ready","Retrieval-Augmented Generation 不只是「向量数据库 + LLM」，分块策略、召回质量、重排序、缓存才是工程核心。",[59,68,60,61],"rag",1613,{"slug":71,"title":72,"description":73,"pub_date":57,"tags":74,"draft":15,"word_count":76},"git-advanced-workflow","Git 进阶工作流：rebase、cherry-pick、bisect 的正确使用","merge 会了，但 rebase 总搞错？bisect 找 bug 提交？interactive rebase 整理历史？这篇一次说清楚。",[75,61],"git",1396,{"slug":78,"title":79,"description":80,"pub_date":57,"tags":81,"draft":15,"word_count":83},"docker-practical-guide","Docker 实战：从会用到用好","会 docker run 不够，Dockerfile 最佳实践、多阶段构建、Compose 编排、镜像瘦身才是日常真正需要的。",[82,36,61],"docker",1268,{"slug":85,"title":86,"description":87,"pub_date":57,"tags":88,"draft":15,"word_count":90},"anthropics-skills-guide","anthropics\u002Fskills：Anthropic 官方 Agent Skills 仓库解析","Anthropic 官方开源的 Agent Skills 标准仓库，127k stars，解析 SKILL.md 规范、17 个示例 skill 的设计模式，以及如何在 Claude Code \u002F Claude.ai \u002F API 中使用",[59,89,23,24],"Claude",2090,{"slug":92,"title":93,"description":94,"pub_date":57,"tags":95,"draft":15,"word_count":97},"karpathy-claude-code-guidelines","Karpathy 的 LLM 编码批评与 CLAUDE.md 最佳实践","基于 Andrej Karpathy 对 LLM 编程助手的观察，forrestchang 提炼出一个 CLAUDE.md 文件，4 条原则解决 AI 编码的典型失控问题：乱猜假设、过度设计、乱改代码、目标不清",[59,89,96,61],"Claude Code",2699,{"slug":99,"title":100,"description":101,"pub_date":57,"tags":102,"draft":15,"word_count":107},"typescript-advanced-patterns","TypeScript 高级模式：让类型系统为你工作","基础 TS 会了但类型总是 any？条件类型、映射类型、模板字面量类型、infer 关键字才是 TS 的真正威力。",[103,104,105,106],"typescript","类型系统","前端工程","高级模式",1419,{"slug":109,"title":110,"description":111,"pub_date":57,"tags":112,"draft":15,"word_count":116},"linux-performance-tuning","Linux 性能调优实战：从 top 到 perf 的完整工具链","遇到性能问题不知道从哪下手？这篇建立系统化的排查思路，从 CPU\u002F内存\u002FIO\u002F网络逐层分析。",[36,113,114,115],"性能","运维","系统编程",1524,{"slug":118,"title":119,"description":120,"pub_date":57,"tags":121,"draft":15,"word_count":126},"python-functional-programming","Python 函数式编程：map\u002Ffilter\u002Freduce 之外","Python 不是纯函数式语言，但 functools、itertools、偏函数、闭包这些工具用好了能让代码简洁一个量级。",[122,123,124,125],"python","函数式","闭包","装饰器",1867,{"slug":128,"title":129,"description":130,"pub_date":57,"tags":131,"draft":15,"word_count":135},"python-oop-guide","Python 面向对象：__init__ 之外你需要知道的","Python OOP 不只是 class + __init__，魔术方法、描述符、元类才是真正的武器。",[122,132,133,134],"OOP","面向对象","魔术方法",1792,{"slug":137,"title":138,"description":139,"pub_date":57,"tags":140,"draft":15,"word_count":142},"python-data-structures","Python 内置数据结构深度解析","list、dict、set、tuple 不只是数据容器，搞懂它们的底层实现和时间复杂度，才能写出高性能 Python。",[122,141,113,11],"数据结构",1517,{"slug":144,"title":145,"description":146,"pub_date":57,"tags":147,"draft":15,"word_count":150},"python-basics-quick-start","Python 快速上手：写给有编程基础的人","已经会其他语言，想快速掌握 Python 的语法特性和思维方式，这篇是捷径。",[122,148,149],"入门","基础",1607,{"slug":152,"title":153,"description":154,"pub_date":57,"tags":155,"draft":15,"word_count":159},"python-dataclass-pydantic","Python dataclass vs Pydantic：数据类选型指南","dataclass 是标准库的轻量选择，Pydantic v2 是带验证的重武器，什么时候用哪个，这篇说清楚。",[122,156,157,158],"dataclass","pydantic","数据验证",1323,{"slug":161,"title":162,"description":163,"pub_date":57,"tags":164,"draft":15,"word_count":168},"python-asyncio-practical","Python asyncio 实战：从回调地狱到协程优雅","asyncio 是 Python 异步编程的核心，搞懂 event loop、Task、gather 这些概念才能写出真正高效的异步代码。",[122,165,166,167],"asyncio","并发","网络编程",1258,{"slug":170,"title":171,"description":172,"pub_date":57,"tags":173,"draft":15,"word_count":177},"python-type-hints-guide","Python 类型注解完全指南：从入门到实践","Python 3.5+ 引入类型注解，配合 mypy\u002Fpyright 让 Python 也能享受静态类型检查的好处。",[122,174,175,176],"typescript-style","type-hints","工具链",1102,{"slug":179,"title":180,"description":181,"pub_date":182,"tags":183,"draft":15,"word_count":187},"pwa-install-update-button","PWA 踩坑：为什么安装按钮从来不出现","从 beforeinstallprompt 到 Service Worker waiting，把 PWA 的安装与更新提示真正做对","2026-05-02",[184,185,186],"pwa","javascript","web",1683,{"slug":189,"title":190,"description":191,"pub_date":192,"tags":193,"draft":15,"word_count":194},"openclaw-vs-hermes-agent","OpenClaw vs Hermes Agent：两个本地优先 Agent 的设计差异","OpenClaw（Novita AI）和 Hermes Agent（Nous Research）都是本地运行的个人 AI Agent，但在记忆系统、技能学习、运行环境和模型生态上走了不同的路。深入对比两种架构的核心差异。","2026-05-01",[59,23,60],1679,{"slug":196,"title":197,"description":198,"pub_date":192,"tags":199,"draft":15,"word_count":203},"cpp-random-design-patterns","C++ 设计模式实战：RAII、观察者、工厂","用现代 C++（C++17\u002F20）实现三种高频设计模式：RAII 资源管理、观察者模式事件系统、工厂模式插件架构。每种模式给出问题场景、实现代码和真实工程案例。",[14,200,201,202],"设计模式","c++17","工程",2613,{"slug":205,"title":206,"description":207,"pub_date":192,"tags":208,"draft":15,"word_count":209},"data-structures-fundamentals","数据结构基础：从数组到红黑树","系统梳理常用数据结构的核心原理、时间复杂度和适用场景。数组、链表、栈、队列、哈希表、二叉树、堆、图，每种结构附实现要点和 C++ 代码片段。",[141,11,14,149],3004,{"slug":211,"title":212,"description":213,"pub_date":9,"tags":214,"draft":15,"word_count":215},"ai-agent-what-is","什么是 AI Agent？从 LLM 到自主执行","LLM 本身是无状态问答机，Agent 是什么让它’动’起来的？本文深入解析 Agent 的四个核心能力、ReAct 框架、工具调用原理，以及主流框架横向对比。",[59,23,60],2116,{"slug":217,"title":218,"description":219,"pub_date":9,"tags":220,"draft":15,"word_count":221},"ai-agent-memory","AI Agent 的记忆系统：从上下文窗口到长期记忆","深入拆解 AI Agent 的四种记忆类型、上下文窗口压缩策略、RAG 向量检索原理，以及三种典型失败模式和工程选型建议。",[59,23,68],2052,{"slug":223,"title":224,"description":225,"pub_date":9,"tags":226,"draft":15,"word_count":230},"network-proxy-vpn-guide","代理与翻墙技术原理：从 HTTP 代理到现代协议","深入解析代理与 VPN 的本质区别，梳理从 SOCKS5 到 Shadowsocks、V2Ray\u002FXray、Hysteria2 的协议演进，以及机场订阅的技术本质。",[227,228,229],"网络","代理","协议",2148,{"slug":232,"title":233,"description":234,"pub_date":9,"tags":235,"draft":15,"word_count":150},"algorithm-binary-search","二分查找：永远写不对？记住这个模板","彻底搞清楚二分查找的边界问题：闭区间和左闭右开两套模板、三道经典 LeetCode 题目完整 C++ 实现，以及二分答案的进阶思路。",[11,236,13,14],"二分查找",{"slug":4,"title":5,"description":6,"pub_date":9,"tags":238,"draft":15,"word_count":239},[11,12,13,14],1943,{"slug":241,"title":242,"description":243,"pub_date":9,"tags":244,"draft":15,"word_count":247},"network-clash-config","Clash \u002F Mihomo 配置详解：规则、策略组与分流","深入解析 Clash\u002FMihomo 的核心配置结构，包括代理节点、策略组类型、规则优先级、DNS fake-ip 模式，以及一份实用的完整配置模板。",[227,245,228,246],"clash","配置",1292,{"slug":249,"title":250,"description":251,"pub_date":252,"tags":253,"draft":15,"word_count":257},"hid-hotplug","HID 设备热插拔检测：从 udev 到 node-hid","在 Linux 上用 node-hid + usb 库实现可靠的 USB HID 设备热插拔检测，踩坑记录","2026-04-28",[14,254,36,255,256],"hid","nodejs","electron",2039,{"slug":259,"title":260,"description":261,"pub_date":262,"tags":263,"draft":15,"word_count":266},"electron-ipc-types","Electron IPC 类型安全：从 any 到完全类型化","用 TypeScript 泛型封装 Electron IPC，彻底消灭 any，preload 契约集中管理","2026-04-25",[256,103,264,265],"ipc","vue",1446,{"slug":268,"title":269,"description":270,"pub_date":271,"tags":272,"draft":15,"word_count":275},"element-plus-popover-hide","手动关闭多个 el-popover（不用 v-model:visible）","通过 ref + Reflect.get 调用 hide() 方法手动关闭 Element Plus Popover，解释 Vue3 Proxy 导致无法直接调用实例方法的原因。","2024-10-25",[265,273,274],"element-plus","vue3",1321,{"slug":277,"title":278,"description":279,"pub_date":280,"tags":281,"draft":15,"word_count":285},"vite-vue3-ts-elementplus-pinia","用 Vite+（vp）从零搭建 Vue3 + TypeScript + Element Plus + Pinia + Vue Router","使用 Vite+ 统一工具链（vp）一条命令搭建 Vue3 全家桶，涵盖按需导入、Pinia store、路由配置，以及常见坑的解决方案。","2024-08-27",[265,282,103,273,283,284],"vite","pinia","vite-plus",1960,{"slug":287,"title":288,"description":289,"pub_date":290,"tags":291,"draft":15,"word_count":295},"cef-lnk2038-iterator-debug-level","CEF LNK2038：解决 _ITERATOR_DEBUG_LEVEL 不匹配错误","分析 CEF（Chromium Embedded Framework）集成时出现的 LNK2038 _ITERATOR_DEBUG_LEVEL 链接错误，从根本原因到解决方案的完整指南。","2024-05-07",[14,292,293,294],"CEF","Visual Studio","链接错误",1509,{"slug":297,"title":298,"description":299,"pub_date":300,"tags":301,"draft":15,"word_count":305},"npm-electron-install-fix","彻底解决 npm 安装 Electron 失败的问题","分析 npm install electron 失败的根本原因（下载二进制超时\u002F被墙），通过国内镜像（npmmirror）彻底解决，并介绍多种备选方案和常见错误排查。","2024-03-01",[256,302,303,304],"npm","前端工具链","国内镜像",1494,{"slug":307,"title":308,"description":309,"pub_date":310,"tags":311,"draft":15,"word_count":313},"git-out-of-memory","解决 git 报错：Fatal: Out of memory, malloc failed","分析 git 大仓库操作时出现 Out of memory malloc failed 的根本原因，通过调整 pack.windowMemory、http.postBuffer 和 git repack 彻底解决。","2024-01-31",[75,36,312],"工具",2244,{"slug":315,"title":316,"description":317,"pub_date":318,"tags":319,"draft":15,"word_count":323},"vmware-tools-install","在 VMware 虚拟机中安装 open-vm-tools 完整指南","详解 VMware Tools 的作用、open-vm-tools 与官方 VMware Tools 的区别，以及在 Ubuntu 虚拟机中安装并生效的完整步骤和常见问题排查。","2023-11-21",[320,36,321,322],"VMware","Ubuntu","虚拟机",2523,{"slug":325,"title":326,"description":327,"pub_date":328,"tags":329,"draft":15,"word_count":334},"load-balancing-algorithms","负载均衡算法完全指南：从轮询到一致性哈希","系统梳理静态与动态负载均衡算法，涵盖轮询、随机、权重、IP Hash、一致性 Hash、最少连接、最快响应等，并对比 Nginx、Dubbo、Spring Cloud LoadBalancer 的实现差异。","2023-11-15",[330,331,332,333],"分布式","负载均衡","Nginx","微服务",1764,{"slug":336,"title":337,"description":338,"pub_date":339,"tags":340,"draft":15,"word_count":344},"win-cw2a-ca2w","ATL 字符串转换：CW2A 与 CA2W 完全指南","详解 ATL 宏 CW2A\u002FCA2W 在 Unicode 与 ANSI 之间的字符串转换用法、头文件依赖、USES_CONVERSION 宏的作用与常见陷阱。","2023-06-09",[14,341,342,343],"windows","ATL","字符串",1665,{"slug":346,"title":347,"description":348,"pub_date":339,"tags":349,"draft":15,"word_count":353},"csharp-sendmessage-cpp","C# 通过 SendMessage 向 C++ 窗口发送消息与字符串","使用 P\u002FInvoke 调用 user32.dll 的 SendMessage，从 C# 发送自定义 WM_USER 消息及字符串指针给 C++ 原生窗口，并在 C++ 侧正确接收和转换。",[350,14,341,351,352],"C#","互操作","PInvoke",1554,{"slug":355,"title":356,"description":357,"pub_date":358,"tags":359,"draft":15,"word_count":361},"win-postmessage-vector","Windows PostMessage 跨线程传递 std::vector 指针","通过 PostMessage 在 Windows 消息队列中传递 std::vector 指针，使用 reinterpret_cast 将指针装入 LPARAM，并在接收方正确释放内存。","2023-05-26",[14,341,360],"WinAPI",1823,{"slug":363,"title":364,"description":365,"pub_date":358,"tags":366,"draft":15,"word_count":367},"exe-dll-single-package","将 EXE 和 DLL 打包成单一可执行文件","介绍两种将 exe 和依赖 dll 打包成单文件的方案：Enigma Virtual Box 和 WinRAR 自解压，适合发布 Windows 桌面程序时简化分发流程。",[341,14,312],1619,{"slug":369,"title":370,"description":371,"pub_date":358,"tags":372,"draft":15,"word_count":375},"cpp-random-mt19937","C++ 现代随机数生成：用 mt19937 彻底告别 rand()","深入讲解为什么 rand() 不够用，以及如何用 C++11 的 \u003Crandom> 库正确生成高质量随机数，涵盖 mt19937、各种分布和线程安全。",[14,373,374],"c++11","random",1549,{"slug":377,"title":378,"description":379,"pub_date":380,"tags":381,"draft":15,"word_count":383},"win-startup-registry","C++ 实现程序开机自启动：注册表方式详解","通过操作 Windows 注册表 Run 键实现程序开机自启动，包括 HKCU 与 HKLM 区别、完整封装代码、工作目录问题和 UAC 权限处理。","2022-12-26",[341,14,382],"registry",1201,{"slug":385,"title":386,"description":387,"pub_date":388,"tags":389,"draft":15,"word_count":391},"mfc-cstring-wparam","MFC 中 CString 与 WPARAM 之间的转换","详解 MFC 消息传递中 CString 无法直接强转为 WPARAM 的原因，以及两种正确的转换方案，并介绍结构体指针传递的正确姿势。","2022-11-25",[390,14,341],"mfc",1546,{"slug":393,"title":394,"description":395,"pub_date":396,"tags":397,"draft":15,"word_count":399},"duilib-static-build","正确编译 Duilib 静态库：避免 ATL 依赖和链接错误","详解如何用 DuiLib_Static.vcxproj 编译 Duilib 静态库，解决 VARIANT 未定义、Unicode 配置不匹配和 ATL 依赖等常见问题。","2022-08-24",[14,398,341,390],"duilib",2639,{"slug":401,"title":402,"description":403,"pub_date":404,"tags":405,"draft":15,"word_count":407},"mfc-dpi-adaptive","MFC 界面自适应不同分辨率","MFC 对话框程序实现控件和字体随分辨率自动缩放的完整方案，附 DPI Awareness 配置说明","2022-08-17",[390,14,341,406],"dpi",1414,{"slug":409,"title":410,"description":411,"pub_date":412,"tags":413,"draft":15,"word_count":414},"mfc-drag-window","MFC 无标题栏窗口客户区拖动：三种方法对比","MFC 对话框去掉标题栏后如何实现拖动移动窗口，三种方案完整实现与适用场景分析","2022-08-16",[390,14,341],1633,{"slug":416,"title":417,"description":418,"pub_date":419,"tags":420,"draft":15,"word_count":422},"algorithm-number-complement","整数的补数：位运算掩码解法","LeetCode 476 题，用掩码 XOR 实现整数补数，附 C++\u002FPython\u002FJava 三种实现及补数与补码的区别","2021-03-08",[11,421,13],"位运算",1374,[]]