剑指offer-13、调整数组顺序使奇数位于偶数前面(一)

题⽬描述

输⼊⼀个⻓度为 n 整数数组,数组⾥⾯不含有相同的元素,实现⼀个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前⾯部分,所有的偶数位于数组的后⾯部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

示例1 输⼊:[1,2,3,4] 返回值:[1,3,2,4]

示例2 输⼊:[2,4,6,5,7] 返回值:[5,7,2,4,6]

示例3 输⼊:[1,3,5,6,7] 返回值:[1,3,5,7,6]

思路及解答

空间换时间(辅助数组)

通过创建两个临时数组分别存储奇数和偶数,然后合并它们。这种方法简单易懂,但需要额外的空间。

新建⼀个数组, copy ⼀份,先计算出奇数的个数,也就是能够知道第⼀个偶数应该放在数组的哪⼀个位置,然后再遍历⼀次,依次放到对应的位置即可。

java 复制代码
public int[] reorderArray(int[] nums) {
    if (nums == null || nums.length == 0) {
        return nums;
    }
    
    // 使用ArrayList动态存储,避免预先计算大小
    List<Integer> oddList = new ArrayList<>();
    List<Integer> evenList = new ArrayList<>();
    
    // 第一次遍历:分离奇偶数
    for (int num : nums) {
        if (num % 2 != 0) {
            oddList.add(num);
        } else {
            evenList.add(num);
        }
    }
    
    // 合并结果
    int[] result = new int[nums.length];
    int index = 0;
    for (int odd : oddList) {
        result[index++] = odd;
    }
    for (int even : evenList) {
        result[index++] = even;
    }
    
    return result;
}
  • 时间复杂度:O(n),需要遍历数组两次(分离和合并各一次)
  • 空间复杂度:O(n),需要额外的两个列表存储所有元素

双指针原地排序(类似插入排序)

使用类似插入排序的思想,维护一个"已排序奇数"的边界,当遇到奇数时,将其插入到边界位置并移动边界。这种方法不需要额外空间,但时间复杂度较高。

java 复制代码
public int[] reorderArray(int[] nums) {
    if (nums == null || nums.length == 0) {
        return nums;
    }
    
    int oddBoundary = 0; // 奇数边界
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] % 2 != 0) {
            // 从i位置向前移动到oddBoundary位置
            int temp = nums[i];
            // 将[i-1, oddBoundary]区间元素后移一位
            for (int j = i - 1; j >= oddBoundary; j--) {
                nums[j + 1] = nums[j];
            }
            nums[oddBoundary] = temp;
            oddBoundary++;
        }
    }
    
    return nums;
}
  • 时间复杂度:O(n²),最坏情况下每次奇数都需要移动大量元素
  • 空间复杂度:O(1),原地排序,不需要额外空间

两次遍历填充法

通过两次遍历数组,第一次填充所有奇数,第二次填充所有偶数。这种方法结合了方法一的思路,但使用固定大小的结果数组

java 复制代码
public int[] reorderArray(int[] nums) {
    if (nums == null || nums.length == 0) {
        return nums;
    }
    
    int[] result = new int[nums.length];
    int index = 0;
    
    // 第一次遍历:填充奇数
    for (int num : nums) {
        if (num % 2 != 0) {
            result[index++] = num;
        }
    }
    
    // 第二次遍历:填充偶数
    for (int num : nums) {
        if (num % 2 == 0) {
            result[index++] = num;
        }
    }
    
    return result;
}
  • 时间复杂度:O(n),需要遍历数组两次
  • 空间复杂度:O(n),需要一个结果数组

稳定的双指针交换法

使用两个指针,一个从前往后找偶数,一个从后往前找奇数,然后交换它们的位置。这种方法需要特别注意保持相对顺序

java 复制代码
public int[] reorderArray(int[] nums) {
    if (nums == null || nums.length == 0) {
        return nums;
    }
    
    int left = 0, right = nums.length - 1;
    
    while (left < right) {
        // 从左找第一个偶数
        while (left < right && nums[left] % 2 != 0) {
            left++;
        }
        // 从右找第一个奇数
        while (left < right && nums[right] % 2 == 0) {
            right--;
        }
        
        if (left < right) {
            // 交换并保持相对顺序
            int temp = nums[left];
            // 将[left+1, right]区间元素前移一位
            for (int i = left + 1; i <= right; i++) {
                nums[i - 1] = nums[i];
            }
            nums[right] = temp;
        }
    }
    
    return nums;
}
  • 时间复杂度:O(n²),最坏情况下需要移动大量元素
  • 空间复杂度:O(1),原地操作

优化的双指针法(保持稳定性)

结合双指针和插入排序的思想,维护奇数指针和遍历指针,当遇到奇数时,将其插入到奇数指针位置并移动指针。

java 复制代码
public int[] reorderArray(int[] nums) {
    if (nums == null || nums.length == 0) {
        return nums;
    }
    
    int oddPos = 0;
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] % 2 != 0) {
            // 记录当前奇数
            int temp = nums[i];
            // 将[oddPos, i-1]区间元素后移一位
            for (int j = i; j > oddPos; j--) {
                nums[j] = nums[j - 1];
            }
            nums[oddPos] = temp;
            oddPos++;
        }
    }
    
    return nums;
}
  • 时间复杂度:O(n²),最坏情况下需要移动大量元素
  • 空间复杂度:O(1),原地操作

方法对比与总结

方法 时间复杂度 空间复杂度 优点 缺点
辅助数组法 O(n) O(n) 实现简单,顺序有保证 空间开销大
双指针原地排序 O(n²) O(1) 空间效率高 时间效率低
两次遍历填充法 O(n) O(n) 时间效率高 空间开销中等
稳定双指针交换法 O(n²) O(1) 空间效率高 实现复杂,时间效率低
优化双指针法 O(n²) O(1) 空间效率高,顺序保证好 时间效率低
相关推荐
手握风云-1 分钟前
JavaEE初阶第九期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(七)
java·开发语言
码农小灰6 分钟前
单体VS微服务:如何选择最适合的架构?
java·微服务·架构
参宿四南河三24 分钟前
还在使用 Java 8 语法?Java实用新特性来一波
java
lifallen26 分钟前
Paimon INSERT OVERWRITE
java·大数据·数据库·flink
鼠鼠我捏,要死了捏27 分钟前
Java并发编程性能优化实践指南:锁分离与无锁设计
java·concurrency·performance-optimization
哪个旮旯的啊32 分钟前
你要的synchronized锁升级与降级这里都有
java
哪个旮旯的啊32 分钟前
Java并发机制的底层实现之volatile关键字
java
这世界那么多上官婉儿34 分钟前
多实例的心跳检测不要用lock锁
java·后端
snakeshe101035 分钟前
深入理解Java对象引用:地址、拷贝与传递机制
java
哪个旮旯的啊37 分钟前
synchronized锁及其原理
java