leetcode Hot100 之88合并两个有序数组、283移动零、448找到所有数组中消失的数字

183 阅读4分钟

前言

继续学习一下算法题吧,本期是leetcode Hot100 之88合并两个有序数组、283移动零、448找到所有数组中消失的数字的几种解题方法分享,包括哈希map,原地哈希map,快慢指针用法。

一、88合并两个有序数组

合并两个有序数组leetcode连接

题目描述

image.png

1、简单粗暴解法

最直接的方式就是先合并数组nums2的子项到数组nums1上,然后使用 Array.prototype.sort() ,数组自身的原型方法进行排序。

/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
let merge = function(nums1, m, nums2, n) {
    for(let i = 0; i < n; i++){
        nums1[m+i] = nums2[i]
    }
    nums1.sort((a,b) => a- b)
};

我们用for循环把nums2的每一项给添加到nums1上面去,然后使用数组的api方法sort在将nums1升序排序。

2、双指针的解法

因为题目中原本的数组nums1nums2s是有序的,所以我们可以使用双指针的方法,避免在排序。下面代码,前两个if`` else if 条件判断我们先考虑了nums1nums2 的数值有一方遍历完的情况,后两步条件else if`` else对比 nums1 nums2中的值,小的拿值先放入temp

let merge = function (nums1, m, nums2, n) {
  let temp = [], k = m + n;
  for (let i = 0, index1 = 0, index2 = 0; i < k; i++) {
    if (index1 >= m) {
      temp[i] = nums2[index2++]
    } else if (index2 >= n) {
      temp[i] = nums1[index1++]
    } else if (nums1[index1] < nums2[index2]) {
      temp[i] = nums1[index1++]
    } else {
      temp[i] = nums2[index2++]
    }
  }
  for (let i = 0; i < k; i++) {
    nums1[i] = temp[i]
  }
};

大致的移动过程 1.gif 可能有同学对直接定义在for循环里的index1 index2指针变量看得不习惯,对直接在数组里面进行++也看不习惯,那其实也可以不简写,像下面这样,对变量定义比较明确一些哈。。。

let merge = function (nums1, m, nums2, n) {
  let temp = [], k = m + n,index1 = 0, index2 = 0;
  for (let i = 0; i < k; i++) {
    if (index1 >= m) {
      temp[i] = nums2[index2]
      index2++
    } else if (index2 >= n) {
      temp[i] = nums1[index1]
      index1++
    } else if (nums1[index1] < nums2[index2]) {
      temp[i] = nums1[index1]
      index1++
    } else {
      temp[i] = nums2[index2]
      index2++
    }
  }
  for (let i = 0; i < k; i++) {
    nums1[i] = temp[i]
  }
};

这里和上面的区别就是把定义指针和++的操作,提取出来了而已。

3、双指针解法优化【逆向思维】

这里还是用到双指针的解法,因为,nums1数组是的长度里的补充值0是能够包含nums2的长度的,所以我们可以不用创建临时数组temp,直接将使用双指针的方法将数组nums2,合并到nums1中,由于是升序排序,所以不创建临时数组的情况下,要从头开始遍历的话就无法进行升序排序。 这时候就要发挥一下,【逆向思维】了,从数组中大的值开始取值,存放到nums1的末端,往前逆序遍历,把遍历初始值i= k-1,注意我们遍历的是数组下标, 从numsnums2的后面往前对比,所以设置index1= m-1,index2 = n -1,遍历方法就和上面的相似了,,代码如下:

var merge = function (nums1, m, nums2, n) {
  let k = m + n
  for (let i = k-1,index1 = m-1, index2 = n-1; i >= 0; i--) {
    if (index1 < 0) {
      nums1[i] = nums2[index2--]
    } else if (index2 < 0) {
     break // 这里nums2取值取完了,就跳出取nums1的了,因为nums1比nums2长
    } else if (nums1[index1] > nums2[index2]) {
      nums1[i] = nums1[index1--]
    } else {
      nums1[i] = nums2[index2--]
    }
  }
};

注意下break后的注释部分

二、283移动零

题目描述 image.png

一、双指针移动

读题,题目是让我们把一个数组的非零的项在不改变顺序的情况下移动到前面,把零的项移动到后面。

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
let moveZeroes = function(nums) {
    if(nums == null || nums.length == 0) return;
    let j = 0;
    for(let i = 0; i < nums.length; i++){
        if(nums[i]!=0){
            nums[j++] = nums[i]
        }
    }

    for(let i = j; i < nums.length;i++){
        nums[i] = 0
    }
};

9669b4ffb158eaeeee6f0cd66a70f24411575edab1ab8a037c4c9084b1c743f5-283_1.gif

2、双指针交换

使用leftright作为指针记录,慢指针left记录遇到0的时候他指向0,快指针right不断往前移动,遇到非零的值时就要和前面的遇到的第一个零进行交换。

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function(nums) {
    let len = nums.length, left = 0, right = 0;
    while(right < len){
        if(nums[right]!= 0){
            swap(nums, left, right)
            left++
        }
        right++
    }
};

let swap = function(arr,l,r){
    let temp = arr[l]
    arr[l] = arr[r]
    arr[r] = temp
}

具体的调试过程可以打开vscode调试工具F5运行调试比较直观的看到数据的指向变化。 image.png

3、技巧性解法【记录0,逆向思维】

这个解法声明变量k 来记录数组中零的个数,如果零的个数大于0个,就需要移动位置,遇到非零的项时,逐个和前面的零的项进行位置交换,遍历完数组,就把所有的零项都移动到了数组末尾。

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function(nums) {
   let k = 0,len = nums.length;
   for(let i = 0; i< len; i++){
       if(nums[i] == 0) k++
       else{
           if(k > 0){ // 必要的判断,如果k=0,说明数组中没有0,不用操作数组
               nums[i-k] = nums[i]
               nums[i] = 0
           }
       }
   }
}

三、448找到所有数组中消失的数字

题目描述

image.png

1、 使用Map对象存值

使用Map对象的特点,我们将nums数组的值作为map 的key,将【1,n】这些数作为map 的value,一轮遍历之后就得到了一个在[1,n]范围内没有出现在nums中数字的map对象,然后再遍历将目标值放入arr数组中。这样时间复杂度是O(n),空间复杂度也是O(n).

/**
 * @param {number[]} nums
 * @return {number[]}
 */
let findDisappearedNumbers = function (nums) {
  let map = new Map(), arr = [], len = nums.length;
  for (let i = 0; i < len; i++) {
    if (!map.get(nums[i])) {
      map.set(nums[i], i+1)
    }
  }
  for (let i = 1; i <= len; i++) {
    if(!map.has(i)) {
      arr.push(i)
    }
  }
  return arr
};

2、原地哈希修改

一般的Map对象,会叫做哈希map
根据题意含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内,说明nums数组的每一项都是在【1,n】区间内的,这样就可以在数组内原地哈希操作了, 第一步,把数组的项变成数组的下标, 第二步,给数组中所有在 [1, n] 范围内出现在 nums 中的数字,加一个可以区分的值,确保加完这个值后,有数组值变成的下标的数组的值要大于【1,n】区间,这里加n , 第三步,第二步中我们区别开了,在【1,n】内的数组项值,这里判断判断大于n的项加一就是没有出现在这个区间的值了,加一看注释。

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var findDisappearedNumbers = function (nums) {
  let arr = [], n = nums.length;
  for (let num of nums) {
    let x = (num - 1) % n // (1) 因为数组下标从0开始,而这里数组的值区间是【1,n】,所以要减一,下面 +n ,所以这里要%取余
    nums[x] += n // 
  }
  for (let i = 0; i < n; i++) {
    if(nums[i] <= n){
        arr.push(i+1) // (2)上面减一取【0,n-1】的数组下标,这会要找的值,要把一加回来
    }
  }
  return arr
};

总结

本章题解了leetcode Hot10088合并两个有序数组、283移动零、448找到所有数组中消失的数字。

那么下一章,我们基于本章和上一章的leetcode Hot100 之爬楼梯、斐波那契数列、两数之和,我们来进行实践。

快来动手试试吧!

如果本专栏对你有帮助,不妨点赞、评论、关注~