复杂度分析
大 O O O记号
在问题规模足够大后,计算成本如何增长?(更侧重整体增长趋势)
T(n):需执行的基本操作次数
T(n)=
O
O
O( f(n) )
if
∃
\exists
∃ c > 0 , 当 n >> 2 后 , 有T(n) < c
⋅
\cdot
⋅f(n)
例:T(n)= 5 n ⋅ [ 3 n ⋅ ( n + 2 ) + 4 ] + 6 \sqrt{5n\cdot[3n\cdot(n+2)+4]+6} 5n⋅[3n⋅(n+2)+4]+6 对其简化
5 n ⋅ [ 3 n ⋅ ( n + 2 ) + 4 ] + 6 \quad\sqrt{5n\cdot[3n\cdot(n+2)+4]+6} 5n⋅[3n⋅(n+2)+4]+6 \quad // 2–>n
\quad < 5 n ⋅ [ 6 n 2 + 4 ] + 6 \sqrt{5n\cdot[6n^2+4]+6} 5n⋅[6n2+4]+6 \quad \quad \quad \quad // 4–> n 2 n^2 n2
\quad < 35 n 3 + 6 \sqrt{35n^3+6} 35n3+6 \quad \quad \quad \quad \quad \quad \quad \quad // 6–> n 3 n^3 n3
\quad 6 ⋅ \cdot ⋅ n 1.5 n^{1.5} n1.5 = O O O ( n 1.5 n^{1.5} n1.5)
与T(n)相比,f(n)更为简洁,但依然反应前者的增长趋势
- 常系数可忽略: O O O( f(n) ) = O O O( c × \times ×f(n) )
- 低次项可忽略:
O
O
O(
n
a
+
n
b
n^a+n^b
na+nb ) =
O
O
O(
n
a
n^a
na) , a > b > 0
大 Ω \Omega Ω记号和大 Θ \Theta Θ记号
- T(n) =
Ω
\Omega
Ω( f(n) ) :
∃ \exists ∃ c > 0 , 当 n >> 2 后 , 有T(n) > c ⋅ \cdot ⋅f(n) - T(n) =
Θ
\Theta
Θ( f(n) ) :
∃ \exists ∃ c 1 c_1 c1 > c 2 c_2 c2 > 0 , 当 n >> 2 后 , 有 c 1 c_1 c1 ⋅ \cdot ⋅ f(n) > T(n) > c 2 c_2 c2 ⋅ \cdot ⋅ f(n)
常见的 O O O()
O O O(1)
常数
2 = 2013 = 2013
×
\times
× 2013 =
O
O
O(1) , 甚至
201
3
2013
2013^{2013}
20132013 =
O
O
O(1)
这类算法的效率最高
O O O( l o g c n log^cn logcn)
对数
lnn | lgn |
l
o
g
100
n
log_{100}n
log100n |
l
o
g
2013
n
log_{2013}n
log2013n
常底数无所谓
∀
\forall
∀ a , b > 0 ,
l
o
g
a
n
log_a n
logan =
l
o
g
a
b
log_ab
logab
⋅
\cdot
⋅
l
o
g
b
n
log_bn
logbn =
Θ
\Theta
Θ(
l
o
g
b
n
log_bn
logbn)
常数次幂无所谓
∀
\forall
∀ c > 0 ,
l
o
g
n
c
logn^c
lognc = c
⋅
\cdot
⋅ logn =
Θ
\Theta
Θ(log n)
对数多项式
123*
l
o
g
321
n
log^{321}n
log321n +
l
o
g
105
(
n
2
−
n
+
1
)
log^{105}(n^2 - n +1)
log105(n2−n+1) =
Θ
\Theta
Θ(
l
o
g
105
n
log^{105}n
log105n)
这类算法非常有效,复杂度无限接近于常数
∀
\forall
∀ c > 0 , logn =
O
O
O(
n
c
n^c
nc)
O O O( n c n^c nc)
一般的: a k n k + a k − 1 n k − 1 + . . . + a 1 n + a 0 = O ( n k ) , a k > 0 a_kn^k +a_{k-1}n^{k-1}+...+a_1n+a_0=O(n^k) , a_k>0 aknk+ak−1nk−1+...+a1n+a0=O(nk),ak>0
很明显,这是一种多项式的形式
例如:
100
n
+
200
=
O
(
n
)
100n+200=O(n)
100n+200=O(n)
(
100
n
−
500
)
(
20
n
2
−
300
n
+
2013
)
=
O
(
n
×
n
2
)
=
O
(
n
3
)
(100n-500)(20n^2-300n+2013)=O(n\times n^2)=O(n^3)
(100n−500)(20n2−300n+2013)=O(n×n2)=O(n3)
(
2013
n
2
−
20
)
/
(
1999
n
−
1
)
=
O
(
n
2
/
n
)
=
O
(
n
)
(2013n^2-20)/(1999n-1)=O(n^2/n)=O(n)
(2013n2−20)/(1999n−1)=O(n2/n)=O(n)
[
(
n
2013
−
24
n
2009
)
1
/
3
+
512
n
567
−
1978
n
123
]
1
/
11
=
O
(
n
61
)
[(n^{2013}-24n^{2009})^{1/3}+512n^{567}-1978n^{123}]^{1/11}=O(n^{61})
[(n2013−24n2009)1/3+512n567−1978n123]1/11=O(n61)
线性复杂度:所有 O ( n ) O(n) O(n)类函数
这类算法的效率通常认为已可令人满意了
O O O( 2 n 2^n 2n)
指数: T ( n ) = a n T(n)=a^n T(n)=an
c
>
0
,
n
c
=
O
(
2
n
)
/
/
e
n
=
1
+
n
+
n
2
/
2
!
+
n
3
/
3
!
+
n
4
/
4
!
+
.
.
.
c > 0 , n^c=O(2^n) \quad\quad\quad\quad \quad\quad\quad\quad//e^n=1+n+n^2/2!+n^3/3!+n^4/4!+...
c>0,nc=O(2n)//en=1+n+n2/2!+n3/3!+n4/4!+...
n
1000
=
O
(
1.000000
1
n
)
=
O
(
2
n
)
n^{1000}=O(1.0000001^n)=O(2^n)
n1000=O(1.0000001n)=O(2n)
1.000000
1
n
=
Ω
(
n
1000
)
1.0000001^n=\Omega(n^{1000})
1.0000001n=Ω(n1000)
这类算法的计算成本增长极快,通常被认为不可忍受
从 O ( n c ) O(n^c) O(nc)到 O ( 2 n ) O(2^n) O(2n),是从有效算法到无效算法的分水岭,有些问题的 O ( 2 n ) O(2^n) O(2n)算法往往显而易见,但设计出 O ( n c ) O(n^c) O(nc)算法却极其不易,甚至有时注定地只能是徒劳无功
例如 2-Subset 问题
问题描述:
S包含n个正整数,
∑
S
=
2
m
\sum S=2m
∑S=2m
S是否有子集,满足
∑
T
=
m
\sum T=m
∑T=m
问题分析:简而言之,S集合内n个整数能否被恰好分成两部分,要求元素的个数不一定相同,但两部分的总和应相同
直觉算法:逐一枚举S的每一子集,并统计其中元素的总和
定理:
∣
2
s
∣
=
2
∣
s
∣
=
2
n
|2^s|=2^{|s|}=2^n
∣2s∣=2∣s∣=2n
显而易见 ,直觉算法需要迭代2轮,并(在最坏情况下)至少需要花费很多时间——不堪理想!
那是否可以进行优化?
答:2-Subset is NP-complete(无法优化)
就目前的计算模型而言,不存在可在多项式时间内回答此问题的算法,即上述的直觉算法已属最优
O ( ) O() O()图像
算法分析
两个主要任务 = 正确性(不变性 × \times ×单调性)+ 复杂度
关键在于复杂度的分析!
复杂度分析的主要方法:
- 迭代:级数求和
- 递归:递归跟踪 + 递推方程
- 猜测 + 验证
级数
-
算术级数:与末项平方同阶
T ( n ) = 1 + 2 + . . . + n = n ( n + 1 ) / 2 = O ( n 2 ) T(n)=1+2+...+n=n(n+1)/2=O(n^2) T(n)=1+2+...+n=n(n+1)/2=O(n2) -
幂方级数:比幂次高出一阶
T 2 ( n ) = 1 2 + 2 2 + 3 2 + . . . + n 2 = n ( n + 1 ) ( 2 n + 1 ) / 6 = O ( n 3 ) T_2(n)=1^2+2^2+3^2+...+n^2=n(n+1)(2n+1)/6=O(n^3) T2(n)=12+22+32+...+n2=n(n+1)(2n+1)/6=O(n3)
T 3 ( n ) = 1 3 + 2 3 + 3 3 + . . . + n 3 = n 2 ( n + 1 ) 2 / 4 = O ( n 4 ) T_3(n)=1^3+2^3+3^3+...+n^3=n^2(n+1)^2/4=O(n^4) T3(n)=13+23+33+...+n3=n2(n+1)2/4=O(n4)
T 4 ( n ) = 1 4 + 2 4 + 3 4 + . . . + n 4 = n ( n + 1 ) ( 2 n + 1 ) ( 3 n 2 + 3 n − 1 ) / 30 = O ( n 5 ) T_4(n)=1^4+2^4+3^4+...+n^4=n(n+1)(2n+1)(3n^2+3n-1)/30=O(n^5) T4(n)=14+24+34+...+n4=n(n+1)(2n+1)(3n2+3n−1)/30=O(n5) -
几何级数( a>1 ):与末项同阶
T a ( n ) = a 0 + a 1 + . . . + a n = ( a n + 1 − 1 ) / ( a − 1 ) = O ( a n ) T_a(n)=a^0+a^1+...+a^n=(a^{n+1}-1)/(a-1)=O(a^n) Ta(n)=a0+a1+...+an=(an+1−1)/(a−1)=O(an) -
收敛级数
1 / 1 / 2 + 1 / 2 / 3 + 1 / 3 / 4 + . . . + 1 / ( n − 1 ) / n = 1 − 1 / n = O ( 1 ) 1/1/2+1/2/3+1/3/4+...+1/(n-1)/n=1-1/n=O(1) 1/1/2+1/2/3+1/3/4+...+1/(n−1)/n=1−1/n=O(1)
1 + 1 / 2 2 + . . . + 1 / n 2 < 1 + 1 / 2 2 + . . . = π 2 / 6 1+1/2^2+...+1/n^2<1+1/2^2+...=\pi^2/6 1+1/22+...+1/n2<1+1/22+...=π2/6=O(1)
1 / 3 + 1 / 7 + 1 / 8 + 1 / 15 + 1 / 24 + 1 / 31 + 1 / 35 + . . . = 1 = O ( 1 ) 1/3+1/7+1/8+1/15+1/24+1/31+1/35+...=1=O(1) 1/3+1/7+1/8+1/15+1/24+1/31+1/35+...=1=O(1) -
调和级数
h ( n ) = 1 + 1 / 2 + 1 / 3 + . . . + 1 / n = Θ ( l o g n ) h(n)=1+1/2+1/3+...+1/n=\Theta(logn) h(n)=1+1/2+1/3+...+1/n=Θ(logn) -
对数级数
l o g 1 + l o g 2 + l o g 3 + . . . + l o g n = l o g ( n ! ) = Θ ( n l o g n ) log1+log2+log3+...+logn=log(n!)=\Theta(nlogn) log1+log2+log3+...+logn=log(n!)=Θ(nlogn)
循环和级数
————————————————————————
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
算数级数:
Σ
i
=
0
n
−
1
n
=
n
+
n
+
.
.
.
+
n
=
n
∗
n
=
O
(
n
2
)
\Sigma^{n-1}_{i=0}n=n+n+...+n=n*n=O(n^2)
Σi=0n−1n=n+n+...+n=n∗n=O(n2)
可以把这个过程想象为点到线再到面的过程,i=0,j=0为第一个点,j++就是点到线的过程,然后i++就是线到面的过程;
————————————————————————
for(int i=0;i<n;i++)
for(int j=0;j<i;j++)
算数级数:
Σ
i
=
0
n
−
1
i
=
0
+
1
+
.
.
.
+
(
n
−
1
)
=
n
(
n
−
1
)
/
2
=
O
(
n
2
)
\Sigma^{n-1}_{i=0}i=0+1+...+(n-1)=n(n-1)/2=O(n^2)
Σi=0n−1i=0+1+...+(n−1)=n(n−1)/2=O(n2)
————————————————————————
for(int i=0;i<n;i++)
for(int j=0;j<i;j+=2013)
同理,也是算数级数;
————————————————————————
for(int i=1;i<n;i<<=1)
for(int j=0;j<i;j++)
几何级数:
1
+
2
+
4
+
.
.
.
+
2
⌊
l
o
g
2
(
n
−
1
)
⌋
1+2+4+...+2^{\lfloor {log_2(n-1)} \rfloor}
1+2+4+...+2⌊log2(n−1)⌋
=
Σ
k
=
0
⌊
l
o
g
2
(
n
−
1
)
⌋
2
k
/
/
(让
k
=
l
o
n
g
2
i
)
=\Sigma^{\lfloor {log_2(n-1)} \rfloor}_{k=0}2^k // (让k=long_2i)
=Σk=0⌊log2(n−1)⌋2k//(让k=long2i)
=
2
⌊
l
o
g
2
n
⌋
−
1
=2^{\lfloor {log_2n} \rfloor}-1
=2⌊log2n⌋−1
=
O
(
n
)
=O(n)
=O(n)
取非极端元素
问题:给定整数子集S,
∣
S
∣
=
n
≥
3
|S|=n\ge3
∣S∣=n≥3
找出元素
a
∈
S
,
a
≠
m
a
n
(
S
)
且
a
≠
m
a
∈
(
S
)
a\in S, a\ne man(S) 且 a\ne ma\in(S)
a∈S,a=man(S)且a=ma∈(S)
算法:
- 从S中任取三个元素{ x,y,z }
//若s以2数组形式给出,不妨取前三个
//由于s是集合,这三个元素必然互异
- 确定并排除其中的 最小,最大者
//不妨设 x = m a x ( x , y , z ) , y = m i n ( x , y , z ) x=max(x,y,z),y=min(x,y,z) x=max(x,y,z),y=min(x,y,z)
- 输出剩下的元素 z
结论:无论输入规模n多大,上述算法需要执行的时间都不变
T
(
n
)
=
常数
=
O
(
1
)
=
Ω
(
1
)
=
Θ
(
1
)
T(n)=常数=O(1)=\Omega(1)=\Theta(1)
T(n)=常数=O(1)=Ω(1)=Θ(1)
起泡排序
问题:给定n个整数,将它们按(非降)序排序
观察:有序/无序序列中,任意/总有一对相邻元素顺序/逆序
算法:扫描交换
依次比较每一对相邻原色,如有必要,交换之,若整趟都没有进行交换,则排序完成;否则,再做一趟扫描交换
void bubblesort(int A[],int n)
{
for(bool sorted = false;(sorted=!sorted);n--)//逐趟扫描交换,直至完全有序
{
for(int i=1;i<n;i++)//自左向右,逐对检查A[0,n]内各相邻元素
{
if(A[i-1]>A[i])//若逆序
{
swap(A[i-1],A[i]);//交换
sorted = false;//清除(全局)有序标志
}
}
}
}
分析:该算法必然会结束吗?至少需要迭代多少趟?
- 不变性:经 k 轮扫描交换后,最大的 k 个元素必然就位
例如,按照要求给数组 A[ ] = {5,2,7,4,6,3,1}进行非递减的起泡排序,则第1轮扫描交换后,7这个元素一定在数组的最后一个位置,即 A[6] ;第2轮扫描交换后,6这个元素一定在 A[5] ,依次类推
- 单调性:经 k 轮扫描交换后,问题规模缩减至 n-k
根据不变性,每一轮扫描交换后,总有一个元素确定好位置,则对于下一轮扫描交换来说,未确定位置的元素数量一定减一,即问题规模缩减至 n-k
- 正确性:经至多 n 趟扫描后,算法必然终止,且能给出正确解答
迭代和递归
数组求和【迭代】
问题:计算任意n个整数之和
实现:逐一取出每个元素,累加之
int SumI(int A[],int n){
int sum=0;//o(1)
for(int i=0;i<n;i++)//o(n)
sum+=A[i];//o(1)
return sum;//o(1)
}
无论A[ ]内容如何,都有:
T
(
n
)
=
1
+
n
∗
1
+
1
=
n
+
2
=
O
(
n
)
=
Ω
(
n
)
=
Θ
(
n
)
T(n)=1+n*1+1=n+2=O(n)=\Omega(n)=\Theta(n)
T(n)=1+n∗1+1=n+2=O(n)=Ω(n)=Θ(n)
减而治之
为求解一个大规模的问题,可以将其划分为两个子问题:其一平凡,另一规模缩减;分别求解子问题,由子问题的解,得到原问题的解。
数组求和【线性递归】【减而治之】
sum(int A[],int n){
return
(n<1)?0:sum(A,n-1)+A[n-1];
}
递归跟踪分析:检查每个递归实例,累计所需时间(调用语句本身,计入对应的子实例),其总和即算法执行时间;
本例中,单个递归实例自身只需
O
(
1
)
O(1)
O(1)时间
T
(
n
)
=
O
(
1
)
∗
(
n
+
1
)
=
O
(
n
)
T(n)=O(1)*(n+1)=O(n)
T(n)=O(1)∗(n+1)=O(n)
从递推角度看,为求解sum(A , n),需递归求解规模为n-1的问题sum()A , n-1),再累加上A[n-1];递归基:sum(A , 0);
递推方程
T
(
n
)
=
T
(
n
−
1
)
+
o
(
1
)
T(n)=T(n-1)+o(1)
T(n)=T(n−1)+o(1)
T
(
0
)
=
O
(
1
)
T(0)=O(1)
T(0)=O(1)
求解
T
(
n
)
−
n
=
T
(
n
−
1
)
−
(
n
−
1
)
=
.
.
.
T(n)-n=T(n-1)-(n-1)= ...
T(n)−n=T(n−1)−(n−1)=...
=
T
(
2
)
−
2
=T(2)-2
=T(2)−2
=
T
(
1
)
−
1
=T(1)-1
=T(1)−1
=
T
(
0
)
=T(0)
=T(0)
T
(
n
)
=
O
(
1
)
+
n
=
O
(
n
)
T(n)=O(1)+n=O(n)
T(n)=O(1)+n=O(n)
数组倒置【减而治之】
任给数组A[0 , n),将其前后颠倒
统一接口:
void reverse(int* A,int low,int high);
递归版
if(low < high){//问题规模的奇偶性不变,需要两个递归基
swap(A[low],A[high]);
reverse(A,low+1,high-1);
}
迭代版
while(low < high){
swap(A[low++],A[high--]);
}
分而治之
为求解一个大规模的问题,可以将其划分若干(通常两个)子问题,规模大体相当,分别求解子问题,由子问题的解,得到原问题的解。
数组求和【二分递归】【分而治之】
sum(int A[],int low,int high){
if(low == high) return A[1ow];
int middle=(low + high) >> 1;
return sum(A,low,high) + sum(A,middle+1,high);
}//入口形式为sum(A,0,n-1)
T
(
n
)
=
各层递归实例所需时间之和
T(n)=各层递归实例所需时间之和
T(n)=各层递归实例所需时间之和
=
O
(
1
)
∗
(
2
0
+
2
1
+
2
2
+
2
l
o
g
n
)
=O(1)*(2^0+2^1+2^2+2^{logn})
=O(1)∗(20+21+22+2logn)
=
O
(
1
)
∗
(
2
l
o
g
n
+
1
−
1
)
=
O
(
n
)
=O(1)*(2^{logn+1}-1)=O(n)
=O(1)∗(2logn+1−1)=O(n)
从递推角度看,为求解sun(A, low, high),需递归求解sum(A, low, middle)和sum(A, middle+1, high),进而将子问题的解累加;递归基:sum(A, low, low);
递推关系
T
(
n
)
=
2
∗
T
(
n
/
2
)
+
o
(
1
)
T(n)=2*T(n/2)+o(1)
T(n)=2∗T(n/2)+o(1)
T
(
1
)
=
O
(
1
)
T(1)=O(1)
T(1)=O(1)
求解
T
(
n
)
T(n)
T(n)
=
2
∗
T
(
n
/
2
)
+
c
1
=2*T(n/2)+c_1
=2∗T(n/2)+c1
T
(
n
)
+
c
1
=
2
∗
(
T
(
n
/
2
)
+
c
1
)
=
2
2
∗
(
T
(
n
/
4
)
+
c
1
)
T(n)+c_1=2*(T(n/2)+c_1)=2^2*(T(n/4)+c_1)
T(n)+c1=2∗(T(n/2)+c1)=22∗(T(n/4)+c1)
=
.
.
.
= ...
=...
=
2
l
o
g
n
(
T
(
1
)
+
c
1
)
=
n
∗
(
c
2
+
c
1
)
=2^{logn}(T(1)+c_1)=n*(c_2+c_1)
=2logn(T(1)+c1)=n∗(c2+c1)
T
(
n
)
T(n)
T(n)
=
(
c
1
+
c
2
)
n
−
c
1
=
O
(
n
)
=(c_1+c_2)n-c_1=O(n)
=(c1+c2)n−c1=O(n)
动态规划
所谓的动态规划,其实可以理解为,通过递归找出算法的本质,并且给出一个初步的解,再将其等效得转化为迭代的形式。
FIB()
递归: f i b ( n ) = f i b ( n − 1 ) + f i b ( n − 2 ) : 0 , 1 , 1 , 2 , 3 , 5 , 8...... fib(n)=fib(n-1)+fib(n-2):{0,1,1,2,3,5,8......} fib(n)=fib(n−1)+fib(n−2):0,1,1,2,3,5,8......
int fib(n)
{
return (2 > n) ? n : fib(n-1)+fib(n-2);
}
复杂度分析:
T
(
0
)
=
1
,
T
(
1
)
=
1
,
T
(
n
)
=
T
(
n
−
1
)
+
T
(
n
+
1
)
+
1
,
(
n
>
1
)
T(0)=1,T(1)=1,T(n)=T(n-1)+T(n+1)+1,(n>1)
T(0)=1,T(1)=1,T(n)=T(n−1)+T(n+1)+1,(n>1)
令
S
(
n
)
=
[
T
(
n
)
+
1
]
/
2
令 S(n)=[T(n)+1]/2
令S(n)=[T(n)+1]/2
则
S
(
0
)
=
1
=
f
i
b
(
1
)
,
S
(
1
)
=
1
=
f
i
b
(
2
)
则S(0)=1=fib(1),S(1)=1=fib(2)
则S(0)=1=fib(1),S(1)=1=fib(2)
故
S
(
n
)
=
S
(
n
−
1
)
+
S
(
n
−
2
)
=
f
i
b
(
n
+
1
)
故S(n)=S(n-1)+S(n-2)=fib(n+1)
故S(n)=S(n−1)+S(n−2)=fib(n+1)
T
(
n
)
=
2
∗
S
(
n
)
−
1
=
2
∗
f
i
b
(
n
+
1
)
−
1
=
O
(
2
∗
f
i
b
(
n
+
1
)
)
=
O
(
n
2
)
T(n)=2*S(n)-1=2*fib(n+1)-1=O(2*fib(n+1))=O(n^2)
T(n)=2∗S(n)−1=2∗fib(n+1)−1=O(2∗fib(n+1))=O(n2)
递归版 fib() 低效地根源在于,各递归实例均被大量重复地调用
解决方法A( 记忆化 )
将已计算过的实例地结果制表备查
解决方法B( 动态规划 )
颠倒计算方向:由自顶而下递归,为自底而上迭代
f=0;//fib(0)
g=1;//fib(1)
while(0 < n--)
{
g=g+f;
f=g-f;
}
return g;
T
(
n
)
=
O
(
n
)
,且仅只需
O
(
1
)
空间
T(n)=O(n),且仅只需 O(1) 空间
T(n)=O(n),且仅只需O(1)空间
LCS
子序列:由序列中若干字符,按原想对次序构成
最长公共子序列:两个序列公共子序列中的最长者(可能有多个,也可能有歧义)
递归
对于序列A[0,n]和B[0,m],即LCS(A,B)就有三种情况
- 若n=-1或m=-1,则取作空序列(‘’‘’) //递归基
- 若A[n]=‘X’=B[m],则取作 L C S ( A [ 0 , n ) , B [ 0 , m ) ) + ′ X ′ LCS(A[0,n),B[0,m))+'X' LCS(A[0,n),B[0,m))+′X′ //减而治之
- A[n]!=B[m],则在 L C S ( A [ 0 , n ] , B [ 0 , m ) ) 与 L C S ( A [ 0 , n ) , B [ 0 , m ] ) LCS(A[0,n],B[0,m))与LCS(A[0,n),B[0,m]) LCS(A[0,n],B[0,m))与LCS(A[0,n),B[0,m])中取更长者 //分而治之