问题描述
假设我们有一组观光景点,每个景点都有一个评分,保存在数组 values
中。其中 values[i]
表示第 i 个景点的评分。景点之间的距离则通过下标差值 ∣j−i| 来衡量。
景点组合的得分由以下公式计算:
values[i] + values[j] + i − j
其中,i 和 j 满足 i < j。显然,这里的组合得分受到评分和距离的共同影响。目标是找到一种组合,使得得分最大化。
解题思路
公式重构
通过观察公式 values[i] + values[j] + i − j,我们可以将其分解为:
(values[i] + i ) + (values[j] − j)
这个形式揭示了一个关键点:最高得分的计算可以看作是两部分的加和:
- 左侧的最大值 (values[i] + i ) ,只与第 i 个景点有关。
- 当前景点的值减去其下标 (values[j] − j) 。
在整个数组遍历过程中,左侧的最大值 (values[i] + i ) 是动态变化的。因此,我们只需在遍历数组时动态维护左侧的最大值 (values[i] + i ) ,同时计算每个位置的得分。这样,问题的求解可以优化为 O(n) 时间复杂度。
算法步骤
通过上述分析,我们可以设计一个高效算法:
-
初始化变量
maxScore
为 0,用于记录当前的最高得分。 -
初始化变量
maxLeft
为 values[0] + 0,表示初始的左侧最大值。 -
遍历数组的每一个位置 j(从 1 开始):
- 计算当前组合的得分:
maxLeft + values[j] - j
。 - 如果得分更高,更新
maxScore
。 - 更新左侧最大值
maxLeft
为 max(maxScore, maxLeft + values[j] - j) ,为后续计算提供支持。
- 计算当前组合的得分:
-
遍历完成后,返回
maxScore
。
代码实现(Java)
以下是基于上述思路的 Java 实现代码:
ini
public class SightseeingPair {
public int maxScoreSightseeingPair(int[] values) {
int maxScore = 0; // 初始化最高得分
int maxLeft = values[0]; // 初始的左侧最大值
for (int j = 1; j < values.length; j++) {
// 计算当前组合得分
maxScore = Math.max(maxScore, maxLeft + values[j] - j);
// 更新左侧最大值
maxLeft = Math.max(maxLeft, values[j] + j);
}
return maxScore;
}
}
示例分析
输入:
ini
int[] values = {8, 1, 5, 2, 6};
SightseeingPair sp = new SightseeingPair();
System.out.println(sp.maxScoreSightseeingPair(values)); // 输出: 11
算法复杂度分析
- 时间复杂度: O(n)。数组仅遍历一次,每次操作均为常数时间。
- 空间复杂度: O(1)。只使用了少量变量,未使用额外的存储空间。
相比于暴力解法 O(n^2) 的复杂度,这种优化算法显著提升了效率,尤其是在数组较大的情况下。
思考与总结
这个问题其实暴力法很简单就能做出,写文章的目的是为了记录、引导编程思想,用更有效率的算法解决问题。这也是动态规划、回溯等等算法的优势所在,学习常见的算法也是很有必要的。