# 分治
递归的一种细分,问题花解成好几个子问题
结果返回需要组合
# 分治的解题策略
- 分解,将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
- 解决,解决各个子问题
- 合并,将各个子问题的解合并为原问题的解
# 模版
递归终止条件,没有问题要解决
处理当前层逻辑
下探下一层
组装结果返回(合并子结果)
清理当前层状态(可选)
# 适用场景
当出现满足以下条件的问题,可以尝试只用分治策略进行求解:
- 原始问题可以分成多个相似的子问题
- 子问题可以很简单的求解
- 原始问题的解是子问题解的合并
- 各个子问题是相互独立的,不包含相同的子问题
# 使用分治法求解的一些经典问题
- 二分查找
- 归并排序
- 快速排序
- 汉诺塔问题
- React 时间分片
# 二分查找
- 前提条件
目标函数单调性(单调递增或递减)
存在上下界
能够通过索引访问
- 代码模版
let left ,right = 0
const end= len(array)-1
while(left<=right){
const mid = (left+right)/2
if(array[mid]==target){
// find the target
break or return result
}
else if(array[mid]<target){
left= mid+1
}
else {
right =mid-1
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 高频题
- 分治
- 二分查找
# Sqrt(x) 求平方根(简单) (opens new window)
- 二分查找
一开始把右边界粗略的设定为目标值 x,左右边界的中间值设为 mid,然后在二分过程中每次发现 mid * mid < x 的情况,就把这个 mid 值记录为 ans。
如果计算出的乘积正好等于 x,就直接返回这个 mid 值。
如果二分查找超出边界了,无论最后的边界是停留在小于 x 的位置还是大于 x 的位置,都返回 ans 即可,因为它是最后一个乘积小于 x 的值,一定是正确答案。
let mySqrt =function (x){
let left = 0;
let right =x;
let ans =-1;
while(left <=right){
let mid =Math.round((left +right) /2)
let product = mid*mid;
if(product <=x){
ans =mid;
left =mid +1
} else if(product >x){
right =mid -1
}else{
return mid
}
}
return ans
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 二分查找(简单) (opens new window)
//左闭右闭区间
const search =function (nums,target){
let start = 0;
let end =nums.length -1
while(start <= end){
const mid =start +((end - start)>>1)
if(nums[mid]===target)return mid
if(nums[mid]<target){
start =mid +1
}else{
end =mid-1
}
}
return -1
}
// 左闭右开区间
const search =function (nums, target){
let start =0;
let end =nums.length
while(start <end ){
const mid = start +((end -start)>>1)
if(nums[mid] ===target ) return mid
if(nums[mid]<target){
start =mid+1
}else{
end = mid
}
}
return -1
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 搜索插入位置(简单) (opens new window)
- 二分查找
const searchInsert =function(nums,target){
let start = 0;
let end =nums.length -1
while(start <=end){
const mid =start +((end -start) >> 1)
if(nums[mid] ===target) return mid
if(nums[mid]<target){
start = mid+1
}else{
end =mid -1
}
}
return start
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 二维数组中查找(中等) (opens new window)
- 类似二分查找的做法:
- 从左下角开始寻找,因为左下角的元素是当前行最小的、当前列表最大的
- 比较元素,如果太大了,上移一行
- 如果太小了,右移一列
- 找到就返回true
- 遍历完,返回false
const findNumberIn2DArray= (matrix,target)=>{
const [m,n] =[matrix.length, matrix[0]?.length];
if(!m){return false}
// 定义左下角到坐标
let [row,col]= [m-1,0];
// 坐标在矩阵中,就一直寻找
while(row >= 0 && col <= n-1){
// 当前元素
const item = matrix[row]][col];
if(item === target){
//找到,返回true
return true
} else if(item >target){
// 太大了,上移一行
row--;
} else{
// 太小了,右移一列
col++;
}
}
return false
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 旋转数组的最小数字(简单) (opens new window)
- 二分查找
- 若mid 大于high 的数,则最小值一定在mid 右侧
- 若mid 小于high 的数,则最小值有两种可能:(1) 最小值在mid 最侧(2)mid 就是最小值
- 若mid 等于high 的数,high--
- 最后返回low 所在数
const minArray =numbers=>{
let [low ,high] =[0,numbers.length -1];
while(low <=high){
const mid =(low + high) >>1;
if(numbers[mid]>numbers[high]){
//最小值一定在mid 右侧
low =mid+1
} else if(numbers[mid]<numbers[high]){
//最小值在mid 左侧,或者mid 就是最小值
high =mid
}else{
high--
}
}
return numbers[low]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 最大子数组和(简单) (opens new window)
- 暴力法(超时,不推荐)
function LSS(list){
const len =list.length;
let max = -Number.MAX_VALUE;
let sum = 0;
for(let i =0; i<len;i++){
sum =0;
for(let j = i ;j<len;j++){
sum +=list[j];
if(sum > max){
max =sum
}
}
}
return max
}
//时间复杂度为 O(N^2)
//空间复杂度为 O(1)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 分治法
- 我们来分析一下这个问题, 我们先把数组平均分成左右两部分。
此时有三种情况:
最大子序列全部在数组左部分 最大子序列全部在数组右部分 最大子序列横跨左右数组 2. 对于前两种情况,我们相当于将原问题转化为了规模更小的同样问题。
对于第三种情况,由于已知循环的起点(即中点),我们只需要进行一次循环,分别找出 左边和右边的最大子序列即可。
所以一个思路就是我们每次都对数组分成左右两部分,然后分别计算上面三种情况的最大子序列和, 取出最大的即可。
const LSS=(list)=>{
return helper(list,0,list.length-1)
}
const helper(list,m,n){
if(m===n){
return list[m]
}
let sum = 0;
let lmax = - Number.MAX_VALUE;
let rmax = - Number.MAX_VALUE;
const mid =((n-m)>>1) + m;
const l =helper(list,m,mid);
const r =helper(list,mid+1,n);
for(let i =mid; i>=m;i--){
sum +=list[i];
if(sum>lmax){
lmax=sum
}
}
sum =0;
for(let i =mid+1;i<=n;i++){
sum += list[i];
if(sum>rmax){
rmax=sum
}
}
return Math.max(l,r,lmax+rmax);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- 动态规划
地推关系式:Q(list, i) = Math.max(0, Q(list, i - 1)) + list[i]
function LSS(list){
const len =list.length
let max =list[0];
for(let i =1;i<len;i++){
list[i]=Math.max(0,list[i-1]) +list[i]
if(list[i]>max) max=list[i]
}
return max
}
2
3
4
5
6
7
8
9
# Pow(x,n)(中等) (opens new window)
- 二分查找
用一种快速幂计算的方式,也就是把 x 的 n 次方转化为 x * x 的 n / 2 次方。
比如求 2 的 10 次方可以转为 4 的 5 次方,这时候遇到奇数次方了,就转化为 4* (4 的 4 次方)。
然后对于 4 的 4 次方,再进一步转化为 16 的 2 次方,最后转为 256 的 1 次方 * 4,就得出最终解 1024。
const myPow =function (x,n){
if(n===0) return 1;
if(n===1) return x;
let abs =Math.abs(n)
let isMinus =abs !==n;
let res =abs % 2 ===0 ?myPow(x*x ,abs/2):x * myPow(x,abs -1)
return isMinus? 1/res :res
}
2
3
4
5
6
7
8
9
# 数组中对逆序对(困难) (opens new window)
- 分治
若没了解过归并排序,建议先熟悉归并排序算法再来看本题。
直接将归并排序进行改进,把数据分成N个小数组。
合并数组 left - mid , mid+1 - right,合并时, 若array[leftIndex] > array[rightIndex],则比右边 rightIndex-mid个数大
count += rightIndex-mid
- 注意和归并排序的区别: 归并排序是合并数组数从小数开始,而本题是从大数开始
function InversePairs(data) {
return mergeSort(data, 0, data.length - 1, []);
}
function mergeSort(array, left, right, temp) {
if (left < right) {
const mid = parseInt((left + right) / 2);
const l = mergeSort(array, left, mid, temp);
const r = mergeSort(array, mid + 1, right, temp);
const m = merge(array, left, right, mid, temp);
return l + m + r;
} else {
return 0;
}
}
function merge(array, left, right, mid, temp) {
let leftIndex = mid;
let rightIndex = right;
let tempIndex = right - left;
let count = 0;
while (leftIndex >= left && rightIndex > mid) {
if (array[leftIndex] > array[rightIndex]) {
count += (rightIndex - mid);
temp[tempIndex--] = array[leftIndex--];
} else {
temp[tempIndex--] = array[rightIndex--];
}
}
while (leftIndex >= left) {
temp[tempIndex--] = array[leftIndex--];
}
while (rightIndex > mid) {
temp[tempIndex--] = array[rightIndex--];
}
tempIndex = 0;
for (let i = left; i <= right; i++) {
array[i] = temp[tempIndex++];
}
return count;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42