最近学习了这个基础算法原型,最长递增子序列,所谓子序列,就是可以不连续,去除数组中任意元素获得的子数组都是子序列。
举个栗子,现在有数组arr,长度为5,分别是10,4,5,12,8
那么这个数组的最长递增子序列长度为3。
解法一:
动态规划思想,原问题分成子问题,那么自问题就是到结尾为arr[i]时的最大递增子序列长度。
创建一个辅助数组dp,和原数组长度相等,这个数组中存放的是以arr[i]结尾的时候,获得的最大递增子序列长度。
还是上面的栗子,arr数组中的元素为10,4,5,12,8,
那么dp[0]=1,这个子序列数字10本身
dp[1]=1,因为4比10小,再来看dp[2],这时候5>4,但是比10小,所以dp[2]=dp[1]+1。
dp[3],12比4和5都大,所以dp[3]=max(dp[1],dp[2])+1……最终得出dp数组为1,1,2,3,3
所以得到结果3。这种解法时间复杂度O(n²)
解法二:
解法二属于那种打死我我自己也想不出那种,同样的,这种方法也需要一个辅助数组,我们还叫做dp好了。
但是这次dp存放的不一样了,dp这次存放的是当最长递增子序列为i是,这个序列的最小结尾,注意了,是结尾!
继续看上面的栗子,10,4,5,12,8,
dp[0]=10,先把第一个放进去,因为当我们检索到10的时候,最长子序列长度为1,且最小结尾是10,所以存放这个10,
遍历到4的时候,我们发现,最长长度还是1,但是4比10小,所以4替换掉10,
继续遍历,5的时候,发现最长长度是2了,ok,那有有效区扩大为2,把5放进去,这时候数组里是4,5,
12的时候一样,数组变成4,5,12,然后最后一个8比12小,替换掉12,遍历结束,有效区长度为3,所以结果为3
这个方法的主要思想就是遍历到arr[i]的时候,去找之前的dp数组中第一个比他大的数,有的话替换掉,没有的话,扩大有效区。
这么一想其实这个方法也是两个循环,但是这个方法比方法一快,原因就在于我们可以加速第二个循环。
第二层循环我们去找比他大的数,可以用二分啊,所以时间复杂度加速到O(nlogn)。
下面上代码,
1 public class GetLis { 2 // O(n²) 3 public static int getLis1(int[] arr) { 4 int maxLen = 1; 5 int[] dp = new int[arr.length]; 6 for (int i = 0; i < arr.length; i++) { 7 dp[i] = 1; 8 for (int j = 0; j < i; j++) { 9 if (arr[i] > arr[j] && dp[j] + 1 > dp[i]) { 10 dp[i] = dp[j] + 1; 11 } 12 } 13 maxLen = Math.max(maxLen, dp[i]); 14 } 15 return maxLen; 16 } 17 18 // O(nlogn) 19 public static int getLis2(int[] arr) { 20 int maxLen = 1; 21 int[] dp = new int[arr.length]; 22 int right = 0; 23 int l = 0; 24 int r = 0; 25 int m = 0; 26 dp[0] = arr[0]; 27 for (int i = 1; i < arr.length; i++) { 28 l = 0; 29 r = right; 30 while (l <= r) { 31 m = (l + r) / 2; 32 if (arr[i] > dp[m]) { 33 l = m + 1; 34 } else { 35 r = m - 1; 36 } 37 } 38 right = Math.max(l, right); 39 dp[l] = arr[i]; 40 41 } 42 maxLen = right + 1; 43 return maxLen; 44 } 45 46 public static void main(String[] args) { 47 48 /* 49 * Scanner scan = new Scanner(System.in); int n = scan.nextInt(); int[] 50 * arr = new int[n]; for (int i = 0; i < n; i++) { arr[i] = 51 * scan.nextInt(); } System.out.println(getLis1(arr)); 52 * System.out.println(getLis2(arr)); scan.close(); 53 */ 54 55 // 对数器验证一波 56 57 for (int i = 0; i < 100000; i++) { 58 int n = (int) (Math.random() * 98 + 2); 59 int[] arr = new int[n]; 60 for (int j = 0; j < n; j++) { 61 arr[j] = (int) (Math.random() * 200); 62 } 63 int res1 = getLis1(arr); 64 int res2 = getLis2(arr); 65 if (res1 != res2) { 66 System.out.println("出错了!"); 67 } 68 } 69 70 } 71 }