news 2026/10/31 5:32:06

LeetCode 1014题解析:最佳观光组合的线性时间复杂度解法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 1014题解析:最佳观光组合的线性时间复杂度解法

1. 问题背景与核心理解

最佳观光组合(Best Sightseeing Pair)是LeetCode第1014题,属于数组类中等难度问题。题目要求我们在给定的景点评分数组中找到一对景点(i, j),使得它们的观光得分最大化。这个得分计算公式为:values[i] + values[j] + i - j,其中i < j。

这个看似简单的公式实际上蕴含了几个关键信息点:

  • values[i]和values[j]代表两个景点的固有价值
  • i - j部分实际上等于-(j - i),即两个景点之间的距离的负数
  • 因此整个公式可以理解为:两个景点的价值之和减去它们之间的距离

在实际旅行场景中,这模拟了一个现实决策:我们既希望参观价值高的景点,又不希望因为景点距离过远而消耗太多时间和精力。这种权衡在路线规划、时间管理等实际应用中非常常见。

2. 暴力解法与复杂度分析

2.1 直观暴力解法

最直接的思路是使用双重循环枚举所有可能的景点对:

def maxScoreSightseeingPair(values): max_score = 0 n = len(values) for i in range(n): for j in range(i+1, n): current = values[i] + values[j] + i - j max_score = max(max_score, current) return max_score

这种解法的时间复杂度是O(n²),当n较大时(比如n=50000),计算量会达到25亿次操作,显然无法在合理时间内完成。

2.2 暴力解法的优化空间

观察得分公式values[i] + values[j] + i - j,我们可以将其重组为: (values[i] + i) + (values[j] - j)

这意味着对于每个j,我们需要在前面找到最大的(values[i] + i)。这提示我们可以将问题分解为两个部分,从而避免双重循环。

3. 线性时间复杂度解法

3.1 关键思路突破

我们维护一个变量max_left来记录遍历过程中遇到的max(values[i] + i),然后对于每个j,当前最大得分就是max_left + (values[j] - j)。

def maxScoreSightseeingPair(values): max_score = 0 max_left = values[0] + 0 # 初始化为第一个元素的values[i]+i for j in range(1, len(values)): max_score = max(max_score, max_left + values[j] - j) max_left = max(max_left, values[j] + j) return max_score

这个算法只需要一次遍历,时间复杂度降为O(n),空间复杂度为O(1),完美解决了大规模数据的问题。

3.2 算法正确性证明

该算法的正确性基于以下观察:

  1. 对于任意j,最优的i一定在j之前
  2. 我们只需要记住之前最大的(values[i] + i),因为这是对当前j贡献最大的部分
  3. 在遍历过程中,我们不断更新这个最大值,确保每个j都能看到它之前的最佳i

4. 算法实现细节与优化

4.1 边界条件处理

在实际编码中,需要注意几个边界条件:

  • 数组长度小于2的情况(虽然题目保证n≥2)
  • 整数溢出问题(Python不需要考虑,但其他语言如Java需要注意)
  • 初始值的设置(max_left初始为values[0] + 0)

4.2 代码优化技巧

def maxScoreSightseeingPair(values): max_score = float('-inf') max_left = values[0] for j in range(1, len(values)): max_score = max(max_score, max_left + values[j] - j) max_left = max(max_left, values[j] + j) return max_score

优化点:

  • 使用float('-inf')初始化max_score以处理可能的负值
  • 简化max_left的初始计算(因为i从0开始,所以values[0]+0=values[0])

5. 相似问题与扩展思考

5.1 LeetCode相似题目

    1. Best Time to Buy and Sell Stock - 同样需要维护一个前缀最小值
    1. Maximum Subarray - Kadane算法的变种
    1. Maximum Product Subarray - 维护最大和最小值

5.2 问题变种思考

如果得分公式变为values[i] + values[j] + values[k] - (j-i) - (k-j)(三个景点的组合),该如何解决?

这种扩展问题仍然可以分解为: (values[i] + i) + (values[j] * 0) + (values[k] - k) + (i - j + j - k) = (values[i] + i) + (values[k] - k)

这表明对于更复杂的组合问题,寻找数学上的分解仍然是关键。

6. 实际应用场景

这个问题虽然以旅游观光为背景,但其核心算法可以应用于:

  1. 股票交易时机选择(类似买卖股票问题)
  2. 资源分配优化(如服务器负载均衡)
  3. 路径规划中的权衡决策(如时间与成本的平衡)
  4. 任何需要权衡"价值"与"距离/成本"的决策场景

7. 常见错误与调试技巧

7.1 常见错误类型

  1. 初始值设置错误(特别是max_left的初始化)
  2. 循环起始点错误(j应该从1开始)
  3. 更新顺序错误(应先计算max_score再更新max_left)
  4. 整数溢出(在某些语言中需要注意)

7.2 调试技巧

  1. 使用小测试案例手动验证
    • 输入:[8,1,5,2,6]
    • 预期输出:11(i=0,j=2)
  2. 打印中间变量:
    for j in range(1, len(values)): print(f"j={j}, current={values[j]}, max_left={max_left}, score={max_left + values[j] - j}") max_score = max(max_score, max_left + values[j] - j) max_left = max(max_left, values[j] + j)
  3. 边界测试:
    • 两个元素的情况
    • 所有元素相同的情况
    • 递减序列的情况

8. 算法复杂度对比

方法时间复杂度空间复杂度适用场景
暴力枚举O(n²)O(1)仅适用于极小规模n
线性扫描O(n)O(1)适用于任意规模n
分治法O(nlogn)O(logn)理论可行但不实用

9. 不同语言实现要点

9.1 Java实现

public int maxScoreSightseeingPair(int[] values) { int maxScore = Integer.MIN_VALUE; 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; }

注意事项:

  • 使用Integer.MIN_VALUE初始化
  • 注意可能的整数溢出(但题目限制在合理范围内)

9.2 C++实现

int maxScoreSightseeingPair(vector<int>& values) { int max_score = INT_MIN; int max_left = values[0]; for (int j = 1; j < values.size(); j++) { max_score = max(max_score, max_left + values[j] - j); max_left = max(max_left, values[j] + j); } return max_score; }

10. 进阶挑战与扩展思考

10.1 如果要求返回最佳组合而非最大分数

我们需要额外记录最佳i和j的位置:

def maxScoreSightseeingPair(values): max_score = float('-inf') max_left = values[0] best_i, best_j = 0, 0 for j in range(1, len(values)): current_score = max_left + values[j] - j if current_score > max_score: max_score = current_score best_j = j if values[j] + j > max_left: max_left = values[j] + j best_i = j # 注意:这个i可能不是当前最优i # 需要重新确认best_i max_left = values[0] best_i = 0 for i in range(1, best_j): if values[i] + i > max_left: max_left = values[i] + i best_i = i return (best_i, best_j, max_score)

10.2 如果景点是环形排列的

即j可以小于i(环形路线),问题会变得更加复杂。可能的解决方案:

  1. 将数组复制一份接在后面,然后限制j的范围在[i+1, i+n-1]
  2. 使用单调栈等更高级的数据结构

11. 实际面试中的考察点

面试官可能会从以下几个角度考察:

  1. 能否从暴力解法出发,找到优化思路(20%)
  2. 能否正确推导出公式分解(30%)
  3. 能否正确实现线性解法(30%)
  4. 能否处理边界条件和特殊情况(10%)
  5. 能否讨论时间/空间复杂度(10%)

12. 学习建议与刷题策略

  1. 先尝试自己思考暴力解法,不要直接看答案
  2. 对于优化问题,尝试将公式分解重组
  3. 多练习类似的前缀最值问题(如股票买卖问题)
  4. 养成写测试用例的习惯,包括边界情况
  5. 尝试用不同语言实现,加深理解

13. 数学视角的深入分析

从数学上看,这个问题可以表示为: maximize (A[i] + i) + (A[j] - j) where i < j

这属于一类特殊的优化问题,可以看作是在寻找:

  • 前半部分(A[i] + i)的最大值
  • 后半部分(A[j] - j)的最大值 但受限于i < j的顺序关系

这种结构在优化问题中很常见,类似的模式也出现在:

  • 动态规划中的状态转移
  • 贪心算法中的局部最优选择
  • 信号处理中的前后向传播

14. 性能测试与大数据量处理

对于极端大规模数据(如n=10^6):

  1. Python的实现可能因为解释器开销而变慢
  2. 可以考虑使用PyPy等JIT编译器
  3. 或者用Cython编写核心部分
  4. 多线程处理可能帮助不大,因为算法本质是顺序依赖的

测试样例生成代码:

import random n = 1000000 large_input = [random.randint(1, 1000) for _ in range(n)]

15. 可视化理解算法过程

我们可以将计算过程可视化:

Index: 0: 8 1: 1 2: 5 3: 2 4: 6 A[i]+i: 8 2 7 5 10 A[j]-j: 8 0 3 -1 2 计算过程: j=1: max_left=8, current=8+0=8 j=2: max_left=8, current=8+3=11 j=3: max_left=8, current=8+(-1)=7 j=4: max_left=8, current=8+2=10

从可视化可以看出,最大值11出现在j=2时。

16. 算法竞赛中的技巧

在编程竞赛中,这类问题通常需要:

  1. 快速识别问题模式(前缀最大值)
  2. 熟练编写模板代码
  3. 注意输入输出效率(特别是C++中使用scanf/printf)
  4. 使用更简洁的代码实现:
def maxScoreSightseeingPair(A): cur = res = 0 for j in range(len(A)): res = max(res, cur + A[j] - j) cur = max(cur, A[j] + j) return res

17. 多解法对比与选择

虽然我们已经有了O(n)的最优解,但了解其他方法也有价值:

  1. 分治法:将数组分成两半,分别求解然后合并

    • 合并时需要检查跨左右的情况
    • 时间复杂度O(nlogn),不如线性解法
  2. 动态规划:

    • 定义dp[j]为以j结尾的最大分数
    • dp[j] = max(dp[j-1] - (A[j-1] - (j-1)) + (A[j] - j), A[j-1] + A[j] + (j-1) - j)
    • 实际上与我们的线性解法类似,但更复杂

因此,线性扫描法仍然是最好选择。

18. 真实场景下的应用变种

在实际旅行路线规划中,可能还需要考虑:

  1. 景点开放时间限制
  2. 交通时间而不仅仅是距离
  3. 多个景点的组合优化
  4. 用户偏好权重

这些因素会使问题变得更加复杂,可能需要:

  • 更复杂的评分函数
  • 图算法(如最短路径)
  • 启发式算法(如遗传算法)
  • 机器学习模型预测用户偏好

19. 历史与相关研究

这类问题在计算机科学中属于:

  • 序列分析问题
  • 在线算法(online algorithm)的一种
  • 与滑动窗口问题相关
  • 在数据库查询优化中有类似应用

最早可以追溯到1970年代的序列分析研究,后来在1990年代被广泛应用于数据流处理。

20. 个人刷题心得

在解决这类问题时,我总结了几点经验:

  1. 先写暴力解法确保理解题意
  2. 仔细观察公式,尝试重组或分解
  3. 画图或举例帮助理解
  4. 注意更新变量的顺序
  5. 边界条件要单独测试
  6. 多思考问题的实际意义,而不仅是抽象算法

例如在这个问题中,将公式重组为(A[i] + i) + (A[j] - j)是关键突破点。这种技巧在很多数组问题中都适用,如买卖股票的最佳时机问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 5:31:50

从TTS到VoiceStudio:AI语音合成与音色克隆实战全攻略

1. 内容整体设计与思路拆解1.1 核心需求解析&#xff1a;VoiceStudio是什么第一次看到"VoiceStudio"这个名字&#xff0c;你可能和我一样&#xff0c;会下意识把它当成某某录音软件的换皮版。但实际深入用过之后&#xff0c;我越来越倾向于把它定义为"AI语音合成…

作者头像 李华
网站建设 2026/10/31 5:30:23

Python+Django构建智能小说推荐系统实践

1. 项目背景与核心价值小说推荐与阅读系统是当前数字阅读领域的热门应用方向。随着网络文学市场的爆发式增长&#xff0c;用户面临的选择越来越多&#xff0c;如何从海量作品中快速找到符合个人口味的小说成为痛点。传统的人工推荐方式效率低下且难以个性化&#xff0c;这正是我…

作者头像 李华
网站建设 2026/10/31 5:29:32

论文降重工具评测与实战指南

1. 项目概述&#xff1a;论文降重工具的实战需求2025届毕业生正面临前所未有的学术写作挑战。随着高校对学术原创性要求的不断提高&#xff0c;论文查重率成为横亘在学位证书前的最后一道关卡。我最近辅导了37名应届生的毕业论文&#xff0c;发现重复率问题普遍集中在三个层面&…

作者头像 李华
网站建设 2026/10/31 5:29:15

英伟达Vera Rubin平台:AI算力革命与架构创新

1. 英伟达Vera Rubin平台的算力革命当我在实验室第一次接触到Vera Rubin平台的测试数据时&#xff0c;那组3.5倍的性能提升数字确实让人震撼。这不是简单的硬件迭代&#xff0c;而是AI服务器架构的一次范式转移。作为专为代理式AI设计的基础设施&#xff0c;Vera Rubin将五个机…

作者头像 李华
网站建设 2026/10/31 5:27:02

机试通关:查找、贪心、链表三大核心考点与边界处理实战

计算机机试里的查找、贪心和链表这三块内容&#xff0c;看起来是三个独立的知识点&#xff0c;实际上在真实的笔试和机试场景里&#xff0c;它们经常纠缠在一起出现。我当年第一次系统刷这类题目的时候&#xff0c;最大的感受就是&#xff1a;单看每一个知识点都不难&#xff0…

作者头像 李华
网站建设 2026/10/31 5:27:01

Claude MCP能力工程:从技能列表到可编排软件服务

1. “awesome-claude-skills”不是资源列表&#xff0c;而是一套可落地的Agent能力工程方法论你点开 GitHub 上那个标着 ⭐ 2.4k 的仓库awesome-claude-skills&#xff0c;第一眼看到的是一堆链接&#xff1a;MCP、Composio、LangChain Tools、VS Code 插件、本地模型调用指南……

作者头像 李华