前言
本文简单介绍递归的使用(依次打印出一个 int 整数的每一位)和求最大子序列的方法(以 int 数组为例)。
一、递归
递归的基本法则:
1,基准情形:能够直接解出结果的情形。
2,不断推进:保证每一次递归调用能向基准情形推进。
3,设计法则:所有的递归调用都能运行。(一般不考虑这点)
4,合成效益法则:在求解一个问题时,切忌在不同的递归调用中做重复性的工作。比如:求斐波那契数列中的: f(n)=f(n-1)+f(n-2)。 f(n-2) 被单独计算一次,但是当计算 f(n-1) 时,f(n-2) 又被计算一次,以此类推,程序做了大量重复性的工作。
递归示例,依次打印出一个整数的每一位:
public final class HelloWorld {
public static void print(int n){
if(n>=10)
print(n/10);
System.out.println(n%10);
}
public static void main(String[] args){
int n=12345678;
if(n>=0&&n<=Integer.MAX_VALUE)
print(n);
}
}
二、求最大子序列和
1,最朴素的解法
其思想为:将一个序列的所有可能的子序列和都计算一遍,取最大值。最外层循环 i 用来标记待计算序列的起始下标,j 用来标记待计算序列的结束下标,k 用来遍历待计算序列,得到其和,并且保留最大的序列和值。
该解法的时间复杂度为O(n3)。
public static int findMaxSeries_1(int[] n){
int re=Integer.MIN_VALUE,tmp;
for(int i=0;i<n.length;i++)
for(int j=i;j<n.length;j++) {
tmp=0;
for(int k=i;k<j+1;k++)
tmp+=n[k];
if(re<tmp)
re=tmp;
}
return re;
}
2,较朴素的解法(更进一步)
上面代码中在 j 循环中,可以在 j 每次变化时计算新的子序列和并判断大小,从而可以省掉不必要的最内层循环:
该解法的时间复杂度为O(n2)。
public static int findMaxSeries_2(int[] n){
int re=Integer.MIN_VALUE,tmp;
for(int i=0;i<n.length;i++){
tmp=0;
for(int j=i;j<n.length;j++) {
tmp+=n[j];
if(re<tmp)
re=tmp;
}
}
return re;
}
3,分治和递归
试一下时间复杂度为O(nlogn)的解法。采用分治的思想,那需要将原序列分为两个子序列来计算(递归进行分配,直到设定的某个约束条件),原序列的最大子序列和有三种情况:1,当最大子序列在左边序列时:它的值为左边序列中的最大子序列和值;2,当最大子序列在右边序列时:它的值为右边序列中的最大子序列和值;3,当最大子序列横跨左右序列时:此时需要,从左右两边的分界线处开始计算,分别求左边和右边序列中的最大值(时间复杂度为 O(n)),然后左右相加便得到结果。
具体地见下面代码:
public static int findMaxSeries_3(int[] n){
return findMaxSeries_3_1(n,0,n.length-1);
}
public static int findMaxSeries_3_1(int[] n, int left, int right){
if(left==right)//当序列只有一个值的时候,直接返回。
return n[left];
int mid=(left+right)/2;
int maxLeftSum= findMaxSeries_3_1(n,left,mid);
int maxRightSum= findMaxSeries_3_1(n,mid+1,right);
int maxLeftBorderSum=Integer.MIN_VALUE,leftBorderSum=0;
for(int i=mid;i>=left;i--)
{
leftBorderSum+=n[i];
if(leftBorderSum>maxLeftBorderSum)
maxLeftBorderSum=leftBorderSum;//求左边序列的最大值(从元素 n[center]开始)
}
int maxRightBorderSum=Integer.MIN_VALUE,rightBorderSum=0;
for(int i=mid+1;i<=right;i++)
{
rightBorderSum+=n[i];
if(rightBorderSum>maxRightBorderSum)
maxRightBorderSum=rightBorderSum;//求左边序列的最大值(从元素 n[center+1])开始)
}
return maxLeftSum>maxRightSum? maxLeftSum: Math.max(maxRightSum, (maxLeftBorderSum + maxRightBorderSum));
}
4,精妙的解法
和上一个解法中求左右序列最大值的方法类似,不过需要带点儿变化。
1,在一次遍历中,刚开始保存一个元素组成的序列和,即它本身。
2,当子序列长度加一时,此时子序列中有两个元素,判断此时的序列和与之前值的大小关系,若比之前值大,认为增加的元素产生了积极因素,保存此时的序列和。若比之前值小,那表明此时增加了一个元素之后的结果更坏,而这样一个更坏的序列和值就不需要了,这时保存的值为序列中第一个元素的值;(注意:此时前两个元素已经处理过了,并且永远不会在使用它们了)
3,然后重新计算仅由第三个元素组成的序列,判断它和之前保存的值的大小关系,执行步骤 2。
依次类推。
该解法的时间复杂度为O(n)。
public static int findMaxSeries_4(int[] n){
int max=Integer.MIN_VALUE,tmp=0;
for(int i=0;i<n.length;i++){
tmp+=n[i];
if(tmp>max)
max=tmp;
else
tmp=0;
}
return max;
}
该解法存在一个不太明显的缺陷:如果一个序列全为负数时,在某些情况下比如:int[] n={-5,-6,-3,-1,-5}; 它无法得到正确结果 -1。
大致规律为:最大负值在奇数下标时(上例 -1 所在下标为 3),无法得到正确结果。
总结
完,如有问题后续再更改。