一:基础概念
1.什么是数据结构?(存储数据的不同方式);
2.什么是算法?(同一个问题的不同解决方法,往往是针对数据结构的---设计流程);
3.如何测算算法的优劣:
1)时间测算:
a)计算算法时间差;
b)幅度不够循环来凑;
2)空间测算;
4.Big O时间复杂度:计算机解决一个问题的时间,随着问题规模的扩大时间的变化规律 (O(1):常量,O(N):变化规律);
时间复杂度是衡量算法流程的复杂程度的一种指标,该指标与数量有关,与过程之外的优化无关;
时间复杂度排名:
5.如何写算法程序:
1)由简单到复杂;
a)验证一步走一步;
b)多打印中间结果;
2)先局后整体;
a)没思路时先细分;
3)先粗糙后精细;
a)变量更名;
b)语句合并;
c)边界处理;
6. 如何验算你的算法是否正确?(对数器)
1)肉眼观察;
2) 产生足够多随机样本;
3)用确定正确的算法计算样本结果;
4)对比验证算法的结果;
二:排序算法;
1.常见的几种排序列表:
2.选择排序(无序数组):
1).设计思想:通过多次循环遍历,两辆大小的比较方式依次排序;
2) 代码实现:
package ccom.cy;
/**
* 选择排序
*/
public class SelecttionSort {
public static void main(String[] args) {
//1.创建无序数组
int[] arr={5,3,6,8,1,7,9,4,2};
//遍历轮数
for (int k = 0; k < arr.length-1; k++) {
//定义最小值位置下标
int minPos=k;
//比较次数
for (int j = k+1; j <arr.length ; j++) {
//获取最小值位置下标
minPos=arr[j] < arr[minPos]? j:minPos;
}
System.out.println("第"+(k+1)+"次最小值下标为:"+minPos);
//调用交换方法;
swap(arr,k,minPos);
System.out.println("经过第"+(k+1)+"次循环,数组内容为:");
//调用打印方法
print(arr);
}
}
/**
* 变量交换方法
* @param arr
* @param i
* @param minPos
*/
public static void swap(int[] arr,int i,int minPos ){
//交换值
int tem=arr[i];
arr[i]=arr[minPos];
arr[minPos]=tem;
}
/**
*
* 封装在相同代码
* @param arr
*/
public static void print(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
3)大O分析: 时间复杂度:O(n的平方);
空间复杂度:O(1);
4)稳定性:不稳定(由于,该算法是通过循环遍历来确定最大值的下标,从而通过下标位置寻找值,如果初始化的数组中含有相同的元素,会极大地造成程序的不稳定)
5) 采用对数器验证算法是否正确:
package ccom.cy;
import java.util.Arrays;
import java.util.Random;
/**
* 对数器
*/
public class DataChecker {
public static void main(String[] args) {
check();
}
/**
* 产生随机数组
* @return
*/
public static int[] generateRandomArray(){
//产生随机数
Random random = new Random();
//初始化随机数组
int[] arr=new int[10000];
//遍历随机数加入数组
for (int i = 0; i < arr.length; i++) {
arr[i]=random.nextInt(10000);
}
return arr;
}
/**
* 检验
*/
public static void check(){
//获取随机数组
int[] arr = generateRandomArray();
//拷贝数组
int[] copyArr=new int[arr.length];
System.arraycopy(arr,0,copyArr,0,arr.length);
//系统排序
Arrays.sort(arr);
//自己写的程序排序
SelecttionSort.sort(copyArr);
//初始化结果变量
boolean same=true;
//循环比较
for (int i = 0; i < copyArr.length; i++) {
//若不相等返回flase
if (arr[i]!=copyArr[i]){
same=false;
}
}
System.out.println(same==true? "right":"wrong");
}
}
3.冒泡排序(无序数组):
1)设计思想:
2)代码实现:
package ccom.cy;
/**
* 冒泡排序算法
*/
public class BubbleSort {
public static void main(String[] args) {
//初始化随机数组
int arr[]={9,3,1,4,6,8,7,5,2};
//调用排序方法
sort(arr);
//调用打印方法
print(arr);
}
/**
* 排序方法
* @param arr
*/
public static void sort(int[] arr){
//循环遍历
//控制轮数--长度为9,比较8次
for (int i = arr.length-1; i >0 ; i--) {
System.out.println("第"+i+"轮");
//控制比较次数--比较i-1次
for (int j = 0; j < i; j++) {
System.out.println("第"+j+"次比较");
//如果前一个数大于后一个数
if (arr[j]>arr[j+1]){
//调用变量交换方法
swap(arr,j,j+1);
}
}
}
}
/**
* 变量交换方法
* @param arr
* @param i
* @param minPos
*/
public static void swap(int[] arr,int i,int minPos ){
//交换值
int tem=arr[i];
arr[i]=arr[minPos];
arr[minPos]=tem;
}
/**
*
* 封装在相同代码
* @param arr
*/
public static void print(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
}
}
}
3)使用对数器进行检验:
package ccom.cy;
import java.util.Arrays;
import java.util.Random;
/**
* 对数器
*/
public class DataChecker {
public static void main(String[] args) {
check();
}
/**
* 产生随机数组
* @return
*/
public static int[] generateRandomArray(){
//产生随机数
Random random = new Random();
//初始化随机数组
int[] arr=new int[9];
//遍历随机数加入数组
for (int i = 0; i < arr.length; i++) {
arr[i]=random.nextInt(9);
}
return arr;
}
/**
* 检验
*/
public static void check(){
//获取随机数组
int[] arr = generateRandomArray();
//拷贝数组
int[] copyArr=new int[arr.length];
System.arraycopy(arr,0,copyArr,0,arr.length);
//系统排序
Arrays.sort(arr);
//自己写的程序排序
//选择排序
// SelecttionSort.sort(copyArr);
//冒泡排序
BubbleSort.sort(copyArr);
//初始化结果变量
boolean same=true;
//循环比较
for (int i = 0; i < copyArr.length; i++) {
//若不相等返回flase
if (arr[i]!=copyArr[i]){
same=false;
}
}
System.out.println(same==true? "right":"wrong");
}
}
4)时间复杂度:最坏:O(n的平方),最好:O(n);
空间复杂度: O (1);
5)稳定性;稳定(由于该算法是通过数组中相邻两位数两两比较的方式来确定最大值的,即使存在相同的两个元素,也依然可以会按照陈旭的顺序执行);
4.插入排序:(基本有序的数组)
1)设计思想:从数组下标为1的位置开始遍历,每一个位置从后往前插入;
2)代码实现:
package ccom.cy;
/**
* 插入排序算法
*/
public class InsertionSort {
public static void main(String[] args) {
//初始化一个数组
int[] arr={9,3,1,4,6,8,7,5,2};
//调用排序方法
sort(arr);
//调用打印方法
print(arr);
}
/**
* 排序方法
* @param arr
*/
public static void sort(int[] arr){
for (int i = 1; i < arr.length ; i++) {
for (int j = i; j >0&& arr[j]<arr[j-1] ; j--) {
swap(arr,j,j-1);
}
}
}
/**
* 变量交换方法
* @param arr
* @param firstIndex
* @param maxIndex
*/
public static void swap(int[] arr, int firstIndex, int maxIndex){
//变量交换
int temp=arr[firstIndex];
arr[firstIndex]=arr[maxIndex];
arr[maxIndex]=temp;
}
/**
* 打印交换方法
* @param arr
*/
public static void print(int arr[]){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
3) 对数器验证:
package ccom.cy;
import java.util.Arrays;
import java.util.Random;
/**
* 对数器
*/
public class DataChecker {
public static void main(String[] args) {
check();
}
/**
* 产生随机数组
* @return
*/
public static int[] generateRandomArray(){
//产生随机数
Random random = new Random();
//初始化随机数组
int[] arr=new int[1000];
//遍历随机数加入数组
for (int i = 0; i < arr.length; i++) {
arr[i]=random.nextInt(1000);
}
return arr;
}
/**
* 检验
*/
public static void check(){
//获取随机数组
int[] arr = generateRandomArray();
//拷贝数组
int[] copyArr=new int[arr.length];
System.arraycopy(arr,0,copyArr,0,arr.length);
//系统排序
Arrays.sort(arr);
//自己写的程序排序
//选择排序
// SelecttionSort.sort(copyArr);
//冒泡排序
// BubbleSort.sort(copyArr);
//插入排序
InsertionSort.sort(copyArr);
//初始化结果变量
boolean same=true;
//循环比较
for (int i = 0; i < copyArr.length; i++) {
//若不相等返回flase
if (arr[i]!=copyArr[i]){
same=false;
}
}
System.out.println(same==true? "right":"wrong");
}
}
4)时间复杂度:最坏:O(n的平方),最好:O(n);
空间复杂度:O(1);
5)稳定性:稳定;
5.简单排序总结:
1)选择排序:
基本不用,不稳;(时间上没插入快,而且不稳定)
2)冒泡排序:
基本不用,太慢;(两两比较,两两交换)
3)插入排序;
针对于数据量比较小,且基本有序的时候效率比较高;(时间快,稳定)
4)优缺点比较:
a): 区别
1.冒泡排序是比较相邻位置的两个数,而选择排序是按顺序比较,找最大值或者最小值;
2.冒泡排序每一轮比较后,位置不对都需要换位置,选择排序每一轮比较都只需要换一次位置;
3.冒泡排序是通过数去找位置,选择排序是给定位置去找数;
b): 冒泡排序优缺点
1.优点:比较简单,空间复杂度较低,是稳定的;
2.缺点:时间复杂度太高,效率慢;
c): 选择排序优缺点
1.优点:一轮比较只需要换一次位置;
2.缺点:效率慢,不稳定(举个例子5,8,5,2,9 我们知道第一遍选择第一个元素5会和2交换,那么原序列中2个5的相对位置前后顺序就破坏了)。
6.希尔排序:
1) 设计思想:以一定的间隔进行分组排序,一次缩小间隔排序,最后进行一次插入排序;(有效减少交换次数和移动次数)
2)代码实现:采用减半序列;
package ccom.cy;
/**
* 希尔排序
*/
public class ShellSort {
public static void main(String[] args) {
//初始化一个数组
int[] arr = {9, 6, 11, 3, 5, 12, 8, 7, 10, 15, 14, 4, 1, 13, 2};
//调用排序方法
sort(arr);
//调用打印方法
print(arr);
}
/**
* 排序方法
*
* @param arr
*/
public static void sort(int[] arr) {
//间隔自减--除2间隔序列
for (int gap = arr.length/2; gap>0 ; gap/=2) {
//根据间隔循环抽取
for (int i = gap; i < arr.length ; i++) {
//在每个抽取的新数组中大小比较
for (int j = i; j >gap-1&& arr[j]<arr[j-1] ; j-=gap) {
swap(arr,j,j-gap);
}
}
}
}
/**
* 变量交换
* @param arr
* @param firstIndex
* @param maxIndex
*/
public static void swap(int[] arr, int firstIndex, int maxIndex) {
int temp=arr[firstIndex];
arr[firstIndex]=arr[maxIndex];
arr[maxIndex]=temp;
}
/**
* 打印方法
* @param arr
*/
public static void print(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
3)Knuth序列:确定最优间隔序列;
h=1 ;(最小间隔)
h=3*h+1;(最大间隔不能超过数组长度的三分之一,否则无意义)
代码实现:
package ccom.cy;
/**
* 希尔排序
*/
public class ShellSort {
public static void main(String[] args) {
//初始化一个数组
int[] arr = {9, 6, 11, 3, 5, 12, 8, 7, 10, 15, 14, 4, 1, 13, 2};
//调用排序方法
sort(arr);
//调用打印方法
print(arr);
}
/**
* 排序方法
*
* @param arr
*/
public static void sort(int[] arr) {
//定义最近小间隔
int h=1;
//相邻间隔h=h*3+1
//最大间隔长度不能大于数组长度的三分之一,否则无意义
while (h<= arr.length/3){
h=h*3-1;
}
//间隔自减
for (int gap = h; gap>0 ; gap=(gap-1)/3) {
//根据间隔循环抽取
for (int i = gap; i < arr.length ; i++) {
//在每个抽取的新数组中大小比较
for (int j = i; j >gap-1&& arr[j]<arr[j-1] ; j-=gap) {
swap(arr,j,j-gap);
}
}
}
}
/**
* 变量交换
* @param arr
* @param firstIndex
* @param maxIndex
*/
public static void swap(int[] arr, int firstIndex, int maxIndex) {
int temp=arr[firstIndex];
arr[firstIndex]=arr[maxIndex];
arr[maxIndex]=temp;
}
/**
* 打印方法
* @param arr
*/
public static void print(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
4)时间复杂度:最好情况下O(n的1.3)~O(n的2次方);
空间复杂度:O(1);
5) 稳定性:不稳;
7.归并算法
1)递归:方法调用方法本身;
a) 简单代码实现:
package ccom.cy;
/**
* 递归算法
*/
public class Recursion {
public static void main(String[] args) {
System.out.println(calculate(10));
}
/**
* 递归计算1-10
* @param num
* @return
*/
public static long calculate(int num){
//判断当num=1时直接返回1;
if(num==1){
return num;
}else if (num>1){
return num+calculate(num-1);
}
return 0;
}
}
b) 执行过程:
首先从calculate(10)~calculate(1)依次调用, 每次调用会将方法存入一个栈针空间内部;
接着依次从calculate(1)~claculate(10)接收返回值记录保存到临时变量;
最后返回给main方法,每次执行的参数值、临时变量值是不相同的;
2)归并设计思想:折半子数组排序;(前提的是子数组排好顺序)
3) 代码实现:
package ccom.cy;
/**
* 归并算法
*/
public class MergeSort {
public static void main(String[] args) {
//初始化一个数组
int arr[]={1,4,7,8,3,6,9};
//调用排序方法
sort(arr,0, arr.length-1);
//调用打印方法
print(arr);
}
/**
* 排序方法
* @param arr
* @param left 左边界
* @param right 右边界
*/
public static void sort(int[] arr,int left, int right){
//判断左边界是否等于右边界
if (left==right){
return;
}
//分两半
int mid= left+ (right-left)/2;
//左边排序
sort(arr,left,mid);
//右边排序
sort(arr,mid+1,right);
//总体归并
merge(arr,left,mid+1, right);
}
/**
* 前提是归并的该部分数组已经排好序
* @param arr
* @param leftPtr 第一个子数组的第一个下标 左指针指的位置,左边界
* @param rightPtr 第二个子数组的第一个下标 右指针指的位置,中间位置
* @param rightBound 右边界
*/
public static void merge(int[] arr, int leftPtr, int rightPtr, int rightBound){
//获取中点下标
int mid= rightPtr-1;
//初始化一个等长数组
int[] temp= new int[rightBound-leftPtr+1];
//定义三个指针
//第一个子数组的第一个下标
int i=leftPtr;
//第二个子数组的第一个下标
int j=rightPtr;
//新数组的第一个下标
int k=0;
//循环遍历交换
while (i <=mid && j<= rightBound){
temp[k++] = (arr[i] <=arr[j] ? arr[i++] : arr[j++]);
}
//如果第一个字数组有剩余
while (i<= mid){
temp[k++]= arr[i++];
}
//如果第二个数组有剩余
while (j<rightBound+1){
temp[k++]= arr[j++];
}
//复制原数组
for (int m = 0; m < temp.length ; m++) {
arr[leftPtr+m]=temp[m];
}
}
/**
* 打印方法
* @param arr
*/
public static void print(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
4)时间复杂度:O(n*log2 N);
空间复杂度:O(n);
5) 稳定性:稳定;
6) Java对象排序:对象排序一般要求稳定;
排序源码:
TimSort源码:如果整个数组比最小分组值(nRemaining)还要小,采用binarySort(二分插入);
如果整个数组的值比最小分组值(nRemaining)大,就采用TimSort,先分组,在组内采用二分插入排序,再实现归并;
8.快速排序:
1)设计思想:
2) 代码实现:
package ccom.cy;
/**
* 快速排序
*/
public class QuickSort {
public static void main(String[] args) {
//初始化一个数组
int[] arr={7,3,2,10,8,1,9,5,4,6};
//调用排序方法
sort(arr,0, arr.length-1);
//调用打印方法
print(arr);
}
/**
* 排序方法
* @param arr
* @param leftBound 左边界
* @param rightBound 右边界
*/
public static void sort(int[] arr, int leftBound, int rightBound){
//判断左右两边界是否相等
if (leftBound >= rightBound){
return;
}
//进行分区排序
int mid = partition(arr, leftBound, rightBound);
//递归再次分区
//左边
sort(arr,leftBound,mid-1);
//右边
sort(arr,mid+1,rightBound);
}
/**
* 分区排序方法
* @param arr
* @param leftBound
* @param rightBound
*/
public static int partition(int[] arr,int leftBound, int rightBound){
//判断
if (rightBound <=leftBound){
return 0;
}
//定义轴
int pivot=arr[rightBound];
//定义左指针
int left=leftBound;
//定义右指针
int right=rightBound;
while (left < right){
//左指针指向的值小于等于轴,指针依次向后移,直到找到第一个比轴大的数
while (left <right && arr[left] <= pivot) left++;
//右指针指向的值大于等于轴,指针依次向左移,直到找到第一个比值下的数
while (left <right && arr[right] >=pivot) right--;
//当停止时,左指针小于右指针
if (left < right){
//两变量交换位置,直到两指针相遇过去
swap(arr,left,right);
}
}
swap(arr,left,rightBound);
//返回轴的值
return left;
}
/**
* 变量交换
* @param arr
* @param left 左指针
* @param right 右指针
*/
public static void swap(int[] arr, int left, int right){
int temp=arr[left];
arr[left]=arr[right];
arr[right]=temp;
}
/**
* 打印方法
* @param arr
*/
public static void print(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
3)时间复杂度:平均情况下:O(N *(log2 N)), 最坏情况下:O(n平方)
空间复杂度: O(log2 N)
9.双轴快排(java内部工业排序):
源码解析:
双轴快速排序方法:
当右指针减去左指针小于QUICKSORT_THRESHOLD(默认值是286)时,采用单轴快排;
将数组采用归并排序进行分组,如果组数小于MAX_RUN_COUNT(默认值是67)时,采用TimSort归并排序;
然后继续,判断数组长度是否小于INSERTION_SORT_THRESHOLD(默认值是47),如果小于就使用双插入排序;
否则,使用双轴快速排序:
找轴: 首先找到终点,一次依数组长度的七分之一单位继续定轴,在前后各找两个轴,如果这五个数都不相等,就取第二个和第四的数为轴进行快排;
如果取出的5个数中值相等,就取用最中间的值进行作为值,用单轴排序,采用三相划分法来做;
总结:
10.计数排序:
1)使用范围: 量大,取值范围比较小;
2)设计思想: 创建一个计数数组通过下标值,来表示可能出现的值的范围,然后通过value值,来记录出现的次数;然后创建一个与元数组长度相等的数组按照计数数组来排列;
代码实现:
package ccom.cy;
import java.util.Arrays;
/**
*
* 计数排序
*/
public class CountSort {
public static void main(String[] args) {
//初始化一个原数组
int[] arr={2,4,2,3,7,1,1,0,0,5,6,9,8,5,7,4,0,9};
//调用排序方法
int[] result = sort(arr);
System.out.println(Arrays.toString(result));
}
/**
* 排序方法
* @param arr
* @return
*/
public static int[] sort(int[] arr){
//初始化一个等长数组
int[] result=new int[arr.length];
//初始化一个计数数组
int[] count=new int[10];
//对于原数组进行计数
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
System.out.println(Arrays.toString(count));
//根据计数数组排序到新数组中
for (int i= 0,j=0; i <count.length ; i++) {
while (count[i]-- >0){
result[j++]=i;
}
}
return result;
}
}
代码改进: 采用累加数组的方式,解决算法的稳定性;
package ccom.cy;
import java.util.Arrays;
/**
*
* 计数排序
*/
public class CountSort {
public static void main(String[] args) {
//初始化一个原数组
int[] arr={2,4,2,3,7,1,1,0,0,5,6,9,8,5,7,4,0,9};
//调用排序方法
int[] result = sort(arr);
System.out.println(Arrays.toString(result));
}
/**
* 排序方法
* @param arr
* @return
*/
public static int[] sort(int[] arr){
//初始化一个等长数组
int[] result=new int[arr.length];
//初始化一个计数数组
int[] count=new int[10];
//对于原数组进行计数
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
System.out.println(Arrays.toString(count));
// //根据计数数组排序到新数组中
// for (int i= 0,j=0; i <count.length ; i++) {
// while (count[i]-- >0){
// result[j++]=i;
// }
// }
//采用累加数组的方式解决稳定性问题--直接计算出当前值,在原数组中所对应的下标值,以及每个值所对应的个数
//当前值所对应的下标值=cout数组的value值;
//当前值的个数,等于当前值所对应的value值,减去下一个value值;
for (int i = 1; i < count.length ; i++) {
count[i]=count[i]+count[i-1];
}
System.out.println(Arrays.toString(count));
//按照逆向思想根据下标在新数组中倒序迭代
for (int i = arr.length-1; i>=0 ; i--) {
result[--count[arr[i]]]=arr[i];
}
return result;
}
}
3)时间复杂度:O(n+k)
空间复杂度:O (n+k)
11.基数排序
1) 本质:多关键字排序;
2)设计思想:先按照个位数排序,再按十位数排序,最后按百位数排序;
3)时间复杂度:O(n*k);
空间复杂度:O(n)
4) 代码实现:
package ccom.cy;
import java.util.Arrays;
/**
* 基数排序
*/
public class RadixSort {
public static void main(String[] args) {
//初始化一个数组
int[] arr = {421, 240, 115, 532, 305, 430, 124};
//调用sort方法
int[] result = sort(arr);
System.out.print(Arrays.toString(result));
}
/**
* 排序方法
*
* @param arr
* @return
*/
public static int[] sort(int[] arr) {
//创建一个等长数组
int[] result = new int[arr.length];
//创建计数数组
int[] count = new int[10];
//循环遍历位数
for (int i = 0; i < 3; i++) {
//确定除数
int division = (int) Math.pow(10, i);
//遍历原数组
for (int j = 0; j < arr.length; j++) {
//获取每一位上的数字
int num = arr[j] / division % 10;
System.out.println(num);
count[num]++;
}
System.out.println(Arrays.toString(count));
//使用累加数组确定
for (int m = 1; m < count.length; m++) {
count[m] = count[m] + count[m - 1];
}
//将原数组逆向迭代,根据count数组,排序到新的数组
for (int n = arr.length - 1; n > 0; n--) {
int num = arr[n] / division % 10;
result[--count[num]] = arr[n];
}
//复制回原数组
System.arraycopy(result, 0, arr, 0, arr.length);
//count数组初始化
Arrays.fill(count, 0);
}
return result;
}
}
12.桶排序:
1)设计思想:
2)时间复杂度:最好情况: O(n), 最坏情况:O(n的平方)
空间复杂度: O(n+k);
注意:空间做到最好的话,就只能用链表,但此时时间上就做不到最好;
三:高级算法进阶:
1.常见的常数时间操作:
1)常见的算术运算(+、-、*、/、%等);
2)常见的位运算(>>(带符号位右移,最高为为正补0,为负补1)、>>>(不带符号位右移,默认补0)、<<、|、&、^等);
3)赋值、比较、自增、自减操作;
4)数组寻址操作;(通过算偏移量来确定)
总结:执行时间固定的操作都是常数时间的操作;
2.评价算法优劣的核心指标;
1)时间复杂度;(流程决定);
2)额外空间复杂度;(流程决定);
a) 在执行算法流程过程中,为了实现业务所开辟的必要空间不算额外空间;
b) 在执行算法流程过程中,除了非必要的空间还需要开辟空间才能让你的流程继续走下去,这部分空间成为额外空间;
c) 如果你的流程只需要开辟有限的几个变量的空间,额外空间复杂度为O(1);
3)常数项时间(实现细节决定);
a)当两种算法时间复杂度相同时,建议采用大量数据来比较时间,来得出常数项的大小;
最优解:时间复杂度最低,空间复杂度较小;