三种解法解决最长上升子序列(LIS)问题

最长上升子序列问题

描述:
   给定一个长度为 n 的数组 arr,求它的最长严格上升子序列的长度。
所谓子序列,指一个数组删掉一些数(也可以不删)之后,形成的新数组。例如 [1,5,3,7,3] 数组,其子序列有:[1,3,3]、[7] 等。但 [1,6]、[1,3,5] 则不是它的子序列。

输入描述:
第一行输入一个正整数 n ,表示数组的长度
第二行输入 n 个整数表示数组的每个元素。

输出描述:
输出最长严格上升子序列的长度

LIS问题有三种解法:        

解法一: 可以先将序列从小到大排序,然后求出排序后的序列和原来的序列的LCS长度就是最长上升子序列(LIS)长度
       需要注意的是:
         这种解法只能用于序列中没有重复元素的情况,因为要求子序列是严格上升的

解法二:  step 1:用dp[i]表示到元素i结尾时,最长的子序列的长度,初始化为1,因为只有数组有元素,至少有一个算是递增。
         step 2:第一层遍历数组每个位置,得到n个长度的子数组。        
         step 3:第二层遍历相应子数组求对应到元素i结尾时的最长递增序列长度,期间维护最大值。    
         step 4:对于每一个到i结尾的子数组,如果遍历过程中遇到元素j小于结尾元素,说明以该元素结尾的子序列加上子数组末尾元素也是严格递增的,因此转移方程为dp[i]=dp[j]+1.

前面两种解法的时间复杂度都一样,都是O(n^2),第三种解法的效率更高

解法三:
     步骤(具体实现见代码注释):
         1、准备一个辅助数组temp[]统计最长递增子序列的长度,逐个处理原来数组arr中的数字,例如处理的是arr[k]
         2、如果arr[k]>temp[temp.length()-1],就将arr[k]加入到temp数组的末尾
         3、如果arr[k]<temp[temp.length()-1],就替换temp[]数组中第一个大于arr[k]的数字。
     该方法的时间复杂度为O(nlog2n)

代码实现:

​

import java.util.*;

public class Main {

    static int[] sortBefore; //记录原来的数组

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = scanner.nextInt();
        }
        //保存原来的数组
        sortBefore = Arrays.copyOf(arr,arr.length);
        System.out.println("解法一的结果:" + firstSolve(arr));
        System.out.println("解法二的结果:" + secondSolve(sortBefore));
        System.out.println("解法三的结果:" + thirdSolve(sortBefore));
    }

    //解法一,这种解法只能用于序列中没有重复元素的情况,因为要求子序列是严格上升的
    public static int firstSolve(int[] arr){
        //给原来的数组排序
        Arrays.sort(arr);
        //求出两个数组的最长公共子序列的长度
        return LCS(sortBefore,arr);
    }

    //求最长公共子序列
    private static int LCS(int[] sortBefore, int[] arr) {
        int[][] dp = new int[arr.length+1][arr.length+1];
        for (int i = 1; i <= arr.length; i++) {
            for (int j = 1; j <= sortBefore.length; j++) {
                if (arr[i-1] == sortBefore[j-1])
                    dp[i][j] = dp[i-1][j-1] + 1;
                else
                    dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
            }
        }
        return dp[arr.length][arr.length];
    }

    //解法二
    public static int secondSolve(int[] arr){
        //dp[i]表示长度为i的序列中构成的最长上升子序列
        int[] dp = new int[arr.length];
        //设置数组长度大小的动态规划辅助数组
        Arrays.fill(dp, 1);
        int res = 0;
        for(int i = 1; i < arr.length; i++){
            for(int j = 0; j < i; j++){
                //可能j不是所需要的最大的,因此需要dp[i] < dp[j] + 1
                if(arr[i] > arr[j] && dp[i] < dp[j] + 1){
                    //i点比j点大,理论上dp要加1
                    dp[i] = dp[j] + 1;
                    //找到最大长度
                    res = Math.max(res, dp[i]);
                }
            }
        }
        return res;
    }

    //解法三
    public static int thirdSolve(int[] arr){
        //定义一个辅助数组
        int[] temp = new int[arr.length];
        //记录最终结果
        int len = 0;
        //将辅助数组中的值设为无穷小,方便判断
        Arrays.fill(temp,Integer.MIN_VALUE);
        //先将原来数组中的第一个元素放进temp中
        temp[0] = arr[0];
        for (int i = 1; i < arr.length; i++) {
            //找出temp中最后一个元素,并记录
            int k ,j;
            for (j = 0; j < temp.length; j++) {
                if (temp[j] == Integer.MIN_VALUE)
                    break;
            }
            //k此时记录的是temp中最后一个元素的下标(不包括无穷小的元素)
            k = j - 1;
            if (arr[i] > temp[k]){
                temp[k+1] = arr[i];
            }else {
                //找出temp数组中第一个大于arr[i]的值并替换他
                //只需要循环k+1次即可
                for (int m = 0; m < k + 1; m++) {
                    if (temp[m] > arr[i]){
                        temp[m] = arr[i];
                        break;
                    }
                }
            }
        }
        //统计最长上升子序列长度
        for (int i = 0; i < temp.length; i++) {
            if (temp[i] == Integer.MIN_VALUE)
                break;
            len++;
        }
        //返回结果
        return len;
    }
}

​

不了解LCS问题的码友可以看看我上一篇文章

动态规划——最长公共子序列(LCS)问题

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

终有弱水替沧海i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值