题目解析: 观光景点组合得分问题 | 豆包MarsCode AI刷题

45 阅读8分钟

题目解析

小R正在研究一组观光景点,每个景点都有一个评分,存储在数组 values 中,其中 values[i] 表示第 i 个景点的评分。两个景点之间的距离由它们的下标差 j - i 表示(假设 i < j)。小R希望找到一对景点 (i, j),使得组合得分最大。组合得分的计算公式为:

得分=values[i]+values[j]+i−j\text{得分} = \text{values}[i] + \text{values}[j] + i - j

简化后可以表示为:

得分=(values[i]+i)+(values[j]−j)\text{得分} = (\text{values}[i] + i) + (\text{values}[j] - j)

因此,我们需要找到一对 (i, j)i < j),使得 (\text{values}[i] + i) + (\text{values}[j] - j) 最大。

解题思路

为了高效地解决这个问题,我们需要优化遍历过程,避免使用双重循环(这会导致时间复杂度为 O(n²),在数据量较大时效率低下)。以下是详细的解题步骤和思路:

1. 重组得分公式

首先,我们将组合得分公式进行重组:

得分=values[i]+values[j]+i−j=(values[i]+i)+(values[j]−j)\text{得分} = \text{values}[i] + \text{values}[j] + i - j = (\text{values}[i] + i) + (\text{values}[j] - j)

这表明,得分可以分为两部分:

  • 第一部分:values[i] + i,仅与第一个景点 i 相关。
  • 第二部分:values[j] - j,仅与第二个景点 j 相关。

为了最大化得分,我们需要:

  1. 对于每个 j,找到之前所有 i 中最大的 values[i] + i
  2. 然后,将这个最大值与当前 jvalues[j] - j 相加,得到当前的组合得分。
  3. 记录所有组合得分中的最大值。

2. 维护一个变量记录当前最大 values[i] + i

为了实现上述思路,我们在遍历数组时,维护一个变量 max_so_far,用于记录到当前 j 之前(即 i < j)的最大 values[i] + i。这样,当我们遍历到位置 j 时,可以立即计算出以 j 为第二个景点的最佳组合得分。

3. 更新最大得分

在遍历过程中,我们不断更新 max_score,以确保它始终保存着当前最大的组合得分。同时,更新 max_so_far,以便在后续的计算中使用。

4. 处理特殊情况

如果数组为空或者长度小于2,则无法形成一对景点组合,此时返回得分 0

5. 具体步骤

以下是详细的步骤:

  1. 初始化

    • 检查数组是否为空或长度是否小于2,若是,则返回 0
    • 初始化 max_so_far 为第一个景点的 values[0] + 0
    • 初始化 max_score 为负无穷,以便后续更新。
  2. 遍历数组

    • 从第二个景点开始遍历(索引 j1values.length - 1)。
    • 对于每个 j,计算当前可能的组合得分 current_score = max_so_far + values[j] - j
    • 如果 current_score 大于当前的 max_score,则更新 max_score
    • 计算当前位置 j 可能作为新的 i 的值 potential_new_max = values[j] + j
    • 如果 potential_new_max 大于 max_so_far,则更新 max_so_far
  3. 返回结果

    • 遍历结束后,max_score 即为最大的组合得分。

代码实现

基于上述思路,以下是Java代码的详细实现:

public class Main {
    public static int solution(int[] values) {
        // 检查输入是否有效
        if (values == null || values.length < 2) {
            return 0;
        }

        // 初始化最大值变量
        int max_so_far = values[0] + 0; // 初始时,i = 0
        int max_score = Integer.MIN_VALUE; // 初始最大得分为最小整数

        // 遍历数组,从第二个元素开始
        for (int j = 1; j < values.length; j++) {
            // 计算以j为第二个景点的组合得分
            int current_score = max_so_far + values[j] - j;
            // 更新最大得分
            if (current_score > max_score) {
                max_score = current_score;
            }

            // 计算当前位置j作为i时的值
            int potential_new_max = values[j] + j;
            // 更新max_so_far
            if (potential_new_max > max_so_far) {
                max_so_far = potential_new_max;
            }
        }

        return max_score;
    }

    public static void main(String[] args) {
  
        System.out.println(solution(new int[]{8, 3, 5, 5, 6}) == 11 ? 1 : 0);

        System.out.println(solution(new int[]{10, 4, 8, 7}) == 16 ? 1 : 0);

        System.out.println(solution(new int[]{1, 2, 3, 4, 5}) == 8 ? 1 : 0);
    }
}

代码解释

  1. 输入检查

    • 首先检查输入数组是否为 null 或者长度是否小于2,如果是,则直接返回 0,因为无法形成一对景点组合。
  2. 初始化变量

    • max_so_far 用于记录当前遍历到 j 之前的最大 values[i] + i,初始值为第一个元素的 values[0] + 0
    • max_score 用于记录当前的最大组合得分,初始值设为 Integer.MIN_VALUE,以确保任何组合得分都会更新它。
  3. 遍历数组

    • 从第二个元素开始遍历数组,索引 j1values.length - 1
    • 对于每个 j,计算 current_score = max_so_far + values[j] - j,即将之前的最大 values[i] + i 与当前的 values[j] - j 相加,得到当前的组合得分。
    • 如果 current_score 大于 max_score,则更新 max_score
    • 计算当前 j 作为 i 时的值 potential_new_max = values[j] + j,并更新 max_so_far
  4. 返回结果

    • 遍历结束后,max_score 就是所有可能组合中最大的得分,返回它作为结果。

示例分析

让我们通过详细的示例来理解算法的运行过程。

示例 1

输入values = [8, 3, 5, 5, 6]

期望输出11

步骤

  1. 初始化

    • max_so_far = 8 + 0 = 8
    • max_score = -∞
  2. 遍历过程

    • j = 1

      • current_score = 8 + 3 - 1 = 10
      • 更新 max_score = 10
      • potential_new_max = 3 + 1 = 4(不更新 max_so_far,因为 4 < 8)
    • j = 2

      • current_score = 8 + 5 - 2 = 11
      • 更新 max_score = 11
      • potential_new_max = 5 + 2 = 7(不更新 max_so_far,因为 7 < 8)
    • j = 3

      • current_score = 8 + 5 - 3 = 10(不更新 max_score,保持 11)
      • potential_new_max = 5 + 3 = 8(不更新 max_so_far,因为 8 == 8)
    • j = 4

      • current_score = 8 + 6 - 4 = 10(不更新 max_score,保持 11)
      • potential_new_max = 6 + 4 = 10(更新 max_so_far = 10
  3. 最终结果max_score = 11

解释:最佳组合为 (i=0, j=2),即 values[0] + values[2] + 0 - 2 = 8 + 5 + 0 - 2 = 11

复杂度分析

  • 时间复杂度:O(n),其中 n 是数组 values 的长度。我们只需要一次遍历即可得到结果。
  • 空间复杂度:O(1),只使用了常数级别的额外空间。

边界情况考虑

在实际编程中,除了正常情况外,还需要考虑一些边界情况,以确保程序的健壮性:

  1. 数组为空或长度小于2

    • 根据题意,如果数组为空或只有一个元素,则无法形成一对景点组合,此时应返回 0
  2. 所有景点评分为负数

    • 即使所有评分为负,依然需要找到得分最大的组合。由于得分公式中包含 +i - j,可能仍然存在正得分的组合。
  3. 多个组合得分相同

    • 如果存在多个组合得分相同且都是最大的,算法只需要返回其中一个即可,不需要列举所有组合。
  4. 最大或最小整数值

    • 确保在计算过程中不会发生整数溢出。例如,如果 values[i]values[j] 都非常大,values[i] + i 可能超过整数范围。
    • 在Java中,int 类型的范围是 -2^312^31 - 1。若有可能超过这个范围,可以考虑使用 long 类型进行计算。

扩展思考

1. 变体问题

  • 找到所有最大得分的组合

    • 如果需要列举所有得分为最大值的组合,可以在遍历过程中记录所有满足条件的 (i, j) 对。
  • 优化空间使用

    • 如果需要处理多个数组或需要进行多次查询,可以考虑预处理或使用动态规划等方法来进一步优化。

2. 其他解决方案

虽然当前的解决方案已经达到了线性时间复杂度,但在某些情况下,可能会有其他解决方案。例如:

  • 双指针法

    • 对于特定类型的问题,双指针法可以有效减少时间复杂度。但在此问题中,双指针法并不直接适用,因为得分的计算涉及到最大值的维护。
  • 分治法

    • 对于一些复杂问题,分治法可以将问题分解成更小的子问题,然后合并结果。然而,对于本问题,分治法并不能直接简化问题的复杂度。

总结

通过对题目进行深入分析,我们将组合得分公式进行了重组,将问题转化为在遍历过程中持续跟踪最大 values[i] + i,从而高效地找到最大组合得分。这种方法不仅简单直观,而且具备线性时间复杂度,适用于大规模数据的处理。

关键在于理解得分公式的结构,通过合理的变量维护和更新,实现了高效的求解过程。在编程实现中,细心处理边界情况和潜在的溢出问题,确保程序的正确性和健壮性。

通过这些步骤和分析,相信读者能够深入理解这道题目的解法,并能够将类似的思路应用到其他相关问题中。