快速幂、树状数组、线段树、优先队列

快速幂

快速幂是一种用于快速计算一个数的整数次幂的算法。该算法通过将指数n拆分成二进制表示形式中的若干项,从而将计算量降至对数级别。

具体来说,假设我们要计算a的n次幂,可以将n写成二进制形式,如n=101101(二进制),则有:

a^n = a^(2^5) * a^(2^3) * a^(2^2) * a^(2^0)

其中,2^5、2^3、2^2、2^0分别表示二进制数101101中为1的位所对应的幂次。上式中每个乘积都可以通过不断平方得到,例如a^(2^5)可以通过不断将a平方5次得到。

使用这种方法,计算a的n次幂的时间复杂度为O(logn),比直接计算a^n的时间复杂度O(n)要快得多。

以下是一个使用Python实现快速幂算法的例子:

def fast_pow(a, n):
    result = 1
    while n > 0:
        if n % 2 == 1:
            result *= a
        a *= a
        n //= 2
    return result

在这个实现中,我们使用了while循环和位运算来对指数进行二进制拆分,并使用了一个变量result来记录每个乘积的结果。

优先队列

优先队列是一种数据结构,它可以存储具有优先级的元素,并在需要时根据优先级访问和删除它们。下面是一个用Python实现最小优先队列的示例代码:

import heapq

class PriorityQueue:
    def __init__(self):
        self._queue = []
        self._index = 0

    def push(self, item, priority):
        heapq.heappush(self._queue, (priority, self._index, item))
        self._index += 1

    def pop(self):
        return heapq.heappop(self._queue)[-1]

这个代码使用了Python的heapq模块,它实现了一个最小堆,也就是说,具有最高优先级的元素是队列中的最小元素。在这个实现中,每个元素都是一个元组(priority, index, item),其中priority是元素的优先级,index用于处理相同优先级元素的顺序,item是元素本身。

push()方法将一个元素和其优先级添加到队列中。在这个实现中,我们使用一个自增的索引来确保相同优先级元素的顺序。pop()方法从队列中弹出具有最高优先级的元素,并返回其item值。这个实现还允许相同优先级元素的存在,它们将按照它们被添加到队列中的顺序弹出。

下面是一个简单的使用优先队列的示例:

q = PriorityQueue()
q.push('task1', 1)
q.push('task2', 3)
q.push('task3', 2)

print(q.pop())  # 输出: 'task1'
print(q.pop())  # 输出: 'task3'
print(q.pop())  # 输出: 'task2'

在这个示例中,我们首先创建了一个优先队列,并将三个任务添加到队列中。由于第一个任务具有最高优先级,因此它将首先从队列中弹出。随后弹出的任务3和任务2分别具有第二和第三高的优先级。

树状数组

树状数组(Fenwick Tree)是一种可以高效地处理动态数组前缀和问题的数据结构。它可以在O(logn)时间复杂度内完成单点更新和前缀查询操作。下面是一个用Python实现树状数组的示例代码:

class FenwickTree:
    def __init__(self, n):
        self._sums = [0] * (n + 1)

    def update(self, i, delta):
        while i < len(self._sums):
            self._sums[i] += delta
            i += i & -i

    def query(self, i):
        res = 0
        while i > 0:
            res += self._sums[i]
            i -= i & -i
        return res

在这个实现中,我们使用了一个长度为$n+1$的数组_sums来存储前缀和,其中_sums[i]存储了前i个元素的和。update()方法用于更新第$i$个元素的值。它从$i$开始,一直到数组末尾,更新中的元素,直到处理完整个前缀。query()方法用于查询前缀和。它从i开始,一直到数组开头,累加_sums中的元素,直到处理完整个前缀。

下面是一个简单的使用树状数组的示例:

arr = [1, 3, 2, 4, 5, 6, 1]
n = len(arr)
tree = FenwickTree(n)

# 构建树状数组
for i in range(1, n + 1):
    tree.update(i, arr[i - 1])

# 查询前缀和
print(tree.query(3))  # 输出: 6

# 更新元素
tree.update(3, 1)
print(tree.query(3))  # 输出: 7

在这个示例中,我们首先构建了一个长度为7的数组,并将其存储在一个树状数组中。我们可以使用query()方法查询前缀和,例如查询前三个元素的和为6。随后,我们更新了数组中的第三个元素,并再次查询前三个元素的和,得到了7作为结果。

线段树

线段树(Segment Tree)是一种常用的数据结构,它可以高效地处理区间查询和单点更新问题。它的核心思想是将一个区间划分成若干个小区间,然后将每个小区间的信息合并起来,以表示整个区间的信息。下面是一个用Python实现线段树的示例代码:

class SegmentTree:
    def __init__(self, nums):
        n = len(nums)
        self.tree = [0] * (2 * n)
        self.nums = nums

        # 构建线段树
        for i in range(n):
            self.tree[n + i] = nums[i]
        for i in range(n - 1, 0, -1):
            self.tree[i] = self.tree[2 * i] + self.tree[2 * i + 1]

    def update(self, i, val):
        n = len(self.nums)
        pos = n + i
        diff = val - self.nums[i]
        self.nums[i] = val

        # 更新线段树
        while pos > 0:
            self.tree[pos] += diff
            pos //= 2

    def query(self, l, r):
        n = len(self.nums)
        l += n
        r += n
        res = 0

        # 区间查询
        while l <= r:
            if l % 2 == 1:
                res += self.tree[l]
                l += 1
            if r % 2 == 0:
                res += self.tree[r]
                r -= 1
            l //= 2
            r //= 2

        return res

在这个实现中,我们使用一个数组tree来存储线段树。对于每个节点$i$,tree[2i]和tree[2i+1]表示它的左右子节点,tree[i]表示从i到其子节点的所有元素的和。构建线段树的过程可以使用递归或迭代方式完成。在这个实现中,我们使用了迭代的方式。

update()方法用于更新线段树中的一个元素。我们首先计算该元素在tree中的位置pos,并计算出新旧值之间的差diff。然后,我们从pos开始向上更新整个线段树,直到到达根节点。query()方法用于查询区间和。我们首先将区间左端点和右端点都转换为线段树中的下标,然后从下往上遍历线段树,依次累加相应的元素,直到覆盖了整个区间。

下面是一个简单的使用线段树的示例:

nums = [1, 3, 5, 7, 9, 11]
n = len(nums)
tree = SegmentTree(nums)

# 查询区间和
print(tree.query(1, 4))  # 输出: 24

# 更新元素
tree.update(2, 6)
print(tree.query(1, 4))  # 输出: 25

在这个示例中,我们首先使用一个数组构建了一个线段树,然后查询了区间[1,4]的和,结果为24。接着,我们更新了第3个元素的值为6,并查询了区间[1,4]的和,结果为25。

总的来说,线段树是一种非常实用的数据结构,它可以在O(log n)的时间内完成区间查询和单点更新操作,可以应用于很多算法问题中,如RMQ(区间最小值)、区间求和、区间加、区间乘等。

单调栈/队列

单调栈/队列(Monotonic Stack/Queue)是一种常用的数据结构,它可以在O(n)的时间内解决很多与区间最值相关的问题。单调栈/队列有两种类型:单调递增和单调递减。这里我们以单调递减栈为例,来介绍单调栈的基本思想和用法。

单调递减栈的核心思想是:维护一个单调递减的栈,每次将一个元素压入栈中时,先将比它小的元素都弹出栈,然后再将它压入栈。这样,栈顶元素就是当前栈中最小的元素。当需要求解区间最值时,我们可以用单调栈来维护当前区间的最小值或最大值。具体实现如下:

def mono_stack(nums):
    stack = []
    res = []

    for i in range(len(nums)):
        while stack and nums[i] <= nums[stack[-1]]:
            stack.pop()
        res.append(stack[-1] if stack else -1)
        stack.append(i)

    return res

在这个实现中,我们使用了一个列表stack来存储单调递减栈。对于每个元素nums[i],我们先将比它小的元素都从栈中弹出,直到栈为空或栈顶元素比nums[i]大。然后,我们将i压入栈中,并将当前栈顶元素的下标加入到结果列表res中。

下面是一个简单的使用单调栈求解区间最小值的示例:

nums = [3, 1, 4, 2]
res = mono_stack(nums)
print(res)  # 输出: [-1, -1, 1, 1]

在这个示例中,我们输入一个数组[3, 1, 4, 2],并调用mono_stack()函数求解区间最小值。输出结果为[-1, -1, 1, 1],表示每个元素左边第一个比它小的元素的下标。例如,对于元素nums[2]=4,它左边第一个比它小的元素是nums[1]=1,其下标为1。因此,res[2]=1。由此可见,单调栈/队列是一种非常实用的数据结构,它可以在O(n)的时间内完成区间最值的计算。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值