1. 问题背景与需求分析
这道题目来自《剑指Offer》第68题,属于数组操作类的基础算法题。题目要求我们调整数组元素的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。看似简单的需求背后,实际上考察了以下几个核心能力:
- 对数组基本操作的理解程度
- 双指针算法的实际应用能力
- 边界条件的处理意识
- 代码的简洁性和效率把控
在实际面试中,这类题目经常作为热身题出现,主要考察候选人对基础数据结构的掌握程度和编码基本功。我在多次技术面试中,都会用类似的题目来快速评估候选人的编码素养。
2. 解法思路与算法选择
2.1 暴力解法分析
最直观的解法是创建一个新数组,然后遍历原数组两次:第一次把所有奇数放入新数组,第二次把所有偶数放入新数组。这种方法的时间复杂度是O(n),空间复杂度也是O(n)。
python复制def reOrderArray(array):
odd = []
even = []
for num in array:
if num % 2 == 1:
odd.append(num)
else:
even.append(num)
return odd + even
虽然这种解法能通过测试用例,但存在明显的缺陷:
- 需要额外的存储空间
- 遍历了数组两次
- 没有体现原地修改的思想
2.2 双指针解法优化
更优的解法是使用双指针技术,在原数组上进行原地修改。我们可以维护两个指针:
- 左指针从数组头部开始,寻找第一个偶数
- 右指针从数组尾部开始,寻找第一个奇数
- 当找到符合条件的元素时,交换两个指针指向的元素
python复制def reOrderArray(array):
left, right = 0, len(array) - 1
while left < right:
while left < right and array[left] % 2 == 1:
left += 1
while left < right and array[right] % 2 == 0:
right -= 1
if left < right:
array[left], array[right] = array[right], array[left]
return array
这种解法的时间复杂度是O(n),空间复杂度是O(1),是最优的解法。
3. 关键实现细节解析
3.1 指针移动条件
在实现双指针算法时,指针移动的条件判断尤为关键:
python复制while left < right and array[left] % 2 == 1:
left += 1
这段代码有两个判断条件:
left < right:确保指针不会越界array[left] % 2 == 1:判断当前元素是否为奇数
3.2 元素交换逻辑
交换元素时需要注意:
- 只有在
left < right时才进行交换 - Python中可以使用元组解包语法简化交换操作
- 交换后需要移动指针,否则可能陷入死循环
3.3 边界条件处理
需要考虑的特殊情况包括:
- 空数组
- 全奇数数组
- 全偶数数组
- 只有一个元素的数组
4. 算法复杂度分析
4.1 时间复杂度
双指针算法的时间复杂度是O(n),因为:
- 每个元素最多被访问一次
- 指针移动的总次数不超过数组长度
4.2 空间复杂度
空间复杂度是O(1),因为:
- 只使用了固定数量的额外空间(两个指针)
- 没有使用额外的数据结构存储数据
5. 变种问题与扩展思考
5.1 保持相对顺序的版本
如果题目要求保持奇数和偶数各自的相对顺序不变,双指针解法就不适用了。这时可以考虑:
- 使用两个临时数组分别存储奇数和偶数
- 最后合并两个数组
- 时间复杂度O(n),空间复杂度O(n)
python复制def reOrderArray(array):
return [x for x in array if x % 2 == 1] + [x for x in array if x % 2 == 0]
5.2 通用条件判断
可以将判断条件抽象为函数,使算法更通用:
python复制def reOrderArray(array, condition):
left, right = 0, len(array) - 1
while left < right:
while left < right and condition(array[left]):
left += 1
while left < right and not condition(array[right]):
right -= 1
if left < right:
array[left], array[right] = array[right], array[left]
return array
# 使用示例
reOrderArray(nums, lambda x: x % 2 == 1)
6. 实际应用场景
这类算法在实际开发中有多种应用场景:
- 数据预处理:将满足某种条件的数据集中存放
- 内存优化:将频繁访问的数据放在连续内存位置
- 缓存优化:提高数据局部性,减少缓存未命中
- 数据分区:为后续处理做准备(如快速排序的partition操作)
7. 常见错误与调试技巧
7.1 指针越界问题
初学者常犯的错误是忘记检查指针边界:
python复制# 错误示例
while array[left] % 2 == 1: # 可能越界
left += 1
正确的做法是始终先检查left < right。
7.2 死循环问题
如果交换后不移动指针,可能导致死循环:
python复制# 错误示例
if left < right:
array[left], array[right] = array[right], array[left]
# 缺少 left += 1 和 right -= 1
7.3 测试用例设计
建议测试用例包括:
- 常规测试用例:[1,2,3,4,5] → [1,5,3,4,2]
- 边界测试用例:[], [1], [2]
- 特殊测试用例:[1,3,5], [2,4,6]
- 混合测试用例:[2,4,6,1,3,5]
8. 性能优化建议
- 对于大型数组,可以考虑并行处理
- 在C++等语言中,使用位运算判断奇偶性更高效:
(num & 1) == 1 - 如果数组已经基本有序,可以添加提前终止条件
9. 不同语言实现对比
9.1 Java实现
java复制public void reOrderArray(int[] array) {
int left = 0, right = array.length - 1;
while (left < right) {
while (left < right && (array[left] & 1) == 1) left++;
while (left < right && (array[right] & 1) == 0) right--;
if (left < right) {
int temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}
}
9.2 C++实现
cpp复制void reOrderArray(vector<int>& array) {
int left = 0, right = array.size() - 1;
while (left < right) {
while (left < right && (array[left] & 1)) left++;
while (left < right && !(array[right] & 1)) right--;
if (left < right) swap(array[left], array[right]);
}
}
10. 面试技巧与注意事项
- 先沟通清楚题目要求(是否保持相对顺序)
- 从暴力解法开始,逐步优化
- 主动分析时间复杂度和空间复杂度
- 考虑边界条件并设计测试用例
- 写出整洁、可读的代码
- 讨论可能的优化空间和变种问题
在实际面试中,我建议采用以下步骤:
- 明确问题需求
- 提出暴力解法并分析缺点
- 提出优化思路(双指针)
- 实现代码并检查边界条件
- 设计测试用例验证
- 讨论扩展问题
这道题目虽然简单,但能很好地展示候选人的算法思维和编码能力。我在面试中遇到过不少候选人因为忽视边界条件或者代码风格问题而失分。建议平时练习时就要养成严谨的习惯,注意代码的鲁棒性和可读性。