二分查找:
只能是有序的数组,而且应该用在插入删除不频繁的场景中。
public static int commonBinarySearch(int[] arr,int key){
int low = 0;
int high = arr.length - 1;
if(key < arr[low] || key > arr[high] || low > high){
return -1;
}
while(low <= high){
int middle = low+((high-low)>>1);//这种写法性能最优 // low+(high-low)/2; //(low + high) / 2 这种写法可能会造成溢出
if(arr[middle] > key){
//比关键字大则关键字在左区域
high = middle - 1;
}else if(arr[middle] < key){
//比关键字小则关键字在右区域
low = middle + 1;
}else{
return middle;
}
}
return -1; //最后仍然没有找到,则返回-1
}
递归的二分查找
public static int bsearch(int[] arr,int key){
return recursionBinarySearch(arr,key,0,arr.length-1);
}
public static int recursionBinarySearch(int[] arr,int key,int low,int high){
if(key < arr[low] || key > arr[high] || low > high){
return -1;
}
int middle = (low + high) / 2; //初始中间位置
if(arr[middle] > key){
//比关键字大则关键字在左区域
return recursionBinarySearch(arr, key, low, middle - 1);
}else if(arr[middle] < key){
//比关键字小则关键字在右区域
return recursionBinarySearch(arr, key, middle + 1, high);
}else {
return middle;
}
}
二分查找(重复数据)
查找第一个值等于给定的元素
public static int bserach1(int[] a,int n,int value){
int low= 0;
int high = n-1;
while(low<=high){
int mid = low + ((high-low)>>1);
if(a[mid]>value){
high=mid-1;
}else if(a[mid]<value){
low=mid+1;
}else {
if((mid==0)||(a[mid-1] != value)) return mid;
else high = mid-1;
}
}
return -1;
}
二分查找 (重复)
查找第一个值大于给定的元素
public static int bserach2(int[] a,int n,int value){
int low= 0;
int high = n-1;
while(low<=high){
int mid = low + ((high-low)>>1);
if(a[mid]>=value){
if((mid==0)||(a[mid-1]<value)){return mid;}
else high = mid -1;
}else {
low= mid +1;
}
}
return -1;
}
跳表
我们通过索引层结点的 down 指针,下降到原始链表这一层,继续遍历。
第 k级索引结点的个数就是 n/(2k次方)。
整个链表高度是log2n。
如果每一层都要遍历 m 个结点,那在跳表中查询一个数据的时间复杂度就是 O(m*logn)。
可知m=3。
所以时间复杂度是O(logn)(时间换空间)
空间复杂度是O(n)(n/2+n/4+n/8…+8+4+2=n-2)
但是在实际的软件开发中,原始链表中存储的有可能是很大的对象,而索引结点只需要存储关键值和几个指针,并不需要存储对象,所以当对象比索引结点大很多时,那索引占用的额外空间就可以忽略了。
跳表的插入、删除(时间复杂度也是 O(logn)
插入
如果插入的数据过多,很有可能会退化成单链表,所以我们需要动态的更新索引。
可通过一个随机函数,生成一个K值,K值是第几层就在第几层添加该元素的索引。
当新增一个 key 值为 8 的节点时,首先新增一个节点到最底层的链表中,根据概率算出 level 值,再根据 level 值新建索引层,最后链接索引层的新节点。新增节点和链接索引都是基于 CAS 操作实现。
删除跳表结点
如果这个结点在索引中也有出现,我们除了要删除原始链表中的结点,还要删除索引中的。
当删除一个 key 值为 7 的结点时,首先找到待删除结点,将其 value 值设置为 null;之后再向待删除结点的next 位置新增一个标记结点,以便减少并发冲突;然后让待删结点的前驱节点直接越过本身指向的待删结点,直接指向后继结点,中间要被删除的结点最终将会被 JVM垃圾回收处理掉;最后判断此次删除后是否导致某一索引层没有其它节点了,并视情况删除该层索引 。
Redis 中的有序集合是通过跳表来实现的(其实是复合数据结构)(应该是由一个双hashmap构成的字典和跳跃表实现的)
插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。
但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。
对于按照区间查找数据这个操作,跳表可以做到 O(logn)的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了。这样做非常高效。
跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。
斐波那契数列优化:
/**
* 递归优化
* HashMap 缓存
* @param n
* @return
*/
public static Map<Integer,Integer> map = new HashMap<>();
public int Fibonacci3(int n){
if(n<=0){
return 0;
}
if(n==1||n==2){
return 1;
}
if(!map.containsKey(n)){
map.put(n,Fibonacci3(n-1)+Fibonacci3(n-2));
}
return map.get(n);
}
/*
非递归方法
*/
public int Fibonacci2(int n){
int a = 0;
int b = 1;
if(n == 0){
return 0;
}
if (n == 1||n==2) {
return 1;
}
int i=2;
int sum=0;
while(i<=n){
sum=a+b;
a=b;
b=sum;
i++;
}
return sum;
}
LRU缓存算法
(自己实现的,时间复杂度有点高,运行大量数据会超出时间限制)
class LRUCache {
private int capacity;
private int count = 0;
private List<Map<Integer,Integer>> list_HashMap ;
public LRUCache(int capacity) {
this.capacity = capacity;
list_HashMap = new ArrayList<Map<Integer,Integer>>(capacity);
}
public int get(int key) {
int value = 0;
boolean flag = false;
for(int i =0;i<list_HashMap.size();i++){
if(list_HashMap.get(i).containsKey(key)){
flag = true;
Map<Integer, Integer> map = list_HashMap.get(i);
value = map.get(key);
list_HashMap.remove(i);
list_HashMap.add(0,map);
}
}
if(!flag){
return -1;
}
return value;
}
public void put(int key, int value) {
HashMap<Integer,Integer> map = new HashMap<>();
map.put(key,value);
if(count<capacity){
for(int i =0;i<list_HashMap.size();i++){
if(list_HashMap.get(i).containsKey(key)){
list_HashMap.remove(i);
count--;
break;
}
}
list_HashMap.add(0,map);
count++;
}else{
boolean flag = false;
for(int i =0;i<list_HashMap.size();i++){
if(list_HashMap.get(i).containsKey(key)){
flag = true;
list_HashMap.remove(i);
list_HashMap.add(0,map);
break;
}
}
if(!flag){//false 没重复
list_HashMap.add(0,map);
list_HashMap.remove(capacity);
}else{
}
}
}
}
贪心算法
1 针对一组数据,我们定义了限制值和期望值,希望从中选出几个数据,在满足限制值的情况下,期望值最大。
2 每次选择当前情况下,在对限制值同等贡献量的情况下,对期望值贡献最大的数据。
3 我们举几个例子看下贪心算法产生的结果是否是最优的。
霍夫曼编码:
我们把每个字符看作一个节点,并且辅带着把频率放到优先级队列中。我们从队列中取出频率最小的两个节点 A、B,然后新建一个节点 C,把频率设置为两个节点的频率之和,并把这个新节点 C 作为节点 A、B 的父节点。最后再把 C 节点放入到优先级队列中。重复这个过程,直到队列中没有数据。
分治算法
分而治之:将原问题划分成 n 个规模较小,并且结构与原问题相似的子问题,递归地解决这些子问题,然后再合并其结果,就得到原问题的解。
需满足的条件:
1 原问题与分解成的小问题具有相同的模式;
2 原问题分解成的子问题可以独立求解,子问题之间没有相关性
3 具有分解终止条件,也就是说,当问题足够小时,可以直接求解;
4 可以将子问题合并成原问题,而这个合并操作的复杂度不能太高,否则就起不到减小算法总体复杂度的效果了。
例:求数组的逆序对
套用分治的思想来求数组 A 的逆序对个数。我们可以将数组分成前后两半 A1 和 A2,分别计算 A1 和 A2 的逆序对个数 K1 和 K2,然后再计算 A1 与 A2 之间的逆序对个数 K3。那数组A的逆序对个数就等于 K1+K2+K3。分治中可以使用归并排序,每次合并操作,我们都计算逆序对个数,把这些计算出来的逆序对个数求和,就是这个数组的逆序对个数了。
private int num = 0; //全局变量或者成员变量
public int count (int[] arr,int n){
num = 0;
mergeSortCounting(arr,0,n-1);
return num;
}
private void mergeSortCounting(int[] arr, int p, int r) {
if(p >=r){
return ;
}
int q = (p+r)/2;
mergeSortCounting(arr,p,q);
mergeSortCounting(arr,q+1,r);
merge(arr,p,q,r);
}
private void merge(int[] arr, int p, int q, int r) {
int i = p,j = q+1,k=0;
int[] tmp = new int[r-p+1];
while(i<=q && j<=r){
if(arr[i] <=arr[j]){
tmp[k++] = arr[i++];
}else{
num +=(q-i+1);//统计p-q之间,比a[j]大的元素
tmp[k++] = arr[j++];
}
}
while(i<=q){ //处理剩下的
tmp[k++] = arr[i++];
}
while(j <= r){//处理剩下的
tmp[k++] = arr[j++];
}
for(i = 0;i<r-p;i++){//从tmp拷回给arr
arr[p+i] = tmp[i];
}
}
如:亦可以使用分治,利用MapReduce提供高可靠,高性能,高容错的并行计算框架。
回溯算法:
深度优先搜索、八皇后、0-1 背包问题、图的着色、旅行商问题、数独、全排列、正则表达式匹配等等。
/**
* 八皇后
*/
public class Question2_EightQueens {
int[] result = new int[8];//成员变量,下标表示行,值表示queen存储在哪一列
public void cal8Queens(int row){//调用方法:cal8Queens(0);
if(row==8){//8个棋子都放置好了,打印结果
printQueens(result);
return; //8 行棋子都放好了,已经没法再往下递归了,
}
for(int column = 0;column<8;column++){//每一行都有8种放法
if(isOk(row,column)){//有些放法不满足要求
result[row] = column;//第row行的棋子放到了column列
cal8Queens(row+1);//考察下一行
}
}
}
private boolean isOk(int row,int column){//判断row行column列放置是否合适
int leftup = column-1;
int rightup = column+1;
for(int i =row-1;i>=0;--i){//逐行往上考察每一行
if(result[i]==column){
return false;//第i行的column列有棋子吗?
}
if(leftup>=0){ //考察左上对角线,dii行leftup列有棋子吗
if(result[i]==leftup){
return false;
}
}
if(rightup<8){ //考察右上对角线,第i行rightup列有棋子吗
if(result[i]==rightup){
return false;
}
}
--leftup;
++rightup;
}
return false;
}
private void printQueens(int[] result){//打印一个二维矩阵
for(int row = 0;row<8;row++){
for(int column = 0;column<8;column++){
if(result[row] == column){
System.out.print("Q ");
}else{
System.out.println("* ");
}
}
System.out.println();
System.out.println();
}
}
}
/**
* 01背包问题
* 使用递归 回溯算法
* 时间复杂度O(2^n)
* 可以优化:递归中存在很多重复计算,可以用一个二维数组来记录每层可以达到的不同状态。
*/
public class Question3_01Bag {
public int maxW = Integer.MIN_VALUE;//存储背包中总重量的最大值
/**
* @param i 考察到哪个物品
* @param cw 表示当前已经装进去的物品重量
* @param items 装到第几个了
* @param n 物品个数
* @param w 背包重量
*/
public void f(int i ,int cw,int[] items,int n,int w){
if (cw == w | i == n) {
if(cw>maxW){
maxW = cw;
}
return;
}
f(i+1,cw,items,n,w);//当前物品不装进背包
if(cw+items[i]<=w){ //已经超过背包重量
f(i+1,cw+items[i],items,n,w);//当前物品装进背包
}
}
}
/**
*01背包问题变形
* 存储的最大价值是多少
* @param i 考察到哪个物品
* @param cw 表示当前已经装进去的物品重量
* @param items 装到第几个了
* @param n 物品个数
* @param w 背包重量
*/
public int maxV= Integer.MIN_VALUE;//存储背包中总价值
private int[] items = {2,2,4,6,3};//物品重量
private int[] value = {3,4,8,9,6}; //物品的价值
private int n = 5; //物品个数
private int w = 9; //背包承受的最大重量
public void f(int i ,int cw, int cv){
if (cw == w | i == n) {
if(cv>maxV){
maxV = cv;
}
return;
}
f(i+1,cw,cv);
if(cw+items[i]<=w){ //已经超过背包重量
f(i+1,cw+items[i],cv+value[i]);//选择装第i个物品
}
}
/**
* 正则表达式
*/
public class Question4_Pattern {
private boolean matched = false;
private char[] pattern ; //正则表达式
private int plen; //正则表达式长度
public Question4_Pattern(char[] pattern,int plen){
this.pattern = pattern;
this.plen = plen;
}
public boolean match(char[] text,int tlen){//文本串及长度
matched = false;
rmatch(0,0,text,tlen);
return matched;
}
private void rmatch(int ti, int pj, char[] text, int tlen) {
if(matched){
return; //已经匹配了,就不要继续再递归
}
if(pj==plen){//正则表达式到结尾了
if(ti == tlen){//文本串也到结尾了
matched = true;
}
}
if(pattern[pj] == '*'){ //匹配任意字符
for(int k = 0;k<=tlen-ti;k++){
rmatch(ti+k,pj+1,text,tlen);
}
}else if(pattern[pj] == '?'){//匹配0-1个
rmatch(ti,pj+1,text,tlen);//0
rmatch(ti+1,pj+1,text,tlen);//1
}else if(ti<tlen && pattern[pj]==text[ti]){//纯字符匹配
rmatch(ti+1,pj+1,text,tlen);
}
}
}
动态规划:(最优)
大部分动态规划能解决的问题,都可以通过回溯算法来解决,只不过回溯算法解决起来效率比较低。
把问题分解为多个阶段,每个阶段对应一个决策。记录每一个阶段可达的状态集合(去掉重复的),然后通过当前阶段的状态集合,来推导下一个阶段的状态集合,动态地往前推进。
1.最优子结构
最优子结构指的是,问题的最优解包含子问题的最优解。那我们也可以理解为,后面阶段的状态可以通过前面阶段的状态推导出来。
2.无后效性
第一层含义是,在推导后面阶段的状态的时候,我们只关心前面阶段的状态值,不关心这个状态是怎么一步一步推导出来的。第二层含义是,某阶段状态一旦确定,就不受之后阶段的决策影响。
3.重复子问题
不同的决策序列,到达某个相同的阶段时,可能会产生重复的状态。
最长公共子序列
/**
* 最长公共子序列
*/
public class Question9_LongestIncrementalSubsequence {
public int LIS1(int[] arr ,int length){
int[] longest = new int[length];
int i,j;
for(i = 0;i<length;i++){
longest[i] = 1;
}
int nLis = 1;
for(i = 1;i<length;i++){
for(j = 0;j<i;j++){ //当前值前面的值
if(arr[j] <= arr[i]){
longest[i] = Math.max(longest[i],longest[j]+1);//看看原来的和前面的+1比较
}
}
nLis = Math.max(nLis,longest[i]);//遍历一遍之后找到最大值
}
return nLis;
}
}
01背包问题
时间复杂度:O(n*w)
我们把整个求解过程分为 n 个阶段,每个阶段会决策一个物品是否放到背包中。每个物品决策(放入或者不放入背包)完之后,背包中的物品的重量会有多种情况,也就是说,会达到多种不同的状态,对应到递归树中,就是有很多不同的节点。我们把每一层重复的状态(节点)合并,只记录不同的状态,然后基于上一层的状态集合,来推导下一层的状态集合。我们可以通过合并每一层重复的状态,这样就保证每一层不同状态的个数都不会超过 w 个(w 表示背包的承载重量)
/**
*
* @param weight 物品重量
* @param n 物品个数
* @param w 背包可承受的重量
* @return
*/
public int knapsack(int[] weight,int n,int w){
boolean[][] states = new boolean[n][w+1]; //默认为false
states[0][0] = true ; //第一行的数据要特殊处理,可以利用哨兵优化
states[0][weight[0]] = true;
for(int i =1;i<n;i++){ //动态规划 状态转移
for(int j = 0;j<w;j++){ //不把第i个物品放入背包
if(states[i-1][j]==true){
states[i][j] = states[i-1][j];//不放进去的情况同步上一层的数据
}
}
for(int j = 0;j<=w-weight[i];j++){//放入第i个物品
if(states[i-1][j]==true){
states[i][j+weight[i]] = true;
}
}
}
for(int i = w;i>0;i--){//对最下层最后一个为true的结果输出
if(states[n-1][i]==true){
return i;
}
}
return 0;
}
/**
*减少空间消耗
* @param weight 物品重量
* @param n 物品个数
* @param w 背包可承受的重量
* @return
*/
public int knapsack2(int[] weight,int n,int w){
boolean[] states = new boolean[w+1]; //默认为false
states[0] = true ; //第一行的数据要特殊处理,可以利用哨兵优化
for(int i =1;i<n;i++){ //动态规划 状态转移
for(int j = w-weight[i];j>=0;j--){ //第i个物品放入背包
if(states[j]==true){
states[j+weight[i]] =true;
}
}
}
for(int i = w;i>0;i--){//输出结果
if(states[i]==true){
return i;
}
}
return 0;
}
/**
* 01背包 优化回溯算法中不必要的计算
* 时间复杂度和空间复杂度都是O(n*w)
* 加入物品价值的考虑,使得背包中拥有的物品价值最大
* @param weight
* @param value
* @param n
* @param w
* @return
*/
public static int knapsack3(int[] weight,int[] value,int n,int w){
int[][] states = new int[n][w+1];
for(int i = 0;i<n;i++){//初始化states
for(int j = 0;j<w+1;j++){
states[i][j] = -1;
}
}
states[0][0] = 0;
states[0][weight[0]] = value[0];
for(int i =1;i<n;i++){//动态规划,状态转移
for(int j = 0;j<=w;j++){//不选择第i个物品
if(states[i-1][j]>=0){
states[i][j] = states[i-1][j];//同步上一层的状态
}
}
for(int j = 0;j<=w-weight[i];j++){//选择第i个物品
if(states[i-1][j] >=0){
int v = states[i-1][j]+value[i];//拿上一层的总价值加上当前i的价值
if(v>states[i][j+weight[i]]){
states[i][j+weight[i]] = v;
}
}
}
}
//找出最大值
int maxValue = -1;
for(int j = 0;j<=w;j++){
if(states[n-1][j] >maxValue){
maxValue = states[n-1][j];
}
}
return maxValue;
}
例子:求出从最高层移动到最底层的最短路径长度。(还没弄明白)
/**
* 求出杨辉三角中从最高层移动到最底层的最短路径长度。
*
* @param matrix 杨辉三角
* @return
*/
int[][] matrix = {{5},{7,8},{2,3,4},{4,9,6,1},{2,7,9,4,5}};
public int yangHuiTriangle(int[][] matrix){
int[][] state = new int[matrix.length][matrix.length];
state[0][0] = matrix[0][0];//初始状态
for(int i = 1;i<matrix.length;i++){
for(int j = 0;j<matrix.length;j++){
if(j==0){
state[i][j] = state[i-1][j]+matrix[i][j];//上一层的state加上这层的权值
}else if(j==matrix[i].length-1){
state[i][j] = state[i-1][j-1]+matrix[i][j];
}else{
int top1 = state[i-1][j];
int top2 = state[i-1][j];
state[i][j] = Math.min(top1,top2)+matrix[i][j];
}
}
}
int minDis = Integer.MAX_VALUE;
for(int i =0;i<matrix[matrix.length-1].length;i++){
int distance = state[matrix.length-1][i];
if(distance<minDis){
minDis = distance;
}
}
return minDis;
}
二维数组最短路径问题
public int minDist = Integer.MAX_VALUE;//全局变量保存最短路径值
/**
* 回溯算法求最短路径
* @param i
* @param j
* @param dist
* @param w
* @param n
*/
public void minDistBT(int i,int j,int dist,int[][] w,int n){
//到达了n-1,n-1这个位置了
if(i==n && j==n ){
if(dist<minDist){
minDist = dist;
}
return;
}
if(i<n){//往下走
minDistBT(i+1,j,dist+w[i][j],w,n);
}
if(j<n){//往下走
minDistBT(i,j+1,dist+w[i][j],w,n);
}
}
/**
* 动态规划
* @param matrix
* @param n
* @return
*/
public int minDistDP(int[][] matrix,int n){
int[][] states = new int[n][n];
int sum = 0;
for(int j = 0;j<n;j++){//初始化第一行数据
sum += matrix[0][j];
states[0][j] = sum;
}
sum = 0;
for(int i = 0;i<n;i++){//初始化第一列shuju
sum += matrix[i][0];
states[i][0] = sum;
}
for(int i = 1;i<n;i++){
for(int j= 1;j<n;j++){
states[i][j] = matrix[i][j] + Math.min(states[i][j-1],states[i-1][j]);
}
}
return states[n-1][n-1];
}
/**
* 备忘录+递归方式
* @param i
* @param j
* @return
*/
private int[][] matrix = {{1,3,5,9},{2,1,3,4},{5,2,7,6},{6,8,4,3}};
private int n = 4;
private int[][] mem = new int[4][4];
public int minDist(int i,int j){//调用minDIst(n-1,n-1);
if(i == 0 && j ==0 ){
return matrix[0][0];
}
if(mem[i][j] > 0){
return matrix[i][j];
}
int minLeft = Integer.MAX_VALUE;
if(j-1>=0){
minLeft = minDist(i,j-1);
}
int minUp = Integer.MAX_VALUE;
if(i-1 >= 0){//不是(0,0)
minUp = minDist(i-1,j);
}
int currMinDist = matrix[i][j] + Math.min(minLeft,minUp);
mem[i][j] = currMinDist;
return currMinDist;
}
莱文斯坦距离
/**
*回溯算法
* @param i
* @param j
* @param edist
*/
private char[] a = "mitcmu".toCharArray();
private char[] b = "mtacnu".toCharArray();
private int n = 6;
private int m = 6;
private int minDist = Integer.MAX_VALUE;//存储结果
public void lwstBT(int i,int j,int edist){
if( i==n || j== m){
if(i<n){
edist += (n-i);
}
if(j<m){
edist += (m-j);
}
if(edist<minDist){
minDist = edist;
}
return ;
}
if(a[i] == b[j]){ //两字符匹配
lwstBT(i+1,j+1,edist);
}else {
lwstBT(i+1,j,edist+1);//删除a[i] 或者b[j]前添加一个字符
lwstBT(i,j+1,edist+1);//删除b[j] 或者a[i]前添加一个字符
lwstBT(i+1,j+1,edist+1);//将a[i]和b[j]替换为相同的字符
}
}
/**
* 动态规划
* 维护一个二维状态表
* @param a
* @param n
* @param b
* @param m
* @return
*/
public int lwstDP(char[] a,int n,char[] b,int m){
int[][] minDist = new int[n][m];
for(int j = 0;j<m;j++){//初始化第0行
if(a[0] == b[j]){
minDist[0][j] = j;//0 1 2 3 4 5
}else if(j!=0){
minDist[0][j] = minDist[0][j-1]+1;
}else {
minDist[0][j] = 1;
}
}
for(int i = 0;i<m;i++){//初始化第0列
if(a[i] == b[0]){
minDist[i][0] = i;//0 1 2 3 4 5
}else if(i!=0){
minDist[i][0] = minDist[i-1][0]+1;
}else {
minDist[i][0] = 1;
}
}
for(int i = 1;i<n;i++){//按行填表
for(int j = 1;j<m;j++){
if(a[i] == b[j]){
minDist[i][j] = min(minDist[i-1][j]+1,minDist[i][j-1]+1,minDist[i-1][j-1]);
}else{
minDist[i][j] = min(minDist[i-1][j]+1,minDist[i][j-1]+1,minDist[i-1][j-1]+1);
}
}
}
return minDist[n-1][m-1];
}
private int min(int x, int y, int z) {
int minv = Integer.MAX_VALUE;
if(x<minv){
minv = x;
}
if(y<minv){
minv = y;
}
if(z<minv){
minv = z;
}
return minv;
}
计算最长公共字串长度
/**
*
* 维护一个二维状态表
* @param a
* @param n
* @param b
* @param m
* @return
*/
public int lcs(char[] a,int n,char[] b,int m){
int[][] maxlcs = new int[n][m];
for(int j = 0;j<m;j++){//初始化第0行
if(a[0] == b[j]){
maxlcs[0][j] = 1;
}else if(j!=0){
maxlcs[0][j] = maxlcs[0][j-1];
}else {
maxlcs[0][j] = 0;
}
}
for(int i = 0;i<m;i++){//初始化第0列
if(a[i] == b[0]){
maxlcs[i][0] =1;
}else if(i!=0){
maxlcs[i][0] = maxlcs[i-1][0];
}else {
maxlcs[i][0] = 0;
}
}
for(int i = 1;i<n;i++){//按行填表
for(int j = 1;j<m;j++){
if(a[i] == b[j]){
maxlcs[i][j] = max(maxlcs[i-1][j]+1,maxlcs[i][j-1]+1,maxlcs[i-1][j-1]+1);//看看哪种方式得到的长度最长
}else{
maxlcs[i][j] = max(maxlcs[i-1][j],maxlcs[i][j-1],maxlcs[i-1][j-1]);
}
}
}
return maxlcs[n-1][m-1];
}
private int max(int x, int y, int z) {
int maxv = Integer.MIN_VALUE;
if(x>maxv){
maxv = x;
}
if(y>maxv){
maxv = y;
}
if(z>maxv){
maxv = z;
}
return maxv;
}