最大公因数等于 K 的子数组数目

简介: 最大公因数等于 K 的子数组数目

说在前面

🎈不知道大家对于算法的学习是一个怎样的心态呢?为了面试还是因为兴趣?不管是出于什么原因,算法学习需要持续保持。

题目描述

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 nums 的子数组中元素的最大公因数等于 k 的子数组数目。

子数组 是数组中一个连续的非空序列。

数组的最大公因数 是能整除数组中所有元素的最大整数。

示例 1:

输入:nums = [9,3,1,2,6,3], k = 3
输出:4
解释:nums 的子数组中,以 3 作为最大公因数的子数组如下:
- [9,3,1,2,6,3]
- [9,3,1,2,6,3]
- [9,3,1,2,6,3]
- [9,3,1,2,6,3]

示例 2:

输入:nums = [4], k = 7
输出:0
解释:不存在以 7 作为最大公因数的子数组。

提示:

  • 1 <= nums.length <= 1000
  • 1 <= nums[i], k <= 10^9

思路分析

首先我们要先理解一下题目的意思,题目会给我们一个整数数组 nums 和一个整数 k,我们需要统计并返回 nums 的子数组中元素的最大公因数等于 k 的子数组数目。

一个序列中最大公因数等于k需要满足以下条件:

  • 1、所有元素均为k的倍数;
  • 2、至少一个元素与其他所有元素的最大公因数都是k

知道了这两个条件之后我们便可以开始编写代码了:

  • 1、记录每个位置前后连续元素为k的倍数的个数;

因为一个序列中最大公因数等于k时,序列中所有元素均为k的倍数,所以我们可以先统计每个位置前后连续元素为k的倍数的个数。

const q1 = new Array(nums.length).fill(0);
const q2 = new Array(nums.length).fill(0);
for(let i = 0; i < q1.length; i++){
    if(i == 0){
        if(nums[i] % k == 0) q1[i] = 1;
        if(nums[q2.length - 1] % k == 0) q2[q2.length - 1] = 1;
    }else{
        if(nums[i] % k == 0) q1[i] = q1[i-1] + 1;
        if(nums[q2.length - 1 - i] % k == 0) q2[q2.length - 1 - i] = q2[q2.length - i] + 1;
    }
}
  • 2、遍历找到两个相邻元素之间的最大公因数为k的位置;

如果一个序列中有两个元素的最大公因数为k,且其他元素均可以被k整除,那么这个序列所有元素的最大公因数也为k,所以我们可以找到任意一组最大公因数为k的元素来进行计算。

for(let i = 0; i < nums.length; i++){
    if(nums[i] == k) res++;
    if(i < nums.length - 1 && gcd(nums[i],nums[i + 1]) == k){
        ……
    }
}
  • 3、计算当前位置可以组成满足条件的序列数。

如果找到一组相邻的元素他们的最大公因数为k,那么从当前位置向两边延伸,找到连续为k的倍数的元素组成的序列都为满足条件的序列。

如:nums = [3,3,4,1,2],k = 1

  • (1)、情况1(左边序列数 * 右边序列数 = 总序列数)

我们可以看到在下标i为1的时候,gcd(nums[i],nums[i + 1]) == k,此时从i往左看,我们可以得到两个连续元素均为k的倍数,[3,3];从i + 1往右看,我们可以得到三个连续元素均为k的倍数,[4,1,2]

我们只需计算两个数组的组合数即可,这里的子序列需要是连续的,所以我们可以得到的组合数应该是2 * 3 = 6;分别为:[3,4],[3,4,1],[3,4,1,2],[3,3,4],[3,3,4,1],[3,3,4,1,2];

  • (2)、情况2((左边序列数 - 上一次计算的左边序列数) * 右边序列数 = 总序列数)

第二个满足条件的下标i为2,此时从i往左看,我们可以得到两个连续元素均为k的倍数,[3,3,4];从i + 1往右看,我们可以得到三个连续元素均为k的倍数,[1,2]

我们只需计算两个数组的组合数即可,这里的子序列需要是连续的,所以我们可以得到的组合数应该是2 * 3 = 6;分别为:[4,1],[4,1,2],[3,4,1],[3,4,1,2],[3,3,4,1],[3,3,4,1,2],这时我们会发现,当前得出的结果与上一次得到的结果中有重复的子序列:[3,4,1],[3,4,1,2],[3,3,4,1],[3,3,4,1,2],因为左边的序列中[3,3]在上一次计算中已经计算过了,所以我们需要将这两个减去,可以得到(3 - 2) * 2 = 2,所以当前位置可以得到新的组合数为2;

  • (3)、子数组长度为1

题目中是这样定义的子数组:子数组 是数组中一个连续的非空序列。,所以在遇到nums[i] == k时,该元素可以单独成组。

let res = 0;
let flag = 0;
for(let i = 0; i < nums.length; i++){
    if(nums[i] == k) res++;
    if(i < nums.length - 1 && gcd(nums[i],nums[i + 1]) == k){
        res += (i > 0 ? (q1[i] - flag) : 1) * (q2[i + 1] || 1);
        flag = q1[i];
    }
    if(nums[i] % k !== 0) flag = 0;
}

完整AC代码如下:

AC代码

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
 var subarrayGCD = function(nums, k) {
    const gcd = (a, b) => {
        return a % b === 0 ? b : gcd(b, a % b);
    }
    const q1 = new Array(nums.length).fill(0);
    const q2 = new Array(nums.length).fill(0);
    for(let i = 0; i < q1.length; i++){
        if(i == 0){
            if(nums[i] % k == 0) q1[i] = 1;
            if(nums[q2.length - 1] % k == 0) q2[q2.length - 1] = 1;
        }else{
            if(nums[i] % k == 0) q1[i] = q1[i-1] + 1;
            if(nums[q2.length - 1 - i] % k == 0) q2[q2.length - 1 - i] = q2[q2.length - i] + 1;
        }
    }
    let res = 0;
    let flag = 0;
    for(let i = 0; i < nums.length; i++){
        if(nums[i] == k) res++;
        if(i < nums.length - 1 && gcd(nums[i],nums[i + 1]) == k){
            res += (i > 0 ? (q1[i] - flag) : 1) * (q2[i + 1] || 1);
            flag = q1[i];
        }
        if(nums[i] % k !== 0) flag = 0;
    }
    return res;
};

公众号

关注公众号『前端也能这么有趣』,获取更多有趣内容。

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

目录
相关文章
|
存储 API 流计算
Flink DataStream API-概念、模式、作业流程和程序
前几篇介绍了Flink的入门、架构原理、安装等,相信你对Flink已经了解入门。接下来开始介绍Flink DataStream API内容,先介绍DataStream API基本概念和使用,然后介绍核心概念,最后再介绍经典案例和代码实现。本篇内容:Flink DataStream API的概念、模式、作业流程和程序。
Flink DataStream API-概念、模式、作业流程和程序
|
NoSQL Java 测试技术
Golang内存分析工具gctrace和pprof实战
文章详细介绍了Golang的两个内存分析工具gctrace和pprof的使用方法,通过实例分析展示了如何通过gctrace跟踪GC的不同阶段耗时与内存量对比,以及如何使用pprof进行内存分析和调优。
364 0
Golang内存分析工具gctrace和pprof实战
|
存储 安全 大数据
对象存储的意义:探索数据新纪元的关键基石
在信息爆炸时代,数据成为核心资产,而高效安全的数据存储至关重要。对象存储作为一种新兴技术,起源于20世纪90年代,旨在解决传统文件系统的局限性。随着云计算和大数据技术的发展,它已成为关键技术之一。对象存储具备高可扩展性、高可靠性、低成本、易于管理和多协议支持等优点。它支撑大数据发展、推动云计算繁荣、助力企业数字化转型并保障数据安全。未来,对象存储将进一步提升性能,实现智能化管理,并与边缘计算融合,获得政策支持,成为数据新时代的关键基石。
421 3
|
负载均衡 Java Nacos
python flask服务如何注册到nacos
一文讲清楚python flask服务如何注册到nacos
509 2
python flask服务如何注册到nacos
|
消息中间件 存储 缓存
不看损失大了,刨根问底,Kafka消息中间件到底会不会丢消息
不看损失大了,刨根问底,Kafka消息中间件到底会不会丢消息
879 120
不看损失大了,刨根问底,Kafka消息中间件到底会不会丢消息
|
JavaScript 前端开发 API
jquery是什么-是否还有必要学-与JS的区别-学习技巧-文末附资料、案例、作业
jquery是什么-是否还有必要学-与JS的区别-学习技巧-文末附资料、案例、作业
264 0
|
数据库连接 数据库 关系型数据库
ETL工具 kettle
Kettle简介:Kettle是一款国外开源的ETL工具,纯java编写,可以在Window、Linux、Unix上运行,数据抽取高效稳定。Kettle 中文名称叫水壶,该项目的主程序员MATT 希望把各种数据放到一个壶里,然后以一种指定的格式流出。Kettle这个ETL工具集,它允许你管理来自不同数据库的数据,通过提供一个图形化的用户环境来描述你想做什么,而不是你想怎么做。Kettl
9891 0
|
数据库 开发者
Gremlin 语法入门| 学习笔记
快速学习 Gremlin 语法入门。
Gremlin 语法入门| 学习笔记
|
存储 NoSQL 算法
Redis中ZSet的底层数据结构跳跃表skiplist,你真的了解吗?
Redis中ZSet的底层数据结构跳跃表skiplist,你真的了解吗?
439 0