Q5-剑指offer51/ LCR170-数组中的逆序对

83 阅读3分钟

1 实现思路

1 最容易想到的暴力解法是,通过双层遍历一一比较 arr[i]和 arr[i+1~ end]的数值,是逆序对就计数

2 从暴力解法可以看出,要统计逆序对数量,本质上: 成员A一定是需要 和所有非成员A 一一进行比较的

  • a: 归并排序本质上,也会在mergePair这一步,让所有成员都进行一一对标,比如
   a,b,c,d,e,f,g,h
      ↙      ↘
    abcd        efgh
    ↙  ↘       ↙    ↘
   ab   cd    ef     gh
   ↙↘   ↙ ↘   ↙ ↘    ↙ ↘
  a  b  c d   e f   g   h
  
  
开始排序并返回1: ab会进行比较
   ba  dc   ef    hg
   
开始排序并返回2: ab已经比较过 + a会和dc进行对比
   bacd       efgh
   
开始排序并返回3: abdc已经比较过 + a会和efgh进行对比
       abcdefgh
       
也就是说: a会和除了和a本身的 其他所有元素进行比较
  • b: 归并的第2个特点是: 他的每次归并过程,都会返回有序的数组成员,这就意味着我们可以减少比较次数
如果 bacd, efgh的第一轮比较循环中,b比e大,那么比b大的 acd也一定大于e,不用再让 acd和e 显式比较了

2 代码实现

1 方法1: 归并排序 时间复杂度 O(nlogn); 空间复杂度O(n)

let copy = [];

function reversePairs(record) {
  copy = new Array(record.length);
  return mergeSort(record, 0, record.length - 1);
}

// 归并排序子过程
function mergeSort(arr, l, r) {
  if (l >= r) return 0;
  const mid = l + ((r - l) >> 1);
  // 易错点1: 要明确 递归分治的返回值含义,按实际要求明确清楚 递归函数返回值
  const count1 = mergeSort(arr, l, mid);
  const count2 = mergeSort(arr, mid + 1, r);
  // 优化点1: 说明必然是有序(升序)情况,本轮无需再进行 左右区间归并
  if (arr[mid] <= arr[mid + 1]) return count1 + count2;
  // 否则除了左右子区间逆序对数量,还要计算 左右区间合并时的逆序对数量
  return mergePart(arr, l, mid, r) + count1 + count2;
}

function mergePart(arr, l, mid, r) {
  let res = 0;
  // 易错点2.1: 这里不能使用copy = arr.slice(l, r+1),因为这样会形成索引位置偏移
  // 即 recode[5~9]会偏移为copy[0,4],导致后续从copy[l/mid]取值比较时,会形成空值比较
  // 导致 record数组内容错误 && res值计算错误

  // 易错点2.2: 这里不能使用copy = [...arr], 因为存在用例会导致超时
  for (let i = l; i <= r; i++) {
    copy[i] = arr[i];
  }
  let p1 = l, p2 = mid + 1, p = l;
  while (p1 <= mid && p2 <= r) {
    if (copy[p1] <= copy[p2]) {
      arr[p++] = copy[p1++];
    } else {
      arr[p++] = copy[p2++];
      // 易错点3.1: 这里应该是 res += mid - p1 + 1; 而不是 res+= 1
      // 因为 在这种情况下,copy[p1~mid]必然 都会大于 copy[p2]
      // 且索引是从0开始,所以其对应数量就是 mid - i + 1, 而不仅仅是当前p1这一个

      // 易错点3.2: 之所以是+=,是因为该次比较完成后,会继续进入下一次符合的 while循环
      // 所以需要累计 逆序对数量
      res += mid - p1 + 1;
    }
  }
  while (p1 <= mid) arr[p++] = copy[p1++];
  while (p2 <= r) arr[p++] = copy[p2++];
  return res;
}