训练计划 I

标签: 数组 双指针 排序

难度: Easy

教练使用整数数组 actions 记录一系列核心肌群训练项目编号。为增强训练趣味性,需要将所有奇数编号训练项目调整至偶数编号训练项目之前。请将调整后的训练项目编号以 数组 形式返回。

示例 1:

输入:actions = [1,2,3,4,5]
输出:[1,3,5,2,4] 
解释:为正确答案之一

提示:

  • 0 <= actions.length <= 50000
  • 0 <= actions[i] <= 10000

Submission

运行时间: 60 ms

内存: 18.8 MB

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        fast, slow = 0, 0
        while fast < len(nums):
            if nums[fast] % 2 == 1:
                nums[slow], nums[fast] = nums[fast], nums[slow]
                slow += 1
            fast += 1
        return nums

Explain

题解采用双指针技巧来重新排列数组元素,确保所有奇数编号在偶数编号前。其中,'fast' 指针用于遍历数组,'slow' 指针用于追踪奇数应该放置的位置。当 'fast' 指针指向的元素是奇数时,我们将它与 'slow' 指针指向的元素交换,然后 'slow' 指针向前移动一位。这样,所有的奇数最终会被收集在数组的前半部分,而所有偶数则会被移动到后半部分。

时间复杂度: O(n)

空间复杂度: O(1)

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        fast, slow = 0, 0  # 初始化快慢指针
        while fast < len(nums):  # 遍历数组
            if nums[fast] % 2 == 1:  # 如果当前元素是奇数
                nums[slow], nums[fast] = nums[fast], nums[slow]  # 交换奇数与慢指针位置的元素
                slow += 1  # 慢指针向前移动,指向下一个可能的交换位置
            fast += 1  # 快指针总是向前移动
        return nums  # 返回重新排列后的数组

Explore

在这种双指针算法中,'fast'指针的作用是遍历整个数组,查找奇数元素。'slow'指针则用于跟踪奇数应该插入的位置,即它始终指向当前已找到的奇数序列的下一个位置。使用两个指针的原因是为了一次遍历中即可完成奇偶分离。'slow'指针实际上将数组分为两部分:'slow'指针左侧(包括'slow'指向的位置)是已经排序好的奇数,而'slow'指针右侧是尚未检查的元素。这样的配置允许算法在O(n)时间内通过单次遍历解决问题,无需使用额外空间。

当'fast'指针指向的元素为奇数时,进行交换的目的是将这个奇数移动到数组的前部,即奇数区域。'slow'指针标记了奇数区域的结束位置,因此,将'fast'指向的奇数与'slow'指向的元素(它可能是一个偶数或未处理的奇数)交换可以确保所有奇数都被集中在数组的前半部分。这种交换逻辑背后的考量是保持算法的空间复杂度为O(1),同时确保所有奇数都能在一次遍历后聚集在一起。

如果数组全部为奇数,'slow'指针将移动到数组的末端,因为每个元素都被交换到了前面。即便如此,算法仍然会遍历整个数组,但不会有额外的交换操作,因为每次检查的都是奇数。对于全是偶数的数组,'slow'指针不会移动,因为没有奇数进行交换。在这种情况下,'fast'指针仍会遍历整个数组,但除了检查之外不会做任何操作。因此,虽然无谓的遍历仍然会发生,但无谓的交换则不会。

在这个算法中,0被视为偶数,因为0除以2的余数是0。这意味着如果'fast'指针遇到0,它不会与'slow'指针指向的元素交换。0和其他偶数一样,会留在数组的后半部分。因此,算法对0的处理与对其他偶数相同,没有特殊处理。