题目解析
小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相关。
为了最大化得分,我们需要:
- 对于每个
j,找到之前所有i中最大的values[i] + i。 - 然后,将这个最大值与当前
j的values[j] - j相加,得到当前的组合得分。 - 记录所有组合得分中的最大值。
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. 具体步骤
以下是详细的步骤:
-
初始化:
- 检查数组是否为空或长度是否小于2,若是,则返回
0。 - 初始化
max_so_far为第一个景点的values[0] + 0。 - 初始化
max_score为负无穷,以便后续更新。
- 检查数组是否为空或长度是否小于2,若是,则返回
-
遍历数组:
- 从第二个景点开始遍历(索引
j从1到values.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。
- 从第二个景点开始遍历(索引
-
返回结果:
- 遍历结束后,
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);
}
}
代码解释
-
输入检查:
- 首先检查输入数组是否为
null或者长度是否小于2,如果是,则直接返回0,因为无法形成一对景点组合。
- 首先检查输入数组是否为
-
初始化变量:
max_so_far用于记录当前遍历到j之前的最大values[i] + i,初始值为第一个元素的values[0] + 0。max_score用于记录当前的最大组合得分,初始值设为Integer.MIN_VALUE,以确保任何组合得分都会更新它。
-
遍历数组:
- 从第二个元素开始遍历数组,索引
j从1到values.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。
- 从第二个元素开始遍历数组,索引
-
返回结果:
- 遍历结束后,
max_score就是所有可能组合中最大的得分,返回它作为结果。
- 遍历结束后,
示例分析
让我们通过详细的示例来理解算法的运行过程。
示例 1
输入:values = [8, 3, 5, 5, 6]
期望输出:11
步骤:
-
初始化:
max_so_far = 8 + 0 = 8max_score = -∞
-
遍历过程:
-
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)
-
-
最终结果:
max_score = 11
解释:最佳组合为 (i=0, j=2),即 values[0] + values[2] + 0 - 2 = 8 + 5 + 0 - 2 = 11。
复杂度分析
- 时间复杂度:O(n),其中 n 是数组
values的长度。我们只需要一次遍历即可得到结果。 - 空间复杂度:O(1),只使用了常数级别的额外空间。
边界情况考虑
在实际编程中,除了正常情况外,还需要考虑一些边界情况,以确保程序的健壮性:
-
数组为空或长度小于2:
- 根据题意,如果数组为空或只有一个元素,则无法形成一对景点组合,此时应返回
0。
- 根据题意,如果数组为空或只有一个元素,则无法形成一对景点组合,此时应返回
-
所有景点评分为负数:
- 即使所有评分为负,依然需要找到得分最大的组合。由于得分公式中包含
+i - j,可能仍然存在正得分的组合。
- 即使所有评分为负,依然需要找到得分最大的组合。由于得分公式中包含
-
多个组合得分相同:
- 如果存在多个组合得分相同且都是最大的,算法只需要返回其中一个即可,不需要列举所有组合。
-
最大或最小整数值:
- 确保在计算过程中不会发生整数溢出。例如,如果
values[i]和values[j]都非常大,values[i] + i可能超过整数范围。 - 在Java中,
int类型的范围是-2^31到2^31 - 1。若有可能超过这个范围,可以考虑使用long类型进行计算。
- 确保在计算过程中不会发生整数溢出。例如,如果
扩展思考
1. 变体问题
-
找到所有最大得分的组合:
- 如果需要列举所有得分为最大值的组合,可以在遍历过程中记录所有满足条件的
(i, j)对。
- 如果需要列举所有得分为最大值的组合,可以在遍历过程中记录所有满足条件的
-
优化空间使用:
- 如果需要处理多个数组或需要进行多次查询,可以考虑预处理或使用动态规划等方法来进一步优化。
2. 其他解决方案
虽然当前的解决方案已经达到了线性时间复杂度,但在某些情况下,可能会有其他解决方案。例如:
-
双指针法:
- 对于特定类型的问题,双指针法可以有效减少时间复杂度。但在此问题中,双指针法并不直接适用,因为得分的计算涉及到最大值的维护。
-
分治法:
- 对于一些复杂问题,分治法可以将问题分解成更小的子问题,然后合并结果。然而,对于本问题,分治法并不能直接简化问题的复杂度。
总结
通过对题目进行深入分析,我们将组合得分公式进行了重组,将问题转化为在遍历过程中持续跟踪最大 values[i] + i,从而高效地找到最大组合得分。这种方法不仅简单直观,而且具备线性时间复杂度,适用于大规模数据的处理。
关键在于理解得分公式的结构,通过合理的变量维护和更新,实现了高效的求解过程。在编程实现中,细心处理边界情况和潜在的溢出问题,确保程序的正确性和健壮性。
通过这些步骤和分析,相信读者能够深入理解这道题目的解法,并能够将类似的思路应用到其他相关问题中。