力扣 922. 按奇偶排序数组 II :双指针原地交换

43 阅读3分钟

力扣 922. 按奇偶排序数组 II :双指针原地交换

目录

[TOC]

题目解析

题目要求将非负整数数组重新排列,使得所有偶数元素位于偶数索引(0, 2, 4, ...),所有奇数元素位于奇数索引(1, 3, 5, ...)。题目保证至少存在一个有效解,且答案不唯一。

示例输入输出

  • 输入: nums = [4,2,5,7]

  • 输出: [4,5,2,7] (或其他合法排列)


算法思路:双指针原地交换

核心思想是通过双指针分别跟踪偶数索引和奇数索引,逐步将元素调整到正确的位置。

关键步骤:
  1. 初始化双指针
  • even 指针从 0 开始,步长为 2 ,用于寻找偶数索引处的奇数元素。

  • odd 指针从 1 开始,步长为 2 ,用于寻找奇数索引处的偶数元素。

  1. 循环处理
  • even 指向的元素是偶数,说明当前位置正确, even 后移 2 步。

  • odd 指向的元素是奇数,说明当前位置正确, odd 后移 2 步。

  • 否则,交换 evenodd 指向的元素,这样两个位置的元素均满足条件。

  1. 终止条件
  • 当任意指针超出数组长度时,所有元素已正确排列。

正确性证明:

每次交换操作至少修复两个位置的错误(一个偶数的位置和一个奇数的位置),由于题目保证输入有解,算法最终会将所有元素归位。

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArrayByParityII = function(nums) {
    let even = 0; // 偶数索引
    let odd = 1; // 奇数索引
    while (even < nums.length && odd < nums.length) {
        if (nums[even] % 2 === 0) { // 如果偶数索引处的数字是偶数
            even += 2; // 移动到下一个偶数索引
        } else if (nums[odd] % 2 === 1) { // 如果奇数索引处的数字是奇数
            odd += 2; // 移动到下一个奇数索引
        } else { // 如果偶数索引处的数字是奇数且奇数索引处的数字是偶数
            [nums[even], nums[odd]] = [nums[odd], nums[even]]; // 交换它们
        }
    }
    return nums; // 返回排序后的数组
};
 
console.log(sortArrayByParityII([4, 2, 5, 7]));
示例详解

以输入 nums = [4,2,5,7] 为例:

  1. 初始状态
  • even = 0odd = 1

  • 检查 nums[0] = 4 (偶数), even 移动到 2

  1. 第二轮循环
  • nums[2] = 5 (奇数),检查 odd = 1 处的 nums[1] = 2 (偶数)。

  • 交换两者,数组变为 [4,5,2,7]

  1. 指针更新
  • even 移动到 4 ,超出数组长度,循环终止。

最终结果为 [4,5,2,7] ,所有偶数位于偶数索引,奇数位于奇数索引。


复杂度分析
  • 时间复杂度 :O(n),每个元素最多被访问一次。

  • 空间复杂度 :O(1),原地交换,仅使用常数空间。


对比其他解法
  1. 额外空间法
  • 分别收集偶数和奇数,依次填充到偶数索引和奇数索引。

  • 时间复杂度 O(n),但空间复杂度 O(n)。

  1. 两次遍历法
  • 第一次处理偶数,第二次处理奇数,但实现较为繁琐。

当前的双指针法在时间和空间上均最优,适合大数据量的场景。


总结

通过双指针一次遍历,原地交换元素,该算法高效且优雅地解决了奇偶排序问题。理解双指针移动的条件和交换的动机是掌握此算法的关键。