[ 位运算、自定义排序 ] 905. 按奇偶排序数组

380 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第28天,点击查看活动详情

每日刷题 2021.04.28

题目

  • 给你一个整数数组 nums,将 nums 中的的所有偶数元素移动到数组的前面,后跟所有奇数元素。
  • 返回满足此条件的 任一数组 作为答案。

示例

  • 示例1
输入: nums = [3,1,2,4]
输出: [2,4,3,1]
解释: [4,2,3,1][2,4,1,3][4,2,1,3] 也会被视作正确答案。
  • 示例2
输入: nums = [0]
输出: [0]

提示

  • 1 <= nums.length <= 5000
  • 0 <= nums[i] <= 5000

解题思路

  • 分析题目:需要将nums数组中的元素根据奇偶性进行排序,偶数放在数组的前半部分,奇数放在数组的后半部分。

二次遍历 + 位运算

  • 思路:开辟一个额外的数组空间作为最终的答案数组,记为ans;再开辟一个额外的数组空间存储nums数组中出现的奇数元素。
    • 第一次遍历遍历数组nums,遇到偶数将其加入到ans数组中;遇到奇数将其加入到odd数组中。
    • 第二次遍历:将odd数组中的元素,加入到ans数组的尾部。
  • 其中判断当前的数是偶数还是奇数的时候,可以给当前的数&1,这样要比%2快很多,要多使用位运算。

sort自定义排序

  • 回顾sort函数,如果sort函数中的比较函数未声明,则按照字符串的形式进行比较。
  • 如果声明了比较函数,分以下三种情况:
    • compare(a, b) < 0, a放在b之前
    • compare(a, b) == 0,a与b的相对位置不变
    • compare(a, b) > 0,b放在a的前面
  • 那么结合本题,可知:偶数&1 == 0, 奇数&1 == 1
  • 想要将偶数放在前面,奇数放在后面,就需要将比较函数写成:
// 偶数放在前面,奇数放在后面,那么就需要偶数&1 - 奇数&1 是返回小于0的
// 刚好0 - 1 = -1;反之1 - 0 = 1
nums.sort((a, b) => a&1 - b&1);

一次遍历 + 位运算

  • 优化:两次遍历的解题思路,是否能够通过一次遍历就实现呢?
  • 思路:从nums数组中的第一个数开始遍历,如果当前的数是偶数,那么就接着遍历下一个数;如果当前的数是奇数,那么就需要一个指针rnums数组的末尾开始往前遍历,查找是否有偶数,找到第一个偶数,就将当前位的奇数与其交换。
  • 循环重复上述过程,直到nums数组中的所有元素都被处理完成。
  • 注意⚠️:r指针从数组尾部遍历的时候,要循环遍历到nums[r]等于偶数时,才能够停止,如果一直找不到,且已经小于了当前的i,就说明后面不存在可以交换的偶数,也就是说数组已经被排好序了。

AC代码

自定义sort()

var sortArrayByParity = function(A) {
  // 位运算更快,取模可以通过位运算实现
  return A.sort((a, b) => (a & 1) - (b & 1))
};

一次遍历+位运算

var sortArrayByParity = function(nums) {
  let len = nums.length,ans = [],odd = [];
  let r = len - 1;
  for(let i = 0; i < len; i++) {
    if((nums[i] & 1) == 1){
      // console.log(nums[i])
      // 奇数情况: 需要从后往前找偶数
      while((nums[r] & 1) == 1 && r > i) {
        r--;
      }
      if((nums[r] & 1) == 0){
        // 找到偶数,位置互换
        let tempt = nums[r];
        nums[r] = nums[i];
        nums[i] = tempt;
      }
      // 如果找到最后,都一直没有,那说明当前的数组已经排好了
    }
    if(r <= i) return nums;
  }
};

总结

  • 奇偶性的判断,使用位运算num % 2的速度要远慢于num & 1.