这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战
分治法介绍
在计算机科学中,分治法是建基于多项分支递归的一种很重要的算法范型。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
算法优势
-
解决困难问题
分治算法是一个解决复杂问题的好工具,它可以把问题分解成若干个子问题,把子问题逐个解决,再组合到一起形成大问题的答案。比如,汉诺塔问题如果采用分治算法,可以把高度为n的塔的问题转换成高度为n-1的塔来解决,如此重复,直至问题化简到可以很容易的处理为止。
-
算法效率 有很多效率很高的分治算法,比如,Karatsuba快速乘法算法、归并排序、快速排序算法和快速傅里叶变换等。
算法实现
分治法通常采用递归实现,每层递归基本上要实现以下三个步骤
- 分解:将原问题分解为若干个规模较小,相对独立,与原问题形式相同的子问题。
- 解决:若子问题规模较小且易于解决时,则直接解。否则,递归地解决各子问题。
- 合并:将各子问题的解合并为原问题的解。
分治法例题
-
- 给表达式加括号
-
- 不同的二叉搜索树
1. 给表达式加括号
- Different Ways to Add Parentheses (Medium)
Input: "2-1-1".
((2-1)-1) = 0
(2-(1-1)) = 2
Output : [0, 2]
为运算表达式设计优先级,就是设置哪个运算符先算,哪个运算符后算。
首先,拿到一个式子,每一个运算符都把它分割成了两个式子,左式和右式。
我们可以让左式右式先算出来,然后算最后一个这个运算符,(这样就实现了设置这个运算符最后算)
左式右式的计算(都是多解的),就是这个大问题的子问题(分而治之)。
直到左式或右式是一个数字,结束分治。
解题方案
本题解 采用了 分治
的思想:
- 遍历 字符串
- 遇到操作符,就将左右两边的字符串,分别当作 两个表达式
public List<Integer> diffWaysToCompute(String input) {
List<Integer> ways = new ArrayList<>();
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
if (c == '+' || c == '-' || c == '*') {
List<Integer> left = diffWaysToCompute(input.substring(0, i));
List<Integer> right = diffWaysToCompute(input.substring(i + 1));
for (int l : left) {
for (int r : right) {
switch (c) {
case '+':
ways.add(l + r);
break;
case '-':
ways.add(l - r);
break;
case '*':
ways.add(l * r);
break;
}
}
}
}
}
if (ways.size() == 0) {
ways.add(Integer.valueOf(input));
}
return ways;
}
其他算法
这个题目直接使用分治法,会导致一个子问题被求解多次,影响算法的复杂度,这种情况下,有两种解决办法:
- 记忆化搜索:使用map等数据结构记忆下之前计算过的子问题的解,在第二次计算到时,可以直接从之前的记忆中取子问题的解。
- 动态规划:一个子问题被多次求解的根本问题在于,分治法一般是采用递归的实现方式,自顶向下的解决拆解开的子问题,所以,我们可以在使用动态规划的算法,自底向上的计算问题的解,更改了计算方向,就不会再将子问题重复计算两边
2. 不同的二叉搜索树
- Unique Binary Search Trees II (Medium)
给定一个数字 n,要求生成所有值为 1...n 的二叉搜索树。
解题思路
同样采用分治法
- 分:我们可以通过遍历来确定当前树的root节点,这样,要想构建一个二叉搜索树,就需要使用数组中root节点的左边的数字和右边的数字分别构建左右子树,这样就分为两个子问题递归地解决
- 解:我们递归的终点应该是当我们要用于构建子树的左右边界大小关系变反,没有数字用于构建子树时,直接构建一个null返回
- 合:左右子树构建完毕后,我们得到了所有的左子树和右子树,将所有的左子树和右子树分别挂在新构建的root节点上,组成新的树返回 具体代码如下:
public List<TreeNode> generateTrees(int n) {
if (n < 1) {
return new LinkedList<TreeNode>();
}
return generateSubtrees(1, n);
}
private List<TreeNode> generateSubtrees(int s, int e) {
List<TreeNode> res = new LinkedList<TreeNode>();
if (s > e) {
res.add(null);
return res;
}
for (int i = s; i <= e; ++i) {
List<TreeNode> leftSubtrees = generateSubtrees(s, i - 1);
List<TreeNode> rightSubtrees = generateSubtrees(i + 1, e);
for (TreeNode left : leftSubtrees) {
for (TreeNode right : rightSubtrees) {
TreeNode root = new TreeNode(i);
root.left = left;
root.right = right;
res.add(root);
}
}
}
return res;
}