化繁为简的分治法

1.算法解释

顾名思义,分治问题由“分”(divide)和“治”(conquer)两部分组成,通过把原问题分为子问题,再将子问题进行处理合并,从而实现对原问题的求解。我们在排序章节展示的归并排序就是典型的分治问题,其中“分”即为把大数组平均分成两个小数组,通过递归实现,最终我们会得到多个长度为 1 的子数组;“治”即为把已经排好序的两个小数组合成为一个排好序的大数组,从长度为 1 的子数组开始,最终合成一个大数组。
我们也使用数学表达式来表示这个过程。定义 T(n) 表示处理一个长度为 n 的数组的时间复杂度,则归并排序的时间复杂度递推公式为 T(n) = 2T(n/2) + O(n)。其中 2T(n/2) 表示我们分成了两个长度减半的子问题,O(n) 则为合并两个长度为 n/2 数组的时间复杂度。那么怎么利用这个递推公式得到最终的时间复杂度呢?这里我们可以利用著名的主定理 (Master theorem)求解:

通过主定理我们可以知道,归并排序属于第二种情况,且时间复杂度为 O(n log n)。其他的分治问题也可以通过主定理求得时间复杂度。
另外,自上而下的分治可以和 memoization 结合,避免重复遍历相同的子问题。如果方便推导,也可以换用自下而上的动态规划方法求解。

2.例题

给定一个只包含加、减和乘法的数学表达式,求通过加括号可以得到多少种不同的结果

题解:利用分治思想,可以把加括号转化为,对于每个运算符号,先执行处理两侧的数学表达式,再处理此运算符号。注意边界情况,即字符串内无运算符号,只有数字。

vector<int>diffWaysToCompute(string input)
{vector<int>ways;for(int i=0;i<input.length();i++){char c = input[i];if(c=='+'||c=='-'||c=='*'){vector<int>left=diffWaysToCompute(input.sub(0,i));vector<int>right=diffWaysToCompute(input.sub(i+1));for(const int & 1: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(input));return ways;
}

我们发现,某些被 divide 的子字符串可能重复出现多次,因此我们可以用 memoization 来去重。或者与其我们从上到下用分治处理 +memoization,不如直接从下到上用动态规划处理。

vector<int>diffWaysToCompute(string input){vector<int>data;vector<char>ops;int num=0;char op=' ';istringstream ss(input + "+");while(ss>>num&&ss>>op){data.push_back(num);ops.push_back(op);}int n=data.size();vector<vector<vector<int>>> dp(n,vector<vector<int<<(n,vector<int>()));for(int i=0;i<n;++i){for(int j=i;j>=0;--j){if(i==j){dp[j][i].push_back(data[i]);}else{for(int k=j;k<i;k+=1){for(auto left : dp[j][k]){for(auto right : dp[k+1][i]){int val=0;switch(ops[k]){case '+':val=left+right;break;case '-':val=left-right;break;case '*':val=left*right;break;}dp[j][i].push_back(val);}}}}}}return dp[0][n-1];
}

上节动态规划还有一解决股票交易问题没写,这里补上:

3.股票交易

题目一:给定一段时间内每天的股票价格,已知你只可以买卖各一次,求最大的收益、
例:输入一个数组,表示每天的股票价格;输出一个整数,表示最大的收益

我们可以遍历一遍数组,在每一个位置 i 时,记录 i 位置之前所有价格中的最低价格,然后将当前的价格作为售出价格,查看当前收益是不是最大收益即可。

int maxProfit(vector<int>&prices)
{int sell=0,buy=INT_MIN;for(int i=0;i<prices.size();++i){buy=max(buy,-prices[i]);sell=max(sell,buy+prices[i]);}return sell;
}

题目二:给定一段时间内每天的股票价格,已知你只可以买卖各k次,且每次只能拥有一只股票,求最大的收益。

如果 k 大于总天数,那么我们一旦发现可以赚钱就进行买卖。如果 k 小于总天数,我们可以
建立两个动态规划数组 buy 和 sell,对于每天的股票价格,buy[j] 表示在第 j 次买入时的最大收
益,sell[j] 表示在第 j 次卖出时的最大收益。

//辅函数
int maxProfitUnlimited(vector<int>prices)
{int maxProfit=0;for(int i=1;i<prices.size();++i){if(peices[i]>prices[i-1]){maxProfit += prices[i] - prices[i-1];}}return maxProfit;
}
//主函数
int maxProfit(int k,vector<int>&prices)
{int days=prices.size();if(days<2){return 0;}if(k>=days){return maxProfitUnlimited(prices);}vector<int>buy(k+1,INT_MIN),sell(k+1,0);for(int i=0;i<days;++i){for(int j=0;j<=k;++k){buy[j] = max(buy[j],sell[j-1] - prices[i]);sell[j] = max(sell[j],buy[j] + prices[i]);}}return sell[k];
}

题目三:给定一段时间内每天的股票价格,已知每次卖出之后必须冷却一天,且每次只能拥有一支股票,求最大的收益。

我们可以使用状态机来解决这类复杂的状态转移问题,通过建立多个状态以及它们的转移方式,我们可以很容易地推导出各个状态的转移方程。如图所示,我们可以建立四个状态来表示带有冷却的股票交易,以及它们的之间的转移方式。

int maxProfit(vector<int>&prices)
{int n=prices.size();if(n==0){return 0;}vector<int>buy(n),sell(n),s1(n),s2(n);s1[0]=buy[0]=-prices[0];sell[0]=s2[0]=0;for(int i=1;i<n;i++){buy[i]=s2[i-1]-prices[i];s1[i]=max(buy[i-1],s1[i-1]);sell[i]=max(buy[i-1],s1[i-1])+prices[i];s2[i]=max(s2[i-1],sell[i-1]);}return max(sell[n-1],s2[n-1]);
}

2021.09.09
滴滴,入职工作5个月零3天了
经验、方法、思考、练习、积累
好好加油,以一个好的面貌迎接2022年!

C++算法之化繁为简的分治法相关推荐

  1. 算法设计与分析——分治法

    主要思想 (其实有这个思想也想不出来): 1.划分:整个问题划分成多个子问题 2.求解:求解各子问题的解 3.合并:合并子问题的解 (手说:"我会了",脑子:"不会&qu ...

  2. 《github一天一道算法题》:分治法求数组最大连续子序列和

    看书.思考.写代码. /**************************************** copyright@hustyangju * blog: http://blog.csdn.n ...

  3. 算法设计与分析:分治法输出数字旋转方阵

    分治法输出数字旋转方阵 数字旋转方阵如下图所示: 从下图可以看出,从左上角开始,逆时针放置每次增加1的数字,直到"旋转"到最里层终止. 分治:将这个矩阵填数之时一层一层地填进去,也 ...

  4. 普利姆算法(prem)分治法

    #include<stdio.h> //全排列 void swap(int A[],int i ,int j){//交换函数 int temp = A[i];A[i]=A[j];A[j]= ...

  5. Java常用算法二:分治法

    文章目录 一.分治算法的基本步骤 二.分治算法解决汉诺塔问题 2.1 汉诺塔的规则: 2.2 使用分治算法 笔记参考:尚硅谷 分治法就是把很复杂的问题分而治之,把一个很大的问题分成几个很小的问题,再把 ...

  6. C#内功修炼(算法)——分治法(一)

    分治法(递归的解决问题) 分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这 ...

  7. chatGPT教你算法(4)——分治法

    0. 引言 在计算机科学中,分治法是一种用于解决复杂问题的常用方法.它的核心思想是将大问题分解为若干个规模较小的子问题,递归地解决这些子问题,最后再将它们的结果组合起来得到原问题的解. 本博客将向大家 ...

  8. 分治法——最近点对问题

    最近点对问题 最近点对问题是利用分治法解决的经典问题之一. 问题描述 设在一个点集S中存在n个点,找出距离最相近的一对点p1和p2,最近点对可能不止一对,输出一对即可. 分析设计 在n个点中找出距离最 ...

  9. 分治法与蛮力法求最近点对问题(分治法时间复杂度O(nlogn))

    讲解分治法求最近点对问题的思想与算法实现. 利用分治法求最近点对与归并排序的结构上的相同,将时间复杂度降到真正意义上的O(nlogn)而不是O(nlognlogn). 1. 预处理:创建结构体Node ...

  10. 循环赛赛程表的分治法实现

    目录 一.问题介绍 二.算法思想: 1.分治法的基本思想 2.问题规模扩大的算法思想: 3.填充算法思想 三.算法思想C语言描述 1.非递归法: 2.递归调用的方法: 一.问题介绍 设有n=2k(k= ...

最新文章

  1. OKR管理和绩效考核有什么不一样呢?
  2. PHP邮件队列,php群发邮件,用数据库做邮件队列
  3. 史上最通俗易懂的IPFS入门介绍:01
  4. uni-app 使用vue的语法+小程序的标签和API。
  5. C++paranthesis matching括号匹配的算法(附完整源码)
  6. Angular NgModule providers字段维护了多个字段后的初始化实现
  7. 前端学习(1978)vue之电商管理系统电商系统之为每一行数据提供单独的value
  8. oracle 数据掩码,oracle格式掩码
  9. iOS Nib文件一览
  10. C# 处理应用程序减少内存占用
  11. 认知之经济学:经济是如何运行的
  12. python 服务器和客户端 学习http请求和响应报文头
  13. Flask图片验证码注册功能
  14. 微信公众号开发之配置开发服务器
  15. c# - Owin Katana
  16. 学习笔记之 初试Linux遇到的问题
  17. Panoramic 控件设计举例
  18. 文本关键词的提取算法实验
  19. Android开发高德地图定位中GPS坐标转换
  20. 智能手持终端CPU选型报告

热门文章

  1. Java标准教程:Java 2D绘图--第4章 使用Text API
  2. 小天鹅全自动洗衣机的PLC控制
  3. 洛谷P1600 天天爱跑步
  4. GPS在ROS中的测试和使用
  5. RS法计算Hurst指数
  6. CSDN 空间:“迷你博客”很迷人(2)
  7. balenaEtcher for mac(U盘启动盘制作工具)
  8. while循环实例C语言,实例之while循环
  9. 如何手动启动消防广播_消防应急广播的设置要求是怎样的?
  10. 2019年规划与目标