⑫ 算法设计思想之“分而治之”

一、理论

1. 简介

  • 分而治之是 算法设计 中的一种方法
  • 它将一个问题 成多个和原问题相似的小问题, 递归解决 小问题,再将结果 并以解决原来的问题

2. 场景

2.1 归并排序

  • 分:把数组从中间一分为二
  • 解:递归地对两个子数组进行归并排序
  • 合:合并有序子数组

2.2 快速排序

  • 分:选基准,按基准把数组分成两个子数组
  • 解:递归地对两个子数组进行快速排序
  • 合:对两个子数组进行合并

二、刷题

1. 猜数字大小(374)

1.1 题目描述

  • 猜数字游戏的规则如下:
    • 每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
    • 如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。
    • 你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):
      • -1:我的数字比较小 pick < num
      • 1:我的数字比较大 pick > num
      • 0:恭喜!你猜对了!pick == num

1.2 解题思路

  • 二分搜索,同样具备“分解合”的特性
  • 考虑选择分而治之

1.3 解题步骤

  • 分:计算中间元素,分割数组
  • 解:递归地在较大或较小子数组进行二分搜索
function guessNumber(n) {
  const rec = (low, high) => {
    if(low > high) return
    const mid = Math.floor((low + high) / 2)
    const res = guess(mid)
    if(res === 0) {
      return mid
    } else if(res === 1) {
      return rec(mid+1, high)
    } else if(res === -1) {
      return rec(1, mid-1)
    }
  }
  return rec(1, n)
}

1.4 时间复杂度&空间复杂度

  • 时间复杂度:O(logn)
  • 空间复杂度:O(n)

2. 翻转二叉树(226)

2.1 题目描述

  • 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点

2.2 解题思路

  • 先翻转左右子树,再将子树换个位置
  • 符合 “分 解 合” 特性

2.3 解题步骤

  • 分:获取左右子树
  • 解:递归地翻转左右子树
  • 合:将翻转后的左右子树换个位置放到根节点上
function invertTree(root) {
  if(!root) return null
  return {
    val: root.val,
    right: invertTree(root.left),
    left: invertTree(root.right)
  }
}

2.4 时间复杂度&空间复杂度

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

3. 相同的树(100)

3.1 题目描述

  • 给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同
  • 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的

3.2 解题思路

输入:p = [1,2,3], q = [1,2,3]
输出:true

  • 两个树:根节点的值相同,左子树相同,右子树相同
  • 符合 “分 解 合” 特性

3.3 解题步骤

  • 分:获取两个树的左子树和右子树
  • 解:递归地判断两个树的左子树是否相同,右子树是否相同
  • 合:将上述结果合并,如果根节点的值也相等,树就相同
function isSameTree(p, q) {
  if(!p && !q) return true
  if(p && q && p.val === q.val && 
    isSameTree(p.left, q.left) &&
    isSameTree(p.right, q.right)
  ) {
    return true
  } 
  return false
}

3.4 时间复杂度&空间复杂度

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

4. 对称二叉树(101)

4.1 题目描述

  • 给你一个二叉树的根节点 root , 检查它是否轴对称

4.2 解题思路

输入:root = [1,2,2,3,4,4,3]
输出:true

  • 转化为:左右子树是否镜像
  • 分解为:树1的左子树和树2的右子树是否镜像;树1的右子树和树2的左子树是否镜像
  • 符合 “分 解 合” 特性

4.3 解题步骤

  • 分:获取两个树的左子树和右子树
  • 解:递归地判断树1的左子树和树2的右子树是否镜像;树1的右子树和树2的左子树是否镜像
  • 合:如果上述成立且根节点值相同,两个树就是镜像
function isSymmetric(root) {
  if(!root) return true
  const isMirror = (l, r) => {
    if(l && r && l.val === r.val &&
      isMirror(l.left, r.right) &&
      isMirror(l.right, r.left)
    ) {
      return true
    }
    return false
  }
  isMirror(root.left, root.right)
}

4.4 时间复杂度&空间复杂度

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

三、总计 -- 技术要点

  • 分而治之是 算法设计 中的一种方法
  • 它将一个问题 成多个和原问题相似的小问题, 递归解决 小问题,再将结果 并以解决原来的问题
  • 应用场景:归并排序、快速排序、二分搜索、翻转二叉树……
posted on 2022-01-28 15:36  pleaseAnswer  阅读(64)  评论(0编辑  收藏  举报