最长公共上升子序列
给定两个字符串 str1str1str1 和 str2str2str2,返回两个字符串的最长公共子序列的长度(每个字符串长度不超过 100010001000)。
(1)N^4
代码块:
int lengthOfLICS(int *nums1, int length1, int *nums2, int length2) {
int **dp = (int **)calloc(length1 + 1, sizeof(int *));
int i, j, k, l;
for (i = 0; i < length1; ++i) {
dp[i] = (int *)calloc(length2 + 1, sizeof(int));
}
int ans = 0;
for (i = 0; i < length1; ++i) {
for (j = 0; j < length2; ++j) {
if (nums1[i] == nums2[j]) {
dp[i][j] = 1;
for (k = 0; k < i; ++k) {
for (l = 0; l < j; ++l) {
if (nums1[k] != nums2[l]) continue;
if (nums1[k] >= nums1[i]) continue;
if (dp[k][l] + 1 <= dp[i][j]) continue;
dp[i][j] = dp[k][l] + 1;
}
}
if (dp[i][j] > ans) ans = dp[i][j];
} else {
if (i > 0) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = 0;
}
}
}
}
for (i = 0; i < length1; ++i) {
free(dp[i]);
}
free(dp);
return ans;
}
算法思路:
状态定义:
dp[i][j]为nums1
前i个数
与nums2前j个数
所能匹配成功的公共上升子序列的最大长度。
状态转移方程:
1.当nums1[i]与nums2[j]相等时,先将dp[i][j]赋值为1,再次
遍历nums1的前i-1位与nums2的前j-1位
,当满足1.
nums1[k]==nums2[l] 2.
nums1[k]
(2)n^3
int lengthOfLICS(int *nums1, int length1, int *nums2, int length2) {
int *dp[2];
dp[0] = (int *)calloc(length2 + 1, sizeof(int));
dp[1] = (int *)calloc(length2 + 1, sizeof(int));
int ans = 0;
for (int i = 0; i < length1; i++) {
for (int j = 0; j < length2; j++) {
if (nums1[i] != nums2[j]) {
dp[i & 1][j + 1] = dp[!(i & 1)][j + 1];
continue;
}
dp[i & 1][j + 1] = 1;
for (int k = j - 1; k >= 0; k--) {
if (nums2[k] >= nums2[j]) continue;
if (dp[!(i & 1)][k + 1] + 1 > dp[i & 1][j + 1]) {
dp[i & 1][j + 1] = dp[!(i & 1)][k + 1] + 1;
}
}
if (dp[i & 1][j + 1] > ans) ans = dp[i & 1][j + 1];
}
}
return ans;
}
算法思路:
状态定义:
dp[i+1][j+1]为nums1
前i个数
与nums2以第j个数为结尾
所能匹配成功的公共上升子序列的最大长度。
状态转移方程:
1.当nums1[i]与nums2[j]相等时,先将dp[i&1][j+1]赋值为1,
只需遍历一遍
nums2的前 j-1位,当满足1
nums2[k]
(3)n^2 * long(n)
#include <limits.h>
int binary_search(int *nums, int n, int x) {
int head, tail, mid;
head = 0, tail = n - 1;
while (head < tail) {
mid = (head + tail) >> 1;
if (nums[mid] >= x) {
tail = mid;
} else {
head = mid + 1;
}
}
return head;
}
int lengthOfLICS(int *nums1, int length1, int *nums2, int length2) {
int *dp[2];
int *len = (int *)calloc(length2 + 1, sizeof(int));
dp[0] = (int *)calloc(length2 + 1, sizeof(int));
dp[1] = (int *)calloc(length2 + 1, sizeof(int));
int ans = 0;
for (int i = 0; i < length1; i++) {
len[0] = INT_MIN;
for (int j = 0; j < length2; j++) {
len[j + 1] = INT_MAX;
int ind = binary_search(len, j + 2, nums2[j]);
if (nums1[i] != nums2[j]) {
if (nums2[j] < len[dp[!(i & 1)][j + 1]]) {
len[dp[!(i & 1)][j + 1]] = nums2[j];
}
dp[i & 1][j + 1] = dp[!(i & 1)][j + 1];
} else {
dp[i & 1][j + 1] = ind;
len[ind] = nums2[j];
if (dp[i & 1][j + 1] > ans) ans = dp[i & 1][j + 1];
}
}
}
return ans;
}
算法思路:
状态定义:
dp[i+1][j+1]为nums1
前i个数
与nums2以第j个数为结尾
所能匹配成功的公共上升子序列的最大长度。
状态转移方程:
1.添加了len数组辅助查找,len数组内存储的是nums1的前i位与nums2的以第j位为结尾的公共上升子序列,len数组每次内层循环结束后更新为len数组的初始值。
2.当nums1[i]与nums2[j]相等时,
只需二分查找一遍
len数组的前 j+2位,找到插入位置ind,更新dp[i&1][j+1]=ind,同时更新len数组的第ind位为nums2[j]。
3.当nums1[i]与nums2[j]不等时,更新dp[i&1][j+1]=dp[!(i&1)][j+1],并且判断nums2[j]是否小于len[dp[!(i & 1)][j + 1]],(判断nums2[j]之前是否匹配成功过)是则更新len[dp[!(i & 1)][j + 1]]=nums2[j]。
4.最大值记录在变量ans里,在nums1[i]与nums2[j]相等时的逻辑代码快末尾判断dp[i&1][j+1]是否大于ans,是则更新ans,程序的最后返回ans。
5.空间复杂度为2×m。
6.时间复杂度为n^2×logn
(4)n^2
int lengthOfLICS(int *nums1, int length1, int *nums2, int length2) {
int *dp[2];
dp[0] = (int *)calloc(length2 + 1, sizeof(int));
dp[1] = (int *)calloc(length2 + 1, sizeof(int));
int ans = 0, k_max;
for (int i = 0; i < length1; i++) {
k_max = 0;
for (int j = 0; j < length2; j++) {
if (nums1[i] > nums2[j] && dp[!(i & 1)][j + 1] > k_max){
k_max = dp[!(i & 1)][j + 1];
}
if (nums1[i] != nums2[j]) {
dp[i & 1][j + 1] = dp[!(i & 1)][j + 1];
} else {
dp[i & 1][j + 1] = k_max + 1;
if (ans < k_max + 1) ans = k_max + 1;
}
}
}
return ans;
}
算法思路:
状态定义:
dp[i+1][j+1]为nums1
以第i个数为结尾
与nums2以第j个数为结尾
所能匹配成功的公共上升子序列的最大长度。
状态转移方程:
1.如果在内层循环存在着一个nums2[k]与nums1[i]相等的情况,则可以在每次循环时,用nums1[i]与nums2[k]进行比较,如果发现nums2[k]小于nums1[i],并且nums1[i]的前i位与nums2[k]第k位曾经匹配过的长度大于k_max,则更新k_max=dp[!(i&1)][j+1]。
2.当nums1[i]与nums2[j]相等时,更新dp[i&1][j+1]=k_max+1。
3.当nums1[i]与nums2[j]不等时,dp[i&1][j+1]=dp[!(i&1)][j+1]。
4.最大值记录在变量ans里,在nums1[i]与nums2[j]相等时的逻辑代码快末尾判断dp[i&1][j+1]是否大于ans,是则更新ans,程序的最后返回ans。
5.空间复杂度为2×m。
6.时间复杂度为n^2。
化简思路
空间:
由于每一轮的状态只与上一轮的状态有关,所以可以将空间复杂度从n×m化简为2×m。
时间
1.
n^4思路为两个串嵌套扫一遍,找到相等的位置还得再在相等的位置前嵌套扫一遍,一共嵌套了4层。
2.
n^3思路为两个串嵌套扫一遍,找到相等的位置时只扫一边nums2数组,找到一个比其小并且之前匹配成功过的数从而更新dp[i][j]。
3.
n^2logn思路为在n^3基础上找上一个数的方法改成了利用len数组进行二分查找,从而把时间复杂度从n^3降到了n^2logn。
4.
n^2的思路是将dp[i][j]重新定义为nums1
以第i个数为结尾
与nums2以第j个数为结尾
所能匹配成功的公共上升子序列的最大长度,所以轮如果存在nums2[j]与nums1[i]能够匹配的话,就可以在循环的时候直接用nums1[i]去与nums2[k]进行比较了,将可以匹配的长度存放在k_max里。从而在找到两个数相等的时候,可以直接将dp[i][j]更新为k_max+1。
5.
整体思想就是想办法化简在找到两个数字相等的时候去寻找上一个比其小并且相等的位置在哪,省去不必要的循环查找,从而节省算法时间。