【面试题21 调整数组顺序使奇数位于偶数前面】
难度: 简单
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
Leetcode题目对应位置: 面试题21:调整数组顺序使奇数位于偶数前面
不考虑时间复杂度,最简单的思路就是从头到尾扫描数组,每碰到一个偶数就取出,把该数后面所有的数字往前挪动一位,然后在最后补上这个偶数。由于需要从头到尾扫描数组,且每次碰到偶数就需要挪动 O(n) 个数,因此总的时间复杂度是 O(n^2)。这种方法太暴力了,不能达到要求,在 LeetCode 提交也会报超时。
基本思路: 需要设置 3 个辅助变量,i
、j
和 count
。i
用于遍历数组 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
来辅助。
交换偶数和奇数也有两种思路,一种是从前往后遍历,一种是从两头分别遍历。
第一种思路:快慢双指针。维护两个指针 i
和 j
,i
用于遍历数组,每遇到一个偶数,就让 j
指向它,然后 i
继续往后走寻找下一个奇数,找到后将 i
和 j
所指向的内容互换。接着恢复 i
到 j+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
第二种思路:头尾双指针。从两头交换,维护两个指针 i
和 j
,分别指向数组的首位和末位,规定 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 < length
和 j >= 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