最大乘积问题

52 阅读10分钟

小R最近遇到了一个数组问题。他有一个包含 NN 个元素的数组,记作 a1,a2,...,aNa1​,a2​,...,aN​。为了分析这个数组的特性,小R定义了两个函数 L(i)L(i) 和 R(i)R(i),并希望通过这两个函数来找到一些有趣的结论。

  • L(i)L(i) 是满足以下条件的最大的 jj 值:
  • j<ij<i
  • a[j]>a[i]a[j]>a[i]
  • 如果找不到这样的 jj,那么 L(i)=0L(i)=0;如果有多个满足条件的 jj,选择离 ii 最近的那个。
  • R(i)R(i) 是满足以下条件的最小的 kk 值:
  • k>ik>i
  • a[k]>a[i]a[k]>a[i]
  • 如果找不到这样的 kk,那么 R(i)=0R(i)=0;如果有多个满足条件的 kk,选择离 ii 最近的那个。

最终,小R定义 MAX(i)=L(i)∗R(i)MAX(i)=L(i)∗R(i),他想知道在 1≤i≤N1≤i≤N 的范围内,MAX(i)MAX(i) 的最大值是多少。

1. 问题理解

本题的核心在于根据给定的数组以及定义的函数  和 ,计算出在特定范围内每个  对应的  的值,并找出这些值中的最大值。

2. 关键函数定义及含义

函数 

  • 条件描述:对于给定的索引 , 要找到满足  且  的最大的  值。也就是说,在数组中位于  之前的元素中,寻找比  大的元素,且要取其中索引最大(即离  最近且在其左边)的那个元素的索引作为  的值。
  • 特殊情况处理:如果找不到这样满足条件的 ,那么按照规定 。

函数 

  • 条件描述:对于给定的索引 , 要找到满足  且  的最小的  值。即在数组中位于  之后的元素中,寻找比  大的元素,且要取其中索引最小(即离  最近且在其右边)的那个元素的索引作为  的值。
  • 特殊情况处理:如果找不到这样满足条件的 ,那么规定 。

3. 解决思路分析

计算  和  的值

  • 对于计算 :

    • 可以从数组的开头开始,依次遍历到索引为  的元素,在这个过程中,记录下满足  条件的最大的  值。每次遇到一个比  大的元素,就更新这个最大的  值。这样当遍历完索引为  的元素后,就得到了  的值。
    • 或者,也可以采用一种更高效的方式,比如利用栈的数据结构。从数组的开头开始,将元素依次压入栈中,当遇到一个元素  时,在栈中查找比  大的元素,找到后将其对应的索引作为  的值,同时将栈中小于等于  的元素弹出,这样可以保证栈中的元素始终是可能成为后续  值的候选元素,并且可以在一定程度上提高查找效率。
  • 对于计算 :

    • 类似地,可以从数组的末尾开始,依次遍历到索引为  的元素,在这个过程中,记录下满足  条件的最小的  值。每次遇到一个比  大的元素,就更新这个最小的  值。当遍历完索引为  的元素后,就得到了  的值。
    • 同样,也可以考虑使用更高效的数据结构来辅助计算,比如双端队列。从数组的末尾开始,将元素依次加入双端队列,当遇到一个元素  时,在双端队列中查找比  而在队列前端的元素作为  的值,同时将队列中小于等于  的元素从前端删除,这样可以保证队列中的元素始终是可能成为后续  值的候选元素,并且能提高查找效率。

计算  并找出最大值

  • 一旦得到了每个  对应的  和  的值,就可以根据定义  来计算出每个  的  值。
  • 然后,通过遍历所有可能的  值(即从  到 ),记录下在这个过程中遇到的  的最大值。

通过以上分析的步骤,就可以按照小 R 的要求,准确地计算出在  的范围内, 的最大值。

代码

    def solution(n, array):  
    # L(i) calculation  
    L = [0] * n  
    stack = []  
    
    for i in range(n):  
        while stack and array[stack[-1]] <= array[i]:  
            stack.pop()  
        L[i] = stack[-1] + 1 if stack else 0  # +1 to convert to 1-based index  
        stack.append(i)  

    # R(i) calculation  
    R = [0] * n  
    stack = []  

    for i in range(n - 1, -1, -1):  
        while stack and array[stack[-1]] <= array[i]:  
            stack.pop()  
        R[i] = stack[-1] + 1 if stack else 0  # +1 to convert to 1-based index  
        stack.append(i)  

    # Calculate MAX(i) and find the maximum  
    max_value = 0  
    for i in range(n):  
        current_max = L[i] * R[i]  
        if current_max > max_value:  
            max_value = current_max  

    return max_value  

# Test cases  
if __name__ == "__main__":  
    print(solution(5, [5, 4, 3, 4, 5]) == 8)  
    print(solution(6, [2, 1, 4, 3, 6, 5]) == 15)  
    print(solution(7, [1, 2, 3, 4, 5, 6, 7]) == 0)

1. 代码功能概述

这段代码定义了一个名为 solution 的函数,它接受两个参数:n 表示数组的长度,array 是一个包含 n 个元素的数组。函数的主要目的是根据给定数组中每个元素的情况,按照特定的规则分别计算出 L(i) 和 R(i) 的值(其中 i 表示数组元素的索引),然后通过 MAX(i) = L(i) * R(i) 计算出每个 i 对应的 MAX(i) 值,并最终找出在 1 <= i <= n 范围内 MAX(i) 的最大值并返回。

2. 代码逻辑分析

计算 L(i) 的值

  • 初始化

    • L = [0] * n:创建一个长度为 n 的列表 L,并将其所有元素初始化为 0。这个列表将用于存储每个索引 i 对应的 L(i) 的值。
    • stack = []:初始化一个空栈 stack,这个栈将在计算 L(i) 的过程中起到辅助作用。
  • 遍历数组计算 L(i)

    • for i in range(n)::通过一个循环遍历数组 array 的每个索引 i,从 0 开始到 n - 1

    • 在循环内部,有以下操作:

      • while stack and array[stack[-1]] <= array[i]::当栈不为空且栈顶元素所对应的数组值(通过 array[stack[-1]] 获取)小于等于当前数组元素 array[i] 时,执行以下操作。

        • stack.pop():将栈顶元素弹出,这意味着在寻找满足 L(i) 条件的元素过程中,那些小于等于当前元素 array[i] 的元素不符合要求,所以将它们从栈中移除,以便后续找到更合适的元素作为 L(i) 的值。
      • L[i] = stack[-1] + 1 if stack else 0:在上述循环结束后,如果栈不为空,那么栈顶元素的索引就是满足 L(i) 条件的元素索引(因为在前面的循环中已经排除了不符合要求的元素),此时将栈顶元素索引加 1(这里是为了将索引转换为以 1 为起始的索引,符合常规的计数习惯)作为 L(i) 的值赋给 L 列表中的对应位置;如果栈为空,说明没有找到满足条件的元素,按照规则将 L(i) 设置为 0

      • stack.append(i):无论是否找到了满足条件的元素,都将当前索引 i 加入到栈中,这样栈中就保存了可能成为后续其他索引 i 的 L(i) 值的候选元素。

计算 R(i) 的值

  • 初始化

    • R = [0] * n:创建一个长度为 n 的列表 R,并将其所有元素初始化为 0。这个列表将用于存储每个索引 i 对应的 R(i) 的值。
    • stack = []:再次初始化一个空栈 stack,这个栈将在计算 R(i) 的过程中起到辅助作用,其作用机制与计算 L(i) 时的栈类似,但方向相反(因为是从数组末尾往开头遍历)。
  • 遍历数组计算 R(i)

    • for i in range(n - 1, -1, -1)::通过一个循环从数组的末尾开始,反向遍历数组 array 的每个索引 i,从 n - 1 开始到 0

    • 在循环内部,有以下操作:

      • while stack and array[stack[-1]] <= array[i]::当栈不为空且栈顶元素所对应的数组值(通过 array[stack[-1]] 获取)小于等于当前数组元素 array[i] 时,执行以下操作。

        • stack.pop():将栈顶元素弹出,原因与计算 L(i) 时类似,即排除那些小于等于当前元素 array[i] 的元素,以便找到满足 R(i) 条件的元素。
      • R[i] = stack[-1] + 1 if stack else 0:在上述循环结束后,如果栈不为空,那么栈顶元素的索引就是满足 R(i) 条件的元素索引(同样因为前面排除了不符合要求的元素),此时将栈顶元素索引加 1(为了转换为以 1 为起始的索引)作为 R(i) 的值赋给 R 列表中的对应位置;如果栈为空,说明没有找到满足条件的元素,按照规则将 R(i) 设置为 0

      • stack.append(i):无论是否找到了满足条件的元素,都将当前索引 i 加入到栈中,以便保存可能成为后续其他索引 i 的 R(i) 值的候选元素。

计算 MAX(i) 并找出最大值

  • 初始化

    • max_value = 0:创建一个变量 max_value,并将其初始化为 0。这个变量将用于存储在计算过程中遇到的 MAX(i) 的最大值。
  • 遍历数组计算 MAX(i) 并更新最大值

    • for i in range(n)::通过一个循环遍历数组 array 的每个索引 i,从 0 到 n - 1

    • 在循环内部,有以下操作:

      • current_max = L[i] * R[i]:根据前面计算得到的 L(i) 和 R(i) 的值,通过乘法运算计算出每个索引 i 对应的 MAX(i) 值,即 L(i) 乘以 R(i),并将结果存储在 current_max 变量中。

      • if current_max > max_value::将计算得到的 current_max 与当前存储的 max_value 进行比较,如果 current_max 大于 max_value,说明找到了一个更大的 MAX(i) 值。

        • max_value = current_max:此时将 max_value 更新为新找到的更大的 MAX(i) 值,以便后续继续寻找更大的值并更新。

返回结果

  • return max_value:在遍历完整个数组并完成所有计算后,将最终找到的 MAX(i) 的最大值 max_value 返回。

3. 所使用的算法

这段代码主要使用了以下两种算法思想:

单调栈算法

在计算 L(i) 和 R(i) 的过程中,都使用了单调栈的算法思想。以计算 L(i) 为例,通过维护一个栈,当遍历数组元素时,不断将元素压入栈中,但如果遇到一个元素使得栈顶元素所对应的数组值小于等于它,就将栈顶元素弹出,这样就保证了栈中的元素是单调递减的(从栈底到栈顶)。这种单调递减的栈结构有助于快速找到满足 L(i) 条件的元素,即找到在当前元素之前且比当前元素大的元素中,离当前元素最近的那个元素。同样的道理,在计算 R(i) 时,通过从数组末尾反向遍历并维护一个类似的单调栈,也能快速找到满足 R(i) 条件的元素。单调栈算法在处理这类需要根据元素相对大小关系在序列中快速找到特定元素的问题时非常有效,它能够将原本可能需要多次遍历或复杂比较的操作简化,提高计算效率。

线性遍历算法

整个代码在计算 L(i)R(i) 以及 MAX(i) 的过程中,都涉及到对数组的线性遍历。比如在计算 L(i) 时,从数组的开头开始,依次遍历每个元素的索引 i;在计算 R(i) 时,从数组的末尾开始,依次遍历每个元素的索引 i;在计算 MAX(i) 时,又从数组的开头开始,依次遍历每个元素的索引 i。这种线性遍历的方式是一种基本的算法操作,通过按照顺序依次访问数组中的每个元素,能够全面地处理数组中的所有元素,确保计算的完整性和准确性。

综上所述,这段代码通过上述的代码逻辑和所使用的算法,有效地实现了根据给定数组计算出 MAX(i) 的最大值的功能。