📜  数组的边界元素可能导致的最长递增序列(1)

📅  最后修改于: 2023-12-03 15:40:04.108000             🧑  作者: Mango

数组的边界元素可能导致的最长递增序列

介绍

在算法竞赛和编程面试中,求一个数组的最长递增子序列是一个非常常见的问题。但是,在实际编码中,我们经常会忽略数组边界元素对最长递增序列的影响,导致算法错误或者不够优秀。

本文将从以下几个方面介绍数组边界元素可能导致的最长递增序列问题:

  1. 什么是最长递增子序列?
  2. 数组边界元素的特殊性质
  3. 数组边界元素的影响
  4. 解决方案
什么是最长递增子序列?

最长递增子序列是指给定一个序列,找到其中最长的严格递增的子序列。(严格递增:$a_i < a_{i+1}$)

举个例子,对于序列 [8, 2, 3, 9, 6, 5],其最长递增子序列为[2, 3, 5, 9]。

最长递增子序列问题有很多解法,其中最经典的是动态规划(DP)。

数组边界元素的特殊性质

在数组的两端,左边和右边的元素有特殊性质:

  • 左边元素没有前驱,即没有比它更小的元素
  • 右边元素没有后继,即没有比它更大的元素

这两个性质会对求解最长递增子序列问题造成影响。

数组边界元素的影响
左边元素的影响

在常规动态规划求解最长递增子序列时,我们需要枚举在当前元素前面的所有元素,求得它们的最长递增子序列中最大的那个值,然后加上本身成为当前元素的最长递增子序列。这个过程可以用以下公式表示:

$$f[i] = max{ f[j]+1 | 1 \le j < i, a[j] < a[i]} $$

当i=1时,由于左边没有元素,无法使用上述动态转移公式。所以我们需要在程序实现中特判。

右边元素的影响

当i=n时(n为数组的长度),右边没有元素了。最长递增子序列是包括自己在内的。所以答案是$1 + \max(f[1], f[2], \cdots , f[n-1])$。

解决方案
左边元素问题的解决

我们可以将dp数组的长度加一,即f[n+1]存储一下最终答案。初始化$f[0]=0, f[n+1]=0$。这样左边元素的情况就被包含在其中了,无需特判。

右边元素问题的解决

由于最终答案只依赖于$f[1], f[2], \cdots , f[n-1]$,而右边的元素对这些值不产生任何影响,我们可以不必使用$f[n+1]$。

代码片段
// 定义 dp 数组
int f[MAXN];

// 计算答案
int solve() {
    int res = 0;
    for (int i = 1; i <= n; i++) {
        f[i] = 1;
        for (int j = 1; j < i; j++) {
            if (a[j] < a[i]) {
                f[i] = max(f[i], f[j] + 1);
            }
        }
        res = max(res, f[i]);
    }
    // 特判左边元素
    res = max(res, f[0]);
    // 特判右边元素
    res = max(res, 1 + res_f);
    return res;
}

如果您使用Python:

# 定义 dp 数组
f = [0] * (n+2)

# 计算答案
def solve():
    res = 0
    for i in range(1, n+1):
        f[i] = 1
        for j in range(1, i):
            if a[j] < a[i]:
                f[i] = max(f[i], f[j] + 1)
        res = max(res, f[i])
    # 特判左边元素
    res = max(res, f[0])
    # 特判右边元素
    res = max(res, 1 + max(f[:n]))
    return res

最后,我们需要注意到,上述解决方案并不难理解,但在实际编码中确实很容易忽略。为了避免类似问题的发生,在写完代码后,建议花一点时间检查一下边界元素是否被适当考虑到了。