Leetcode 90 最佳观光组合

1 题目

1014. 最佳观光组合

给你一个正整数数组 values,其中 values[i] 表示第 i 个观光景点的评分,并且两个景点 ij 之间的 距离j - i

一对景点(i < j)组成的观光组合的得分为 values[i] + values[j] + i - j ,也就是景点的评分之和减去它们两者之间的距离。

返回一对观光景点能取得的最高分。

示例 1:

复制代码
输入:values = [8,1,5,2,6]
输出:11
解释:i = 0, j = 2, values[i] + values[j] + i - j = 8 + 5 + 0 - 2 = 11

示例 2:

复制代码
输入:values = [1,2]
输出:2

提示:

  • 2 <= values.length <= 5 * 104
  • 1 <= values[i] <= 1000

2 代码实现

思考

其实这个做过的题目,才过了10几天,我又不知道怎么做。

很正常,这也就是复习的必要性了。

我看到这个题目的想法就是 values[i] + values[j] + i - j 这个可以怎么变形,为什么会有这个想法呢?因为见过类似的,不过可以变形的大概是 values[i] + values[j]- i - j

这样才能变成统一的格式,values[ i ] - i , 但是这个题目似乎不太行。

第一次提交的代码

cpp 复制代码
class Solution {
public:
    int maxScoreSightseeingPair(vector<int>& values) {
        int best = values[0]; 
        int ans = 0;

        for (int j = 1 ; j < values.size() ; j++){
            ans = max (ans, best + values[j] - j  );
            best = max(best , values[j] + j );
        }

        return ans;
    }
};

现在的题单是枚举右,维护左的常用枚举技巧。

我个人觉得最经典的就是121. 买卖股票的最佳时机 - 力扣(LeetCode)

这道题目的解法比较巧,其实也许也是比较常规,我只是之前没见过而已。

用循环内的执行顺序,保证了( i < j )这个条件。

维护一个当前最大的 values[i] + i ,后续不断更新,ans就是我们所求的,也在更新,为了更新出最大的值。

为什么这么明确的思路,常见的做法我却没有头绪呢?我卡在什么地方,应该怎么分析这一类问题?

附上买卖股票的代码

cpp 复制代码
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if (n == 0){
            return 0 ;
        }
        
        int maxPro = 0 ;
        int minP = prices[0];

        for (int i = 0 ; i < n ; i ++){
            int curPro = prices[i] - minP;
            maxPro = max (maxPro ,curPro);
            minP = min( minP, prices[i]);
        }

        return maxPro;
    }
};

3 反思

ai老师上场指导。

你现在的核心困惑是:

面对这类需要枚举 + 维护最优左值的题目(比如最佳观光组合、买卖股票),明明解法思路很清晰,但自己上手时却毫无头绪,想知道自己卡在哪、以及该如何分析这类问题。

一、先拆解卡住的核心原因

你之所以面对这类题没头绪,并不是你不会写代码,而是解题的 "思维转化" 环节出了问题,具体体现在这 3 点:

  1. 没有先做 "公式变形" 的意识 :拿到 values[i] + values[j] + i - j 这个式子,第一反应不是 "能不能拆成和 i、j 分别相关的两部分",而是直接想暴力枚举,或者回忆模糊的解法,导致思路卡壳;
  2. 对 "枚举右、维护左" 的核心逻辑理解不深 :你知道这个技巧,但没理解它的本质 ------把 O (n²) 的枚举,转化为 O (n) 的 "一边枚举、一边记录最优解",所以遇到新场景(哪怕只是变形)就无法迁移;
  3. 忽略 "i<j" 的约束对执行顺序的要求:你知道要满足 i<j,但没主动想 "枚举 j 时,只需要用到 j 之前的 i 的最优值",所以不会想到 "遍历 j 的过程中,同步更新 i 的最优值"。

二、这类问题的通用分析方法(以本题为例)

我们用 "标准化步骤" 拆解这类题,帮你建立固定的分析思路:

步骤 1:先做 "公式变形",拆分独立变量

核心目标:把 "同时依赖 i 和 j" 的式子,拆成 "只和 i 相关" + "只和 j 相关" 的两部分。

本题原式:score = values[i] + values[j] + i - j (i<j)变形:把 i 相关的放一起,j 相关的放一起:score = (values[i] + i) + (values[j] - j)

这一步是关键!

变形后问题瞬间清晰:

  • 对于固定的 j,要让 score 最大,只需要找到 j 之前(i<j)的(values[i]+i)的最大值;
  • 遍历所有 j,计算 "最大的 (values [i]+i) + (values [j]-j)",最终的最大值就是答案。

步骤 2:确定 "枚举方向" 和 "维护的变量"

  • 枚举方向:选 j(右)作为遍历对象,因为 j 是 "后出现的",遍历 j 时能覆盖所有 i<j 的情况;
  • 维护的变量:遍历 j 的过程中,同步记录 "j 之前的最大 (values [i]+i)"(记为 best),避免重复计算。

步骤 3:确定执行顺序,满足 i<j 的约束

遍历 j 从 1 开始(因为 i<j,j 最小是 1,i 只能是 0):

  1. 先计算当前 j 对应的 score(用之前记录的 best + (values [j]-j)),更新答案;
  2. 再更新 best(把当前 j 的 (values [j]+j) 和之前的 best 比较,取更大的);→ 这个顺序保证了:计算 j 的 score 时,best 只包含 j 之前的 i 的信息,严格满足 i<j。

三、结合你的代码,拆解核心逻辑

你的代码是正确的,我们逐行解释,帮你把 "模糊的思路" 变成 "清晰的逻辑":

cpp 复制代码
class Solution {
public:
    int maxScoreSightseeingPair(vector<int>& values) {
        // 初始化:j=1时,i只能是0,所以初始best是values[0]+0
        int best = values[0] + 0; 
        int ans = 0; // 存储最终的最高分

        // 枚举右边界j,从1开始(i<j)
        for (int j = 1 ; j < values.size() ; j++){
            // 步骤1:计算当前j的得分(用之前的best + (values[j]-j)),更新答案
            ans = max (ans, best + values[j] - j  );
            // 步骤2:更新best(把当前j的(values[j]+j)加入候选,供后续j'使用)
            best = max(best , values[j] + j );
        }

        return ans;
    }
};

测试用例验证(values = [8,1,5,2,6]):

  • j=1:ans = max(0, 8 + 1-1)=8;best = max(8,1+1)=8
  • j=2:ans = max(8,8 +5-2)=11;best = max(8,5+2)=8
  • j=3:ans = max(11,8 +2-3)=11;best = max(8,2+3)=8
  • j=4:ans = max (11,8 +6-4)=10;best = max (8,6+4)=10最终 ans=11,和示例一致。

四、举一反三:和 "买卖股票最佳时机" 的共性

121 题:利润 = prices[j] - prices[i] (i<j),变形为利润 = ( -prices[i] ) + prices[j]

  • 枚举 j,维护 j 之前的最小 prices [i](即最大的 (-prices [i]));
  • 核心逻辑和本题完全一致:枚举右、维护左的最优值

总结

你解决这类题卡住的核心,以及对应的解决方法,可以总结为 3 个关键点:

  1. 先变形公式:把依赖 i 和 j 的式子拆成 "i 独立项 + j 独立项",这是从 "暴力枚举" 到 "线性遍历" 的关键;
  2. 明确枚举逻辑:固定 "枚举右边界 j",维护 "j 之前的左边界 i 的最优值",把 O (n²) 降到 O (n);
  3. 注意执行顺序:先算当前 j 的答案(用之前的最优左值),再更新最优左值,严格满足 i<j 的约束。

以后遇到类似题,先按这 3 步走:变形公式→确定枚举方向→维护左值,就能把 "没头绪" 的问题,转化为你熟悉的 "遍历 + 更新最值" 的简单逻辑。

相关推荐
薛不痒2 小时前
机器学习算法之SVM
算法·机器学习·支持向量机
QT 小鲜肉2 小时前
【Linux命令大全】001.文件管理之paste命令(实操篇)
linux·运维·服务器·笔记·microsoft
Trouvaille ~2 小时前
【C++篇】智能指针详解(一):从问题到解决方案
开发语言·c++·c++11·类和对象·智能指针·raii
爱睡觉的王宇昊2 小时前
PCB设计完全指南:从软件选择到基础规范(通用电路篇详解)
笔记·stm32·单片机·嵌入式硬件·学习
一个响当当的名号2 小时前
机械硬盘如何工作
学习
白帽子凯哥哥2 小时前
2026零基础如何参与护网行动?(非常详细)
数据库·sql·学习·漏洞·xss
AndrewHZ2 小时前
【复杂网络分析】如何入门Louvain算法?
python·算法·复杂网络·社区发现·community det·louvain算法·图挖掘
AndrewHZ2 小时前
【图像处理基石】如何基于黑白图片恢复出色彩?
图像处理·深度学习·算法·计算机视觉·cv·色彩恢复·deoldify
POLITE32 小时前
Leetcode 3.无重复字符的最长子串 JavaScript (Day 4)
javascript·算法·leetcode