算法学习解题技巧-分而治之,逐个突破-LeetCode215

512 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

在前两篇文章我们学习了算法解题技巧: 双指针. 今天继续来学习一个进阶技巧, 分而治之解决问题: 数组中第k大元素.

分治法

分治法: 分而治之. 是计算机中重要的算法, 也是五大常用算法之一. 前文学到的二分法, 亦可以理解为简单的分治法的一种.

分治就是将复杂的问题分解成一个个小的问题, 逐个突破解决问题, 最后将各个小问题的结果合并起来, 从而得到复杂问题的答案.

分治法能解决那些问题

  1. 问题可以分解成多个若干小问题;
  2. 将复杂问题分解成多个小问题后, 每个小问题就比较容易解决;
  3. 复杂问题的解等于各个小问题的解的合并;
  4. 分解开的各个小问题相互独立.

数组中的第 k 大的元素

这个问题是在一个面试中循序渐进的问题, 由找数组中的最大元素 --> 第二大元素 --> 进而演化到 第k大;

我们来看下 LeetCode 215题: 先看图吧, 比较好理解, 但是解决问题就没有昨天那个问题那么简单了. LeetCode 定义为 中等.

题目描述:

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 1<=k<=数组长度

示例 1:

输入: [3,2,1,5,6,4]k = 2 输出: 5

示例  2:

输入: [3,2,3,1,2,4,5,5,6]k = 4 输出: 4

提示:

  • 1 <= k <= nums.length <= 104
  • -104 <= nums[i] <= 104

题解思路分析:

这个并不是找到第 k 个元素, 按照开始的题目难度演进, 查找最大元素和第二大元素, 就是不难理解, 可以使用排序降序排列, 就找到了答案. 所谓的暴力算法思路...(.也不是不可取.后面再优化嘛..)

1. 直接暴力解法:

直接排序, 找到最大的. 但是我们只是找到第 k 大的元素, 排序的话, 所有的元素都有排序了, 这并不需要!

暴力解法复杂度:

  • 时间复制度: O(nlogn), 取决于我们的排序函数
  • 空间复杂度: 也是取决于排序函数

2. 使用 堆的思路,

第 k 大, 就是 k 堆.

在遍历数组过程中维护一个大小为 k 的最大堆, 遍历结束后 堆顶就是第 k 大元素.

3. 分治法: 快速选择

分析, 如下图:

我们选择 4 最为标定点, 在快排之后, 数组的形态就编程下面的样子了: 4 处在它正确的位置, 比 4 小的放前面, 比 4 大的放后面,

要寻找第二大的元素, 如果这个 4 返回的对应索引是在 第四位, 要在第二大的元素就在 >4 这个部分来继续寻找, 对于<4 的这部分就不需要找了.

image

  • JavaScript 解题代码:
const findKthLargest = (nums, k) => {
  const n = nums.length

  const quick = (l, r) => {
    if (l > r) return
    let random = Math.floor(Math.random() * (r - l + 1)) + l 
    swap(nums, random, r)
    /**
     * 选定一个 pivot 元素,根据它进行 partition
     * 通过 partition 后就找出一个位置: 它左边的元素都比 pivot 小,右边的元素都比 pivot 大
     * 左边和右边的元素的是未排序的,但 pivotIndex 是确定下来的
     */
    let pivotIndex = partition(nums, l, r)
   
    if (n - k < pivotIndex) {
      quick(l, pivotIndex - 1)
    } else {
      quick(pivotIndex + 1, r)
    }
  }

  quick(0, n - 1)
  return nums[n - k]
}

function partition(nums, left, right) {
  let pivot = nums[right] 
  let pivotIndex = left
  for (let i = left; i < right; i++) {
    if (nums[i] < pivot) {
      swap(nums, i, pivotIndex) // 交换
      pivotIndex++
    }
  } // 循环结束时,pivotIndex左边都是比pivot小的
  swap(nums, right, pivotIndex) 
  return pivotIndex
}

function swap(nums, p, q) {
  const temp = nums[p]
  nums[p] = nums[q]
  nums[q] = temp
}

思考

我们遇到的问题总是看起来头大, 但是静下心来想一想, 可不可以将问题化解成若干个小问题, 分而治之, 找到解决问题的答案.