Bootstrap

动态规划中的连续与不连续:子数组与子序列的对比分析

        在动态规划(Dynamic Programming)中,子数组子序列是两个常见的概念,它们的定义和性质直接影响算法的设计。以下是它们的联系、区别与关系的详细说明:


目录

1. 定义与核心区别

子数组 (Subarray)

子序列 (Subsequence)

2. 动态规划中的联系

共同点

典型问题

3. 动态规划中的区别

子数组的动态规划

子序列的动态规划

4. 关键对比总结

5. 关系总结


1. 定义与核心区别

  • 子数组 (Subarray)

    • 子数组是原数组中连续的一段元素。

    • 例如:数组 [1, 2, 3, 4] 的子数组包括 [1, 2][2, 3, 4] 等。

    • 核心要求:必须连续。

  • 子序列 (Subsequence)

    • 子序列是原数组中元素的有序子集,元素之间可以不连续,但必须保持原顺序。

    • 例如:数组 [1, 2, 3, 4] 的子序列包括 [1, 3, 4][2, 4] 等。

    • 核心要求:保持顺序,但允许不连续。


2. 动态规划中的联系

  • 共同点

    • 两者都需要通过递推关系解决问题。

    • 通常需要定义 dp[i],表示以第 i 个元素为结尾的某种最优值。

    • 依赖子问题的解(前驱状态)来推导当前状态。

  • 典型问题

    • 子数组:最大子数组和(如 Kadane 算法)。

    • 子序列:最长递增子序列(LIS)、最长公共子序列(LCS)。


3. 动态规划中的区别

子数组的动态规划

  • 状态定义
    dp[i] 通常表示以 nums[i] 结尾的连续子数组的最优解(如最大和)。

  • 状态转移
    由于子数组必须连续,状态转移仅依赖前一个状态:

    dp[i] = max(nums[i], dp[i-1] + nums[i]); // 最大子数组和
  • 时间复杂度
    通常为 O(n),如 Kadane 算法。

  • 示例代码(最大子数组和)

    int maxSubArray(vector<int>& nums) {
        int dp = nums[0], res = dp;
        for (int i = 1; i < nums.size(); i++) {
            dp = max(nums[i], dp + nums[i]);
            res = max(res, dp);
        }
        return res;
    }

子序列的动态规划

  • 状态定义
    dp[i] 通常表示以 nums[i] 结尾的子序列的最优解(如最长长度)。

  • 状态转移
    由于子序列不要求连续,需要遍历前面所有可能的状态:

    for (int j = 0; j < i; j++) {
        if (nums[i] > nums[j]) {
            dp[i] = max(dp[i], dp[j] + 1); // 最长递增子序列
        }
    }
  • 时间复杂度
    通常为 O(n²),可优化到 O(n log n)(如二分法优化 LIS)。

  • 示例代码(最长递增子序列)

    int lengthOfLIS(vector<int>& nums) {
        vector<int> dp(nums.size(), 1);
        int res = 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;
    }

4. 关键对比总结

特性子数组子序列
连续性必须连续允许不连续
状态转移依赖前一个状态依赖前面所有可能状态
时间复杂度通常 O(n)通常 O(n²) 或优化到 O(n log n)
典型问题最大子数组和、最小子数组和最长递增子序列、最长公共子序列

5. 关系总结

用Veen图来表示其关系:

  • 子数组是子序列的特例:所有子数组都是子序列,但子序列不一定是子数组。

  • 动态规划设计差异:子数组的连续性限制了状态转移的范围,而子序列的灵活性要求更复杂的状态转移逻辑。

理解两者的区别与联系,有助于针对不同问题设计高效的动态规划算法。

;