每日一题:2的幂数组中查询范围内的乘积;快速幂算法

题目选自2438. 二的幂数组中查询范围内的乘积

还是一样的,先讲解思路,然后再说代码。

题目有一定难度,所以我要争取使所有人都能看懂,用的方法会用最常规的思想。关于语言,都是互通的,只要你懂了一门语言,可以做到尽量理解其他语言。

每一次头脑风暴都是一次十足的成长,请有耐心,即使是小白,看完自有收获。

这道题跟之前那道题目重新排序得到 2 的幂呼应起来了,没有看过的可以先去看一下前面那道题。


题目

给你一个正整数 n ,你需要找到一个下标从 0 开始的数组 powers ,它包含 最少 数目的 2 的幂,且它们的和为 n 。powers 数组是 非递减 顺序的。根据前面描述,构造 powers 数组的方法是唯一的。

同时给你一个下标从 0 开始的二维整数数组 queries ,其中 queries[i] = [lefti, righti] ,其中 queries[i] 表示请你求出满足 lefti <= j <= righti 的所有 powers[j] 的乘积。

请你返回一个数组 answers ,长度与 queries 的长度相同,其中 answers[i]是第 i 个查询的答案。由于查询的结果可能非常大,请你将每个 answers[i] 都对 10^9 + 7 取余 。

示例

示例 1:

输入:n = 15, queries = [[0,1],[2,2],[0,3]]
输出:[2,4,64]
解释:
对于 n = 15 ,得到 powers = [1,2,4,8] 。没法得到元素数目更少的数组。
第 1 个查询的答案:powers[0] * powers[1] = 1 * 2 = 2 。
第 2 个查询的答案:powers[2] = 4 。
第 3 个查询的答案:powers[0] * powers[1] * powers[2] * powers[3] = 1 * 2 * 4 * 8 = 64 。
每个答案对 109 + 7 取余得到的结果都相同,所以返回 [2,4,64] 。

示例 2:

输入:n = 2, queries = [[0,0]]
输出:[2]
解释:
对于 n = 2, powers = [2] 。
唯一一个查询的答案是 powers[0] = 2 。答案对 109 + 7 取余后结果相同,所以返回 [2] 。

题目解析

(题目意思很简单,但是给这个题目做翻译的人翻译的很差劲,所以得重新译一遍)

给你一个正整数 n,你需要找到一个数组 powers。这个数组包含若干个 2 的幂(比如 1, 2, 4, 8, 16...),它们加起来正好等于 n。并且,powers 数组要用最少数量的 2 的幂,同时数组里的数必须是从小到大排列的(非递减)。题目保证这种找法是唯一的。

然后,你还得到一个二维数组 queries,里面每个元素是 [left, right]。对于每一个 [left, right],你需要计算 powers 数组中从 left下标到 right下标(包含两端)的所有数字的乘积

最后,因为乘积可能会非常大,所以你计算出来的每个乘积都要对 10^9 + 7 取余。最后把所有查询的结果放在一个数组里返回。

1.题目与“2的幂”关联

这个正整数n是这个数组相加起来的结果,很容易想到每一个数都有它相对应的二进制数。

例如,数字 13。它的二进制是 1101

2.powers数组是由2的幂组成的

每一个数都有它相对应的唯一二进制数,我们只需要把在 n 的二进制表示中出现的那些 2^k 找出来,然后按从小到大的顺序排列就行了

  • 比如 n = 15,二进制是 1111,表示 15 = 8+4+2+1 = 2^3 + 2^2 + 2^1 + 2^0,所以 powers = [1, 2, 4, 8]
  • 比如 n = 12,二进制是 1100,表示 12 = 2^3 + 2^2,所以 powers = [4, 8]

3.如何处理查询 queries?

1.对于每个 [left, right],计算 powers[left] * powers[left+1] * ... * powers[right] 对 10^9 + 7取余。

我们可以写一个循环,从 left 遍历到 right,把每个 powers[j] 乘起来,每乘一次就取余。

# 伪代码
product = 1
for j from left to right:product = (product * powers[j]) % MOD

2.如果 queries 很多,或者 right - left 的距离很大,上面的循环可能会有点慢。有没有更快的办法?

所以我们需要利用指数的性质:2^a * 2^b = 2^(a+b)

也就是说,我们可以把 powers 数组中的每个元素看成是 2 的某个次方,计算乘积时只需要把这些次方加起来,然后再计算 2 的这个总次方。比如 powers = [1, 2, 4, 8],其实是 [2^0, 2^1, 2^2, 2^3],查询 [0, 2] 的乘积是 2^0 * 2^1 * 2^2 = 2^(0+1+2) = 2^3 = 8

这样可以避免直接计算大数。

4.如何快速计算 2 的高次方并取模?

  • 我们需要计算 2^x mod (10^9 + 7),其中 x 可能很大。
  • 直接用 2^x 会溢出,所以需要用到快速幂算法
  • 快速幂的核心思想是把指数表示成二进制形式,每次只处理一位。(后面代码解析会详细说明语法)

完整代码

class Solution(object):def productQueries(self, n, queries):""":type n: int:type queries: List[List[int]]:rtype: List[int]"""MOD = 10**9 + 7# 第一步:把 n 表示成 2 的幂的和,得到 powers 数组powers = []power = 0while n > 0:if n & 1:  # 如果 n 的最低位是 1powers.append(1 << power)  # 加入 2^powern >>= 1  # n 右移一位power += 1# 第二步:把 powers 数组中的每个元素表示成 2 的指数# 比如 powers = [1, 2, 4, 8] 表示成 exponents = [0, 1, 2, 3]exponents = []for p in powers:exp = 0while (1 << exp) != p:exp += 1exponents.append(exp)# 第三步:快速幂算法,计算 2^x mod MODdef quick_pow(base, exp, mod):if exp == 0:return 1result = 1base %= modwhile exp > 0:if exp & 1:  # 如果 exp 的最低位是 1result = (result * base) % modbase = (base * base) % modexp >>= 1return result# 第四步:处理每个查询answers = []for left, right in queries:# 计算范围内所有指数的和total_exp = sum(exponents[left:right + 1])# 用快速幂计算 2^total_exp mod MODresult = quick_pow(2, total_exp, MOD)answers.append(result)return answers

代码解析

代码结构

代码分为四个主要部分:

  1. 把 n 表示成 2 的幂的和,得到 powers 数组。
  2. 把 powers 数组中的每个元素表示成 2 的指数,得到 exponents 数组。
  3. 实现快速幂算法,用于计算 2^x mod MOD
  4. 处理每个查询,计算答案。

详细解析

  1. 第一部分:得到 powers 数组

    powers = []
    power = 0
    while n > 0:if n & 1:  # 如果 n 的最低位是 1powers.append(1 << power)  # 加入 2^powern >>= 1  # n 右移一位power += 1
    • 这一部分的作用是把 n 表示成 2 的幂的和。
    • n & 1 是位运算,检查 n 的最低位是否为 1。如果是 1,说明需要加入 2^power
    • 1 << power 是位运算,表示 2^power
    • n >>= 1 是位运算,把 n 右移一位,相当于除以 2。
    • 比如 n = 15,二进制是 1111,循环过程如下:
      • 第一轮:n = 1111,最低位是 1,加入 2^0 = 1n 右移变成 111
      • 第二轮:n = 111,最低位是 1,加入 2^1 = 2n 右移变成 11
      • 第三轮:n = 11,最低位是 1,加入 2^2 = 4n 右移变成 1
      • 第四轮:n = 1,最低位是 1,加入 2^3 = 8n 右移变成 0
      • 结束,得到 powers = [1, 2, 4, 8]
  2. 第二部分:得到 exponents 数组

    exponents = []
    for p in powers:exp = 0while (1 << exp) != p:exp += 1exponents.append(exp)
    • 这一部分的作用是把 powers 数组中的每个元素表示成 2 的指数。
    • 比如 powers = [1, 2, 4, 8],我们需要得到 exponents = [0, 1, 2, 3],因为 1 = 2^0, 2 = 2^1, 4 = 2^2, 8 = 2^3
    • 对于每个 p,我们用一个循环找到 exp,使得 2^exp == p
  3. 第三部分:快速幂算法

    def quick_pow(base, exp, mod):if exp == 0:return 1result = 1base %= modwhile exp > 0:if exp & 1:  # 如果 exp 的最低位是 1result = (result * base) % modbase = (base * base) % modexp >>= 1return result
    • 这一部分的作用是快速计算 base^exp mod mod
    • 直接计算 base^exp 可能会导致数字非常大(比如 2^100),所以我们用快速幂算法。
    • 快速幂的核心思想是把指数 exp 表示成二进制形式,每次只处理一位。
    • 比如要计算 2^10 mod 1000000007
      • 10 的二进制是 1010
      • 从低位到高位处理:
        • 第 0 位是 0,不用乘。
        • 第 1 位是 1,乘上 2^2
        • 第 2 位是 0,不用乘。
        • 第 3 位是 1,乘上 2^8
      • 每次处理时,base 都会平方(base = base * base),这样可以快速得到高次方的值。
      • 每次乘法后都对 mod 取模,避免数字过大。
  4. 第四部分:处理查询

    answers = []
    for left, right in queries:# 计算范围内所有指数的和total_exp = sum(exponents[left:right + 1])# 用快速幂计算 2^total_exp mod MODresult = quick_pow(2, total_exp, MOD)answers.append(result)
    • 这一部分的作用是处理每个查询。
    • 对于每个查询 [left, right],我们需要计算 powers[left] * powers[left+1] * ... * powers[right]
    • 因为 powers 中的每个元素是 2 的某个次方,所以乘积可以表示成 2 的指数之和。
    • 比如 powers = [1, 2, 4, 8]exponents = [0, 1, 2, 3],查询 [0, 2]
      • 取出 exponents[0:3] = [0, 1, 2]
      • 计算指数之和:0 + 1 + 2 = 3
      • 计算 2^3 mod 1000000007 = 8
      • 答案是 8。
    • 最后把所有答案存入 answers 数组。

时间复杂度

  1. 第一部分:得到 powers 数组

    • 我们用一个 while 循环处理 n,每次把 n 右移一位,直到 n 变成 0。
    • n 的二进制表示有 log n 位(因为 2 的多少次方可以表示 n)。
    • 所以这一部分的时间复杂度是 O(log n)
  2. 第二部分:得到 exponents 数组

    • 对于 powers 数组中的每个元素 p,我们用一个 while 循环找到对应的指数 exp
    • powers 数组的长度最多是 log n(因为 n 的二进制表示最多有 log n 位 1)。
    • 对于每个 p,找到 exp 的循环次数最多是 log p,而 p 最大是 n,所以最坏情况下是 O(log n)
    • 总的时间复杂度是 O(log n * log n),但实际上 p 的值是递增的,平均复杂度更低。
  3. 第三部分:快速幂算法

    • 快速幂算法的时间复杂度是 O(log exp),因为我们把指数 exp 表示成二进制形式,每次处理一位。
    • 在我们的题目中,exp 是指数之和,最坏情况下可能是 O(log n) 级别的(因为 n 本身是 2 的幂的和)。
  4. 第四部分:处理查询

    • 假设有 q 个查询(q 是 queries 的长度)。
    • 对于每个查询,我们需要计算范围内指数的和,时间复杂度是 O(log n)(因为 powers 数组的长度最多是 log n)。
    • 然后用快速幂计算结果,时间复杂度是 O(log exp),其中 exp 最坏情况下是 O(log n)
    • 所以每个查询的复杂度是 O(log n)
    • 总的时间复杂度是 O(q * log n)

总时间复杂度

把所有部分加起来:

  • 第一部分:O(log n)
  • 第二部分:O(log n * log n)(实际更低)。
  • 第四部分:O(q * log n)

总的时间复杂度是 O(log n * log n + q * log n)。通常我们取最高项,所以是 O(q * log n)(因为 q 通常比 log n 大得多)。

空间复杂度

  • powers 数组和 exponents 数组的长度最多是 O(log n)
  • answers 数组的长度是 O(q)
  • 所以总的空间复杂度是 O(log n + q)

欢迎点赞、关注、收藏

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/92976.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/92976.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ceph数据副本机制详解

Ceph 数据副本机制详解 Ceph 的数据副本机制是其保证数据可靠性和高可用性的核心设计&#xff0c;主要通过多副本&#xff08;Replication&#xff09; 和 纠删码&#xff08;Erasure Coding&#xff0c;EC&#xff09; 两种方式实现。以下是对 Ceph 数据副本机制的全面解析&am…

【八股】Mysql中小厂八股

MySQL 基础 数据库三大范式&#xff08;中&#xff09; 第一范式: 要求数据库表的每一列都是不可分割的原子数据项 如详细地址可以分割为省市区等. 第二范式: 非主键属性必须完全依赖于主键, 不能部分依赖 第二范式要确保数据库表中的每一列都和主键相关, 而不能只与主键的某一…

怎么使用python查看网页源代码

使用python查看网页源代码的方法&#xff1a;1、使用“import”命令导入requests包import requests2、使用该包的get()方法&#xff0c;将要查看的网页链接传递进去&#xff0c;结果赋给变量xx requests.get(urlhttp://www.hao123.com)3、用“print (x.text)”语句把网页的内容…

C# 多线程:并发编程的原理与实践

深入探讨 C# 多线程&#xff1a;并发编程的原理与实践引言在现代应用开发中&#xff0c;性能和响应速度往往决定了用户体验的优劣。尤其在计算密集型或者IO密集型任务中&#xff0c;传统的单线程模型可能无法有效利用多核CPU的优势。因此&#xff0c;多线程技术成为了解决这些问…

react 常用组件库

1. Ant Design&#xff08;蚂蚁设计&#xff09;特点&#xff1a;国内最流行的企业级 UI 组件库之一&#xff0c;基于「中后台设计体系」&#xff0c;组件丰富&#xff08;表单、表格、弹窗、导航等&#xff09;、设计规范统一&#xff0c;支持主题定制和国际化。适用场景&…

Python 爬虫获取淘宝商品信息、价格及主图的实战指南

在电商数据分析、竞品调研或商品信息采集等场景中&#xff0c;获取淘宝商品的详细信息&#xff08;如价格、主图等&#xff09;是常见的需求。虽然淘宝开放平台提供了官方的 API 接口&#xff0c;但使用这些接口需要一定的开发和配置工作。本文将通过 Python 爬虫的方式&#x…

Ruby面向对象编程中类与方法的基础学习例子解析

代码示例&#xff1a; Ruby面向对象编程中类与方法的基础学习详细例子 1. 引言 在面向对象编程&#xff08;OOP&#xff09;中&#xff0c;类是定义对象结构和行为的蓝图。Ruby是一种纯面向对象的编程语言&#xff0c;它将一切视为对象&#xff0c;包括基本数据类型。本文将…

[ Mybatis 多表关联查询 ] resultMap

目录 一. resultMap 1. 使用场景: 2. 查询映射: (1)单表查询映射: (2)多表查询映射: a. 在学生表里查专业 b. 在专业表里查学生 二. 其他注意事项 1. 插件下载 2. #{ } 和 ${ }的区别 一. resultMap 1. 使用场景: (1)当数据库列名和java类中的属性名不同时,可⽤ r…

Rust 性能提升“最后一公里”:详解 Profiling 瓶颈定位与优化|得物技术

一、Profiling&#xff1a;揭示性能瓶颈的“照妖镜”在过去的一年里&#xff0c;我们团队完成了一项壮举&#xff1a;将近万核的 Java 服务成功迁移到 Rust&#xff0c;并收获了令人瞩目的性能提升。我们的实践经验已在《RUST练习生如何在生产环境构建万亿流量》一文中与大家分…

STM32H5 的 PB14 引脚被意外拉低的问题解析 LAT1542

关键字&#xff1a;STM32H5&#xff0c; GPIO 1. 问题现象 客户反馈&#xff0c;使用 STM32H523RET6 应用中配置了两个 IO 口&#xff0c;PC9 为输出模式&#xff0c;内部下拉&#xff1b;PB14 为输入模式&#xff0c;内部上拉。在程序中将 PC9 引脚输出高电平&#xff0c;结…

【办公自动化】如何使用Python让Word文档处理自动化?

在日常办公中&#xff0c;Word文档是最常用的文本处理工具之一。通过Python自动化Word文档操作&#xff0c;可以大幅提高工作效率&#xff0c;减少重复劳动&#xff0c;特别适合批量生成报告、合同、简历等标准化文档。本文将介绍几种常用的Python操作Word文档的方法&#xff0…

顺序表的总结及模拟实现

目录 一.线性表 二.顺序表 1.概念 2.结构 3.要实现的接口函数 三.模拟实现顺序表 1.定义出顺序表的基本结构 2.实现检查扩容功能 3.实现尾插 4.实现尾删 5.实现头插和头删 6.查找 7.修改 8.遍历 9.在指定位置插入和删除 四.顺序表的优缺点及思考 a.顺序表的弊端 …

Vue3 vs Vue2:全面对比与面试宝典

文章目录Vue3 vs Vue2&#xff1a;全面对比与面试宝典引言&#xff1a;Vue框架的进化之路一、核心架构对比二、响应式系统的革命Vue2的响应式&#xff1a;像老式监控摄像头Vue3的响应式&#xff1a;像智能AI监控系统三、API风格的进化Vue2的Options API&#xff1a;像填表格Vue…

Java Web开发:Session与Cookie详细入门指南

在Web开发中&#xff0c;状态管理是核心需求之一。本文将深入讲解Java中Session和Cookie的使用方法&#xff0c;帮助你掌握用户状态管理的核心技术。 一、Session与Cookie基础概念 特性SessionCookie存储位置服务器内存/持久化存储客户端浏览器安全性较高&#xff08;敏感数据…

HTTPS与CA证书:安全通信全解析

CA&#xff08;Certificate Authority&#xff09;&#xff1a;证书颁发机构&#xff0c;负责签发和管理数字证书&#xff0c;验证证书持有者的身份。HTTPS&#xff1a;基于 SSL/TLS 协议的 HTTP&#xff0c;通过证书实现客户端与服务器的身份验证和数据加密。HTTPSHTTPSSL/TLS…

AI生成代码时代的商业模式重构:从“软件即产品”到“价值即服务”

2025年,全球AI代码生成市场规模突破63亿元(数据来源:《中国AI代码生成行业发展报告》),开发者效率提升40%以上,软件开发成本下降30%。这一技术浪潮正在颠覆传统软件行业的商业逻辑——当代码生成变得像文字编辑一样简单时,企业如何构建可持续的商业模式? 本文将从硬件…

C#特性与反射知识梳理

C#中的**特性&#xff08;Attributes&#xff09;和反射&#xff08;Reflection&#xff09;**是两个非常重要的概念&#xff0c;它们通常用于代码的元编程&#xff0c;允许你在运行时获取类型信息并对其进行操作。下面对这两个概念进行详细梳理&#xff1a;一、C#中的特性&…

SQL 语法详解

SQL 语法详解 引言 SQL&#xff08;Structured Query Language&#xff09;是一种用于数据库管理的标准语言&#xff0c;它允许用户进行数据的查询、更新、插入和删除等操作。SQL语法是数据库管理和编程的基础&#xff0c;本篇文章将详细介绍SQL的基本语法和常用操作&#xff0…

为什么 sim(3) 中的尺度 s 与旋转 R 相乘,而不是平移 t?

文章目录为什么 sim(3) 中的尺度 s 与旋转 R 相乘&#xff0c;而不是平移 t&#xff1f;1️⃣ sim(3) vs SE(3)&#xff1a;结构对比与核心差异2️⃣ 为什么尺度 s 不乘在 t 上&#xff1f;&#x1f6ab; 数学破坏&#xff1a;&#x1f9ed; 几何解释&#xff1a;3️⃣ t 是“相…

如何为你的 Docker 容器设置代理网络

一文搞定!如何为你的 Docker 容器设置代理网络(及一个最常见的“坑”) 你是否遇到过这样的窘境:在你的服务器上,代理工具(比如 Clash, V2Ray)运行得好好的,浏览器也能科学上网,但一旦把应用放进 Docker 容器,它就瞬间“失联”,无法访问外部世界? 别担心,这是每个…