当前位置:   article > 正文

三种解法解决最长上升子序列(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)

代码实现:

  1. import java.util.*;
  2. public class Main {
  3. static int[] sortBefore; //记录原来的数组
  4. public static void main(String[] args) {
  5. Scanner scanner = new Scanner(System.in);
  6. int n = scanner.nextInt();
  7. int[] arr = new int[n];
  8. for (int i = 0; i < n; i++) {
  9. arr[i] = scanner.nextInt();
  10. }
  11. //保存原来的数组
  12. sortBefore = Arrays.copyOf(arr,arr.length);
  13. System.out.println("解法一的结果:" + firstSolve(arr));
  14. System.out.println("解法二的结果:" + secondSolve(sortBefore));
  15. System.out.println("解法三的结果:" + thirdSolve(sortBefore));
  16. }
  17. //解法一,这种解法只能用于序列中没有重复元素的情况,因为要求子序列是严格上升的
  18. public static int firstSolve(int[] arr){
  19. //给原来的数组排序
  20. Arrays.sort(arr);
  21. //求出两个数组的最长公共子序列的长度
  22. return LCS(sortBefore,arr);
  23. }
  24. //求最长公共子序列
  25. private static int LCS(int[] sortBefore, int[] arr) {
  26. int[][] dp = new int[arr.length+1][arr.length+1];
  27. for (int i = 1; i <= arr.length; i++) {
  28. for (int j = 1; j <= sortBefore.length; j++) {
  29. if (arr[i-1] == sortBefore[j-1])
  30. dp[i][j] = dp[i-1][j-1] + 1;
  31. else
  32. dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
  33. }
  34. }
  35. return dp[arr.length][arr.length];
  36. }
  37. //解法二
  38. public static int secondSolve(int[] arr){
  39. //dp[i]表示长度为i的序列中构成的最长上升子序列
  40. int[] dp = new int[arr.length];
  41. //设置数组长度大小的动态规划辅助数组
  42. Arrays.fill(dp, 1);
  43. int res = 0;
  44. for(int i = 1; i < arr.length; i++){
  45. for(int j = 0; j < i; j++){
  46. //可能j不是所需要的最大的,因此需要dp[i] < dp[j] + 1
  47. if(arr[i] > arr[j] && dp[i] < dp[j] + 1){
  48. //i点比j点大,理论上dp要加1
  49. dp[i] = dp[j] + 1;
  50. //找到最大长度
  51. res = Math.max(res, dp[i]);
  52. }
  53. }
  54. }
  55. return res;
  56. }
  57. //解法三
  58. public static int thirdSolve(int[] arr){
  59. //定义一个辅助数组
  60. int[] temp = new int[arr.length];
  61. //记录最终结果
  62. int len = 0;
  63. //将辅助数组中的值设为无穷小,方便判断
  64. Arrays.fill(temp,Integer.MIN_VALUE);
  65. //先将原来数组中的第一个元素放进temp中
  66. temp[0] = arr[0];
  67. for (int i = 1; i < arr.length; i++) {
  68. //找出temp中最后一个元素,并记录
  69. int k ,j;
  70. for (j = 0; j < temp.length; j++) {
  71. if (temp[j] == Integer.MIN_VALUE)
  72. break;
  73. }
  74. //k此时记录的是temp中最后一个元素的下标(不包括无穷小的元素)
  75. k = j - 1;
  76. if (arr[i] > temp[k]){
  77. temp[k+1] = arr[i];
  78. }else {
  79. //找出temp数组中第一个大于arr[i]的值并替换他
  80. //只需要循环k+1次即可
  81. for (int m = 0; m < k + 1; m++) {
  82. if (temp[m] > arr[i]){
  83. temp[m] = arr[i];
  84. break;
  85. }
  86. }
  87. }
  88. }
  89. //统计最长上升子序列长度
  90. for (int i = 0; i < temp.length; i++) {
  91. if (temp[i] == Integer.MIN_VALUE)
  92. break;
  93. len++;
  94. }
  95. //返回结果
  96. return len;
  97. }
  98. }

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

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

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号