11. 观光景点组合得分问题——双变量问题的 “枚举右,维护左”

236 阅读3分钟

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

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

题目

www.marscode.cn/practice/r3…

image.png

方法一:BF

思路

这道题的暴力解法是采用 O(n2)O(n^2) 的遍历,枚举所有的两两组合,取最大值。

代码

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)

复杂度

  • 时间复杂度 O(n2)O(n^2)
  • 空间复杂度 O(1)O(1)

思考优化点

这道题是leetcode第1014题,在力扣中明确提到了n最大可以取到 51045 * 10^4。而 O(n2)O(n^2) 的算法会执行2510825*10^8,即 2.51092.5*10^9 次,而现代处理器大约能够每秒执行 10810^810910^9 次基本操作。 因此通常认为如果超过 10910^9 则超时

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

image.png

我们对 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 存在重复计算,那显然我们可以用变量将其保存起来。然后在单次循环中进行最大值的维护。

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

代码

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)

复杂度

  • 时间复杂度 O(n)O(n)
  • 空间复杂度 O(1)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…