前端算法面试必刷题系列[33]

376 阅读3分钟

这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。

57. 删除有序数组中的重复项 II (remove-duplicates-from-sorted-array-ii)

标签

  • 双指针
  • 中等

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 最多出现两次 ,返回删除后数组的新长度

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 
并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 
不需要考虑数组中超出新长度后面的元素。

基本思路

判重 类型关注两个要素:

  1. 排序
  2. 前项对比

由于这题排序自带了所以直接考虑前项对比即可。又考虑用O(1)的空间复杂度,就用双指针

我们来进行前项对比,假设数列为1,1,1,2,2,3 那么对应 index 为 i-1, i, j ... 如图

数列为 1,  1,  1,  2,  2,  3
point |   |   |
     i-1, i,  j   

nums[i-1] === nums[j] 时,很明显前三个数重复,那么快指针先后移,看下一个元素,如果还相等,那么前4个数就重复,以此类推。(我们看见移动之后不等了。)

数列为 1,  1,  1,  2,  2,  3
point |   |       |
     i-1  i       j   

如果不等,也就是 nums[i-1] !== nums[j] 时,说明我们遇到了不重复元素,那么 慢指针 i 先后移,然后 nums[i] = nums[j],覆盖我们的重复元素。

数列为 1,  1, [2], 2,  2,  3
point |       |   |
     i-1      i   j   

继续往下遍历到底,这样我们就能得到一个最多出现两次的数列。

基本步骤

  1. 设置快(j)慢(i)指针
  2. 当且仅当遇到下一个不重复的元素时,更新i指针位置为下一个元素
  3. 否则i指针位置不动,j++
  4. 数组遍历完后返回 i+1 因为 i索引位置,而题目要求返回的是长度

写法实现

var removeDuplicates = function(nums) {
  let len = nums.length
  // 长度小于3,直接返回
  if (len < 3) {
    return len
  }
  // 慢指针 i
  let i = 1;
  // 进行遍历
  for(let j = 2; j < len; j++) {
    // 当 nums[i - 1] !== nums[j] 表示遇到了不重复元素,
    // 那么 慢指针后移,并更新
    if(nums[i - 1] !== nums[j]) {
      i++
      nums[i] = nums[j]
    }
  }
  // 返回的是长度
  return i + 1
};

let nums = [1,1,1,2,2,3]
console.log(removeDuplicates(nums))

58. 搜索旋转排序数组 II (search-in-rotated-sorted-array-ii)

标签

  • 二分查找
  • 中等

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

假设按照升序排序的数组在预先未知的某个点上进行了旋转

( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。

编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。注意,本题中的 nums 可能包含重复元素

输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true

相关知识

对二分法不熟悉的同学,移步二分查找

另外这题的类似题也在二分查找这篇最后一题

基本步骤

由于数组基本有序,虽然中间有一个“断开点”,还是可以使用二分搜索的算法来实现。

                                     right
        [   大数值区间    ]              |
        [4,   5,   6,   7,   0,   1,   2]
        |               |    [ 小数值区间 ]
      left            pviot

其实这数列分2段,一段是小数值区间,一个是大数值区间

  • 我们看如果中间的 pviot落在了 大数值区间,则有 nums[pviot] > nums[left]nums[pviot] > nums[right]像上图一样。

  • 如果中间的 pviot落在了 小数值区间,则有 nums[pviot] < nums[left]nums[pviot] < nums[right], 像下图一样。

                                     right
        [ 大数值区间 ]                   |
        [6,   7,   0,   1,   2,   4,   5]
        |              [|   小数值区间   ]
      left            pviot

判断出落在哪个区间内后,再判断基准和target的关系,再调整左右边界用二分法处理问题。

写法实现

var search = function(nums, target) {
  let len = nums.length;
  if (len === 0) {
    return false
  }
  let [left, right] = [0, len - 1]
  // 二分法
  while (left <= right) {
    let pivot = left + Math.floor((right - left) / 2)
    // 匹配到直接返回
    if (nums[pivot] === target) {
      return true;
    } else if (nums[pivot] > nums[left]) {
      // 说明基准在数值大的一部分区间中,再调整左右边界
      if (nums[left] <= target && target < nums[pivot]) {
        right = pivot - 1
      } else {
        left = pivot + 1
      }
    } else if (nums[pivot] < nums[right]) {
      // 说明基准在数值小的一部分区间中,再调整左右边界
      if (nums[pivot] < target && target <= nums[right]) {
        left = pivot + 1
      } else {
        right = pivot - 1
      }
    } else {
      // 由于本题允许重复,所以这里是等于的情况
      if (nums[left] == nums[pivot]) {
        left++;
      }
      if (nums[right] == nums[pivot]) {
        right--;
      }
    }
  }
  return false
};

let nums = [2,5,6,0,0,1,2], target = 0
console.log(search(nums, target))

当然我们用的是 javascript,这么强行一把梭 ^ - ^ 也没毛病

var search = function(nums, target) {
  return nums.includes(target)
};

另外向大家着重推荐下这位大哥的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列

今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 搜索我的微信号infinity_9368,可以聊天说地 加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我 presious tower shock the rever monster,我看到就通过,暗号对不上不加哈,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考