算法题题解记录——双变量问题的 “枚举右,维护左”

算法题的时间复杂度是必须考虑的因素,n的数量级和复杂度二者可以决定算法的执行时间。在有进行时间限制的题中,一个过于复杂的题解会因超时而得不到全分。

本文记录了对观光景点组合得分问题的思考、分析和优化。

题目

www.marscode.cn/practice/r3...

方法一:BF

思路

这道题的暴力解法是采用 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n 2 ) O(n^2) </math>O(n2) 的遍历,枚举所有的两两组合,取最大值。

代码

python 复制代码
def solution(values: list) -> int:
    # PLEASE DO NOT MODIFY THE FUNCTION SIGNATURE
    # write code here

    ans = 0
    l = len(values)
    for i in range(l):
        for j in range(i + 1, l):
            ans = max(ans, values[i] + values[j] + i - j)

    return ans


if __name__ == '__main__':
    print(solution(values=[8, 3, 5, 5, 6]) == 11)
    print(solution(values=[10, 4, 8, 7]) == 16)
    print(solution(values=[1, 2, 3, 4, 5]) == 8)

复杂度

  • 时间复杂度 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n 2 ) O(n^2) </math>O(n2)
  • 空间复杂度 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1)

思考优化点

这道题是leetcode第1014题,在力扣中明确提到了n最大可以取到 <math xmlns="http://www.w3.org/1998/Math/MathML"> 5 ∗ 1 0 4 5 * 10^4 </math>5∗104。而 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n 2 ) O(n^2) </math>O(n2) 的算法会执行 <math xmlns="http://www.w3.org/1998/Math/MathML"> 25 ∗ 1 0 8 25*10^8 </math>25∗108,即 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2.5 ∗ 1 0 9 2.5*10^9 </math>2.5∗109 次,而现代处理器大约能够每秒执行 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 0 8 10^8 </math>108 到 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 0 9 10^9 </math>109 次基本操作。 因此通常认为如果超过 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 0 9 10^9 </math>109 则超时

既然超时,那就一定存在不必要的重复计算,暴力求解的方法哪里重复计算了呢?请看图:

我们对 values[i] + values[j] + i - j 重新组合为values[i] + i + values[j] - j,会发现:对于固定的ji < j,只要 values[i] + i 越大,结果越大。而当 j 移动到 j+1 时,前 j - 1value[i] + i 的最大值已经计算过了。

显然这里可以优化。

方法二:

思路

既然左侧的 values[i] + i 存在重复计算,那显然我们可以用变量将其保存起来。然后在单次循环中进行最大值的维护。

这个思路被叫做双变量问题 的 "枚举右,维护左"。详见文章末尾。

代码

python 复制代码
def solution(values: list) -> int:
    # PLEASE DO NOT MODIFY THE FUNCTION SIGNATURE
    # write code here
    ans = 0
    mx = values[0] + 0
    for j in range(1, len(values)):
        ans = max(ans, mx + values[j] - j)
        # 边遍历边维护
        mx = max(mx, values[j] + j)
    return ans


if __name__ == '__main__':
    print(solution(values=[8, 3, 5, 5, 6]) == 11)
    print(solution(values=[10, 4, 8, 7]) == 16)
    print(solution(values=[1, 2, 3, 4, 5]) == 8)

复杂度

  • 时间复杂度 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)
  • 空间复杂度 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1)

总结

由数据范围反推算法复杂度以及算法内容

一般ACM或者笔试题的时间限制是1秒或2秒。 在这种情况下,C++代码的操作次数控制在10⁷~10⁸为最佳。 下面给出在不同数据范围内,代码的时间复杂度和算法该如何选择:

  1. n ≤ 30,指数级别,dfs+剪枝,状态压缩dp
  2. n ≤ 100,= O(n³),floyd,dp,高斯消元
  3. n ≤ 1000,= O(n²),O(n²logn),dp,二分,朴素版Dijkstra、朴素版Prim、Bellman-Ford
  4. n ≤ 10000,= O(n * √n),块状链表、分块、莫队
  5. n ≤ 100000,= O(nlogn),各种sort,线段树、树状数组、set/map、heap、拓扑排序、dijkstra+heap、prim+heap、Kruskal、spfa、求凸包、求半平面交、二分、CDQ分治、整体二分、后缀数组、树链剖分、动态树
  6. n ≤ 1000000 => O(n),以及常数较小的O(nlogn) 算法 => 单调队列、hash、双指针扫描、BFS、并查集、kmp、AC自动机,常数较小的O(nlogn)的做法: sort、树状数组、heap、dijkstra、spfa
  7. n ≤ 10000000,= O(n),双指针扫描、kmp、AC自动机、线性素数筛
  8. n ≤ 10⁹ => O(√n),判断质数
  9. n ≤ 10¹⁸ => O(logn),最大公约数,快速幂,数位DP
  10. n ≤ 10¹⁰⁰⁰ => O((logn)²),高精度加减乘除
  11. n ≤ 10¹⁰⁰⁰⁰⁰ => O(logk × loglogk),k表示位数,高精度加减、FFT/NTT

枚举右,维护左

对于 双变量问题 ,例如两数之和 aᵢ + aⱼ = t,可以枚举右边的 aⱼ,转换成 单变量问题 ,也就是在 aⱼ 左边查找是否有 aᵢ = t - aⱼ,这可以用哈希表维护。 这个技巧叫做 枚举右,维护左

参考文章:

www.acwing.com/blog/conten...

leetcode.cn/circle/disc...

相关推荐
uzong4 小时前
面试官:Redis中的 16 库同时发送命令,服务端是串行执行还是并行执行
后端·面试·架构
呼啦啦啦啦啦啦啦啦4 小时前
常见的排序算法
java·算法·排序算法
关键帧-Keyframe5 小时前
音视频面试题集锦第 26 期
面试·音视频
喂完待续5 小时前
【Tech Arch】Spark为何成为大数据引擎之王
大数据·hadoop·python·数据分析·spark·apache·mapreduce
胡萝卜3.05 小时前
数据结构初阶:排序算法(一)插入排序、选择排序
数据结构·笔记·学习·算法·排序算法·学习方法
地平线开发者5 小时前
LLM 中 token 简介与 bert 实操解读
算法·自动驾驶
scx201310046 小时前
20250814 最小生成树和重构树总结
c++·算法·最小生成树·重构树
阿巴~阿巴~6 小时前
冒泡排序算法
c语言·开发语言·算法·排序算法
ssshooter6 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
散1126 小时前
01数据结构-交换排序
数据结构·算法