Leetcode刷题:剑指offer【面试题21 调整数组顺序使奇数位于偶数前面】

文章目录

  • 思路 1:常规循环
  • 思路 2:交换奇偶数
  • 扩展:同类问题解法

【面试题21 调整数组顺序使奇数位于偶数前面】

难度: 简单

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

Leetcode题目对应位置: 面试题21:调整数组顺序使奇数位于偶数前面

思路 1:常规循环

不考虑时间复杂度,最简单的思路就是从头到尾扫描数组,每碰到一个偶数就取出,把该数后面所有的数字往前挪动一位,然后在最后补上这个偶数。由于需要从头到尾扫描数组,且每次碰到偶数就需要挪动 O(n) 个数,因此总的时间复杂度是 O(n^2)。这种方法太暴力了,不能达到要求,在 LeetCode 提交也会报超时。

基本思路: 需要设置 3 个辅助变量,ijcounti 用于遍历数组 nums,一旦遇到偶数,则令 j = i 并用 j 循环移动 i 后面的数字。需要注意的是,i 遍历数组是不断 +1,而遇到了偶数并进行了挪动时,i 要额外 -1,因为此时 i 所指向的数实际上已经是下一个数字了,如果不 -1 会遗漏判断。对于循环的终止条件,由于 i 每碰到一个偶数就会向后挪动并自减 1,如果仅靠 i 来做循环,会形成死循环。此时用 count 作为计数器,当已经判断过数组所有的数字时,直接跳出循环。

时间复杂度: O(n^2)
空间复杂度: O(1)

Python 代码:

class Solution:
    def exchange(self, nums):
        if not nums: return []
        length = len(nums)
        i, count = 0, 0
        while i < length and count <= length:
            if nums[i] % 2 == 0:
                temp = nums[i]
                j = i
                while j < length - 1:
                    nums[j] = nums[j + 1]
                    j += 1
                nums[-1] = temp
                i -= 1
            i += 1
            count += 1
        return nums

如果题目规定了数组中没有重复数字的话,就可以用数组中最后一个数字的值作为循环的结束条件。但本题没有限制,所以存在重复数,需要计数器 count 来辅助。

思路 2:交换奇偶数

交换偶数和奇数也有两种思路,一种是从前往后遍历,一种是从两头分别遍历。

第一种思路:快慢双指针。维护两个指针 iji 用于遍历数组,每遇到一个偶数,就让 j 指向它,然后 i 继续往后走寻找下一个奇数,找到后将 ij 所指向的内容互换。接着恢复 ij+1 位置,继续向后遍历,再次寻找偶数并用 j 指向。当 i 遍历完整个数组后就交换完成了。但是这样时间复杂度仍然是 O(n^2)。

时间复杂度: O(n^2),i 遍历整个数组,每次遇到一个偶数,向后查找奇数
空间复杂度: O(1)

Python 代码:

class Solution:
    def exchange(self, nums):
        if not nums: return []
        i, j = 0, 0
        length = len(nums)
        while i < length:
            if nums[i] % 2 == 0:
                j = i # 用j指向偶数,用于后面互换
                # 查找下一个奇数
                while i < length: 
                    if nums[i] % 2 == 0:
                        i += 1
                    else:
                        tmp = nums[i]
                        nums[i] = nums[j]
                        nums[j] = tmp
                        i = j + 1
                        break
            else:
                i += 1
        return nums

上面比较麻烦,其实也可以不管慢指针有没有指向偶数,一律交换即可,这样时间复杂度就是 O(n) 了:

class Solution:
    def exchange(self, nums):
        if not nums: return []
        i, j = 0, 0
        length = len(nums)
        while j < length:
            if nums[j] % 2 != 0:
                tmp = nums[i]
                nums[i] = nums[j]
                nums[j] = tmp
                i += 1
            j += 1
        return nums

第二种思路:头尾双指针。从两头交换,维护两个指针 ij,分别指向数组的首位和末位,规定 i 始终查找并指向偶数,j 始终查找并指向奇数。所以两指针指向的位置就是偶数在奇数之前的情况,此时直接交换两位置上的内容即可。循环终止条件为 i > j

时间复杂度: O(n)
空间复杂度: O(1)

Python 代码:

class Solution:
    def exchange(self, nums):
        if not nums: return []
        length = len(nums)
        i, j = 0, len(nums) - 1
        while i < j:
            while i < length and nums[i] % 2 != 0: i += 1
            while j >= 0 and nums[j] % 2 == 0: j -= 1
            if i < j:
                tmp = nums[i]
                nums[i] = nums[j]
                nums[j] = tmp
        return nums

内部循环的条件 i < lengthj >= 0 也可以改成 i < j

扩展:同类问题解法

本题要求的是将所有奇数放在偶数之前,那么如果要求将所有负数放在正数之前、所有能被 3 整除的数放在不能被 3 整除的数前… 都是同样的解法,只需要修改 while 的条件即可。但是这样做程序的可扩展性差,更好的办法是将该判断单独做成一个功能函数,也就是解耦。如果需求修改了,只需要更改 helper 的内容,解耦的好处是提高了代码的重用性。

Python 代码:

class Solution:
    def exchange(self, nums):
    
        def helper(num):
            return num % 2 == 0
            
        if not nums: return []
        i, j = 0, len(nums) - 1
        while i < j:
            while i < j and not helper(nums[i]): i += 1
            while i < j and helper(nums[j]): j -= 1
            if i < j:
                temp = nums[i]
                nums[i] = nums[j]
                nums[j] = temp
        return nums

你可能感兴趣的:(今天刷题了吗)