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

999 阅读2分钟

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

21. 下一个排列 (next-permutation)

标签

  • 语文理解
  • 中等

题目

leetcode 传送门

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

实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。必须原地修改,只允许使用额外常数空间。

简单来说所谓下一个排列结果其实是这些数排列组合后变成的整数。比如[1, 2, 3] 有排列

[1, 2, 3] < [1, 3, 2] < [2, 1, 3] < [2, 3, 1] < [3, 1, 2] < [3, 2, 1]
 123 < 132 < 213 < 231 < 312 < 321

下一个排列就是这个顺序的下一个是那个,如果已经最大就回到队首变成最小的。

基本思路

  1. 我们希望下一个数比当前数大,这样才满足“下一个排列”的定义。因此只需要将后面的大数与前面的小数交换,就能得到一个更大的数。比如 213,将 13 交换就能得到一个更大的数 231

  2. 我们还希望下一个数增加的幅度尽可能的小,为了满足这个要求,我们需要:在尽可能靠右的低位进行交换,需要从后向前查找将一个尽可能小大数 与前面的小数交换。比如 1243,下一个排列应该把 32 交换而不是把 42 交换。将大数换到前面后,需要将大数后面的所有数重置为升序,升序排列就是最小的排列。

  3. 已经是最大的了,是个降序(最大),直接逆置变成升序(最小)。

步骤

  1. 从后向前查找第一个相邻升序的元素对 (i, j),满足 A[i] < A[j]。此时 [j,end) 必然是降序,如果找不到符合的相邻元素对,说明当前 [begin, end) 为一个降序顺序,则直接跳到步骤 4
  2. 在 [j,end) 从后向前查找第一个满足 A[i] < A[k]kA[i]、A[k] 分别就是上文所说的小数大数
  3. A[i]A[k] 交换
  4. 可以断定这时 [j, end) 必然是降序,逆置 [j, end),使其升序

写法实现

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var nextPermutation = function(nums) {
  let len = nums.length
  if (len <= 1) {
    return nums
  }
  let [i, j, k] = [len - 2, len - 1, len - 1]
  // 找到第一组相邻升序
  while (i >= 0 && nums[i] >= nums[j]) {
    i--;
    j--;
  }
  // 不是最后一个排列,寻找k,使得A[i]<A[k]
  if (i >= 0) {
    while (nums[i] >= nums[k]) {
      k--;
    }
    // 解构赋值交换 A[i], A[k]
    [nums[i], nums[k]] = [nums[k], nums[i]]
  } 
  // [j, end)转升序,使数变小
  for (i = j, j = len - 1; i < j; i++, j--) {
    [nums[i], nums[j]] = [nums[j], nums[i]]
  }
  // console.log(nums)
};

console.log(nextPermutation([3,2,1]))

22. 二分查找 (binary-search)

标签

  • 基本查找
  • 简单

题目

leetcode 传送门

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

二分查找是一种基于比较目标值和数组中间元素的教科书式算法。

基本思想

  1. 如果目标值等于中间元素,则找到目标值
  2. 如果目标值较,继续在侧搜索。
  3. 如果目标值较,则继续在侧搜索。

所以注意使用前提是数组是有序的。每次排除一半错误答案。

步骤

  1. 初始化指针 left = 0, right = n - 1
  2. left <= right
    1. 比较中间元素 nums[pivot] 和目标值 target
    2. 如果 target === nums[pivot],返回 pivot。
    3. 如果 target < nums[pivot],则在左侧继续搜索 right = pivot - 1
    4. 如果 target > nums[pivot],则在右侧继续搜索 left = pivot + 1

写法实现

var binarySearch = function(nums, target) {
  // 设置左右指针和中间位基准
  let [left, right, pivot] = [0, nums.length - 1, 0]
  while (left <= right) {
    pivot = left + Math.floor((right - left) / 2)
    if (nums[pivot] === target) {
      return pivot
    } else if (target < nums[pivot]) {
      right = pivot - 1
    } else {
      left = pivot + 1
    }
  }
  return -1
};

console.log(binarySearch([-1,0,3,5,9,12], 9))

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

标签

  • 二分查找
  • 中等

题目

leetcode 传送门

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

假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。你可以假设数组中不存在重复的元素。(数组中的每个值都 独一无二)

旋转的含义其实就是原本有序的数列中有一个断开点。把后面随机一段有序的序列放到数组前面,这样形成了前后2段有序的序列。

相关知识

上面的二分查找是这题的铺垫,这题是二分查找的稍加变化。

基本思路

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

                                     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, left, right, pivot] = [nums.length, 0, nums.length - 1, 0]
  if (len === 0) {
    return -1
  }
  while (left <= right) {
    pivot = left + Math.floor((right - left) / 2)
    if (nums[pivot] === target) {
      return pivot
    } 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
      }
    }
  }
  return -1
};

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

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

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

参考