最长递增子数组问题 | 豆包MarsCode AI 刷题

88 阅读5分钟

最长递增子数组问题

1. 问题分析

1.1 题目描述

给定两个整数数组 aabb,每个数组的长度为 nn。我们可以通过从 aabb 中轮流选择一个元素加入到新数组 cc 的末尾,从而构造出新的数组 cc。每次可以选择 aabb 的第一个元素,并将它加入到 cc 数组的末尾,然后从原数组中删除该元素,直到两个数组都为空为止。

题目的目标是:在所有可能生成的 cc 数组中,找到其中一个“最长的递增子数组”,该递增子数组需要满足相邻两个元素的差的绝对值为 1。我们的任务是输出这个最长的递增子数组的长度。

1.2 输入输出样例

  • 示例 1

    • 输入: a=[5,2,4]a = [5, 2, 4]b=[7,1,3]b = [7, 1, 3]
    • 输出:4
    • 解释:一种可能的 cc 数组为 [7,5,1,2,3,4][7, 5, 1, 2, 3, 4],其中最长递增子数组为 [1,2,3,4][1, 2, 3, 4],长度为4。
  • 示例 2

    • 输入:a=[3,6,8,1]a = [3, 6, 8, 1]b=[2,7,9,5]b = [2, 7, 9, 5]
    • 输出:4
  • 示例 3

    • 输入:a=[10,15,12,13,11]a = [10, 15, 12, 13, 11]b=[9,14,16,18,17]b = [9, 14, 16, 18, 17]
    • 输出:3

2. 解题思路

本题的难点在于如何有效地从两个数组中选择元素加入 cc,以构造出一个满足条件的最长递增子数组。这需要在选择元素的过程中动态追踪递增子数组的长度。

观察题目可得,这个问题包含以下关键要素:

  1. 递增子数组:子数组需要是递增的,相邻元素的差必须为1。
  2. 双数组交替选择:我们需要交替选择 aabb 中的元素。
  3. 贪心策略:尽量以形成连续序列为优先,通过追踪当前形成的序列长度更新最优解。

接下来我们将逐步分析解决思路。

2.1 思路 1:穷举所有可能的 cc 数组

最朴素的思路是枚举所有可能的 cc 数组,然后在每一个 cc 数组中找到最长的符合条件的递增子数组。这样可以保证找到最长的递增子数组,但计算复杂度极高。假设 n=100n = 100,共有 2n2^n 种组合,显然不具备现实意义。

2.2 思路 2:单数组内部的最长递增子序列

我们可以尝试分别在 aabb 内部寻找满足条件的最长递增子数组,记录它们的最大长度。这是因为最终构造的 cc 数组的最长递增子数组一定包含了 aabb 中的一部分连续子数组。

具体步骤为:

  1. aabb 数组分别进行遍历,每次遇到符合“相邻元素差为1”的元素,累加当前递增子数组的长度;
  2. 每当不满足条件时,更新全局的最长递增子数组长度,并重置开始新的子数组;
  3. 在完成遍历后,将两个数组中的最长递增子数组长度比较,更新全局最优解。

但是该方法未能考虑到 跨数组递增序列 的可能性。例如在 aa 中形成部分递增序列,然后在 bb 中继续形成的情况。因此,我们还需要考虑双数组交替组合的情况。

2.3 思路 3:双数组交替组合(双指针)

在思路2的基础上,我们可以进一步利用 双指针法 来优化计算过程,以构造出两个数组交替组合的最长递增子序列。

具体做法为:

  1. 单数组递增子序列计算:首先在 aabb 中分别计算它们内部的最长递增子数组长度;
  2. 交替组合:以 aa 中的任意元素 a[i]a[i]bb 中的任意元素 b[j]b[j] 为起点,逐步构造从 a[i]a[i] 开始的序列,随后在 bb 中寻找符合条件的下一个元素,形成跨数组的递增序列。
  3. 动态更新:每次递增时记录当前递增子序列的长度,并更新全局最大长度。

这样的组合方法能在较优的时间内找到最优解。

3. 代码实现

以下为该思路的代码实现:

#include <bits/stdc++.h>

using namespace std;

// 函数:计算最大递增子数组的长度
int solution(vector<int> a, vector<int> b) {
    int ans = 0;  // 初始化答案,即最长递增子数组的长度
    int asz = a.size(), bsz = b.size();  // 分别记录 a 和 b 的长度

    // 步骤 1:在 a 和 b 中单独查找最长递增子数组的长度
    for (int i = 0, res = 0, lst = -19999; i < asz; ++i) {
        // 如果 a[i] 是递增的,更新当前递增子数组的长度
        if (a[i] == lst + 1) lst += 1, res += 1;
        else lst = a[i], res = 1;
        ans = max(ans, res);  // 更新全局最长子数组长度
    }
    for (int i = 0, res = 0, lst = -19999; i < bsz; ++i) {
        if (b[i] == lst + 1) lst += 1, res += 1;
        else lst = b[i], res = 1;
        ans = max(ans, res);
    }

    // 步骤 2:以 a[i] 和 b[j] 为起点,交替查找最长递增子数组
    for (int i = 0; i < asz; ++i) {
        for (int j = 0; j < bsz; ++j) {
            int as = i, bs = j, lst, res = 1;
            // 从 a[i] 或 b[j] 开始形成递增序列
            if (a[as] < b[bs]) {
                lst = a[as], as += 1;
            } else lst = b[bs], bs += 1;
            
            // 交替选择 a 和 b 的元素,确保相邻差值为 1
            while (as < asz || bs < bsz) {
                if (as < asz && a[as] == lst + 1) {
                    res += 1, lst += 1, as += 1;
                    ans = max(ans, res);
                } else if (bs < bsz && b[bs] == lst + 1) {
                    res += 1, lst += 1, bs += 1;
                    ans = max(ans, res);
                } else break;  // 无法继续构建递增序列时跳出循环
            }
        }
    }

    return ans;
}

int main() {
    cout << (solution({5, 2, 4}, {7, 1, 3}) == 4) << endl;
    cout << (solution({3, 6, 8, 1}, {2, 7, 9, 5}) == 4) << endl;
    cout << (solution({10, 15, 12, 13, 11}, {9, 14, 16, 18, 17}) == 3) << endl;
    return 0;
}

4. 代码分析

时间复杂度分析

  1. 单数组查找:分别对 aabb 进行一次线性遍历,时间复杂度为 O(n)O(n)
  2. 双数组交替查找:通过双重循环枚举起点 (i,j)(i, j) 并在每次查找中进行线性遍历,总体复杂度约为 O(n3)O(n^3)

综合来看,算法的整体时间复杂度为 O(n3)O(n^3),在 nn 较小时可以满足性能要求。

空间复杂度分析

本算法只使用了常量空间存储临时变量,空间复杂度为 O(1)O(1)

5. 测试与结果分析

在多组测试样例下,代码能够正确输出预期结果,验证了算法的有效性。