最长递增子序列
最长递增子序列
题目:给定数组 arr,返回 arr 的最长递增子序列。
举例:arr=[2,1,5,3,6,4,8,9,7],返回的最长递增子序列为[1,3,4,8,9]。
方法一:动态规划
这题递归可不好搞吧,时间复杂度貌似为o(n^3)。
我们用动态规划,leetcode仅仅让求最长递归子序列的长度,那么dp数组的dp[i]就是以arr[i]为结尾的最长递归子序列长度。(为啥dp[i] 的意义不是以arr[i]为开头的最长长度呢?因为我们连dp[0]都没法初始化,所以不行)
dp[0] 很简单我们就知道是1
dp[1] 为如果arr[1] > arr[0] 则dp[1] = dp[0] + 1
dp[2] 如果arr[2] > arr[0] ,我们还不能着急赋值,如果arr[2] 也大于 arr[1] ,那么我们应该选择dp[0] 和 dp[1] 中最大的一个:dp[2] = Math.max(dp[0], dp[1]) + 1
dp[3] 在arr[3] > arr[i] (0<= i < 3) 的情况下,找到dp[i] 中最大的,加到dp[3]
代码:
/**
* 根据arr,获得dp数组,dp数组意义为以第i个元素结尾的最长递增子序列
* @param arr
* @return
*/
public int[] getdp1(int[] arr) {
int[] dp = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (arr[j] < arr[i]) {
dp[i] = Math.max(dp[j] + 1, dp[i]);
}
}
}
return dp;
}
得到dp数组后,我们就可以还原最长递增子序列了
在dp数组中找到最大值len,这个最大值就是最长递增子序列的长度。
这个最大值在dp数组中的索引值index,然后最长递增子序列的最后一个元素就是arr[index]
然后我们开始找最长递增子序列的倒数第二个元素,它满足:倒数第二个元素肯定比最后一个元素值小,而且倒数第二个元素的dp[i] 正好等于倒数第一元素的dp[i] - 1
然后就是倒数第三个元素和倒数第二个元素比较。。。。。。
代码:
/**
* 根据dp数组的信息:结尾的索引,最长递增子序列的长度。从而求出最长递增子序列
* @param arr
* @param dp
* @return
*/
public int[] generateLIS(int[] arr, int[] dp) {
int len = 0;
int index = 0;
// 找出dp数组的最大值,和最大值的index
for (int i = 0; i < arr.length; i++) {
if (len < dp[i]) {
len = dp[i];
index = i;
}
}
int[] res = new int[len];
res[--len] = arr[index];
for (int i = index - 1; i >= 0; i--) {
if (arr[i] < arr[index] && dp[i] == dp[index] - 1) {
res[--len] = arr[i];
index = i;
}
}
return res;
}
主方法:
// 主方法
public int[] lengthOfLIS(int[] arr) {
if (arr.length == 0 || arr == null) {
return null;
}
int[] dp = getdp1(arr);
return generateLIS(arr, dp);
}
方法二:动态规划 + 二分
我感觉左神和leetcode上的题解思路都是一致的,只不过左神用ends,leetcode上用tail,不过leetcode上给了更详细的思考过程。
添加了ends,right。ends数组的意义:ends[0] = 2,目前为止长度为1的递增子序列中的结尾值最小为2
ends[0..1]=[1,5]表示目前为止,长度为1的递增子序列中的结尾值最小为1,长度为2的递增子序列中的结尾值最小为5
ends[0..2]=[1,3,6]表示目前为止,长度为1的递增子序列中的结尾值最小为1,长度为2的递增子序列中的结尾值最小为3,长度为3的递增子序列中的结尾值最小为6
代码:
/**
* 在查找ends数组中使用二分法,因此降低了时间复杂度
* @param arr
* @return
*/
public int[] getdp2(int[] arr) {
int[] dp = new int[arr.length];
int[] ends = new int[arr.length];
ends[0] = arr[0];
dp[0] = 1;
int right = 0;
int l = 0;
int r = 0;
int m = 0;
for (int i = 1; i < arr.length; i++) {
l = 0;
r = right;
// 二分法
while (l <= r) {
m = (l + r) / 2;
if (arr[i] > ends[m]) {
l = m + 1;
} else {
r = m - 1;
}
}
right = Math.max(right, l);
ends[l] = arr[i];
dp[i] = l + 1;
}
return dp;
}
信封嵌套
给定一个 N 行 2 列的二维数组,每一个小数组的两个值分别代表一个信封的长和宽。如果信封 A 的长和宽都小于信封 B,那么信封 A 可以放在信封 B 里,请返回信封最多嵌套多少层。
matrix = {{3,4},
{2,3},
{4,5},
{1,3},
{2,2},
{3,6},
{1,2},
{3,2},
{2,4}
}
信封最多可以套 4 层,从里到外分别是{1,2},{2,3},{3,4},{4,5},所以返回 4。
这题,既需要长度递增,也需要宽度递增。
先对长度递增排序,对于长度相等的对其宽度递减排序。
按此规律对信封排序,排序后的信封宽度从左到右为{3,2,4,3,2,6,4,2,5}
在这个数组中找到最长递增子序列的长度,就是所求。
public class Envelope{
private int len;
private int wid;
public Envelope(int len, int wid) {
this.len = len;
this.wid = wid;
}
}
public class EnvelopeComparator implements Comparator<Envelope> {
@Override
public int compare(Envelope o1, Envelope o2) {
return o1.len != o2.len ? o1.len - o2.len : o2.wid - o1.wid;
}
}
public Envelope[] getSortedEnvelopes(int[][] matrix) {
Envelope[] res = new Envelope[matrix.length];
for (int i = 0; i < matrix.length; i++) {
res[i] = new Envelope(matrix[i][0], matrix[i][1]);
}
Arrays.sort(res, new EnvelopeComparator());
return res;
}
public int maxEnvelopes(int[][] matrix) {
Envelope[] envelopes = getSortedEnvelopes(matrix);
int[] ends = new int[matrix.length];
ends[0] = envelopes[0].wid;
int right = 0;
int l = 0;
int r = 0;
int m = 0;
for (int i = 1; i < envelopes.length; i++) {
l = 0;
r = right;
while (l <= r) {
m = (l + r) / 2;
if (envelopes[i].wid > ends[m]) {
l = m + 1;
} else {
r = m - 1;
}
}
right = Math.max(right, l);
ends[l] = envelopes[i].wid;
}
return right + 1;
}