JS 刷 Leetcode:剑指offer051.数组中的逆序对数

简介: JS 刷 Leetcode:剑指offer051.数组中的逆序对数

今天同学在群里分享了一到求逆序对数的题目,LeetCode上搜了一下有道差不多的,那我们来做一下吧。

1. 题目

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

  • 示例 1:
输入: [7,5,6,4]
输出: 5

限制:
0 <= 数组长度 <= 50000

2. 暴力梭哈法

一开始我看到这道题的反应是很简单,直接套上两个循环查喽(题目还是刷的少了2333)。

var reversePairs = function(nums) {
  const length = nums.length
  let count = 0
  for(let i = 0; i < nums.length; i++) {
    for(let l = i + 1; l < nums.length; l++) {
      if(nums[l] < nums[i]) {
        count++
      }
    }
  }
  return count
};

image.png

直接就崩了,emmm遇事不决暴力法在这里行不通呀。仔细想想数组长度最大50000,n的二次都快2.5亿次计算了,确实得蹦。

  • 分析:

    • 时间复杂度: O(n2),两个循环,n为数组的长度。
    • 空间复杂度:O(1),只用到一个临时变量。

3. 归并排序优化

使用归并排序,在合并的过程中处理逆序对数,因为在两个有序的序列里,逆序对得个数是很好算的,
可以看下面两张图

3.1 图一解释归并算法:分而治之的思想

分解 -> 排序 -> 合并
image.png

3.2 图二解释取逆序对数

从下图可以看出,每一步的排序中逆序对的个数其实就是

当左边的left[leftIndex] < right[rightIndex]时

$$ 逆序对的个数 = left数组的长度-leftIndex $$

image.png

/**
 * @param {number[]} nums
 * @return {number}
 */
var reversePairs = function(nums) {
  let count = 0 // 记录逆序对数
  const mergeSort = function(nums) {
    const length = nums.length
    if (length < 2) {
      return nums
    }
    const mid = Math.floor((length / 2))   // 获取二分位置
    const leftArr = nums.slice(0, mid)  
    const rightArr = nums.slice(mid)
    return merge(mergeSort(leftArr), mergeSort(rightArr))
  }
  
  const merge = function(leftArr, rightArr) {
    const result = []
    let leftIndex=0,rightIndex=0
    const leftLength = leftArr.length
    const rightLength = rightArr.length
    while(leftIndex < leftLength && rightIndex < rightLength) {
      if(leftArr[leftIndex] <= rightArr[rightIndex]) {
        result.push(leftArr[leftIndex++])
      } else {
        count += (leftLength - leftIndex ) // 关键: 取逆序对的个数
        result.push(rightArr[rightIndex++])
      }
    } 
  
    while(leftIndex < leftLength) {
      result.push(leftArr[leftIndex++])
    }
  
    while(rightIndex < rightLength) {
      result.push(rightArr[rightIndex++])
    }
    return result
  }
  mergeSort(nums)
  return count
};

image.png

  • 分析:

    • 时间复杂度:同归并排序 O(nlogn)。
    • 空间复杂度:同归并排序 O(n)O(n),因为归并排序需要用到一个临时数组。

我最后换了很多种写法,想把时空再优化一下,发现都差不多,然后感觉这种是最好理解了。算了就这样吧~~~

相关文章
|
3月前
|
Go
【LeetCode 热题100】DP 实战进阶:最长递增子序列、乘积最大子数组、分割等和子集(力扣300 / 152/ 416 )(Go语言版)
本文深入解析三道经典的动态规划问题:**最长递增子序列(LIS)**、**乘积最大子数组** 和 **分割等和子集**。 - **300. LIS** 通过 `dp[i]` 表示以第 `i` 个元素结尾的最长递增子序列长度,支持 O(n²) 动态规划与 O(n log n) 的二分优化。 - **152. 乘积最大子数组** 利用正负数特性,同时维护最大值与最小值的状态转移方程。 - **416. 分割等和子集** 转化为 0-1 背包问题,通过布尔型 DP 实现子集和判断。 总结对比了三题的状态定义与解法技巧,并延伸至相关变种问题,助你掌握动态规划的核心思想与灵活应用!
136 1
|
6月前
|
JavaScript 前端开发 API
JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)
array.map()可以用来数据转换、创建派生数组、应用函数、链式调用、异步数据流处理、复杂API请求梳理、提供DOM操作、用来搜索和过滤等,比for好用太多了,主要是写法简单,并且非常直观,并且能提升代码的可读性,也就提升了Long Term代码的可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
6月前
|
数据采集 JavaScript 前端开发
JavaScript中通过array.filter()实现数组的数据筛选、数据清洗和链式调用,JS中数组过滤器的使用详解(附实际应用代码)
用array.filter()来实现数据筛选、数据清洗和链式调用,相对于for循环更加清晰,语义化强,能显著提升代码的可读性和可维护性。博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
11月前
|
自然语言处理 前端开发 JavaScript
🛠️ JavaScript数组操作指南:20个精通必备技巧🚀
本文详细介绍了 JavaScript 中的 20 个高效数组操作技巧,涵盖了从基本的添加、移除元素,到数组转换和去重等高级操作。强调了不可变性的重要性,提供了清晰的代码示例,帮助开发者编写更整洁和高效的代码。无论是新手还是经验丰富的开发者,这些技巧都将显著提升您的编码能力,使您在项目中更具竞争力。
186 2
|
11月前
|
JavaScript 前端开发 测试技术
JS都有哪些操作数组的方法
JS都有哪些操作数组的方法
320 3
|
11月前
|
JavaScript
js删除数组中已知下标的元素
js删除数组中已知下标的元素
272 4
|
11月前
|
JavaScript 前端开发 Java
【javaScript数组,函数】的基础知识点
【javaScript数组,函数】的基础知识点
102 5
|
11月前
【LeetCode-每日一题】 删除排序数组中的重复项
【LeetCode-每日一题】 删除排序数组中的重复项
82 4
|
11月前
|
缓存 JavaScript 前端开发
JavaScript中数组、对象等循环遍历的常用方法介绍(二)
JavaScript中数组、对象等循环遍历的常用方法介绍(二)
157 1
|
11月前
|
索引
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
94 0
Leetcode第三十三题(搜索旋转排序数组)