Skip to content

分治是分而治之,它是将一个大的问题拆分为若干的相同/相似的小问题,而这些小问题是可以快速地解决的,从而推广到解决大问题

它分为两个模块:快排和归并

快排 ​

LC75. 颜色分类 ​

这个是把这个数组分为四个部分,需要使用三个指针

定义三个指针 left, i, right

  • [0, left]: 全是 0 的数
  • [left + 1, i - 1]: 全是 1
  • [i, right - 1]: 待遍历的未知数
  • [right, n - 1]: 全是 2

然后考虑细节就可以了:

  • nums[i] == 0: 只需要把 left + 1 和 i 位置交换一下,然后 left++, i++ 即可
  • nums[i] == 1: 不需要经行任何交换,只要 i++ 就可以了
  • nums[i] == 2: 把 right - 1 和 i 位置交换,把 right-- 就可以了

    i 不能加 1, 因为交换完后,i 的位置依然是未知数

最后只需要考虑初识情况就可以了:为了满足那些区间内的情况,left 必须为 -1, right 必须为 n

LC912. 排序数组-快排 ​

这个题目需要有 LC75. 颜色分类 这个基础,因为快排中元素的优化就是需要涉及到这道题目的思想

思路:

  1. 随机找到一个数 randomNum,把小于这个数的放在一堆,等于这个数的放在一堆,大于这个数的放在一堆
    • [start, left]: 小于 randomNum
    • [left + 1, i - 1]: 等于 randomNum
    • [i, right - 1]: 未知
    • [right, end]: 大于 randomNum

    随机找一个数可以让它的时间复杂度趋于 N×logN

  2. 然后进行分治,由于等于 randomNum 的数已经排序好,那么就只需要再排序 [start, left] 和 [right, end] 这两个区间就可以了

LC215. 数组中的第K个最大元素 ​

这个题目是对 LC912. 排序数组-快排 扩展。

思路:

  1. 前面一部分是执行 LC912. 排序数组-快排 ,把它分为三个部分
    • [start, left]: 小于 randomNum
    • [left + 1, i - 1]: 等于 randomNum
    • [i, right - 1]: 未知
    • [right, end]: 大于 randomNum
  2. 然后就可以根据条件来递归
    • 右边那块区间长度大于 k,说明 k 就在右边那块区间内,那么就只需要处理右边那一部分就可以了
    • 如果上面条件不满足,那么就可以找一下中间的那块区间,如果右边的区间加中间区间的长度比 k 大,那么 k 就在中间那块区间内,直接返回就可以了
    • 如果都不满足,那么就在左边那块区间,但是在处理左边那块区间的时候,需要把 k 减去中间和右边区间长度之和,因为题目要求是求第 k 大的值

LCR 159. 库存管理 III ​

这倒题目和 LC215. 数组中的第K个最大元素 类似,需要有 LC912. 排序数组-快排 这个相关的知识点

思路:

  1. 先遍历一次,分为三块
    • [start, left]: 小于 randomNum
    • [left + 1, i - 1]: 等于 randomNum
    • [i, right - 1]: 未知
    • [right, end]: 大于 randomNum
  2. 根据情况来筛选出对应的条件
    • 在左侧的区间,那么还需要遍历一次,但是不需要排序中间和右侧的区间了
    • 在中间的区间(包含左侧区间的最右边和右侧区间的最左边),那么就可以直接返回
    • 在右侧区间,那么依然还需要遍历一次,但是 cnt 就需要减去相关的元素了

归并 ​

归并和快排很像的。

快排是先排再分:先确定元素的位置,然后一个区间一个区间来排序,类似与二叉树的前序遍历

归并是先分再排:先分到不能分为止,分到只有两个元素为止(一个元素是直接返回,不需要排序),然后把那两块区间看为两个有序的数组,类似二叉树的后续遍历

LC912. 排序数组-归并 ​

这个题目归并排序的一个入门题目,思路就是归并排序的思路(上面刚刚讲述过,这里就不复述了)。

在做之后的归并的题目之前,必须要对这个题目的题解很熟悉,因为后面题目都是以这个题目为基础的

LCR 170. 交易逆序对的总数 ​

这个需要熟悉 LC912. 排序数组-归并 不然会比较难以理解

思路:

  1. 先计算左边那块区间的所有符合条件的值,然后计算右边那块区间的所有符合条件的值,最后在左右两边各个取一个,计算出符合条件的值,然后相加起来(这样是和分治的思想一样)
  2. 分治-归并-LCR170.交易逆序对的总数
    分治-归并-LCR170.交易逆序对的总数
    两边各取一个:这个题目是可以用升序也可以采用降序。就以升序为例
    • 当 record[cur1] > record[cur2] 的时候,发现在 [left, cur1 -1] 这个区间内的值都是比 record[cur2] 小,而在 [cur1, mid] 这个区间内的值都是要比 record[cur2] 大,因此从 record[cur2] 视角来看,它是已经可以算出所有比它大的情况了
    • 当 record[cur1] <= record[cur2] 的时候,你不能计算出比它大的情况,因此只需要赋值给临时数组就可以了
  3. 计算左边或者右边:通过分治的思想来理解就可以了

由于题目是要求获取到 左侧比右侧大 这样的情况,所以只会在 record[cur1] > record[cur2] 这样的情况下 result 才会增加

LC315. 计算右侧小于当前元素的个数 ​

这个题目和 LCR 170. 交易逆序对的总数 很像,都是要求逆序对,只不过之前的那道题目是求和,而这道题目是求每个元素对应的逆序对。

由于题目是求左侧那一块的逆序对,如果采用升序的做法,会比较麻烦,因此采用降序的做法。

升序是在右侧求,然后在左侧寻找,找出比它大的元素个数,然后累加,这个和题目中求左侧的有点出入。

而降序是刚好相反,是在左侧求,寻找右侧比它大的元素之和,这个刚好满足题目的要求,因此选择降序

但是由于原先的数组会排序,因此原来的数组的下标会变化,如果想要找出原来的下标,是不能直接求出来的,需要用一个数据结构来存储

“原来的值-对应的下标” 这就很自然的想到了 Hash 表,但是由于值是可以重复的,因此直接使用 Hash 表是不太合适的,但是可以利用它的思想,创建出 “当前元素的下标-原来元素的下标” 这样的 Hash 表。

只有在最后“合并两个有序数组”的时候下标才会变化,因此也是在这个时候来修改。

而如果在原来的位置上修改比较复杂,因此还需要创建一个临时表来存储对应的下标

LC493. 翻转对 ​

这个题目是思路也是和前面的和 LCR 170. 交易逆序对的总数 很像,只不过 result 添加的时机变了。

由于 mergeSort 完后,它是一个有序的数组,因此可以考虑使用同向双指针来处理这个情况(以降序为例)

  1. 遍历到 nums[cur1] > nums[cur2] * 2 的条件,可以直接计算 result 的结果 (result += right - cur2 + 1)
  2. 计算完后,cur1 的情况已经处理完了,执行 cur1++
  3. 此时 cur2 不需要从 mid + 1 开始,因为当前 nums[cur1] 的值一定不会大于 nums[cur2] \* 2
  4. 如果是 nums[cur1] <= nums[cur2] * 2 说明 cur2 不符合,执行 cur2++
  5. 反复执行 1/2/3/4 这个循环就可以了

由于条件和归并排序有出入,那么就不能像之前的那些题目一样,可以一边归并,一边求结果。这道题目只能把归并和求结果给分开