LeetCode力扣刷题——化繁为简的分治法

分治法


一、算法解释

        顾名思义,分治问题由“分”(divide )和“治”( conquer )两部分组成,通过把原问题分为子问题,再将子问题进行处理合并,从而实现对原问题的求解。我们在排序章节展示的归并排序就是典型的分治问题,其中“分”即为把大数组平均分成两个小数组,通过递归实现,最终我们会得到多个长度为 1 的子数组 ; “治”即为把已经排好序的两个小数组合成为一个排好序的大数组,从长度为 1 的子数组开始,最终合成一个大数组。
        我们也使用数学表达式来表示这个过程。定义 T(n ) 表示处理一个长度为 n 的数组的时间复杂度,则归并排序的时间复杂度递推公式为 T ( n ) = 2 T ( n / 2 ) + O ( n ) 。其中 2 T ( n / 2 ) 表示我们分成了两个长度减半的子问题,O ( n ) 则为合并两个长度为 n / 2 数组的时间复杂度。
        那么怎么利用这个递推公式得到最终的时间复杂度呢?这里我们可以利用著名的主定理(Master theorem)求解:  
       
考虑 T(n ) = aT ( n / b ) + f ( n ) ,定义 k = log b a
        1. 如果 f ( n ) = O ( n p ) p < k ,那么 T ( n ) = O ( n K )
        2. 如果存在 c 0 满足 f ( n ) = O ( n k log c n ) ,那么 T ( n ) = O ( n k log c + 1 n )
        3. 如果 f ( n ) = O ( n p ) p > k ,那么 T ( n ) = O ( f ( n ))
        通过主定理我们可以知道,归并排序属于第二种情况,且时间复杂度为 O ( n log n ) 。其他的分治问题也可以通过主定理求得时间复杂度。
        另外,自上而下的分治可以和 memoization 结合,避免重复遍历相同的子问题。如果方便推 导,也可以换用自下而上的动态规划方法求解。

二、经典问题

1. 表达式问题

241. 为运算表达式设计优先级

241. Different Ways to Add Parentheses

        给你一个由数字和运算符组成的字符串 expression ,按不同优先级组合数字和运算符,计算并返回所有可能组合的结果。你可以 按任意顺序 返回答案。

        生成的测试用例满足其对应输出值符合 32 位整数范围,不同结果的数量不超过 10^4 。

        利用分治思想,我们可以把加括号转化为,对于每个运算符号,先执行处理两侧的数学表达式,再处理此运算符号。注意边界情况,即字符串内无运算符号,只有数字。
class Solution {
public:
    vector<int> diffWaysToCompute(string expression) {
        vector<int> ways;
        for(int i=0; i<expression.length(); ++i){
            char c = expression[i];
            if(c == '+' || c == '-' || c == '*'){
                vector<int> left = diffWaysToCompute(expression.substr(0, i));
                vector<int> right = diffWaysToCompute(expression.substr(i + 1));
                for(const int & l: left){
                    for(const int & r: right){
                        switch(c){
                            case '+': ways.push_back(l + r);    break;
                            case '-': ways.push_back(l - r);    break;
                            case '*': ways.push_back(l * r);    break;
                        }
                    }
                }
            }
        }
        if(ways.empty())    ways.push_back(stoi(expression));
        return ways;
    }
};
        我们发现,某些被 divide 的子字符串可能重复出现多次,因此我们可以用 memoization 来去重。我们可以从上到下用分治处理 +memoization ,当然也可以直接从下到上用动态规划处理。
class Solution {
public:
    unordered_map<string, vector<int>> memo;
    vector<int> diffWaysToCompute(string expression) {
        // 之前计算过这个式子了,直接返回
        if(memo.find(expression) != memo.end()){
            return memo[expression];
        }
        vector<int> ways;
        for(int i=0; i<expression.length(); ++i){
            char c = expression[i];
            if(c == '+' || c == '-' || c == '*'){
                vector<int> left = diffWaysToCompute(expression.substr(0, i));
                vector<int> right = diffWaysToCompute(expression.substr(i + 1));
                for(const int & l: left){
                    for(const int & r: right){
                        switch(c){
                            case '+': ways.push_back(l + r);    break;
                            case '-': ways.push_back(l - r);    break;
                            case '*': ways.push_back(l * r);    break;
                        }
                    }
                }
            }
        }
        // 字符串是纯数字,直接转换
        if(ways.empty())    ways.push_back(stoi(expression));、
        // 记忆化存储
        memo[expression] = ways;
        return memo[expression];
    }
};

三、巩固练习

932. 漂亮数组

932. Beautiful Array

312. 戳气球

312. Burst Balloons


欢迎大家共同学习和纠正指教

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值