【路飞】归并排序-数组中的逆序对

116 阅读2分钟

归并排序

归并排序不是原地排序,时间复杂度为 O(nlogn)

实现

归并排序.png merge过程:将两个排序数组合并成一个数组的过程,将数组分成两部分:[1,3,5,6]、[2,4,7,8],每次比较i、j两个位置元素的大小;

1. 自顶向下的归并排序

function mergeSort(arr, l = 0, r = arr.length - 1) {
  if (l >= r) return arr;
  let mid = Math.floor((l + r) / 2);
  mergeSort(arr, l, mid);
  mergeSort(arr, mid + 1, r);
  merge(arr, l, mid, r);
  return arr;
}

/** 合并两个有序的区间 arr[l, mid] 和 arr[mid + 1, r] */
function merge(arr, l, mid, r) {
  // 拷贝之前的数组
  const temp = arr.slice(l, r + 1);
  let i = l,
    j = mid + 1;
  for (let k = l; k <= r; k++) {
    // 左侧已经全部排序完毕
    if (i > mid) {
      arr[k] = temp[j - l];
      j++;
      // 右侧已经全部排序完毕
    } else if (j > r) {
      arr[k] = temp[i - l];
      i++;
      // 左侧的数字小于右侧的数字
    } else if (temp[i - l] < temp[j - l]) {
      arr[k] = temp[i - l];
      i++;
      // 右侧的数字小于左侧的数字
    } else {
      arr[k] = temp[j - l];
      j++;
    }
  }
  return arr;
}

2.归并排序的优化

  1. 判断是否需要merge
  2. 只创建一个临时空间

function mergeSort2(arr) {
  // 优化2: 只创建一个临时空间
  const temp = arr.slice();
  const sort = (arr, l, r, temp) => {
    if (l >= r) return arr;
    let mid = Math.floor((l + r) / 2);
    sort(arr, l, mid, temp);
    sort(arr, mid + 1, r, temp);
    // 优化1:当arr本身就是有序的时候,不需要再次排序
    if (arr[mid] > arr[mid + 1]) {
      merge2(arr, l, mid, r, temp);
    }
    return arr;
  };
  return sort(arr, 0, arr.length - 1, temp);
}

function merge2(arr, l, mid, r, temp) {
  for (let i = l; i <= r; i++) {
    temp[i] = arr[i];
  }
  let i = l,
    j = mid + 1;
  for (let k = l; k <= r; k++) {
    if (i > mid) {
      arr[k] = temp[j];
      j++;
    } else if (j > r) {
      arr[k] = temp[i];
      i++;
    } else if (temp[i] <= temp[j]) {
      arr[k] = temp[i];
      i++;
    } else {
      arr[k] = temp[j];
      j++;
    }
  }
  return arr;
}

3.自底向上的归并排序

function merge2(arr, l, mid, r, temp) {
  for (let i = l; i <= r; i++) {
    temp[i] = arr[i];
  }
  let i = l,
    j = mid + 1;
  for (let k = l; k <= r; k++) {
    if (i > mid) {
      arr[k] = temp[j];
      j++;
    } else if (j > r) {
      arr[k] = temp[i];
      i++;
    } else if (temp[i] <= temp[j]) {
      arr[k] = temp[i];
      i++;
    } else {
      arr[k] = temp[j];
      j++;
    }
  }
  return arr;
}

function mergeSort3(arr) {
  const temp = arr.slice();
  const len = arr.length;
  for (let size = 1; size < len; size = 2 * size) {
    for (let i = 0; i + size < len; i += 2 * size) {
      if (arr[i + size - 1] > arr[i + size]) {
        merge2(
          arr,
          i,
          Math.min(i + size - 1, len - 1),
          Math.min(i + 2 * size - 1, len - 1),
          temp
        );
      }
    }
  }
  return arr;
}

力扣刷题

题目描述

剑指 Offer 51. 数组中的逆序对

难度为 困难

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]
输出: 5

限制:

0 <= 数组长度 <= 50000

分析

逆序对.png

利用归并排序,后面区间([2,4,7,8])的元素归并上来时,和前面区间([1,3,5,6])剩余元素形成逆序对;

function reversePairs(arr) {
  const temp = arr.slice();
  let res = 0;
  const sort = (arr, l, r, temp) => {
    if (l >= r) return arr;
    let mid = Math.floor((l + r) / 2);
    sort(arr, l, mid, temp);
    sort(arr, mid + 1, r, temp);
    if (arr[mid] > arr[mid + 1]) {
      merge(arr, l, mid, r, temp);
    }
    return arr;
  };

  function merge(arr, l, mid, r, temp) {
    for (let i = l; i <= r; i++) {
      temp[i] = arr[i];
    }
    let i = l,
      j = mid + 1;
    for (let k = l; k <= r; k++) {
      if (i > mid) {
        arr[k] = temp[j];
        j++;
      } else if (j > r) {
        arr[k] = temp[i];
        i++;
      } else if (temp[i] <= temp[j]) {
        arr[k] = temp[i];
        i++;
      } else {
        res += mid - i + 1;
        arr[k] = temp[j];
        j++;
      }
    }
    return arr;
  }
  sort(arr, 0, arr.length - 1, temp);
  return res;
}