【算法】力扣 713题:乘积小于 K 的子数组之深入思考

文章目录

  • 前言
  • 题目:乘积小于 K 的子数组
  • 参考思路
    • 方法一:滑动窗口
    • 方法二:二分查找
  • 参考题解
    • 方法一:滑动窗口解法
    • 方法二:二分查找解法
  • 深入思考
    • 浮点精度?
    • right - left + 1?
    • 二分法?
    • 哈希优化?


前言

在这里插入图片描述

本题与 力扣713题 相同


题目:乘积小于 K 的子数组

给你一个整数数组 nums 和一个整数 k ,请你返回子数组内所有元素的乘积严格小于 k 的连续子数组的数目。

示例 1:
输入:nums = [10,5,2,6], k = 100
输出:8

解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2]、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。

示例 2:
输入:nums = [1,2,3], k = 0
输出:0

提示:
1 <= nums.length <= 3 * 104
1 <= nums[i] <= 1000
0 <= k <= 106

参考思路

方法一:滑动窗口

注意!!这里的数字都是正数
联想之前的滑动窗口,如果满足非负数组的单调性

  • 也就是在非负数组中,子数组和随着窗口的扩展(右边界右移)是单调不减的

参考深入思考中的滑动窗口解析:【算法】力扣560题:和为 K 的连续子数组之深入思考

那么使用滑动窗口是非常好的办法!

  • 使用滑动窗口来维护一个满足条件的子数组范围 [left, right]。
  • 对于每个右边界 right,找到最小的左边界 left,使得窗口内的乘积小于 k。
  • 统计以 nums[right] 结尾的满足条件的子数组个数:right - left + 1。

但是要注意处理极端情况:if (k <= 1) return 0;

复杂度分析

  • 时间复杂度:O(n)
    每个元素最多被访问两次(一次加入窗口,一次移出窗口)。

  • 空间复杂度:O(1)
    只使用了常数级别的额外空间。

方法二:二分查找

如果有做过 和为k的子数组 这个题,可能会延伸思考,那么这题如果求乘积。那么我能不能也有前缀乘积的方法来解题呢?

可以!但是仔细一想前缀乘积会使得数字变得很大,从而溢出

所以我们对于数组中的每个元素 nums[i],取自然对数 ln(nums[i])。
乘积小于 k 的条件可以转换为:

nums[left] * nums[left+1] * ... * nums[right] < k

两边取对数之后

ln(nums[left]) + ln(nums[left+1]) + ... + ln(nums[right]) < ln(k)

此时一看,这不是我们熟悉的前缀和吗?
因此计算对数数组的前缀和

prefixSum[i] = ln(nums[0]) + ln(nums[1]) + ... + ln(nums[i-1])

对其变形
对于每个右边界 right,我们需要找到最小的左边界 left,使得:

prefixSum[right+1] - prefixSum[left] < ln(k)

之后,问题就转换成了找到一某一个 prefixSum 使其值 < ln(k)
所以核心就是:两边取对数将乘积问题转换为求和问题,然后利用前缀和 + 二分查找来解决

那么注意到数组的数字都是大于1的数字
所以计算前缀和数组是单调递增的,因此可以使用二分查找来高效地找到满足条件的 left。
这里可以选择直接调用库函数来实现二分查找,只需要快速找到第一个大于等于 ln(k)的位置即可

此时,可能有疑问
1.之前那题“子数组和为k”不是说不能用二分法吗?为什么这里又可以用?
2.那这里这题可以用哈希表优化来做吗?
参考答案可以看本文的第三部分:深入思考

复杂度分析

  • 时间复杂度:O(n log n)
    计算前缀乘积的时间复杂度是 O(n)。
    对于每个 j,二分查找的时间复杂度是 O(log n),总共有 n 个 j,因此二分查找的总时间复杂度是 O(n log n)。

  • 空间复杂度:O(n)
    需要存储前缀乘积数组。

二分法可以用来解决乘积小于 K 的子数组问题,但它的时间复杂度是 O(n log n),不如滑动窗口方法高效

参考题解

方法一:滑动窗口解法

class Solution {
public:int numSubarrayProductLessThanK(vector<int>& nums, int k) {if (k <= 1) return 0;int result = 0;int left = 0;int product = 1;for (int right = 0; right < nums.size(); ++right) {product *= nums[right];while (product >= k && left <= right) {product /= nums[left];++left;}result += right - left + 1;}return result;}
};

方法二:二分查找解法


class Solution {
public:int numSubarrayProductLessThanK(vector<int>& nums, int k) {if (k <= 1) return 0;int n = nums.size();vector<double> logPrefix(n + 1, 0.0);double logK = log(k); // ln(k)for (int i = 0; i < n; ++i) {logPrefix[i + 1] = logPrefix[i] + log(nums[i]);}int count = 0;// 对于每个右边界 right,使用二分查找找到最小的 leftfor (int right = 0; right < n; ++right) {// 找到第一个大于等于 target 的位置int left = upper_bound(logPrefix.begin(), logPrefix.begin() + right + 1, logPrefix[right + 1] - logK + 1e-10) - logPrefix.begin();count += right + 1 - left;}return count;}
};

深入思考

浮点精度?

在二分查找中,我们比较的是浮点数(prefixSum[mid] 和 target)。
由于浮点数的精度有限,直接比较可能会导致结果不准确。
例如,prefixSum[mid] 可能非常接近 target,但由于精度问题,prefixSum[mid] >= target 的判断可能会出错。

修正方法:
在比较时,添加一个很小的值(例如 1e-10)来避免浮点数精度问题。

这样可以确保 prefixSum[mid] 和 target 的比较更加准确。

right - left + 1?

为什么最后求解的答案是 right - left + 1?

为什么子数组个数是 right - left + 1?

由于子数组必须是连续的,因此以 nums[right] 结尾的子数组可以表示为:

[left, right], [left+1, right], [left+2, right], ..., [right, right]

简而言之,就是当在遍历左右边界的时候,子数组的个数是right - left + 1。表示以 nums[right] 结尾的满足条件的子数组个数。

right - left 表示从 left 到 right 之间的元素个数(不包括 left)。

  • +1 表示包括 left 本身。
  • 因此,right - left + 1 表示从 left 到 right 的所有子数组的个数

二分法?

首先思考这题为什么可以用二分法去查找

因为数组数字都是大于或等于 1 ,因此取对数后的前缀和数组是单调递增的,也就是越乘肯定是越大的
所以说可以使用二分法去快速查找

哈希优化?

这题可以效仿【算法】力扣560题:和为 K 的连续子数组之深入思考 使用哈希优化吗?

需要注意的是,哈希表方法通常用于解决“等于某个值”的问题
哈希表通常用于解决以下类型的问题:

  • 查找“等于某个值”的情况:例如,求和等于 k 的子数组。
  • 快速查找和更新:哈希表可以在 O(1) 时间内完成查找和更新操作。

在求和等于 K 的子数组问题中,哈希表的作用是记录前缀和的出现次数,从而快速判断是否存在子数组的和等于 k。具体来说:

  • 对于当前前缀和 sum,我们查找 sum - k 是否在哈希表中。
  • 如果存在,则说明存在若干个子数组的和等于 k。

而对于这道题:
在乘积小于 K 的子数组问题中,我们需要找到满足以下条件的子数组:

nums[left] * nums[left+1] * ... * nums[right] < k

取对数后:

ln(nums[left]) + ln(nums[left+1]) + ... + ln(nums[right]) < ln(k)

哈希表的核心功能是快速查找“等于某个值”的情况,而“小于”条件需要遍历哈希表中的所有键值对,时间复杂度会从 O(1) 退化为 O(n)。
例如,对于每个右边界 right,我们需要找到所有满足 prefixSum[left] > prefixSum[right+1] - ln(k) 的 left,这需要遍历哈希表中的所有前缀和。

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

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

相关文章

超声重建,3D重建 超声三维重建,三维可视化平台 UR 3D Reconstruction

1. 超声波3D重建技术的实现方法与算法 技术概述 3D超声重建是一种基于2D超声图像生成3D体积数据的技术&#xff0c;广泛应用于医学影像领域。通过重建和可视化三维结构&#xff0c;3D超声能够显著提高诊断精度和效率&#xff0c;同时减少医生的脑力负担。本技术文档将详细阐述…

Docker 部署 Graylog 日志管理系统

Docker 部署 Graylog 日志管理系统 前言一、准备工作二、Docker Compose 配置三、启动 Graylog 服务四、访问 Graylog Web 界面总结 前言 Graylog 是一个开源的日志管理平台&#xff0c;专为实时日志收集、分析和可视化设计。它支持强大的搜索功能&#xff0c;并且与 Elastics…

【图论】并查集的学习和使用

目录 并查集是什么&#xff1f; 举个例子 组成 父亲数组&#xff1a; find函数&#xff1a; union函数&#xff1a; 代码实现&#xff1a; fa[] 初始化code: find code&#xff1a; 递归实现: 非递归实现: union code : 画图模拟&#xff1a; 路径压缩&#xff1a…

FPGA-流水灯

Quartus中使用Verilog实现 根据之前所学内容&#xff0c;打开Quartus 软件&#xff0c;新建FPGA项目文件&#xff0c;建立好空项目过后&#xff0c;选择Verilog HDL File&#xff0c;因为我们要使用Verilog代码实现仿真。 详细操作可参考往期博客&#xff1a; FPGA 实验报告&a…

React19源码系列之createRoot的执行流程是怎么的?

2024年12月5日&#xff0c;react发布了react19版本。后面一段时间都将学习它的源码&#xff0c;并着手记录。 react官网&#xff1a;react19新特性 https://react.dev/blog/2024/12/05/react-19 在用vite创建react项目的使用&#xff0c;main.tsx主文件都会有以下代码。 //i…

全网首创/纯Qt/C++实现国标GB28181服务/实时视频/云台控制/预置位/录像回放和下载/事件订阅/语音对讲

一、前言说明 用纯Qt来实现这个GB28181的想法很久了&#xff0c;具体可以追溯到2014年&#xff0c;一晃十年都过去了&#xff0c;总算是整体的框架和逻辑都打通了&#xff0c;总归还是杂七杂八的事情多&#xff0c;无法静下心来研究具体的协议&#xff0c;最开始初步了解协议后…

Qt 实操记录:打造自己的“ QQ 音乐播放器”

目录 一.界面设计1.成品界面分析2.head界面实现3.body界面实现4.主界面设置(1).设置无标题栏与阴影效果(2).重写鼠标事件实现拖拽 二.自定义控件1.BtFrom界面设计2.推荐页面设计3.recBox页面设计4.recBoxItem页面设计(1).eventFilter介绍和使用(2).QJsonObject介绍和使用(3).向…

如何打造安全稳定的亚马逊采购测评自养号下单系统?

在当今的电商领域&#xff0c;亚马逊作为全球领先的在线购物平台&#xff0c;其商品种类繁多&#xff0c;用户基数庞大&#xff0c;成为了众多商家和消费者的首选。而对于一些需要进行商品测评或市场调研的用户来说&#xff0c;拥有一个稳定、安全的亚马逊账号体系显得尤为重要…

Python文字识别OCR

一.引言 文字识别&#xff0c;也称为光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;&#xff0c;是一种将不同形式的文档&#xff08;如扫描的纸质文档、PDF文件或数字相机拍摄的图片&#xff09;中的文字转换成可编辑和可搜索的数据的技术。随着技…

如何在 Github 上获得 1000 star?

作为程序员&#xff0c;Github 是第一个绕不开的网站。我们每天都在上面享受着开源带来的便利&#xff0c;我相信很多同学也想自己做一个开源项目&#xff0c;从而获得大家的关注。然而&#xff0c;理想很丰满&#xff0c;现实却是开发了很久的项目仍然无人问津。 最近&#x…

汽车机械钥匙升级一键启动的优点

汽车机械钥匙升级一键启动的优点主要包括&#xff1a; 便捷性&#xff1a;一键启动功能的引入极大地提升了用车便捷性。车主无需翻找钥匙&#xff0c;只需在车辆感应范围内轻触启动键&#xff0c;即可轻松发动汽车。 安全性&#xff1a;移动管家专车专用一键启动系统配备了防…

[QT]深入理解Qt中的信号与槽机制

文章目录 信号与槽1. 信号和槽概述信号的本质槽的本质说明 2. 信号和槽的使用2.1 连接信号和槽2.2 查看内置信号和槽2.3 通过 Qt Creator 生成信号槽代码 3. 自定义信号和槽3.1 基本语法3.2 带参数的信号和槽**示例1&#xff1a;重载信号槽****示例2&#xff1a;信号槽参数列表…

Axure设计之下拉多选框制作教程C(中继器)

利用Axure制作下拉多选器组件可以极大地提升原型制作的效率和效果。以下是基于你提供的详细步骤的详细指导&#xff0c;帮助你在Axure中实现一个功能完善、高保真且可复用的下拉多选器组件。 一、案例预览 预览地址&#xff1a;https://pghy0i.axshare.com 实现效果包括&#…

STC89C52单片机学习——第25节: [11-1]蜂鸣器

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.03.18 51单片机学习——第25节: [11-1]蜂鸣器 前言开发板说明引用解答和科普一、蜂鸣器…

Linux上的`i2c-tools`工具集的详细介绍;并利用它操作IMX6ULL的I2C控制器进而控制芯片AP3216C读取光照值和距离值

IC-Tools 工具集介绍 i2c-tools 是 Linux 下用于 IC 设备调试 的用户空间工具集(你也可以把它看成是一个库&#xff0c;类似于之前自己用过的触摸屏库tslib库、FreeType矢量字符库)&#xff0c;它提供了一系列命令行工具&#xff0c;可以扫描、读取、写入 IC 设备&#xff0c;…

《CircleCI:CircleCI:解锁软件开发持续集成(CI)和持续部署(CD)高效密码》:此文为AI自动生成

《CircleCI&#xff1a;CircleCI&#xff1a;解锁软件开发持续集成&#xff08;CI&#xff09;和持续部署&#xff08;CD&#xff09;高效密码》&#xff1a;此文为AI自动生成 一、CircleCI 初印象 在当今软件开发的快节奏赛道上&#xff0c;持续集成&#xff08;CI&#xff0…

LinuX---Shell脚本创建和执行

概述&#xff1a; 它是一个命令行解释器&#xff0c;接收应用程序/用户命令&#xff0c;然后调用操作系统内核。 Shell还是一个功能强大的编程语言&#xff0c;易编写、易调试、灵活性强。 Linux提供的Shell解析器有 atguiguubuntu:~$ cat /etc/shells # /etc/shells: valid …

再学:Solidity数据类型

目录 1.uint&#xff1a;无符号整型 2.引用类型 3.数组 4.注意gas的消耗 ​编辑 5.映射 1.uint&#xff1a;无符号整型 注意能容纳的最大值和最小值 2.引用类型 值类型赋值 相当于 拷贝 若拷贝开销过大&#xff0c;可以考虑引用类型。 memory&#xff1a;只存在于函数内部…

Docker Desktop配置国内镜像源教程

在使用 Docker 时&#xff0c;由于默认镜像源在国外&#xff0c;经常会遇到下载速度慢、连接超时等问题。本文将详细介绍如何在 Windows 系统中为 Docker 配置国内镜像源&#xff0c;以提升镜像拉取速度。 常用国内镜像源 https://docker.1ms.run清华镜像源 https://docker.m…

C#中SerialPort 的使用

最近在学习C#的SerialPort &#xff0c;关于SerialPort 的使用&#xff0c;做如下总结&#xff1a; 1.可以通过函数System.IO.Ports.SerialPort.GetPortNames() 将获得系统所有的串口名称。C#代码如下&#xff1a; string[] sPorts SerialPort.GetPortNames(); foreach(stri…