{"schemaVersion":"drillso.agent.session.v1","scope":"node","resource":{"type":"shared-session","shareId":"JWbudi5kozNH","title":"在不进行网络搜索，不使用外部工具的情况下，你对自己最自信的知识领域是哪些？","canonicalUrl":"https://drillso.com/en/share/sessions/JWbudi5kozNH/%E6%95%B0%E7%BB%84-6c86d6c7","agentUrl":"https://drillso.com/en/share/sessions/JWbudi5kozNH/agent.json?node=%E6%95%B0%E7%BB%84-6c86d6c7","ownerName":"pyth0nb3st","updatedAt":"2026-05-05T03:24:07.994Z"},"currentNode":{"id":"6c86d6c7-3e2e-4a33-b81e-14157be25e3a","slug":"数组-6c86d6c7","title":"数组","type":"page","url":"https://drillso.com/en/share/sessions/JWbudi5kozNH/%E6%95%B0%E7%BB%84-6c86d6c7","agentUrl":"https://drillso.com/en/share/sessions/JWbudi5kozNH/agent.json?node=%E6%95%B0%E7%BB%84-6c86d6c7","text":"## 数组的“奇技淫巧”：本质是利用连续性和下标\n\n数组最大的特点是：**元素按顺序存放，可以通过下标快速访问**。所以很多数组技巧，本质上都是在利用：\n\n- 下标可以直接定位；\n- 元素有固定顺序；\n- 可以从左到右、从右到左扫描；\n- 可以把数组本身当作“状态表”或“映射表”。\n\n下面介绍一些常见又很实用的数组技巧。\n\n---\n\n## 1. 双指针：两个下标配合移动\n\n**双指针**是数组题里最常见的技巧之一。它通常用两个变量表示位置，例如 `left` 和 `right`。\n\n### 常见场景\n\n- 反转数组；\n- 删除重复元素；\n- 判断回文；\n- 有序数组中找两数之和；\n- 原地移动元素。\n\n例如，反转数组：\n\n```python\nnums = [1, 2, 3, 4, 5]\n\nleft, right = 0, len(nums) - 1\n\nwhile left < right:\n    nums[left], nums[right] = nums[right], nums[left]\n    left += 1\n    right -= 1\n\nprint(nums)  # [5, 4, 3, 2, 1]\n```\n\n这个技巧的好处是：**不需要额外数组，空间复杂度是 $O(1)$**。\n\n---\n\n## 2. 快慢指针：原地过滤数组\n\n快慢指针是双指针的一种变体。\n\n- `fast` 负责遍历所有元素；\n- `slow` 负责记录“有效元素应该放在哪里”。\n\n例如：删除数组中的某个值。\n\n```python\nnums = [3, 2, 2, 3, 4]\nval = 3\n\nslow = 0\nfor fast in range(len(nums)):\n    if nums[fast] != val:\n        nums[slow] = nums[fast]\n        slow += 1\n\nprint(nums[:slow])  # [2, 2, 4]\n```\n\n这里并没有真正删除元素，而是把需要保留的元素移动到前面。很多语言中的数组删除中间元素很慢，因为后面的元素要整体移动。快慢指针可以把多次移动优化成一次遍历。\n\n---\n\n## 3. 前缀和：快速求区间总和\n\n如果你经常需要求数组某一段的和，例如 `nums[l]` 到 `nums[r]`，每次循环累加会比较慢。\n\n可以提前构造一个**前缀和数组**。\n\n```python\nnums = [2, 4, 1, 7, 3]\n\nprefix = [0]\nfor x in nums:\n    prefix.append(prefix[-1] + x)\n\n# 求 nums[1] 到 nums[3] 的和：4 + 1 + 7\nl, r = 1, 3\nanswer = prefix[r + 1] - prefix[l]\n\nprint(answer)  # 12\n```\n\n前缀和的核心公式是：\n\n$$\nsum(l, r) = prefix[r + 1] - prefix[l]\n$$\n\n适合场景：\n\n- 多次查询区间和；\n- 统计子数组和；\n- 二维矩阵区域求和；\n- 配合哈希表解决“和为 K 的子数组”。\n\n---\n\n## 4. 差分数组：批量区间修改\n\n前缀和适合“快速查询”，而**差分数组**适合“快速修改区间”。\n\n假设你要对数组某个区间 `[l, r]` 都加上一个值，如果直接遍历区间，可能很慢。差分数组可以做到每次区间修改只改两个位置。\n\n```python\nn = 5\ndiff = [0] * (n + 1)\n\n# 给区间 [1, 3] 加 10\nl, r, val = 1, 3, 10\ndiff[l] += val\ndiff[r + 1] -= val\n\n# 还原最终数组\nnums = [0] * n\ncurrent = 0\nfor i in range(n):\n    current += diff[i]\n    nums[i] = current\n\nprint(nums)  # [0, 10, 10, 10, 0]\n```\n\n它的思想是：**只记录变化从哪里开始，在哪里结束**。\n\n---\n\n## 5. 滑动窗口：维护一段连续区间\n\n滑动窗口常用于处理“连续子数组”或“连续子串”。\n\n它通常有两个边界：\n\n- `left`：窗口左边界；\n- `right`：窗口右边界。\n\n例如，求长度为 `k` 的连续子数组最大和：\n\n```python\nnums = [1, 3, 2, 6, 4]\nk = 3\n\nwindow_sum = sum(nums[:k])\nanswer = window_sum\n\nfor right in range(k, len(nums)):\n    window_sum += nums[right]\n    window_sum -= nums[right - k]\n    answer = max(answer, window_sum)\n\nprint(answer)  # 12，对应 [2, 6, 4]\n```\n\n滑动窗口的关键是：窗口右移时，**加入新元素，移除旧元素**，不用每次重新计算。\n\n---\n\n## 6. 原地哈希：把数组当作标记表\n\n有时候数组元素范围有限，可以利用数组下标做映射。\n\n例如，判断数字是否出现过：\n\n```python\nnums = [2, 3, 1, 2]\nseen = [False] * 5\n\nfor x in nums:\n    if seen[x]:\n        print(\"重复数字:\", x)\n        break\n    seen[x] = True\n```\n\n如果题目限制数字范围是 `1` 到 `n`，甚至可以把原数组本身当作标记空间。不过这种技巧要小心，因为它会修改原数组。\n\n---\n\n## 7. 哨兵技巧：减少边界判断\n\n有些算法中，边界判断很多，可以加一个“哨兵”元素简化逻辑。\n\n例如找最小值时，可以预设一个极大值：\n\n```python\nnums = [5, 2, 8, 1]\n\nmin_value = float(\"inf\")\n\nfor x in nums:\n    if x < min_value:\n        min_value = x\n\nprint(min_value)  # 1\n```\n\n`float(\"inf\")` 就像一个无穷大的哨兵，任何正常数字都比它小。\n\n---\n\n## 8. 排序后再处理：降低问题难度\n\n很多数组问题，一旦排序，就会变得简单。\n\n例如：\n\n- 找重复元素；\n- 合并区间；\n- 双指针找两数之和；\n- 贪心选择；\n- 统计频率。\n\n```python\nnums = [4, 1, 3, 2]\nnums.sort()\n\nprint(nums)  # [1, 2, 3, 4]\n```\n\n不过排序通常需要：\n\n$$\nO(n \\log n)\n$$\n\n所以要权衡：排序能简化逻辑，但也会增加时间成本，并且可能改变原数组顺序。\n\n---\n\n## 总结：数组技巧可以继续深入这些方向\n\n如果你想继续钻研数组，可以重点学习：\n\n- **双指针**\n- **快慢指针**\n- **前缀和**\n- **差分数组**\n- **滑动窗口**\n- **二分查找**\n- **原地修改**\n- **排序 + 贪心**\n- **数组模拟栈和队列**\n\n数组看起来简单，但它是很多高级数据结构和算法的基础。真正的“奇技淫巧”不是死记模板，而是理解：**下标、顺序、连续区间、状态映射**这几个核心思想。","markdown":"## 数组的“奇技淫巧”：本质是利用连续性和下标\n\n数组最大的特点是：**元素按顺序存放，可以通过下标快速访问**。所以很多数组技巧，本质上都是在利用：\n\n- 下标可以直接定位；\n- 元素有固定顺序；\n- 可以从左到右、从右到左扫描；\n- 可以把数组本身当作“状态表”或“映射表”。\n\n下面介绍一些常见又很实用的数组技巧。\n\n---\n\n## 1. 双指针：两个下标配合移动\n\n**双指针**是数组题里最常见的技巧之一。它通常用两个变量表示位置，例如 `left` 和 `right`。\n\n### 常见场景\n\n- 反转数组；\n- 删除重复元素；\n- 判断回文；\n- 有序数组中找两数之和；\n- 原地移动元素。\n\n例如，反转数组：\n\n```python\nnums = [1, 2, 3, 4, 5]\n\nleft, right = 0, len(nums) - 1\n\nwhile left < right:\n    nums[left], nums[right] = nums[right], nums[left]\n    left += 1\n    right -= 1\n\nprint(nums)  # [5, 4, 3, 2, 1]\n```\n\n这个技巧的好处是：**不需要额外数组，空间复杂度是 $O(1)$**。\n\n---\n\n## 2. 快慢指针：原地过滤数组\n\n快慢指针是双指针的一种变体。\n\n- `fast` 负责遍历所有元素；\n- `slow` 负责记录“有效元素应该放在哪里”。\n\n例如：删除数组中的某个值。\n\n```python\nnums = [3, 2, 2, 3, 4]\nval = 3\n\nslow = 0\nfor fast in range(len(nums)):\n    if nums[fast] != val:\n        nums[slow] = nums[fast]\n        slow += 1\n\nprint(nums[:slow])  # [2, 2, 4]\n```\n\n这里并没有真正删除元素，而是把需要保留的元素移动到前面。很多语言中的数组删除中间元素很慢，因为后面的元素要整体移动。快慢指针可以把多次移动优化成一次遍历。\n\n---\n\n## 3. 前缀和：快速求区间总和\n\n如果你经常需要求数组某一段的和，例如 `nums[l]` 到 `nums[r]`，每次循环累加会比较慢。\n\n可以提前构造一个**前缀和数组**。\n\n```python\nnums = [2, 4, 1, 7, 3]\n\nprefix = [0]\nfor x in nums:\n    prefix.append(prefix[-1] + x)\n\n# 求 nums[1] 到 nums[3] 的和：4 + 1 + 7\nl, r = 1, 3\nanswer = prefix[r + 1] - prefix[l]\n\nprint(answer)  # 12\n```\n\n前缀和的核心公式是：\n\n$$\nsum(l, r) = prefix[r + 1] - prefix[l]\n$$\n\n适合场景：\n\n- 多次查询区间和；\n- 统计子数组和；\n- 二维矩阵区域求和；\n- 配合哈希表解决“和为 K 的子数组”。\n\n---\n\n## 4. 差分数组：批量区间修改\n\n前缀和适合“快速查询”，而**差分数组**适合“快速修改区间”。\n\n假设你要对数组某个区间 `[l, r]` 都加上一个值，如果直接遍历区间，可能很慢。差分数组可以做到每次区间修改只改两个位置。\n\n```python\nn = 5\ndiff = [0] * (n + 1)\n\n# 给区间 [1, 3] 加 10\nl, r, val = 1, 3, 10\ndiff[l] += val\ndiff[r + 1] -= val\n\n# 还原最终数组\nnums = [0] * n\ncurrent = 0\nfor i in range(n):\n    current += diff[i]\n    nums[i] = current\n\nprint(nums)  # [0, 10, 10, 10, 0]\n```\n\n它的思想是：**只记录变化从哪里开始，在哪里结束**。\n\n---\n\n## 5. 滑动窗口：维护一段连续区间\n\n滑动窗口常用于处理“连续子数组”或“连续子串”。\n\n它通常有两个边界：\n\n- `left`：窗口左边界；\n- `right`：窗口右边界。\n\n例如，求长度为 `k` 的连续子数组最大和：\n\n```python\nnums = [1, 3, 2, 6, 4]\nk = 3\n\nwindow_sum = sum(nums[:k])\nanswer = window_sum\n\nfor right in range(k, len(nums)):\n    window_sum += nums[right]\n    window_sum -= nums[right - k]\n    answer = max(answer, window_sum)\n\nprint(answer)  # 12，对应 [2, 6, 4]\n```\n\n滑动窗口的关键是：窗口右移时，**加入新元素，移除旧元素**，不用每次重新计算。\n\n---\n\n## 6. 原地哈希：把数组当作标记表\n\n有时候数组元素范围有限，可以利用数组下标做映射。\n\n例如，判断数字是否出现过：\n\n```python\nnums = [2, 3, 1, 2]\nseen = [False] * 5\n\nfor x in nums:\n    if seen[x]:\n        print(\"重复数字:\", x)\n        break\n    seen[x] = True\n```\n\n如果题目限制数字范围是 `1` 到 `n`，甚至可以把原数组本身当作标记空间。不过这种技巧要小心，因为它会修改原数组。\n\n---\n\n## 7. 哨兵技巧：减少边界判断\n\n有些算法中，边界判断很多，可以加一个“哨兵”元素简化逻辑。\n\n例如找最小值时，可以预设一个极大值：\n\n```python\nnums = [5, 2, 8, 1]\n\nmin_value = float(\"inf\")\n\nfor x in nums:\n    if x < min_value:\n        min_value = x\n\nprint(min_value)  # 1\n```\n\n`float(\"inf\")` 就像一个无穷大的哨兵，任何正常数字都比它小。\n\n---\n\n## 8. 排序后再处理：降低问题难度\n\n很多数组问题，一旦排序，就会变得简单。\n\n例如：\n\n- 找重复元素；\n- 合并区间；\n- 双指针找两数之和；\n- 贪心选择；\n- 统计频率。\n\n```python\nnums = [4, 1, 3, 2]\nnums.sort()\n\nprint(nums)  # [1, 2, 3, 4]\n```\n\n不过排序通常需要：\n\n$$\nO(n \\log n)\n$$\n\n所以要权衡：排序能简化逻辑，但也会增加时间成本，并且可能改变原数组顺序。\n\n---\n\n## 总结：数组技巧可以继续深入这些方向\n\n如果你想继续钻研数组，可以重点学习：\n\n- **双指针**\n- **快慢指针**\n- **前缀和**\n- **差分数组**\n- **滑动窗口**\n- **二分查找**\n- **原地修改**\n- **排序 + 贪心**\n- **数组模拟栈和队列**\n\n数组看起来简单，但它是很多高级数据结构和算法的基础。真正的“奇技淫巧”不是死记模板，而是理解：**下标、顺序、连续区间、状态映射**这几个核心思想。","structured":null,"children":[{"id":"ef147811-1c77-42b4-b6c5-e4455f7c8780","slug":"原地过滤数组-ef147811","title":"原地过滤数组","type":"image","url":"https://drillso.com/en/share/sessions/JWbudi5kozNH/%E5%8E%9F%E5%9C%B0%E8%BF%87%E6%BB%A4%E6%95%B0%E7%BB%84-ef147811","agentUrl":"https://drillso.com/en/share/sessions/JWbudi5kozNH/agent.json?node=%E5%8E%9F%E5%9C%B0%E8%BF%87%E6%BB%A4%E6%95%B0%E7%BB%84-ef147811"}]},"breadcrumbs":[{"id":"af6f0c84-909f-4f82-a151-294277e696d5","slug":"在不进行网络搜索，不使用外部工具的情况下，你对自己最自信的知识领域是哪些？-af6f0c84","title":"在不进行网络搜索，不使用外部工具的情况下，你对自己最自信的知识领域是哪些？","type":"page","url":"https://drillso.com/en/share/sessions/JWbudi5kozNH/%E5%9C%A8%E4%B8%8D%E8%BF%9B%E8%A1%8C%E7%BD%91%E7%BB%9C%E6%90%9C%E7%B4%A2%EF%BC%8C%E4%B8%8D%E4%BD%BF%E7%94%A8%E5%A4%96%E9%83%A8%E5%B7%A5%E5%85%B7%E7%9A%84%E6%83%85%E5%86%B5%E4%B8%8B%EF%BC%8C%E4%BD%A0%E5%AF%B9%E8%87%AA%E5%B7%B1%E6%9C%80%E8%87%AA%E4%BF%A1%E7%9A%84%E7%9F%A5%E8%AF%86%E9%A2%86%E5%9F%9F%E6%98%AF%E5%93%AA%E4%BA%9B%EF%BC%9F-af6f0c84","agentUrl":"https://drillso.com/en/share/sessions/JWbudi5kozNH/agent.json?node=%E5%9C%A8%E4%B8%8D%E8%BF%9B%E8%A1%8C%E7%BD%91%E7%BB%9C%E6%90%9C%E7%B4%A2%EF%BC%8C%E4%B8%8D%E4%BD%BF%E7%94%A8%E5%A4%96%E9%83%A8%E5%B7%A5%E5%85%B7%E7%9A%84%E6%83%85%E5%86%B5%E4%B8%8B%EF%BC%8C%E4%BD%A0%E5%AF%B9%E8%87%AA%E5%B7%B1%E6%9C%80%E8%87%AA%E4%BF%A1%E7%9A%84%E7%9F%A5%E8%AF%86%E9%A2%86%E5%9F%9F%E6%98%AF%E5%93%AA%E4%BA%9B%EF%BC%9F-af6f0c84"},{"id":"64148b20-14c6-4f64-aa12-04584e0ce79a","slug":"数据结构-64148b20","title":"数据结构","type":"page","url":"https://drillso.com/en/share/sessions/JWbudi5kozNH/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-64148b20","agentUrl":"https://drillso.com/en/share/sessions/JWbudi5kozNH/agent.json?node=%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-64148b20"}],"parent":{"id":"64148b20-14c6-4f64-aa12-04584e0ce79a","slug":"数据结构-64148b20","title":"数据结构","type":"page","url":"https://drillso.com/en/share/sessions/JWbudi5kozNH/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-64148b20","agentUrl":"https://drillso.com/en/share/sessions/JWbudi5kozNH/agent.json?node=%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-64148b20"},"children":[{"id":"ef147811-1c77-42b4-b6c5-e4455f7c8780","slug":"原地过滤数组-ef147811","title":"原地过滤数组","type":"image","url":"https://drillso.com/en/share/sessions/JWbudi5kozNH/%E5%8E%9F%E5%9C%B0%E8%BF%87%E6%BB%A4%E6%95%B0%E7%BB%84-ef147811","agentUrl":"https://drillso.com/en/share/sessions/JWbudi5kozNH/agent.json?node=%E5%8E%9F%E5%9C%B0%E8%BF%87%E6%BB%A4%E6%95%B0%E7%BB%84-ef147811"}],"fullTree":null,"warnings":[],"truncated":false}