Medium草稿★★★★O(n) 時間 · O(1) 空間
ArrayTwo Pointers
Patterns↔️ 雙指針
尚未複習過

解法已隱藏 — 先讀題目敘述、自己想想看,再點上方按鈕揭曉。

31Next PermutationArrays & HashingMediumArrays & Hashing

將陣列重新排列成「字典序的下一個更大排列」。若已是最大排列(遞減),則回到最小排列(遞增)。必須原地修改、且只用常數額外空間。

Example:

Input: nums = [1,2,3] Output: [1,3,2]

Input: nums = [3,2,1] Output: [1,2,3](已是最大 → 回到最小)

Input: nums = [1,1,5] Output: [1,5,1]

Intuition

TIP

核心思路:從右往左找到第一個「下降點」inums[i] < nums[i+1]),把它換成右側剛好比它大的數,再把後段反轉成最小。

Approaches

⭐ Three Steps: Find Drop → Swap → Reverse Suffix — O(n) / O(1)
  • Idea: 一次從右掃描找轉折點,常數次交換與一次反轉即可
  • Time: O(n) - 最多兩次線性掃描 + 一次反轉
  • Space: O(1) - 原地操作
class Solution {
    fun nextPermutation(nums: IntArray) {
        var i = nums.size - 2
        // 1) 從右找第一個下降點 i:nums[i] < nums[i+1]
        while (i >= 0 && nums[i] >= nums[i + 1]) i--
        if (i >= 0) {
            // 2) 從右找第一個比 nums[i] 大的 nums[j],交換
            var j = nums.size - 1
            while (nums[j] <= nums[i]) j--
            swap(nums, i, j)
        }
        // 3) 反轉 i 之後的後段(無下降點則 i = -1,反轉全部)
        reverse(nums, i + 1, nums.size - 1)
    }

    private fun swap(a: IntArray, i: Int, j: Int) {
        val t = a[i]; a[i] = a[j]; a[j] = t
    }

    private fun reverse(a: IntArray, lo: Int, hi: Int) {
        var l = lo; var r = hi
        while (l < r) { swap(a, l, r); l++; r-- }
    }
}
Note: Why step 3 reverses instead of sorts

交換後,後段 i+1..end 仍維持遞減(因為 nums[j] 是後段中剛好大於 nums[i] 的數,換入 nums[i] 後不破壞單調性)。遞減序列要變最小只需反轉,不必 O(n log n) 排序,所以整體維持 O(n)

🔑 Takeaways