【LeetCode】动态规划—673. 最长递增子序列的个数(附完整Python/C++代码)

动态规划解决最长递增子序列个数问题
部署运行你感兴趣的模型镜像

前言

在算法研究中,序列问题是一个常见而重要的主题。最长递增子序列问题不仅在理论上具有挑战性,同时在许多实际应用中也非常有用,如数据分析、动态规划和计算机视觉等领域。本文将深入探讨如何利用动态规划和计数技巧来解决该问题,同时提供 Python 和 C++ 的具体实现,以帮助读者更好地理解和掌握这一算法。

题目描述

在这里插入图片描述

基本思路

1. 问题定义

给定一个整数数组 nums,我们需要找到其中最长的递增子序列的长度,以及该长度的递增子序列的个数。

2. 理解问题和递推关系

  • 最长递增子序列(LIS)是指在数组中选择若干元素,使得它们的顺序与原数组相同,且毎个元素都大于前一个元素。

・ 定义 dp[i] 为以 nums[i] 结尾的最长递增子序列的长度, count[i] 为以 nums[i] 结尾的最长递增子序列的个数。

・递推关采如下:
・对于每个 nums[i],音看其前面的所有元素 nums[j]( j < i )

  • 如果 nums[j] < nums[i], 则更新 dp[i] :

d p [ i ] = max ⁡ ( d p [ i ] , d p [ j ] + 1 ) d p[i]=\max (d p[i], d p[j]+1) dp[i]=max(dp[i],dp[j]+1)

  • 更新 count[i]:
  • 如果 d p [ j ] + 1 > d p [ i ] d p[j]+1>d p[i] dp[j]+1>dp[i] ,说明找到了一个更长的递增子序列,更新 count[i]count[j]
  • 如果 d p [ j ] + 1 = = d p [ i ] d p[j]+1==d p[i] dp[j]+1==dp[i] ,说明找到了一个同样长度的递增子序列,累加 count[j]count[i]

3. 解决方法

3.1 动态规划方法

  1. 初始化两个数组 d p d p dpcount, 长度与 nums 相同:
    • dp[i] 初始化为 1,因为每个元素本身可以形成长度为 1 的递增子序列。
    • count[i]初始化为 1 ,因为毎个元素自身的子序列个数为 1
  2. 使用双重矿环遍历数组 nums,更新 dp[i]count[i]数组。
  3. 最终,找到 max ⁡ ( d p ) \max (\mathrm{dp}) max(dp) 来获取最长递增子序列的长度,并对 count 数组进行遍历,累加所有 count[i] (当 dp[i] 等于最长长度) 以获得该长度子序列的个数。

3.2 优化方法

  • 使用二分查找技术可以进一步优化 d p d p dp 数组的构建,使时间复杂度降低到 O ( n log ⁡ n ) O(n \log n) O(nlogn)

  • 通过维护一个数组 tails 来记录当前长度的递增子序列的末尾元素,从而更新 count

4. 进一步优化

  • 利用 bisect 库可以方便地在 tails 中找到合适的位置,并更新计数,进一步提高效率。

5. 小总结

  • 本文通过动态规划方法有效解决了计算最长递增子序列及其个数的问题。
  • 该问题展示了动态规划与计数相结合的技巧,适用于类似的序列问题。

以上就是最长递增子序列的个数问题的基本思路。

代码实现

Python

Python3代码实现

class Solution:
    def findNumberOfLIS(self, nums):
        if not nums:
            return 0

        n = len(nums)
        dp = [1] * n  # 初始化 dp 数组
        count = [1] * n  # 初始化 count 数组

        for i in range(n):  # 遍历每个元素
            for j in range(i):  # 检查前面的元素
                if nums[j] < nums[i]:  # 如果找到较小的元素
                    if dp[j] + 1 > dp[i]:  # 找到更长的递增子序列
                        dp[i] = dp[j] + 1
                        count[i] = count[j]  # 更新个数
                    elif dp[j] + 1 == dp[i]:  # 找到相同长度的递增子序列
                        count[i] += count[j]  # 累加个数

        max_length = max(dp)  # 获取最长递增子序列的长度
        return sum(count[i] for i in range(n) if dp[i] == max_length)  # 返回该长度的个数

Python 代码解释

  • 初始化:创建 dpcount 数组,分别存储最长递增子序列的长度和个数。
  • 双重循环:外层循环遍历每个元素,内层循环检查之前的元素,更新 dpcount 数组。
  • 返回结果:找到 max(dp),然后累加所有 count[i](当 dp[i] 等于最大长度)以获得结果。

C++

C++代码实现

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        if (nums.empty()) return 0;

        int n = nums.size();
        vector<int> dp(n, 1);  // dp[i] 表示以 nums[i] 结尾的最长上升子序列的长度
        vector<int> count(n, 1);  // count[i] 表示以 nums[i] 结尾的最长上升子序列的个数
        int max_length = 0;  // 记录最长上升子序列的长度
        int max_count = 0;   // 记录最长上升子序列的个数

        // 动态规划计算 dp 和 count 数组
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (nums[i] > nums[j]) {
                    if (dp[j] + 1 > dp[i]) {
                        dp[i] = dp[j] + 1;  // 更新长度
                        count[i] = count[j]; // 更新个数
                    } else if (dp[j] + 1 == dp[i]) {
                        count[i] += count[j]; // 追加个数
                    }
                }
            }
            max_length = max(max_length, dp[i]); // 更新最大长度
        }

        // 计算最长上升子序列的总个数
        for (int i = 0; i < n; ++i) {
            if (dp[i] == max_length) {
                max_count += count[i]; // 统计个数
            }
        }

        return max_count; // 返回结果
    }
};

C++ 代码解释

1. 初始化:
  • dp[i] 用于存储以 nums[i] 结尾的最长上升子序列的长度。
  • count[i] 用于存储以 nums[i] 结尾的最长上升子序列的个数。
  • max_lengthmax_count 分别用于记录最长上升子序列的长度和个数。
2. 动态规划过程:
  • 外层循环遍历每个元素 i,内层循环遍历 i 之前的所有元素 j
  • 如果 nums[i] 大于 nums[j],检查是否形成了更长的上升子序列。
  • 如果找到了更长的序列,更新 dp[i]count[i];如果找到了相同长度的序列,增加 count[i]
3. 计算结果:
  • 遍历 dp 数组,统计最长上升子序列的总个数。

总结:

  • 本文通过对最长递增子序列及其个数问题的深入分析,展示了动态规划的强大能力。
  • 结合计数的技巧,使得解决方案不仅有效且易于理解,适合解决类似的复杂问题。

您可能感兴趣的与本文相关的镜像

Python3.10

Python3.10

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

LeetCode 673 题“最长递增子序列个数”的动态规划解法及分析如下: ### 分析 - **定义状态**: - `dp[i]`:到`nums[i]`为止的最长递增子序列长度。 - `count[i]`:到`nums[i]`为止的最长递增子序列个数 [^2]。 - **初始化状态**: - `dp = [1] * n`:代表最长递增子序列的长度至少为 1。 - `count = [1] * n`:代表最长递增子序列个数至少为 1 [^2]。 - **状态转移**: - 对于每个`i`,从 0 到`i - 1`遍历`j`,如果`nums[i] > nums[j]`,则有以下两种情况: - 若`dp[i] < dp[j] + 1`,说明找到了更长的递增子序列,更新`dp[i] = dp[j] + 1`,同时`count[i] = count[j]`。 - 若`dp[i] == dp[j] + 1`,说明找到了长度相同的递增子序列,更新`count[i] += count[j]` [^2]。 ### 代码实现 以下是用 C++ 实现的代码: ```cpp class Solution { public: int findNumberOfLIS(vector<int>& nums) { int n = nums.size(); if(n < 2) return n; vector<int> cnts(n, 1); vector<int> lens(n, 1); for(int i = 1; i < n; ++i) { for(int j = 0; j < i; ++j) { if(nums[i] > nums[j]) { // 代表第一次遇到最长子序列 if(lens[i] < lens[j] + 1) { lens[i] = lens[j] + 1; cnts[i] = cnts[j]; } // 代表已经遇到过最长子序列 else if(lens[i] == lens[j] + 1) { cnts[i] += cnts[j]; } } } } // 求最长递增子序列的长度 int maxLen = *max_element(lens.begin(), lens.end()); int maxCnt = 0; // 找到有最长长度的次数,将它们加起来 for(int i = 0; i < n; ++i) { if(maxLen == lens[i]) maxCnt += cnts[i]; } return maxCnt; } }; ``` 以下是用 C 语言实现的代码: ```c int findNumberOfLIS(int* nums, int numsSize) { if(numsSize == 0 || numsSize == 1) return numsSize; int *length = (int*)malloc(sizeof(int) * numsSize); int *count = (int*)malloc(sizeof(int) * numsSize); for(int i = 0; i < numsSize; i++) { count[i] = 1; // 以nums[i] 结尾得最长序列的个数 length[i] = 1; // 以nums[i]结尾的最长序列的长度 } int max = 1; for(int i = 1; i < numsSize; i++) { for(int j = 0; j < i; j++) { if(nums[i] > nums[j]) { if(length[i] < length[j] + 1) { length[i] = length[j] + 1; count[i] = count[j]; } else if(length[i] == length[j] + 1) { count[i] += count[j]; } } } max = max > length[i] ? max : length[i]; } int res = 0; for(int i = 0; i < numsSize; i++) { if(length[i] == max) res += count[i]; } return res; } ``` ### 复杂度分析 - **时间复杂度**:$O(n^2)$,其中`n`是数组的长度,因为有两层嵌套循环 [^3]。 - **空间复杂度**:$O(n)$,主要用于存储`dp`和`count`数组 [^3]。 ### 总结 动态规划是解决最长递增子序列个数问题的一种有效方法。通过定义合适的状态和状态转移方程,可以逐步计算出到每个位置为止的最长递增子序列长度和个数。最后,找出最长递增子序列的长度,并统计拥有该长度的子序列个数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Albert_Lsk

今天又能喝柠檬茶啦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值