第二章算法作业
分治法的核心逻辑很朴素:“分解 - 解决 - 合并”。面对一个复杂问题,先把它拆成若干个结构相似的子问题,解决子问题后,再把结果合并成原问题的解。
对于 “找第 k 小的数”,分治法的优势在于:我们不需要给整个数组排序,只需要通过 “划分” 定位到第 k 小的数所在的子区间,然后递归处理这个子区间即可 —— 这就像在一堆苹果里找第 3 小的,没必要把所有苹果按大小排好,只要先挑一个苹果当 “参照物”,把比它小的放左边、比它大的放右边,再看第 3 小的在左边还是右边,针对性地找就行。
步骤 1:选一个 “基准”,给数组 “分家”
首先从数组中选一个元素作为基准(pivot),然后把数组分成三部分:
左区间(L):所有元素都比 pivot 小;
中间区间(M):所有元素都等于 pivot;
右区间(R):所有元素都比 pivot 大。
比如数组[5, 2, 9, 1, 5, 6],若选 pivot=5,划分后:
L = [2, 1](比 5 小);
M = [5, 5](等于 5);
R = [9, 6](比 5 大)。
步骤 2:判断第 k 小的数在哪,递归求解
划分后,我们只需要关注第 k 小的数在哪个区间:
若 k ≤ 左区间长度(len (L)):说明第 k 小的数在左区间,递归处理左区间,找第 k 小的数;
若左区间长度 <k ≤ 左区间长度 + 中间区间长度(len (L)+len (M)):说明第 k 小的数就是 pivot(因为中间区间的元素都等于 pivot,且排在左区间之后);
若 k > 左区间长度 + 中间区间长度:说明第 k 小的数在右区间,递归处理右区间,找第 k'=k-(len (L)+len (M)) 小的数(因为右区间的元素排在左和中间之后,需要调整 k 的值)。
最好情况:O (n)
如果每次选的 pivot 都能把数组 “均匀劈开”(比如 pivot 是数组的中位数),左、右区间的长度差不多是原数组的一半。
第一次划分遍历数组,耗时 O (n);
下次递归处理的子数组长度是 n/2,耗时 O (n/2);
再下次是 n/4,耗时 O (n/4)……
总耗时就是 O (n) + O (n/2) + O (n/4) + ... ≈ O (n)(等比数列求和,总和趋近于 2n)。
最坏情况:O (n²)
如果每次选的 pivot 是数组中最小或最大的元素(比如数组已经排序,却每次选最后一个元素当 pivot),划分后子数组的长度只比原数组小 1。
第一次划分耗时 O (n);
下次递归处理 n-1 个元素,耗时 O (n-1);
再下次是 n-2……
总耗时就是 O (n) + O (n-1) + ... + O (1) = O (n²),和 “冒泡排序后取第 k 个” 一样慢了。
用分治法找第 k 小的数,本质是通过 “划分 - 定位 - 递归” 的思路,把一个 O (n log n) 的排序问题简化为平均 O (n) 的高效解法。这背后,是分治法 “化繁为简” 的核心思想 —— 面对复杂问题时,与其硬刚,不如拆成小问题逐个击破。
浙公网安备 33010602011771号