题目链接:300.最长递增子序列
dp
数组:以nums[i]
为结尾的最长递增子序列的长度
递推公式:
if (nums[j] < nums[i]) dp[i] = max(dp[i], dp[j] + 1)
max
是因为取的是最长子序列
初始化:
dp[i] = 1
,每个至少为1
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> dp(nums.size(), 1);
int res = 1;//当nums.size == 1时,输出1
for (int i = 1; i < nums.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;
}
};
由于dp
数组是满足 if (nums[i] > nums[j])
条件时,才会进行递推。就是说dp数组不一定是单调递增的,dp.back()
不是所求的最大值。因此需要额外的变量res
来存储最大值。
时间复杂度降低到 O ( n × log n ) O(n \times \log n) O(n×logn)。看到 log n \log n logn,想到二分法
。
通过修改j
的遍历方式,将复杂度由 O ( n × n ) O(n\times n) O(n×n) 修改成 O ( n × log n ) O(n \times \log n) O(n×logn)
通过二分法来维护一个数组tails
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> tails(nums.size(), 0);
int res = 0;
for (auto& num : nums) {
int l = 0, r = res;//双指针
while (l < r) {//通过二分法找出第一个大于等于num的位置l
int m = (l + r) / 2;
tails[m] < num ? l = m + 1 : r = m;
}
tails[l] = num;
if (res == r)
++res;
}
return res;
}
};
时间复杂度为 O ( log n ) O(\log n) O(logn) 的算法通常是指具有二分查找特性的算法。以下是一些常见的时间复杂度为 O ( log n ) O(\log n) O(logn) 的算法:
二分查找:在有序数组中查找指定元素的位置。每次将数组分成两半,然后根据目标元素与中间元素的关系,在前半部分或后半部分继续查找。每次查找都将数据量减半,因此时间复杂度为 O ( log n ) O(\log n) O(logn)。
二叉搜索树的查找操作:在二叉搜索树中查找指定元素的位置。每次比较目标元素与当前节点的值,根据大小关系向左或向右子树继续查找,每次查找都可以将数据量缩小一半,因此时间复杂度为 O ( log n ) O(\log n) O(logn)。
堆排序中的堆操作:在堆排序算法中,建堆和调整堆都具有时间复杂度为 O ( log n ) O(\log n) O(logn)的特性。堆是一种完全二叉树结构,根据堆的性质,可以通过比较父节点与子节点的值进行插入、删除和堆化等操作,每次操作都可以将数据量减半,因此时间复杂度为 O ( log n ) O(\log n) O(logn)。
题目链接:674. 最长连续递增序列
要求连续,只需比较nums[i]
和nums[i - 1]
即可。
上一题不是连续,因此要比较num[i]
和nums[j]
比较
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
vector<int> dp(nums.size(), 1);
int res = 1;//当nums.size == 1时,输出1
for (int i = 1; i < nums.size(); ++i) {
if (nums[i] > nums[i - 1])
dp[i] = dp[i - 1] + 1;
res = max(res, dp[i]);
}
return res;
}
};
题目链接:718. 最长重复子数组
二维dp
数组:
以i-1
为结尾和j-1
为结尾的最长重复子数组的长度为dp[i][j]
不使用 以i
为结尾和j
为结尾的最长重复子数组的长度为dp[i][j]
,是因为在初始化部分,会对dp[0][j]
和dp[i][0
]通过遍历来赋值,增加代码量。
递推公式:
if(nums[i - 1] == nums[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1
初始化:
dp[i][0]和dp[0][j]无意义
,因此dp[i][j]
都初始化为零
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
int res = 0;
for (int i = 1; i <= nums1.size(); ++i) {
for (int j = 1; j <= nums2.size(); ++j) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}
res = max(res, dp[i][j]);
}
}
return res;
}
};
可以看到递推公式dp[i][j] = dp[i - 1][j - 1] + 1;
,只用到了dp[i-1][j-1]
,而没用到dp[i][j-1]
或dp[i-1][j]
,因此可以压缩为一维数组,有
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<int> dp(nums2.size() + 1, 0);
int res = 0;
for (int i = 1; i <= nums1.size(); ++i) {
for (int j = nums2.size(); j > 0; --j) {//从后往前遍历,防止重复
if (nums1[i - 1] == nums2[j - 1]) {
dp[j] = dp[j - 1] + 1;
} else {
dp[j] = 0;//恢复数组的值,以达到压缩数组的效果
}
res = max(res, dp[j]);
}
}
return res;
}
};