前端算法练习:合并两个有序数组

104 阅读5分钟

前端岗位在算法这块一直是比较薄弱的,所以最近在刷leetcode,个人觉得刷算法题应该是一个长久的事情,今后也想去养成刷算法的习惯(每日一题之类的哈哈哈),将自己做的题写成文章输出一下会觉得思路更加清晰,争取将每个做过的题的思路和解法都记录下来。

题目链接见:leetcode 88.合并两个有序数组

说人话版题目内容

  1. 会给两个数组nums1nums2,将这两个数组从小到大合并起来,且不能是返回新的数组,换句话说也就是要在nums1中去修改
  2. 然后题目说会给m和n两个整数,分别对应的是这两个数组中元素的数目,也就是说可能nums1的数组长度是7,但里面可能只有3个元素。
  3. 然后nums1的数组长度是nums1的长度加上nums2的长度
  4. 合并后的数组,也就是nums1要是个非递减数组

那其实我们要做的事情就清晰多了,无非是合并两个数组然后排序,但我们要注意的点是处理后的数组要是nums1,不能对nums1进行赋值之类的操作

解法思路

思路1

我们其实可以直接通过splice函数将nums1nums2合并起来,然后使用sort方法进行排序,这几个方法都是在原有数组上修改数据,也符合在nums1上修改的题目条件

思路2

我们可以定义两个指针,分别从nums1nums2的开头开始往后走,指针走的时候要注意边界值,当指针的索引等于数组的元素的时候,即m===nums1.lengthn===nums2.length的时候,这就代表着我们对应的数组元素已经合并完了,所以就应该去处理没有合并完的数组,弄清楚边界之后其实就简单多了,我们只需要去比较大小,然后往数组里放就可以。

还有一个点是我们往nums1放置元素的时候因为指针是从头开始遍历的关系,nums1的元素一开始就会给覆盖,所以我们需要准备一个nums1的拷贝数组来走我们的指针

思路3

也是类似思路2的一个双指针思路,只不过我们的指针变为从后往前遍历,这样我们可以不用多使用一个数组去存储nums1的拷贝,要注意的边界值是指针小于0的时候证明这个数组的元素已经全部遍历完了(全部合并完成)所以需要去处理另一个数组,其实跟思路2很相似,只是边界值变换了而已

解法1

其实没有什么好说的,了解好splice的用法就能解开这道题了,但还是稍微说一下这行代码的思路吧nums1.splice(m, nums1.length - m, ...nums2.splice(0, n))

我们知道nums1中取m个元素(第一个参数),但nums1中长度肯定是大于m的,因为nums1的长度是nums1的长度+nums2的长度,我们需要将剩下的元素都替换掉(即第二个参数,nums1.length - m),替换成nums2的元素(即第三个参数...nums2.splice(0, n)

const test1 = (nums1, m, nums2, n) => {
  nums1.splice(m, nums1.length - m, ...nums2.splice(0, n))
  nums1.sort((a, b) => a - b)
}

解法2

先贴上代码

const nums1 = [1, 2, 3, 0, 0, 0]
const m = 3

const nums2 = [2, 5, 6]
const n = 3

const test2 = (nums1, m, nums2, n) => {
  // 先定义两个指针,和一个nums1的拷贝
  let p1 = 0
  let p2 = 0
  const copyNums = nums1.slice()

  // 确定好边界,当两个指针的大小都超过数组元素数量的话即两个数组都已经遍历完了
  while (p1 < m || p2 < n) {
    // 取出两个数组中的值
    const value1 = copyNums[p1]
    const value2 = nums2[p2]

    if (p1 === m) {
      nums1[p1 + p2] = value2
      p2++
    } else if (p2 === n) {
      nums1[p1 + p2] = value1
      p1++
    } else if (value1 <= value2) {
      nums1[p1 + p2] = value1
      p1++
    } else {
      nums1[p1 + p2] = value2
      p2++
    }
  }
}

图解

我们先准备好两个指针p1 p2,然后准备好我们copyNums(nums1的拷贝),两个指针分别指向两个数组的开头位置,然后接下来进入while循环,比较p1和p2两个数的大小,小的插入nums1的第一个位置p1+p2

image.png 插入数组的元素后对应的指针往前走一步,接下来几张图都是模拟步骤 image.png

image.png

image.png

image.png 下图进入第一个边界值,即p1已经将copyNums遍历完了,所以接下来将num2的元素插入num1即可 image.png 随后p2也来到了边界,意味着nums2也已经遍历完,此时跳出循环即可 image.png

解法3

其实思路跟解法2十分类似,只是变成了从后遍历而已,即p1一开始是m-1p2一开始是n-1然后递减,注意边界值既p1和p2等于-1时,每次遍历将比较大的值放入数组即可,这边就不贴步骤图了直接附上代码

const test3 = (nums1, m, nums2, n) => {
  // 定义好两个指针
  let p1 = m - 1
  let p2 = n - 1

  // 指针大于等于0时证明没遍历完
  while (p1 >= 0 || p2 >= 0) {
    const value1 = nums1[p1]
    const value2 = nums2[p2]
    
    // nums1已经遍历完了
    if (p1 === -1) {
      nums1[p1 + p2 + 1] = value2
      p2--
    // nums2已经遍历完了
    } else if (p2 === -1) {
      nums1[p1 + p2 + 1] = value1
      p1--
    // 都没遍历完的情况下大的一方放入数组
    } else if (value1 > value2) {
      nums1[p1 + p2 + 1] = value1
      p1--
    } else {
      nums1[p1 + p2 + 1] = value2
      p2--
    }
  }
}