一、题目描述:
二、思路分析:
第一步:这道题我们做过简化版,但这次要我们求的是最长子序列的个数,因此除了最长子序列的长度以外dp[k],我们还需要维护最长子序列的个数这个数组s[k]
第二步:确定状态,之前之所以用一个二维数组来讲最长子序列分解为尾部是当前节点的最长子序列和尾部为其他节点的最长子序列是因为这样在返回的时候不需要再次遍历,本题中由于要统计长度,为了减少繁琐程度,将状态表示改为dp[k]:尾部是当前节点的最长子序列,s[k]:尾部是当前节点的最长子序列的个数。
第三步:状态转移方程,dp前文说了,这里说s[k]的状态转移方程为
s[k] += s[j] ;//如果dp[k] == dp[j] + 1 且 nums[k] > nums[j]
s[k] = s[j] //如果dp[k] > dp[j] + 1 且 nums[k] > nums[j]
第四步: 边界条件,方程需要根据前面的节点而得到答案,因此i == 0时,是步具备前面的节点的,需要单独指定
第五步:实现方式略
第六步:优化略
三、AC 代码:
class Solution {
public:
int findNumberOfLIS(vector<int>& nums) {
int n = nums.size();
int* d = new int[n];
int* s = new int[n];
for (int i = 0; i < n; ++i)
{
d[i] = 1;
s[i] = 1;
}
// 记录最大的长度
int maxRes = 1;
for (int k = 1; k < n; ++k)
{
// cout << "k " << k << " " << nums[k] << endl;
// 当前最大的长度
int currMax = 1;
// 最大长度的子数组的数量
int currSum = 1;
int num = nums[k];
for (int i = 0; i < k; ++i)
{
if (num > nums[i])
{
if (d[i]+1 > currMax)
{
currSum = s[i];
currMax = d[i]+1;
}
else if (d[i]+1 == currMax)
{
currSum += s[i];
}
}
// cout << "i " << i << " " << nums[i] << " currMax " << currMax << " s[k] " << s[k] << endl;
}
s[k] = currSum;
d[k] = currMax;
printf("dk: %d sk: %d \n",d[k],s[k]);
maxRes = max(maxRes, d[k]);
}
int res = 0;
for (int i = 0; i < n; ++i)
{
// cout << d[i] << " " << s[i] << endl;
if (d[i] == maxRes)
{
res += s[i];
}
}
return res;
}
};
四、总结:
这题可以说是模板变种反复横跳,从想着实现无后效性,到不顾及无后效性而增加一次for循环来实现效果,动态规划题也就是这样,多写多想,一个是见识更多的模板的万物皆模板,一个是不拘泥于模板的去模板化。
像这样的,又何止一个动态规划呢
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情