最大上升子序列
递归
int LIS(int arr[],int idx,int upper)
{
if(idx == 0)
{
if(arr[0] < upper) return 1;
else return 0;
}
// 初始化为上一个idx上界为upper
int ans = LIS(arr,idx-1,upper);
if(arr[idx] < upper)
// 如果可以,调整上界
ans = max(ans,LIS(arr,idx-1,arr[idx]) + 1);
return ans;
}
性能不用我说,时间复杂度指数级,记忆化也没有太大优化,弱到全部TLE。
动态规划
for(int i =0; i<n; i++)
{
lis[i] = 1;
for(int j=0; j<i; j++)
if(height[j] < height[i]) lis[i] = max(lis[j]+1,lis[i]);
}
很简单,不用太多解释,时间复杂度 Θ ( n 2 ) \Theta(n^{2}) Θ(n2)。
二分+贪心+动态规划
贪心:某一个上升子序列最后的元素在保证长度不变的情况下,让他尽量的小,这样就有更多的机会去计算更大长度的上升子序列。
动态规划:依次检查数组中的每一个值,找到正确的位置,然后修改这个上升子序列最后的元素,让他减小,有点最短路算法中松弛的意思。
二分:在找到正确的位置这一过程中,我们可以发现g数组是一个单调递增的有序数组,可以使用二分查找来找到正确的位置。
int LIS(int arr[],int n)
{
// g[i] 代表长度为1的LIS序列中的最后元素的值,并且这个最后元素的值一定是相同长度LIS序列里面最小的那一个
int g[105];
// 初始化无穷大
for(int i = 1; i<=n; i++)
g[i] = INT_MAX;
// 第一个设置为arr[0]
g[1] = arr[0];
int len = 1;
for(int i = 1; i<n; i++)
{
// 寻找下界
int loc = lower_bound(g+1,g+n,arr[i])-g;
// 计算长度
len = max(len,loc);
// 修改长度为loc的最大元素,arr[i]一定小于g[loc],保证贪心算法的正确
g[loc] = arr[i];
}
return len;
}
最大连续上升子序列就更简单了,在这里不在赘述。
双上升子序列
如果是严格上升,那么根据第一个关键字排序,如果第一个关键字相同,那么根据第二个关键字倒序排序即可。
如果是不严格上升,那么根据第一个关键字排序,如果第一个关键字相同,那么根据第二个关键字正序排序即可。
struct Obj
{
int hei;
int wei;
bool operator<(const Obj &o) const
{
if (hei == o.hei)
{
return wei > o.wei;
}
else
{
return hei < o.hei;
}
}
};
class Solution
{
public:
int bestSeqAtIndex(vector<int> &height, vector<int> &weight)
{
vector<Obj> objs;
for (int i = 0; i < height.size(); i++)
{
objs.push_back({height[i], weight[i]});
}
sort(objs.begin(), objs.end());
vector<int> g(objs.size() + 1);
g[1] = objs[0].wei;
int len = 1;
for (int i = 1; i < objs.size(); i++)
{
if (objs[i].wei > g[len])
{
len++;
g[len] = objs[i].wei;
}
else
{
int cc = lower_bound(g.begin() + 1, g.end(), objs[i].wei) - g.begin();
g[cc] = objs[i].wei;
}
}
return len;
}
};
仍然是一个双上升子序列问题,考虑按照 a i a_i ai排序,如果 a i a_i ai相同,那么我们可以求 b i b_i bi的最长上升子序列。另外,如果 a i a_i ai相同,我们也可以按照上面的策略,逆序排放 b i b_i bi,这样就不会造成冲突问题。
LIS 计数
现在要求序列中有多少个LIS子序列。
使用动态规划求解,设 c n t [ i ] cnt[i] cnt[i]是以 a i a_i ai结尾的最长上升子序列的个数。
class Solution
{
public:
int findNumberOfLIS(vector<int> &nums)
{
vector<int> dpn(nums.size(), 1);
for (int i = 0; i < nums.size(); i++)
for (int j = 0; j < i; j++)
{
if (nums[j] < nums[i])
{
dpn[i] = max(dpn[i], dpn[j] + 1);
}
}
vector<int> adp(nums.size());
for (int i = 0; i < nums.size(); i++)
{
if (dpn[i] == 1)
{
adp[i] = 1;
continue;
}
for (int j = 0; j < i; j++)
{
if (nums[j] < nums[i] && dpn[j] == dpn[i] - 1)
{
adp[i] += adp[j];
}
}
}
int mx = *max_element(dpn.begin(), dpn.end());
int ans = 0;
for (int i = 0; i < nums.size(); i++)
{
if (dpn[i] == mx)
ans += adp[i];
}
return ans;
}
};
时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
我们考虑ExLIS算法,当我们二分查找到对应位置进行替换的时候,我们要查询前一个位置,并且小于 a i a_i ai的子序列计数,此时可以使用前缀和。
class Solution
{
public:
int binarySearch(int n, function<bool(int)> func)
{
int l = 0;
int r = n;
while (l < r)
{
int mid = (l + r) / 2;
if (func(mid))
{
r = mid;
}
else
{
l = mid + 1;
}
}
return l;
}
int findNumberOfLIS(vector<int> &nums)
{
vector<vector<int>> lis;
vector<vector<int>> cnt;
for (int v : nums)
{
int loc = binarySearch(lis.size(), [&](int id)
{ return lis[id].back() >= v; });
int c = 1;
if (loc > 0)
{
int k = binarySearch(lis[loc - 1].size(), [&](int id)
{ return lis[loc - 1][id] < v; });
c = cnt[loc - 1].back() - cnt[loc - 1][k];
}
if (loc == lis.size())
{
lis.push_back({v});
cnt.push_back({0, c});
}
else
{
lis[loc].push_back(v);
cnt[loc].push_back(c + cnt[loc].back());
}
}
return cnt.back().back();
}
};
时间复杂度 O ( n log n ) O(n \log n) O(nlogn),空间复杂度 O ( n ) O(n) O(n),虽然是二维数组,但每个元素只存放一次。
其次,我们还可以考虑用树状数组进行维护。
struct Node
{
int len;
int cnt;
Node() : len(0), cnt(0) {}
void up(Node &val)
{
if (val.len > len)
{
len = val.len;
cnt = 0;
}
if (val.len == len)
cnt += val.cnt;
}
};
class Solution
{
public:
int n;
vector<Node> tree;
int lowbit(int x)
{
return x & -x;
}
void update(int i, Node val)
{
while (i < n)
{
tree[i].up(val);
i += lowbit(i);
}
}
Node preMax(int i)
{
Node ans;
while (i > 0)
{
ans.up(tree[i]);
i -= lowbit(i);
}
return ans;
}
int findNumberOfLIS(vector<int> &nums)
{
vector<int> decre(nums);
sort(decre.begin(), decre.end());
decre.erase(unique(decre.begin(), decre.end()), decre.end());
tree.resize(decre.size() + 1);
n = decre.size() + 1;
for (int i : nums)
{
int id = lower_bound(decre.begin(), decre.end(), i) - decre.begin() + 1;
Node prv = preMax(id - 1);
prv.len++;
if (prv.cnt == 0)
prv.cnt = 1;
update(id, prv);
}
Node ans = preMax(decre.size());
return ans.cnt;
}
};