算法优化-

单调栈是一种特殊的数据结构,要求栈内元素递增并保持栈顶元素为最大值。它可以用于快速排序、递归问题解决等,Python中可实现为一个类。文章介绍了单调栈的实现、操作方法,并通过动态规划和二分查找优化的示例解释了其在解决问题中的应用。
摘要由CSDN通过智能技术生成

一、单调栈

单调栈 (Monotone Stack) 是一种栈的数据结构,它的每个节点都必须按照递增的顺序存储数据,即栈中的元素都必须按照递增的顺序排列。同时,栈顶元素必须是一个单调递增的元素,即如果栈中有两个元素,一个元素比另一个元素大,那么前者必须是栈顶元素,后者则是次栈顶元素。

单调栈的实现可以使用数组或者链表来实现。使用数组实现单调栈时,需要保证数组的下标从 0 开始,栈顶元素对应的数组下标为栈的大小减 1。在遍历栈的过程中,如果发现栈顶元素比当前元素大,则需要将栈顶元素向后移动一位,同时将次栈顶元素提到栈顶。

单调栈在排序算法中使用的比较广泛,例如快速排序中就可以使用单调栈来维护排好序的数组。此外,单调栈还可以用于解决一些递归算法的问题,例如递归求和、递归排序等。

以下是使用数组实现单调栈的 Python 代码示例:

class MonotoneStack:  
    def __init__(self):  
        self.stack = []  
        self.size = 0

    def is_empty(self):  
        return self.size == 0

    def push(self, value):  
        self.stack.append(value)  
        self.size += 1

    def pop(self):  
        if self.is_empty():  
            raise ValueError("Stack is empty")  
        self.size -= 1  
        return self.stack.pop()

    def peek(self):  
        if self.is_empty():  
            raise ValueError("Stack is empty")  
        return self.stack[self.size - 1]

    def size(self):  
        return self.size  

其中,MonotoneStack 类表示一个单调栈,包含栈顶元素、栈的大小等属性。push() 方法用于将元素添加到栈中,pop() 方法用于从栈中删除元素,peek() 方法用于查看栈顶元素,is_empty() 方法用于检查栈是否为空,size() 方法用于返回栈的大小。

以下是使用该代码实现一个简单的单调栈的示例:

stack = MonotoneStack()

stack.push(1)  
stack.push(2)  
stack.push(3)

print(stack.peek())  # 输出 3  
print(stack.pop())  # 输出 3  
print(stack.peek())  # 输出 2

stack.push(4)  
print(stack.peek())  # 输出 4  
print(stack.pop())  # 输出 4  
print(stack.peek())  # 输出 2  

在这个示例中,我们首先创建一个单调栈 stack,并向其中添加了三个元素。然后,我们使用 peek() 方法查看栈顶元素,使用 pop() 方法删除栈顶元素,并使用 peek() 方法再次查看栈顶元素。最后,我们向栈中添加了第四个元素,但此时栈已经为空,因此 peek() 方法返回的是次栈顶元素,即 2

应用:

单调栈是一种栈的数据结构,它的每个节点都必须按照递增的顺序存储数据,即栈中的元素都必须按照递增的顺序排列。同时,栈顶元素必须是一个单调递增的元素,即如果栈中有两个元素,一个元素比另一个元素大,那么前者必须是栈顶元素,后者则是次栈顶元素。

单调栈在计算机科学中有广泛的应用,其中一些应用包括:

  1. 排序算法:单调栈可以用于快速排序、归并排序、堆排序等排序算法中,用于维护排好序的数组。

  2. 递归算法:单调栈可以用于解决一些递归算法的问题,例如递归求和、递归排序等。

  3. 数据结构:单调栈可以用于实现一些数据结构,例如线段树、二叉搜索树等。

  4. 神经网络:单调栈可以用于神经网络的训练和预测中,用于存储神经网络的输入和输出数据,并支持神经网络的反向传播算法。

总之,单调栈是一种非常重要的数据结构,它在计算机科学中有广泛的应用,能够解决许多复杂的问题。

刷题:

1.给出项数为 n 的整数数列 a1…n​。 定义函数 f(i)代表数列中第 i 个元素之后第一个大于 ai​ 的元素的下标。若不存在,则 f(i)=0。 试求出 f(1…n)。

题解:

我们可以使用动态规划来求解函数 f(1...n)。设 dp[i][j] 表示在前 i 个元素中,第 j 个元素之后第一个大于 ai 的元素的下标。

对于第 i 个元素 ai,它在数列中的下标为 i。因此,dp[i][0] = i。

假设在前 i-1 个元素中,第 j 个元素之后第一个大于 ai 的元素的下标为 dp[i-1][j],那么:

  • 如果第 j 个元素之后的下标为 k,且 k 大于等于 i,则第 i 个元素之后的下标为 k+1。因此,dp[i][j+1] = dp[i-1][j] + 1。
  • 如果第 j 个元素之后的下标为 k,且 k 小于等于 i-1,则第 i 个元素之后的下标为 k+1。因此,dp[i][j+1] = dp[i-1][j]。

因此,我们可以得到以下递推式:

dp[i][j+1] = max(dp[i-1][j+1], dp[i-1][j]) + 1

其中,max(dp[i-1][j+1], dp[i-1][j]) 表示在前 i-1 个元素中,第 j 个元素之后第一个大于 ai 的元素的下标的最大值。

最终的答案为:

f(i) = max(dp[1][i], dp[2][i], ..., dp[n][i])

其中,dp[1][i], dp[2][i], ..., dp[n][i] 分别表示在前 i-1 个元素中,第 1、2、...、n 个元素之后第一个大于 ai 的元素的下标的最大值。

需要注意的是,对于第 i 个元素 ai,它的下标为 i,因此 dp[i][j] = dp[i-1][j]。

最终,我们得到了函数 f(1...n) 的求解方法:

f(i) = max(dp[1][i], dp[2][i], ..., dp[n][i])

其中,dp[1][i], dp[2][i], ..., dp[n][i] 分别表示在前 i-1 个元素中,第 1、2、...、n 个元素之后第一个大于 ai 的元素的下标的最大值。

以下是使用 Python 语言实现的代码:

def f(ai, ai_list):  
    n = len(ai_list)  
    dp = [[0] * (n + 1) for _ in range(n + 1)]  
    dp[0][0] = i  
    for i in range(1, n + 1):  
        for j in range(i):  
            dp[i][j] = dp[i - 1][j]  
            if ai_list[j] > ai:  
                dp[i][j] = max(dp[i][j], dp[i - 1][j + 1])  
                dp[i][j] = max(dp[i][j], dp[i - 1][j])  
    return dp[n][n]  

其中,ai 和 ai_list 分别表示数列 a1...n 和数列 a1...n 的前缀和数组。

函数 f(ai, ai_list) 的输入参数为数列 ai 和 ai_list,输出为函数 f(1...n) 的值。

该函数使用动态规划来求解函数 f(1...n)。具体来说,dp[i][j] 表示在前 i-1 个元素中,第 j 个元素之后第一个大于 ai 的元素的下标。

对于第 i 个元素 ai,它的下标为 i。因此,dp[i][j] = dp[i-1][j]。

递推式为:dp[i][j+1] = max(dp[i-1][j+1], dp[i-1][j]) + 1。

最终的答案为:f(i) = max(dp[1][i], dp[2][i], ..., dp[n][i])。

该函数的时间复杂度为 O(n^2),空间复杂度为 O(n)。

我们可以使用二分答案的方法优化时间复杂度。

假设我们已经找到了前 i 个元素中第 j 个元素之后第一个大于 ai 的元素的下标,即 dp[i][j],那么我们可以使用二分答案的方法找到第 i 个元素之后第一个大于 ai 的元素的下标。

具体来说,我们可以对数组 dp[1...n][1...n] 使用二分答案的方法进行排序,时间复杂度为 O(nlogn)。然后,我们可以使用二分查找来找到第 i 个元素之后第一个大于 ai 的元素的下标,时间复杂度为 O(logn)。

下面是一个使用 Python 语言实现的优化后的算法代码:

def f(ai, ai_list):    
    n = len(ai_list)    
    dp = [[0] * (n + 1) for _ in range(n + 1)]    
    dp[0][0] = i    
    for i in range(1, n + 1):    
        for j in range(i):    
            dp[i][j] = dp[i - 1][j]    
            if ai_list[j] > ai:    
                left, right = 0, n    
                while left <= right:    
                    mid = (left + right) // 2    
                    if dp[mid][j] >= dp[left][j] and dp[mid][j] >= dp[right][j]:    
                        right = mid - 1    
                    else:    
                        left = mid + 1    
                dp[i][j] = left    
    return dp[n][n]    

该函数的时间复杂度为 O(nlogn),空间复杂度为 O(n)。相比于动态规划的算法实现,使用二分答案的方法可以极大地提高算法的时间复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值