1、题目描述:
2、题解:
方法1:动态规划:
动态规划问题,弄清楚三点:
1、重复子问题;
2、最优子结构;
3、无后效性。
动态规划:
1、状态定义;
2、状态转移方程;
3、初始化;base case
4、输出;
5、思考状态压缩。
可以用递归去求,但是会存在重叠子问题,加个备忘录可以解决重复问题。
画dp table
状态定义:
dp[i]的值代表nums以第i个数字为结尾的最长子序列长度
转移方程:
python代码如下:
i遍历数组
dp[i] = max(dp[i],dp[j]+1) for j in [0,i)
解释:对于j∈[0,i):
1、如果nums[j] < nums[i],那么说明nums[i]可以接在nums[j]后面,此时最长上升子序列长度为dp[j]+1
2、如果nums[j] >= nums[i],nums[i]不可以接在nums[j]后面,上升子序列不成立
初始化:
dp[i]所有元素置1,解释:每个元素都可以单独成为一个长度为1子序列
返回值:
dp中的最大值
python:
#动态规划
if not nums:return 0
n = len(nums)
dp = [1] * n
for i in range(1,n):
for j in range(0,i):
if nums[j] < nums[i]:
dp[i] = max(dp[i],dp[j]+1)
return max(dp)
C++:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
// 动态规划
if (nums.empty()) return 0;
int n = nums.size();
vector<int> dp(n,1);
for (int i = 1;i < n;i++){
for (int j = 0;j < i ;j++){
if (nums[j] < nums[i])
dp[i] = max(dp[i],dp[j] + 1);
}
}
int res = 0;
for (auto p : dp)
res = max(res,p);
return res;
}
};
方法2:贪心+二分查找
状态定义:
tail[i] 表示长度为i+1的所有上升子序列的结尾的最小值
状态转移方程:
遍历数组,对于每个值num
如果 num > tail[-1]:
添加到tail后
continue
在tail中用二分查找,找到第一个大于等于nums的位置,然后替换成nums(前只让让第 1 个严格大于 nums 的数变小)
初始化:
tail[0] = nums[0],解释:仅有一个元素时,它一定是长度为1且结尾最小的元素
返回值:
tail的长度
就是可以看成蜘蛛扑克牌的玩法:给一堆扑克牌,让从左到右分成若干堆。有一定规则:只能把点数小的牌放在大牌上,优先放在左边的堆,如果当前堆没有可放的位置,就新建一个堆去放。
python代码如下:
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums: return 0
n = len(nums)
tail = [nums[0]]
for num in nums:
if num > tail[-1]:
tail.append(num)
continue
left, right = 0, len(tail) - 1
while left < right:
mid = left + (right - left) // 2
if tail[mid] < num:
left = mid + 1
else:
right = mid
tail[left] = num
return len(tail)
或者:
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:return 0
n = len(nums)
tail = [nums[0]]
for i in range(1,n):
left,right = 0,len(tail)
while left < right:
mid = left + (right - left) // 2
if tail[mid] < nums[i]:
left = mid + 1
else:
right = mid
if left == len(tail):
tail.append(nums[i])
else:
tail[left] = nums[i]
return len(tail)
或者:
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums: return 0
n = len(nums)
tail = [0] * n
res = 0 #牌堆数初始化为0
for num in nums:
#搜索左侧边界的二分查找
left, right = 0, res
while left < right:
mid = left + (right - left) // 2
if tail[mid] < num:
left = mid + 1
else:
right = mid
if left == res:
res += 1
tail[left] = num
return res
C++代码如下:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
// 贪心 + 二分
if (nums.empty()) return 0;
vector<int> tail;
tail.push_back(nums[0]);
for (auto num : nums){
if (tail.back() < num) //只可以放比上一张小的
{
tail.push_back(num);
continue;
}
int left = 0,right = tail.size() - 1;
while (left < right){
int mid = left + (right - left) / 2;
if (tail[mid] >= num)
right = mid;
else
left = mid + 1;
}
tail[left] = num;
}
return tail.size();
}
};
3、复杂度分析:
方法1:
时间复杂度:O(N^2)
空间复杂度:O(N)
方法2:
时间复杂度:O(NlogN)
空间复杂度:O(N)