300. 最长递增子序列(Longes Increasing Subsequence, LIS)

难度中等1382

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:

  • 1 <= nums.length <= 2500
  • -104 <= nums[i] <= 104

进阶:

  • 你可以设计时间复杂度为 O(n2) 的解决方案吗?
  • 你能将算法的时间复杂度降低到 O(n log(n)) 吗?

基本动态规划解法(O(N2)):个dp[i]记录的是以第 i 个数字作为末尾时,前i个数字的最长严格递增子序列长度,因此动态转移方程为:

dp[i]=max(dp[j])+1,其中0≤j<i且num[j]<num[i])

即当 num[i] > num[j] 时,说明num[i] 做为末尾时,可比dp[j] 的末尾大,即长度+1,比较它能够作为末尾的所有情况,找出最大值dp[i]。记录过程中,比较所有的dp[i],其最大值即为结果。

class Solution {
public:
	int lengthOfLIS(vector<int>& nums) {
		vector<int>dp(nums.size(), 1);
		int res = 1;
		for (int i = 1; i < dp.size(); ++i) {
			for (int j = 0; j < i; ++j) {
				if (nums[i] > nums[j])
					dp[i] = max(dp[i], dp[j] + 1);
			}
			res = max(res, dp[i]);
		}
		return res;
	}
};

通过二分查找,优化后的动态规划((O(NlogN))):为了进一步优化时间复杂度,修改dp数组的含义为,dp[i]表示在之前查找的结果中,最长严格递增子序列长度为 i 时,队列末尾的最小值。因此,在遍历时,只需要遍历该dp数组,比较num[i]其与不同长度的最小末尾,大于则说明长度应加一。由于这里规定的dp[i]是长度为i的最小末尾,因此dp数组是一个严格单调递增的(反证:假设存在非递增dp[i] > dp[j] , i < j ,例如dp[2] = 8,dp[4] = 5, 由于定义的dp[4]为长度为4的递增序列的末尾值,因此之前的三个数必然小于5,因此长度为2的最小末尾也肯定小于5)。因此,可以通过二分查找找到小于num[i]的dp数组的位置,通过比较其与下一个位置的dp值,更新它。最后,dp数组的大小即为结果。

注:lower_bound和upper_bound同为二分查找算法,其均为找出目标值所处位置,不同在于,lower_bound返回的是 第一个 可插入的位置,而 upper_bound找到的是 最后一个 可插入的位置,举例来说,对于[1,3,5,5,7,8]来说,当val = 5时,lower_bound返回的索引为2,upper_bound返回的索引为4。当val = 4时,lower_bound返回的索引为3,upper_bound返回的索引为3。

class Solution {
public:
	int lengthOfLIS(vector<int>& nums) {
		vector<int>dp;
		dp.push_back(INT32_MIN);
		dp.push_back(nums[0]);
		int res = 1;
		for (int i = 1; i < nums.size(); ++i) {
			int index = lower_bound(dp.begin(), dp.end(), nums[i]) - dp.begin();
			//cout << index << ' '<<i<< ' '<< nums[i]<<endl;
			if (index == dp.size() && nums[i]) {
				dp.push_back(nums[i]);
			}
			else {
				dp[index] = min(dp[index], nums[i]);
			}
		}
		return dp.size() - 1;
	}
};

LIS问题进阶:

354. 俄罗斯套娃信封问题

难度困难299

给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。

请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。

说明:
不允许旋转信封。

示例:

输入: envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出: 3 
解释: 最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。

为满足题意,需要保证找到的信封满足w0<w1<w2<...<wn,h0<h1<h2<...<hn的条件,而当我们对所有信封在一个维度w上排序后,在这个纬度w上就已经满足单调要求,然而由于在w纬度存在相等的情况,因此仍需判断在w纬度是否相等,为此,在 w 值互不相同的前提下,小于等于 ≤ 和小于 < 是等价的,那么我们在排序后,就可以完全忽略 w 维度,只需要考虑 h 维度了。此时,我们需要解决的问题即为:

给定一个序列,我们需要找到一个最长的子序列,使得这个子序列中的元素严格单调递增,即上面所说的LIS问题。

而为了忽略 w 维度,只需要考虑 h 维度,我们可以将 h值作为排序的第二关键字进行降序排序,这样一来,对于每一种w值,其对应的信封在排序后的数组中是按照 h 值递减的顺序出现的,那么这些 h值不可能组成长度超过1的严格递增的序列,即以h纬度生成的单点递增子序列不会存在w相同的信封,满足题意。

对二维数组排序后,就转换为上文的一维LIS问题,代码如下:

int cmp1(const void *a, const void *b)
{
	if ((*(vector<int>*)a)[0]  == (*(vector<int>*)b)[0] ) {
		return (*(vector<int>*)a)[1] < (*(vector<int>*)b)[1];   // 从大到小排序
	}
	else
	    return (*(vector<int>*)a)[0] > (*(vector<int>*)b)[0];   // 从小到大排序
}
class Solution {
public:
	int res = 0;

	int maxEnvelopes(vector<vector<int>>& envelopes) {
		vector<int>dp;
		qsort(&envelopes[0], envelopes.size(), sizeof(vector<int>(2)), cmp1);
		// for (auto i : envelopes)
		// 	cout << i[0] << ' ' << i[1] << ' ' << endl;
		dp.push_back(INT32_MIN);
		dp.push_back(envelopes[0][1]);
		for (int i = 1; i < envelopes.size(); ++i) {
			int index = lower_bound(dp.begin(), dp.end(), envelopes[i][1]) - dp.begin();
			if (index == dp.size())
				dp.push_back(envelopes[i][1]);
			else
				dp[index] = min(dp[index], envelopes[i][1]);
		}

		return dp.size() - 1;
	}
};

而在不知道这一方法之前,采用的方式为:

class Node {
public:
	static bool flag;
	int a, b, val;
	Node *left;
	Node *right;
	Node(int x, int y) :a(x), b(y) {
		val = 1;
		left = right = nullptr;
	};
	bool operator <(const Node& N)const {
		if (flag) {
			if (a == N.a)
				return b > N.b;
			else
				return a > N.a;
		}
		else {
			if (b == N.b)
				return a > N.a;
			else
				return b > N.b;
		}
	}
};
int cmp(const void *a, const void *b)
{
	return *(*(Node**)a) < *(*(Node**)b);
}
int cmp1(const void *a, const void *b)
{
	if (*(vector<int>*)a == *(vector<int>*)b)
		return *(vector<int>*)a > *(vector<int>*)b;
	else
		return *((vector<int>*)a + 1) > *((vector<int>*)b + 1);
}
bool Node::flag = 1;
class Solution {
public:
	int res = 0;

	int maxEnvelopes(vector<vector<int>>& envelopes) {
        if(envelopes.size()==0)
            return 0;
		vector<Node*>positive;
		for (auto i : envelopes) {
			Node *mid = new Node(i[0], i[1]);
			positive.push_back(mid);
		}
		qsort(&positive[0], positive.size(), sizeof(Node*), cmp);
		for (auto i : positive)
		{
			cout << i->a << ' ' << i->b << ' ' << i->val << ' ' << (i->left == nullptr) << endl;
		}
		int record = positive.size() - 1;
		for (int i = record - 1; i >= 0; --i) {
			if (positive[i]->a < positive[i + 1]->a)
				record = i + 1;
			if (positive[i]->a < positive[record]->a) {
				int k = 0, yu = positive[record]->a;
				bool judge = 0;	//判断是否存在b小且left==null的节点
				while (record + k < positive.size()) {
					if (positive[record + k]->b > positive[i]->b) {
						positive[i]->left = positive[record + k];
						cout << i << ' ' << record + k << endl;
						break;
					}
					else if(positive[record + k]->left==nullptr){
						judge = 1;	//说明a相同的这层存在b小且left==null的节点
					}
					k++;
					if (record + k < positive.size()) {
						if (positive[record + k]->a > yu) {
							yu = positive[record + k]->a;
							if (judge)
								break;
						}
					}
				}
			}
		}
        Node::flag = 0;
		qsort(&positive[0], positive.size(), sizeof(Node*), cmp);
		for (auto i : positive)
		{
			cout << i->a << ' ' << i->b << ' ' << i->val << ' ' << (i->left == nullptr) << endl;
		}
        record = positive.size() - 1;
        int res = 1;
		for (int i = record - 1; i >= 0; --i) {
			if (positive[i]->b < positive[i + 1]->b)
				record = i + 1;
			if (positive[i]->b < positive[record]->b) {
				int k = 0, yu = positive[record]->b;
				bool judge = 0;	//判断是否存在b小且left==null的节点
				while (record + k < positive.size()) {
					if (positive[record + k]->a > positive[i]->a) {
						positive[i]->right = positive[record + k];
						positive[i]->val = max(positive[i]->val, positive[record + k]->val + 1);
                        res = max(res,positive[i]->val);
						cout << i << ' ' << record + k << ' ' << positive[i]->val << endl;
						break;
					}
					else if (positive[record + k]->right == nullptr) {
						judge = 1;	//说明a相同的这层存在b小且left==null的节点
					}
					k++;
					if (record + k < positive.size()) {
						if (positive[record + k]->b > yu) {
							yu = positive[record + k]->b;
							if (judge)
								break;
						}
					}
				}
			}
		}
		return res;
	}
};

面试题 08.13. 堆箱子

难度困难38收藏分享切换为英文接收动态反馈

堆箱子。给你一堆n个箱子,箱子宽 wi、深 di、高 hi。箱子不能翻转,将箱子堆起来时,下面箱子的宽度、高度和深度必须大于上面的箱子。实现一种方法,搭出最高的一堆箱子。箱堆的高度为每个箱子高度的总和。

输入使用数组[wi, di, hi]表示每个箱子。

示例1:

 输入:box = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
 输出:6

示例2:

 输入:box = [[1, 1, 1], [2, 3, 4], [2, 6, 7], [3, 4, 5]]
 输出:10

提示:

  1. 箱子的数目不大于3000个。

这道题作为三维的LIS问题,其要求的结果与上述不同,不是要求序列长度最长,二是要求某一纬度(高度)求和最大,因此不能使用O(nlogn)复杂度的方法,而是使用基本的O(n2)的方法。如下:

bool cmp(vector<int>a, vector<int>b) {
	if (a[2] == b[2]) {
		if (a[1] == b[1]) {
			return a[0] > b[0];
		}
		else
			return a[1] > b[1];
	}
	else
		return a[2] > b[2];
}
class Solution {
public:
	int pileBox(vector<vector<int>>& box) {
		sort(box.begin(), box.end(), cmp);
		vector<int>dp(box.size() + 1, 0);
		int res = 0;
		for (int i = 0; i < dp.size(); ++i) {
			dp[i] = box[i][2];
			for (int j = 0; j < i; ++j) {
				if (box[j][2] > box[i][2] && box[j][1] > box[i][1] && box[j][0] > box[i][0])
					dp[i] = max(dp[i], dp[j] + box[i][2]);
			}
			res = max(res, dp[i]);
		}
		return res;
	}
};

猜你喜欢

转载自blog.csdn.net/Yanpr919/article/details/114369077