chenjunda
长期主义

189.轮转数组

2025-11-05 leecode Hot100

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 1:

1
2
3
4
5
6
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]

示例 2:

1
2
3
4
5
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]

提示:

  • 1 <= nums.length <= 105
  • -231 <= nums[i] <= 231 - 1
  • 0 <= k <= 105

这题很巧妙,属于技巧题,基本思路就是使用双端队列k次循环来实现,但是有一种思路很巧妙可以实现空间复杂度为O(1)的方法

💡 思路核心:三次反转法(Reverse Algorithm)

思考轮转的本质:
向右轮转 k 位 = 把 后 k 个元素 挪到前面。

也就是:

1
2
3
原数组:    [1,2,3,4,5,6,7]
分成两段: [1,2,3,4] [5,6,7]
轮转结果: [5,6,7,1,2,3,4]

要原地完成这个操作,可以通过“反转”实现:


🪞 三步反转过程

  1. 整体反转

    1
    [1,2,3,4,5,6,7] → [7,6,5,4,3,2,1]
  2. 反转前 k 段(即反转后数组的前 k 个元素)

    1
    [7,6,5,4,3,2,1] → [5,6,7,4,3,2,1]
  3. 反转剩下的部分(即后 n-k 个元素)

    1
    [5,6,7,4,3,2,1] → [5,6,7,1,2,3,4]

这样就得到了正确的轮转结果。

这题的思路很巧妙,很难想到,我如果想做出这道题,我脑子中的分析链条应该是怎么样的呢,我该从哪里切入才能一步一步根据蛛丝马迹嗅到这一巧妙的算法,并且拿着这种思考习惯去思考别的问题,我的意思是我想将这种做算法的思考习惯提炼出一种方法论,以至于我遇见每一个算法题都可以不变应万变,就好比,我是先分析什么?题目要求?空间?O(1)?然后我会去想什么?

ChatGPT 说:

抽象成方法论:把“看到 → 猜测 → 验证 → 抽象”为可复用的解题链

很好的问题 — 不只是想会做一道题,而是把解题的思考习惯抽成一套可以在不同题上复用的方法论。下面给你一套清晰的「分析链 + 工具箱 + 练习流程」,并用 rotate 题做示范,告诉你在脑子里该怎么一步步推进、哪些“蛛丝马迹”会引导你想到三次反转那种巧妙解法。


一条通用的思考链(每题必走的 7 步)

  1. 读题并复述目标(What)
    • 用一句话复述:输入是什么,输出/变更是什么(返回新数组?原地修改?打印?)。
    • 例:把数组向右移动 k 位,要求原地 O(1) 额外空间。
  2. 确定约束/目标(Constraints & Goals)
    • n 的大小、时间/空间上有没有硬性要求(O(1) 还是允许 O(n)),是否可修改输入,k 边界(k > n)。
    • 例:题目要求原地(O(1) 空间),这极大缩小解法范围。
  3. 画小例子 + 手动模拟(Explore examples)
    • 取最小/典型/边界例:n=1、k=0、k=1、k=n、k>n、奇偶长度等。
    • 在纸上、用数组写出每步变化,寻找可以做的局部操作。
  4. 找“不变式 / 想达到的局部状态”(Invariant / target substructure)
    • 思考有没有你可以通过局部操作维护或达到的中间形态(例如把后 k 个元素变成前 k 个元素但顺序颠倒)。
    • 例:把后 k 个元素移动到前面是目标;你可以先把整体颠倒,后 k 段会变成数组前段(但顺序反了)——这是关键不变/中间态。
  5. 联想已知模式与常用手法(Pattern matching)
    • 列出常见技巧/模板:反转、滑动窗口、双指针、快慢指针、异或/位运算、哈希表计数、前缀和、动态规划等。
    • 用约束排除不合适的:若空间要求 O(1) → 排除用额外数组/哈希的想法。
    • 例:原地 + 子段移动 → 反转/交换/循环替换 都是常见技术;反转能把段顺序倒置,合并几步可能恢复顺序 → 联想到三次反转。
  6. 验证与证明(Prove / Dry-run)
    • 在几个例子上手工验证每步是否把数组推进到正确状态;证明为什么操作数次后能恢复原顺序(不变式证明)。
    • 例:整体反转后再反转前 k 个,再反转后 n−k 个 → 逐步恢复两段原来的顺序并实现拼接。
  7. 实现+优化+边界处理(Implement & Harden)
    • 实现时注意取模、边界(k=0、k%=n、n=0 或 n=1),写清楚反转函数并注释时间/空间复杂度。

针对 rotate 题:你的脑子里可以怎么走(一步步示例)

  1. 读题:把数组右移 k 位,要求原地(O(1))。(核心线索:原地 → 不可用额外数组)
  2. 约束:k 可能 > n → 马上想到 k %= n(常见小技巧)。
  3. 画例子:
    • [1,2,3,4,5,6,7], k=3 → [5,6,7,1,2,3,4]
    • 把数组分成两段 [1,2,3,4][5,6,7]
  4. 问自己:如何在数组内部把后半段挪到前面?
    • 直接一个一个挪会 O(k·n) 或需要额外空间。想要 O(n) 且 O(1) → 需要“整体且局部操作”。
  5. 联想到“反转”这个操作:反转是原地且能改变顺序的强工具。试着对例子做反转:
    • 全反 → [7,6,5,4,3,2,1],现在原来的后半段在前面但顺序反了(7,6,5),对这个片段再反一次能恢复顺序(→ 5,6,7)。同理把余下的段反回来。
  6. 得到三步反转法并在几个例子验证。
  7. 实现并处理 k%=nk==0n==1 等边界。

关键点在第4步:把目标分解成更容易实现的“中间状态”(这里是把“后 k 段在前”这一目标转化为“先整体反转再局部反转”这样更容易的中间状态),这是触发巧妙方法的常见套路。


一套可记忆的「触发器(hints)」:看到这些就该想这些技巧

  • 原地 / O(1) + 段操作 → 想:反转 / 交换 / 三路划分
  • 子串最大/最小/和 → 想:滑动窗口 / 前缀和
  • 子序列/子集计数或是否存在 → 想:动态规划 / 状态压缩 / 位掩码
  • pair/两数问题 → 想:双指针(排序后)或哈希表(无序)
  • 重复/出现次数/频率 → 想:哈希计数 / 排序 / 摩尔投票(多数元素)
  • 最优解/贪心 → 想:是否存在“局部最优可扩展到全局”且有可交换性
  • 在数组中原地循环移动一些元素 → 想:取模、环形替换(循环替代)或反转
    把这张清单背一遍,遇到题时迅速对照。

思考习惯(方法论)——你需要养成的 6 个习惯

  1. 先手画例子(至少 3 个:普通/边界/极端)——这是最重要的快速检验手段。
  2. 立刻写出约束清单(n、k 范围、是否允许修改输入、是否要求 O(1))——通过约束缩小搜索空间。
  3. 把目标拆成“局部目标”(中间状态)——很多巧妙解法就是先达成一个“可管理”的中间形态。
  4. 常用模板速查(把上面的触发器做成短卡片)——做题前先在脑中刷一遍模板。
  5. 时间空间权衡:问自己“可以用额外空间吗?”如果不可以,就立刻排除很多直观方法。
  6. 把解法口述一遍并证明不变式(2–3 句)——能口述清楚的解法,通常就是正确且可复现的。

练习计划(把习惯变成长期能力)

  • 每周 3 题:分别从数组、字符串、DP、贪心、图/树 中各挑 1 题练习。
  • 做题流程固定化(用上面的 7 步):读题→约束→例子→触发器→尝试→验证→实现。
  • 专项训练:挑 10 道「原地/空间受限」的题,把你想到的每种可能都写出来并用约束排除。
  • 复盘:每做完一题写 3 行复盘笔记:关键触发点、为什么想到该模板、可推广的模式。
  • 模式卡片:把“触发器”做成 1 页卡片,遇题先过一遍卡片(20–30 秒),形成条件-技巧的快速映射。

给你的心智模型(一句话版)

从“目标 → 约束 → 小例子”出发,寻找能在原地改变顺序维护不变式的基本操作(反转、交换、双指针、哈希、前缀),把大问题拆成能用这些基本操作完成的中间状态,然后验证。


如果你愿意,我可以:

  • 把上面的触发器做成一页可打印的“题型-技巧”速查表;或
  • 针对你当前水平出 8 道练习题(含难度分布)并给每题的快速触发器提示,帮助你把方法论落到实处。

你想先要哪一个?

你说:

找“不变式 / 想达到的局部状态”(Invariant / target substructure) 思考有没有你可以通过局部操作维护或达到的中间形态(例如把后 k 个元素变成前 k 个元素但顺序颠倒)。 例:把后 k 个元素移动到前面是目标;你可以先把整体颠倒,后 k 段会变成数组前段(但顺序反了)——这是关键不变/中间态。这里这一步是最难的,很多时候我总是想着最终的目标,但是困难的目标往往有很多“中间态”需要我去实现,想出这个中间态才是解决巧妙算法的精髓,这很难,我该怎么才能想到中间态呢

ChatGPT 说:

非常棒的洞察👏——你抓到了算法思维中最本质、最“会者不难”的一点:

真正的难点不是想到答案,而是想到「中间态」。

几乎所有巧妙的算法,都是在某个困难目标和直接做不到的局面之间,插入一个“可操作的中间态(中转站)”。
我们来系统讲讲——如何“看见”中间态。


🌱 一、什么是中间态?(核心定义)

中间态 = 比目标更容易实现,又能一步步过渡到目标的局部结构。

它满足两点:

  1. 你能用已有的工具或操作“轻易做到它”;
  2. 达到它之后,剩下的部分更容易继续推到目标。

例子(轮转数组):

  • 目标:把后 k 段搬到前面。
  • 难点:如何原地移动一整段?
  • 可行操作:反转(我们能轻松原地反转数组)。
  • 那就问:如果我反转整个数组,会发生什么?
    👉 “后半段跑到前面了,但顺序错了” → 这正是一个有价值的中间态!

🧭 二、找到中间态的五种思维路径

当你卡在“直接达不到目标”时,就从下面 5 种角度去“嗅”中间态。


🧩 1. 反向思考:从目标往前推

问自己:“要达到这个目标,最后一步必须是什么?”
这最后一步,往往暗示了中间态。

例:
“数组要被右移 k 位”
→ 最后一刻,一定是“右边的元素出现在前面”。
→ 那么在倒数第二步,它们可能已经在前面,只是顺序颠倒。
→ 反转就是可行操作。
→ 找到了中间态:“后半段在前,但顺序反了”

✅ 技巧:想象“倒放视频”,逆推每个操作,找能反转的结构。


🔄 2. 聚焦可操作的原子动作

直接问自己:“我能在 O(1) 空间内做哪些操作?”
→ “交换”、“反转”、“滑动窗口”、“异或”等。
这些是你手上的“操作积木”。
目标做不到没关系,想想这些积木组合后能造出什么结构。

例(rotate):

  • 我能做的:swap、reverse
  • reverse 会让后段跑到前面但顺序颠倒
  • swap 也能局部交换段 → 可以造中间态。

✅ 技巧:先列可做的原子操作,再问它们能改变什么维度(顺序、位置、状态)。


🪞 3. 引入对称性(symmetry)或不变式(invariant)

问:“这个问题里有什么结构不变?我能利用它吗?”

例:

  • 数组长度不变、元素总集不变。
  • 我只改变“顺序”。
  • 那我能用“对称操作”反转顺序。

反转就是一种保持集合内容但改变顺序的对称操作。
利用不变的部分(内容不变)去操作变化的部分(顺序),
→ 就自然导出“反转”这种中间形态。

✅ 技巧:固定一部分,看能否在保持不变的条件下改变另一部分。


🔍 4. 局部实验:暴力思考简化例子

拿一个极小的输入,在纸上试几步。
你会发现某些中间形态会重复出现

例:

1
2
3
4
5
[1,2,3,4,5,6,7], k=3
目标: [5,6,7,1,2,3,4]
尝试反转整体 → [7,6,5,4,3,2,1]
此时后k段(5,6,7)在前,但反了
→ 灵感出现:能不能反转回去?

✅ 技巧:用小例子玩出“局部对称性”,很多中间态会自然显形。


🧠 5. 类比(Analogy)与“操作迁移”

问自己:“我做过类似目标但更简单的问题吗?”
把你以前成功的操作迁移过来。

例:

  • 字符串反转 → 也是在原地颠倒顺序。
  • 如果能原地反转字符串,也能原地反转数组。
  • 于是轮转问题 → “部分反转”问题。

✅ 技巧:建立“动作模板库”。
每次遇新题,就去匹配你脑中旧的可操作模式(反转、双指针、前缀和、排序、堆…)。


⚙️ 三、形成“中间态敏感度”的训练法

  1. 把每道题拆成两层
    • 上层:最终目标
    • 下层:你能直接操作的东西
      每次遇到卡壳,就问:“能否造个中转层(让上层通过下层实现)?”
  2. 每日练 1 题 + “中间态追问”
    做题时,不急着看答案,问自己:
    • “直接做不到目标,我能先让结构变成什么样?”
    • “这一步后,我的目标是否变简单了?”
    • “我手里能做的操作有哪些?”
      这三个问题能帮你自动搜寻中间态。
  3. 复盘:写下“那个关键中间态”
    不总结整个解法,只总结「我发现的中间态是什么、它帮我解题的关键点是什么」。
  4. 对比同类题
    例如:
    • 旋转数组、反转字符串、翻转链表(都是「顺序重排」问题)
    • 都可以用「反转局部 → 达成全局」思路
      这样中间态就会自然成为模板。

🌟 你可以内化的思考模板(总结一句话)

当目标难以直接实现时,不要想着“一步到位”,而要问自己能不能通过某个简单的、可操作的局部变换
让问题“变得更像你已经会的问题”。

所以说想要解决一个最终的目标,往往需要找到中间态去解决,就像要实现一个大目标,就要拆成一点点逼近的小目标,把问题慢慢变成自己一眼就能解决的小问题,而解决这些小问题往往才会需要那些条件反射的需要积累的小方法和经典算法了,这时候才会使用平常学习的一些简单的东西

Author: chenjunda

Link: http://example.com/2025/11/05/189-%E8%BD%AE%E8%BD%AC%E6%95%B0%E7%BB%84/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
41.缺失的第一个正数
NextPost >
diary2025-10-29
CATALOG
  1. 1. 💡 思路核心:三次反转法(Reverse Algorithm)
  2. 2. 🪞 三步反转过程
    1. 2.1. 这题的思路很巧妙,很难想到,我如果想做出这道题,我脑子中的分析链条应该是怎么样的呢,我该从哪里切入才能一步一步根据蛛丝马迹嗅到这一巧妙的算法,并且拿着这种思考习惯去思考别的问题,我的意思是我想将这种做算法的思考习惯提炼出一种方法论,以至于我遇见每一个算法题都可以不变应万变,就好比,我是先分析什么?题目要求?空间?O(1)?然后我会去想什么?
      1. 2.1.0.0.1. ChatGPT 说:
  • 抽象成方法论:把“看到 → 猜测 → 验证 → 抽象”为可复用的解题链
  • 一条通用的思考链(每题必走的 7 步)
  • 针对 rotate 题:你的脑子里可以怎么走(一步步示例)
  • 一套可记忆的「触发器(hints)」:看到这些就该想这些技巧
  • 思考习惯(方法论)——你需要养成的 6 个习惯
  • 练习计划(把习惯变成长期能力)
  • 给你的心智模型(一句话版)
    1. 0.0.0.1. 你说:
  • 0.1. 找“不变式 / 想达到的局部状态”(Invariant / target substructure) 思考有没有你可以通过局部操作维护或达到的中间形态(例如把后 k 个元素变成前 k 个元素但顺序颠倒)。 例:把后 k 个元素移动到前面是目标;你可以先把整体颠倒,后 k 段会变成数组前段(但顺序反了)——这是关键不变/中间态。这里这一步是最难的,很多时候我总是想着最终的目标,但是困难的目标往往有很多“中间态”需要我去实现,想出这个中间态才是解决巧妙算法的精髓,这很难,我该怎么才能想到中间态呢
    1. 0.1.0.0.1. ChatGPT 说:
  • 1. 🌱 一、什么是中间态?(核心定义)
  • 2. 🧭 二、找到中间态的五种思维路径
    1. 2.1. 🧩 1. 反向思考:从目标往前推
    2. 2.2. 🔄 2. 聚焦可操作的原子动作
    3. 2.3. 🪞 3. 引入对称性(symmetry)或不变式(invariant)
    4. 2.4. 🔍 4. 局部实验:暴力思考简化例子
    5. 2.5. 🧠 5. 类比(Analogy)与“操作迁移”
  • 3. ⚙️ 三、形成“中间态敏感度”的训练法
  • 4. 🌟 你可以内化的思考模板(总结一句话)
  • 5. 所以说想要解决一个最终的目标,往往需要找到中间态去解决,就像要实现一个大目标,就要拆成一点点逼近的小目标,把问题慢慢变成自己一眼就能解决的小问题,而解决这些小问题往往才会需要那些条件反射的需要积累的小方法和经典算法了,这时候才会使用平常学习的一些简单的东西