🌸分治与动态规划训练
贴在前面,想了解什么是分治,可查看我的这篇文章二分查找
🍁有序列表搜索位置
已知有序的序列,比如:
2,3,3,5,9,9,9,12,12,13,15,22,22,22,22,23,25,25,91,95
有整数x,比如: x=23
要求找到一个刚好比x稍微大一点的元素位置
当数组较大的时候,需要二分查找加快速度。
🍂分析
一看此题,直接暴力(不要求复杂度的话),题中说明利用二分查找,就直接二分模板上吧
1 | public class Location_Search { |
🍁最大子序和
数组中整数有正有负
求一连续子段,使得和最大化
例如:
2,4,-7,5,2,-1,2,-4,3
最大连续段:
5,2,-1,2
其最大和为8
🍂分析
💥暴力
解题一步一步的来,首先先写好暴力解法,双层循环遍历判断最大和
1 | public class Max_sum_of_subsequences { |
💥暴力优化
优化:事实上,上面的代码有一些重复计算。这是因为相同前缀的区间求和,可以通过类似“状态转移”的方法得到。
例如:计算子区间 [1, 4] 的和可以在计算子区间 [1, 3] 的基础上,再加上 nums[4]
得到。 因此,只需要枚举子序的左端点,然后再扫描右端点,就可以减少一个级别的复杂度。
1 | public class Max_sum_of_subsequences { |
💥动态规划
第 1 步:定义状态 既然一个连续子数组一定要以一个数作为结尾,那么我们就将状态定义成如下。
- dp[i]:表示以 nums[i] 结尾的连续子数组的最大和。
那么为什么这么定义呢?这是因为这样定义状态转移方程容易得到。
- 第2 步:思考状态转移方程 根据状态的定义,由于 nums[i] 一定会被选取,并且 dp[i] 所表示的连续子序列与 dp[i - 1]所表示的连续子序列(有可能)就差一个 nums[i] 。假设数组 nums 全是正数,那么一定有
dp[i] = dp[i - 1] + nums[i]。
在一般情况下 dp[i - 1] 有可能是负数,例如前几个数都是负数,突然来了一个正数。
- 于是分类讨论:
- 第2 步:思考状态转移方程 根据状态的定义,由于 nums[i] 一定会被选取,并且 dp[i] 所表示的连续子序列与 dp[i - 1]所表示的连续子序列(有可能)就差一个 nums[i] 。假设数组 nums 全是正数,那么一定有
如果 dp[i - 1] >= 0,那么可以把 nums[i] 直接接在 dp[i - 1] 表示的那个数组的后面。
如果 dp[i - 1] < 0,那么加上前面的数反而越来越小了,于是“另起炉灶”,单独的一个 nums[i],就是 dp[i]。
以上两种情况的最大值就是 dp[i] 的值,写出如下状态转移方程:
1
2
3dp[i] = dp[i - 1] + nums[i], if dp[i - 1] >= 0
dp[i] = nums[i], if dp[i - 1] < 0
- 记为“状态转移方程 1”。
- 状态转移方程还可以这样写,反正求的是最大值,也不用分类讨论了,就这两种情况,取最大即可(即贪心思想,使每一步都能达到最优解),因此还可以写出状态转移方程如下:
1 | dp[i] = max{nums[i], dp[i - 1] + nums[i]} |
记为“状态转移方程 2”。
动态规划的问题经常要分类讨论,这是因为动态规划的问题本来就有最优子结构的特征,即大问题的最优解通常由小问题的最优解得到,那么我们就需要通过分类讨论,得到大问题的小问题究竟是哪些。
- 第 3 步:思考初始值 dp[0] 根据定义,一定以 nums[0] 结尾,因此 dp[0] = nums[0]。
- 第 4 步:思考输出 这里状态的定义不是题目中的问题的定义,不能直接将最后一个状态返回回去。
- 输出应该是把所有的 dp[0]、dp[1]、……、dp[n - 1] 都看一遍,取最大值。
状态方程一代码:
1 | public class Max_sum_of_subsequences { |
💥状态方程2(贪心解法)
贪心 使用单个数组作为输入来查找最大(或最小)元素(或总和)的问题,
贪心算法是可以在线性时间解决的方法之一。
每一步都选择最佳方案,到最后就是全局最优的方案。
1 | public class Max_sum_of_subsequences { |
💥分治
这个是使用分治法解决问题的典型的例子,并且可以用与合并排序相似的算法求解。
分治法的思路是这样的,其实也是分类讨论。 将问题分解为子问题并递归地解决它们。 合并子问题的解以获得原始问题的解
连续子序列的最大和主要由这三部分子区间里元素的最大和得到:
第 1 部分:子区间 [left, mid];
第 2 部分:子区间 [mid + 1, right];
第 3 部分:包含子区间
[mid , mid + 1]
的子区间,即nums[mid]
与nums[mid + 1]
一定会被选取。对它们三者求最大值即可。
1 | public class Max_sum_of_subsequences { |
注:
- 此分治解法需要仔细推磨想想原理
🍁大数相乘
用串的形式表示大数的乘法。
即求类似:
“23234845847839461464158174814792” * “6457847285617487843234535”
要求结果返回一个串。
🍂分析
这道题既然写在分治里,那必然跟分治脱不了干系,当然一开始也还是想不到分治是怎么分治的(确实好难)。所谓万物皆暴力,一切能暴力出来就先暴力出来,在进行一步步优化升级。好在这道题没有要求不能使用乘法,要是那样的话就整个人都不好了
💥利用大数类BigDecimal
1 | public class String_Multiple { |
💥模拟人工乘法
人工乘法在座的各位没有谁不会吧???手写的过程利用计算机去一步步实现罢了(对于大数不建议使用,复杂度较高)
1 | public class String_Multiple { |
💥分治
对于大整数的乘法,我们可以利用分治法将其两个字符串从中间截断,将两字符串的后半部分,进行相乘
子串a1和子串a2,然后结果就是a1+0…000+a2。递归的边界条件就是当每个子串的长度都小于等于4的时候,就可以通过直接乘法运算返回结果。
大数乘法中必须用到大数加法(这里不用上面已经实现的字符串相加函数),而大数加法的思路和大数乘法也是大同小异的,将串a和串b的末尾都切出一段长度为8的子串出来,a从左到右子串分别是a1和a2,b亦然。之后就是考虑t=a2+b2的不同情况,有可能t.size<8,那么这个时候我们就要通过补0的方式)凑够8位。
1 | public class String_Multiple { |