先分(将问题抽象法)
后解 (将一个小问题一个个具体求解)
合(通过更新,比较等方式将子问题的解合并成一个原问题的解)
package 分治法;
public class solution {
/*
* 问题描述
* 1.用分治算法求解最大子段和问题。要求算法的时间复杂度不超过 O(nlogn)。
最大子段和问题描述:给定由 n 个整数(可能为负整数)组成的序列 a1, a2,„, an,
求该序列形如j k i k a的子段和的最大值。当所有整数均为负整数时定义其最大子段和为 0。
*/
/*
* 解决办法,思考如何分解,分解成每一个子问题
* 然后通过这几个子问题,递归,求出总问题的答案
*/
//分解,左边一半最大,右边一半最大,或者中间最大
public int Divid(int[] nums) {
return CountMinQue(nums,0,nums.length-1);
}
//分解成三部分递归方程,求解每个子问题,这个函数抽象成 “求这一段的最大子序和,先不要想怎么实现”
public int CountMinQue(int []nums,int left,int right){
if(left==right)
return nums[left]>0?nums[left]:0;
else {
int mid=(left+right)/2;
int leftMax=CountMinQue(nums, left, mid);
int RightMax=CountMinQue(nums, mid+1, right);/*思考了很久,为啥上面这两个函数跟下面的是不一样的,解析见思路*//*上面两个是抽象的,下面的是具体实现,求解*/
int MidMax=getMidMax(nums, left, right, mid);
return Math.max(RightMax, Math.max(leftMax, MidMax)); /*这里是合,谁大就返回谁*/
}
}
/*也是求这一段最大子序和的具体实现*/
public int getMidMax(int []nums,int left,int right,int mid) {//这个是这个算法的重点,理解好中间的概念!
int LeftNum=0;
int RightNum=0;
int LeftMax=nums[mid];
int RightMax=nums[mid+1];
//从中间开始计算左边的最大
for(int i=mid;i>=left;i--) {
LeftNum+=nums[i]; /*这里的for循环+LeftNum,保证了该子序号是连续的*/
LeftMax=Math.max(LeftNum, LeftMax);
}
//从中间开始计算右边的最大
for(int i=mid+1;i<=right;i++) {
RightNum+=nums[i]; /*这里的for循环+RightNum,保证了该子序号是连续的*/
RightMax=Math.max(RightNum, RightMax);
}
return LeftMax+RightMax;
}
}
/*测试用例
-1 -1 -2 -3(全是负数) 0
1 2 -1 -1(左边最大) 3
-1 -1 1 2(右边最大) 3
-1 2 1 -1 (中间最大) 3
*/
如何求最大子序和(方法总结)
先分
(其实是先将问题抽象化,譬如该题,最大子序和可能在左边,可能在右边,或者中间,定义一个抽象的函数(不是那种抽象)“求某一段的最大子序列”。然后这个可以作为递归方程式。)
具体思考如何求解
求左边和求右边实质是求中间最大子序和,我们思考如何解决这个小问题:如何具体求解某一段的最大子序和?这里求解问题的核心和关键所在。
合,通过子问题的结果,不断更新,最后得到最终问题的结果。
为什么求左边和求右边和求中间的函数不一样?
实质上求左边/右边,实质上就是用求中间的函数求的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gLiHLMjY-1647878604690)(C:\Users\xie\AppData\Roaming\Typora\typora-user-images\image-20220321090308547.png)]
原因(从最小的子问题分析)
eg:2---------->2
当有2个的时候,左边的最大值,是上一层的最大值。右边的最大值,是上一层的最大值。所以此时就是从中间开始求,然后比较中间,左,右,就可以了。
eg:2 | 3 ------------->5
当有3个的时候,左边的最大值是上一层返回的最大值,右边的数也是他本身,也是最大的。所以此时就是从中间开始求,然后比较中间,左,右,就可以了。
eg:2 3 |-6 -------------------->5
…
以此类推,实际上,左边和右边的结果不用求,都是上一层返回的结果(上一层的最大子序列和),只需要求该层中间的最大子序和,然后比较中间,左边,右边就可以了。同时要注意,左边和右边严格上来说不是不用求,他们其实是上一层最大子序和返回的(用求中间的函数得到的)。
如何保证了该子序列是连续的?
用了for从中间向两边拓展,每拓展一次,LeftSum都在加/减,所以保证了LeftSum一直都在更新,所以该结果是连续的
leetcode53最大子数组和
面试题16.17连续数列
这两题与上面的最大子序列和一模一样,就不写总结了。
/*最大子数组和,唯一不同就是负数也要算*/
class Solution{
public int maxSubArray(int[] nums) {
return max(nums,0,nums.length-1);
}
public int max(int[] nums,int left,int right){
if(left==right)
return nums[left];
else{
int mid=(left+right)/2;
int LeftSum=max(nums,left,mid);
int RightSum=max(nums,mid+1,right);
int MidSum=Mid(nums,left,right,mid);
return Math.max(LeftSum,Math.max(RightSum,MidSum));
}
}
public int Mid(int []nums,int left,int right,int mid){
int LSum=0;
int RSum=0;
int LeftSum=nums[mid];
int RightSum=nums[mid+1];
for(int i=mid;i>=left;i--){
LSum+=nums[i];
LeftSum=Math.max(LeftSum,LSum);
}
for(int i=mid+1;i<=right;i++){
RSum+=nums[i];
RightSum=Math.max(RightSum,RSum);
}
return LeftSum+RightSum;
}
}
leetcode169多数元素
class Solution {
public int majorityElement(int[] nums) {
return divid(nums,0,nums.length-1);
}
//作用:分别求出在某个区间的众数(左边,右边)
public int divid(int []nums,int left,int right){
if(left==right){
return nums[left];
}
else{
int mid=(left+right)/2;
int Left=divid(nums,left,mid);//左边的众数
int Right=divid(nums,mid+1,right);//右边的众数
if(Left==Right)//如果两边的众数都一样,说明这个数就是众数
return Left;
int LeftCount=helpAll(nums,left,right,Left);
int RightCount=helpAll(nums,left,right,Right);
return LeftCount>RightCount?Left:Right;
}
}
public int helpAll(int nums[],int left,int right,int temp){
int count=0;
for(int i=left;i<=right;i++){
if(temp==nums[i])
count++;
System.out.println(nums[i]);
}
return count;
}
}
[leetcode215数组中的第k个最大元素](215. 数组中的第K个最大元素)
class Solution {
public int findKthLargest(int[] nums, int k) {
//快排,归并排序
QuickSort(nums,0,nums.length-1,k);
for(int i=0;i<nums.length;i++){//0 1
System.out.println(nums[i]);
}
return nums[nums.length-k];//直接返回一个第K大的元素
}
//用归并排序,先找一个基准,然后比基准大的数放基准的左边
//比基准小的数放基准的右边
public void QuickSort(int[] nums,int left,int right,int k){
if(left<right){
int mid=Quick(nums,left,right);
if(mid==nums.length-k)
return ;
QuickSort(nums,left,mid-1,k);
QuickSort(nums,mid+1,right,k);
}
else
return ;
}
public int Quick(int[] nums,int left,int right){//找一次基准你
System.out.printf("左边"+left);
System.out.println("一开始的"+left);
// System.out.println("基准元素"+p);
int p=nums[left];
while(left<right){
//
while(nums[right]>=p&&left<right){//比基准大的话,放在基准的左边
// System.out.println(left);
//这里错了
right--;
}
nums[left]=nums[right];
while(nums[left]<=p&&left<right){//比基准大的话,放在基准的左边
left++;
// System.out.println("lll"+right);
}
nums[right]=nums[left];
}
//出来之后,left等于right
nums[left]=p;
return left;
}
}
Pow(x,n)
数值的整数次方
class Solution{
public double myPow(double x, int n){
return n>=0?PowCount(x,n):1.0/PowCount(x,-n);
}
//这里是算幂的
public double PowCount(double x,int n){
if(n==0)
return 1;
if(n==1)
return x;
else if(n%2==0){
double y=PowCount(x,n/2);
return y*y;
}
else {
double y=PowCount(x,n/2);
return y*y*x;
}
}
}
x的平方根
class Solution {
//分治法练习
public int mySqrt(int x) {
if(x==0)
return 0;
if(x==1)//为啥还要考虑这种情况?他边界,0,x我还是好乱,救命,二分查找还是好乱啊
return 1;
int left=-1;//这里是那个视频的讲解
int right=x+1;//这里是那个视频的讲解
while(left+1!=right){
int mid=(left+right)/2;
if(mid>x/mid)
right=mid;//这里是那个视频的讲解
else if(mid<x/mid)
left=mid;//这里是那个视频的讲解
else if(mid==x/mid)
return mid;
// System.out.println(mid);
}
return left;
}
}
二叉搜索数的后序遍历
class Solution {
public boolean verifyPostorder(int[] postorder) {
return verify(postorder,0,postorder.length-1);
}
public boolean verify(int []postorder,int left,int right){
int record=left;
for(;record<=right;record++){
if(postorder[record]>postorder[right])
break;
}//找到一组左子树的,思考,要是没有呢?
for(int i)//如果右子树比左子树小,则返回FALSE;
verify(postorder,left,record-1);
verify(postorder,record,right-1);
}
}
同最大子序和,可以在左边,可能在右边,可能在中间。
但是这样找的方法需要众数比较集中,所以先将众数进行排序。
先排序
/*归并排序*/
package 分治法;
//归并排序
public class MergeSort {
//
//先分
//后合并
//1.先传入一个数组
public void MergeNums(int[] nums) {
DividNums(nums, 0, nums.length-1);
}
//分,然后递归
public void DividNums(int[] nums,int left,int right) {
if(left==right) {
//分到只有一个元素,这里还没有写,等会认真思考
return ;
}
else {
//大于等于一个元素
int mid=(left+right)/2;
DividNums(nums, left, mid);
DividNums(nums, mid+1, right);
Merge(nums,left,mid,right);//这里的关键是归并,前面的重点是中间的求和
}
}
public void Merge(int[] nums,int left,int mid,int right) {
//将左边的数组和右边的数组合起来
//创造一个数组装他们拍完序的数组
int[] MyNew=new int[right-left+1];
//还要建立两个指针,用来移动位置
int PointerLeft=left;
int PointerRight=mid+1;
int Point=0;
while(PointerLeft<=mid&&PointerRight<=right) {
if(nums[PointerLeft]<=nums[PointerRight]) {
MyNew[Point]=nums[PointerLeft];
Point++;
PointerLeft++;
}
else {
MyNew[Point]=nums[PointerRight];
Point++;
PointerRight++;
}
}
while(PointerLeft<mid+1) {
MyNew[Point]=nums[PointerLeft];
Point++;
PointerLeft++;
}
while(PointerRight<=right) {
MyNew[Point]=nums[PointerRight];
Point++;
PointerRight++;
}
for(int i = 0;i<MyNew.length;i++) {
nums[left+i]=MyNew[i];//找了很久的bug,原来在这里!!!!!
}
}
}
后分治
package 分治法;
public class MyMode {
int ModeValue;//众数的值
int ModeCount;//众数的次数
int Myleft;//因为这两个一直变,所以定义成全局变量
int Myright;
//
public void FindMode(int[] nums) {
ResultMyNode(nums, 0,nums.length-1);
System.out.println("众数是"+ModeValue+"重数是"+ModeCount);
}
/*先分 ,将问题抽象化,要不众数在左边,要不在右边,要不在中间*/
/*这里先算众数在中间,这样的话,先分出左边,中间,右边,并且要是左边或者右边的子问题比中间的众数小,那更加不用求了,可以节省时间*/
public void ResultMyNode(int nums[],int left,int right) {
split(nums, left, right);//从中间分开两半先
//算出他的长度
// int len=Myright-Myleft-1;//这里的重数
if(ModeCount<=Myleft-left+1) {//说明中间的重数小,开始求左边的重数
ResultMyNode(nums,left,Myleft);
}
if(ModeCount<=nums.length-Myright) {//说明中间的重数小,开始求右边的重数
ResultMyNode(nums,Myright,nums.length-1);
}
}
//这里是具体求众数,从中间两边出发,统计众数的个数,并且边界在移动(原来的边界在中间)。当遇到不等的时候,跳出循环,更新左边界和右边界,同时这个左边界作为左边数组的右边界,右边界作为右边数组的左边界。继续求解子问题*/
public void split(int nums[],int left,int right) {//扩大左边的和扩大右边的
//前提:有序的数组
int mid=(left+right)/2;
int recover;
for(recover=mid;recover>=left;recover--) {
if(nums[recover]!=nums[mid])
break;//找到不等的值,然后跳出来,找到子问题的边界(left,right)
}
this.Myleft=recover;
for(recover=mid+1;recover<=right;recover++) {
if(nums[recover]!=nums[mid])
break;
}
this.Myright=recover;
if(Myright-Myleft-1>this.ModeCount) {
this.ModeCount=Myright-Myleft-1;
this.ModeValue=nums[mid];
}
if(Myright-Myleft-1==this.ModeCount&&nums[mid]>this.ModeValue) {
this.ModeCount=Myright-Myleft-1;
this.ModeValue=nums[mid];
}
}
}
/*测试用例
1 1 1 2 3(众数在左边) 众数1 重数3
2 3 1 1 1(众数在右边) 众数1 重数3
2 1 1 1 3(众数在中间) 众数1 重数3
2 2 1 1 3(众数有多个,选值大的那一个) 众数2 重数2
*/
如何求众数?
通过不断维护以下四个变量。
int ModeValue;//众数的值
int ModeCount;//众数的次数
int Myleft;//因为这两个一直变,所以定义成全局变量
int Myright;
(此过程,不断地分解递归,不断更新这四个值,或者不更新)
通过MyLeft和Myright(边界)不断地移动,可以求出某个数的重数。
若该数的重数大,更新众数的值。//另外这里有个规则,要是重数一样,谁众数的值大,谁就当众数
否则,原众数及重数不变。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-blnI0r3I-1647878101820)(C:\Users\xie\AppData\Roaming\Typora\typora-user-images\image-20220321130732032.png)]
测试用例
1 1 1 2 3(众数在左边) 众数1 重数3
2 3 1 1 1(众数在右边) 众数1 重数3
2 1 1 1 3(众数在中间) 众数1 重数3
2 2 1 1 3(众数有多个,选值大的那一个) 众数2 重数2
*/
#### 问题 与 思考
1. 如何求众数?
通过不断维护以下四个变量。
```java
int ModeValue;//众数的值
int ModeCount;//众数的次数
int Myleft;//因为这两个一直变,所以定义成全局变量
int Myright;
(此过程,不断地分解递归,不断更新这四个值,或者不更新)
通过MyLeft和Myright(边界)不断地移动,可以求出某个数的重数。
若该数的重数大,更新众数的值。//另外这里有个规则,要是重数一样,谁众数的值大,谁就当众数
否则,原众数及重数不变。
以上内容是3-21所总结的学习动态规划内容,不过学校实验的求子序和要下标,迟点再解出这个问题,切记。
不是很完整,继续完善。