首页 > 其他分享 >【力扣 - 每日一题】3115. 质数的最大距离(一次遍历、头尾遍历、空间换时间、埃式筛、欧拉筛、打表)Golang实现

【力扣 - 每日一题】3115. 质数的最大距离(一次遍历、头尾遍历、空间换时间、埃式筛、欧拉筛、打表)Golang实现

时间:2024-07-02 22:28:15浏览次数:3  
标签:遍历 minPos idx nums int 质数 elem maxPos 3115

原题链接

题目描述

给你一个整数数组 nums。
返回两个(不一定不同的)质数在 nums 中 下标 的 最大距离。

示例 1:

输入: nums = [4,2,9,5,3]
输出: 3
解释: nums[1]、nums[3] 和 nums[4] 是质数。因此答案是 |4 - 1| = 3。

示例 2:

输入: nums = [4,8,2,8]
输出: 0
解释: nums[2] 是质数。因为只有一个质数,所以答案是 |2 - 2| = 0。

提示:

1 < = n u m s . l e n g t h < = 3 ∗ 1 0 5 1 <= nums.length <= 3 * 10^5 1<=nums.length<=3∗105
1 < = n u m s [ i ] < = 100 1 <= nums[i] <= 100 1<=nums[i]<=100
输入保证 nums 中至少有一个质数。

思路1:一次遍历

函数checkIsPrime用于判断num是否为质数,时间复杂度为 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n))
一次遍历,维护minPos表示最小的质数位置,maxPos表示最大的质数位置,最后maxPos-minPos就是答案
维护的时候,如果该数是质数,更新maxPos;如果minPos未被更新过,即minPos为初始值-1,更新minPos

整体时间复杂度 O ( N ∗ s q r t ( M ) ) O(N*sqrt(M)) O(N∗sqrt(M))
代码如下:

func checkIsPrime(num int) bool {
    if num <= 1 {
        return false
    }
    for i := 2; i*i <= num; i ++ {
        if num % i == 0 {
            return false
        }
    }
    return true
}
func maximumPrimeDifference(nums []int) int {
    minPos,maxPos := -1,-1
    for idx,elem := range nums {
        if checkIsPrime(elem) {
            if minPos == -1 {
                minPos = idx
            }
            maxPos = idx
        }
    }
    return maxPos - minPos
}

在这里插入图片描述

思路2:分别从头尾遍历

在思路1的基础上考虑对maxPos的更新过程进行优化,含义为最大的质数出现的位置,所以倒序遍历找第一个质数即可。
极端情况下,最中间的数是质数,还是会把全部的数都判断一遍。

代码:

func checkIsPrime(num int) bool {
    if num <= 1 {
        return false
    }
    for i := 2; i*i <= num; i ++ {
        if num % i == 0 {
            return false
        }
    }
    return true
}
func maximumPrimeDifference(nums []int) int {
    minPos,maxPos := -1,-1
    for idx,elem := range nums {
        if checkIsPrime(elem) {
            minPos = idx
            break
        }
    }
    for idx := len(nums) - 1; idx >= 0; idx -- {
        if checkIsPrime(nums[idx]) {
            maxPos = idx 
            break
        }
    }

    return maxPos - minPos
}

在这里插入图片描述

思路3:标记结果 空间换时间

在思路1的基础上,考虑有的数如果重复出现的话,会被重复判断。
额外开辟map,存储该数是否为素数,空间换时间。
代码如下:

func checkIsPrime(num int) bool {
    if num <= 1 {
        return false
    }
    for i := 2; i*i <= num; i ++ {
        if num % i == 0 {
            return false
        }
    }
    return true
}
func maximumPrimeDifference(nums []int) int {
    minPos,maxPos := -1,-1
    mp := make(map[int]bool,len(nums))
    for idx,elem := range nums {
        if flag,ok := mp[elem]; ok {
            if flag {
                if minPos == -1 {
                minPos = idx
                }
                maxPos = idx
            }
            continue
        }
        if checkIsPrime(elem) {
             if minPos == -1 {
                minPos = idx
            }
            maxPos = idx
            mp[elem] = true
        }else{
            mp[elem] = false
        }
    }
    return maxPos - minPos
}

实际上并没有优化时间,很奇怪
在这里插入图片描述

思路4:埃式筛

可以考虑使用素数筛预处理得到所有质数,其中埃式筛的时间复杂度是 O ( n l o g l o g n ) O(nloglogn) O(nloglogn)

埃式筛优化时间复杂度的原理:

考虑这样一件事情:对于任意一个大于 1 的正整数 n,那么它的 x 倍就是合数(x > 1)。利用这个结论,我们可以避免很多次不必要的检测。
如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数了。

 //埃式筛 
func InitPrime(maxNum int) map[int]struct{} {
    mp := make(map[int]struct{},maxNum)
    mp[1]  = struct{}{} //注意特判
    for i := 2; i <= maxNum; i ++ {
        if _,ok := mp[i]; ok { 
            continue
        }
        for j := 2*i; j <= maxNum; j += i {
            mp[j] = struct{}{} //非素数
        }
    }
    return mp
}
func maximumPrimeDifference(nums []int) int {
    maxNum := 0
    for _,elem := range nums {
        if maxNum < elem {
            maxNum = elem
        }
    }
    primeMap := InitPrime(maxNum)
    
    minPos,maxPos := -1,-1
    for idx,elem := range nums {
        if _,ok := primeMap[elem];!ok {
            if minPos == -1 {
                minPos = idx
            }
            maxPos = idx
        }
    }
    return maxPos - minPos
}

在这里插入图片描述

思路5:欧拉筛

欧拉筛是在埃氏筛的基础上优化的,时间复杂度为 O ( n ) O(n) O(n)

埃氏筛法仍有优化空间,它会将一个合数重复多次标记。有没有什么办法省掉无意义的步骤呢?答案是肯定的。
如果能让每个合数都只被标记一次,那么时间复杂度就可以降到 O(n) 了。

func InitPrime(maxNum int) map[int]struct{} {
    mp := make(map[int]struct{},maxNum)
    mp[1]  = struct{}{} //注意特判
    primes := make([]int,0,1000)
    for i := 2; i <= maxNum; i ++ {
        if _,ok := mp[i]; !ok { 
            primes = append(primes,i)
        }
        for j := 0; primes[j] <= maxNum/i; j++ {
            mp[primes[j]*i] = struct{}{} //非素数
            if i % primes[j] == 0 {
                break
            }
        }
    }
    return mp
}
func maximumPrimeDifference(nums []int) int {
    maxNum := 0
    for _,elem := range nums {
        if maxNum < elem {
            maxNum = elem
        }
    }
    primeMap := InitPrime(maxNum)
    
    minPos,maxPos := -1,-1
    for idx,elem := range nums {
        if _,ok := primeMap[elem];!ok {
            if minPos == -1 {
                minPos = idx
            }
            maxPos = idx
        }
    }
    return maxPos - minPos
}

在这里插入图片描述

思路6: 打表

考虑到 1 < = n u m s [ i ] < = 100 1 <= nums[i] <= 100 1<=nums[i]<=100,100以内的素数个数是有限的,离线把这些数据处理出来

func checkIsPrime(num int) bool {
    if num <= 1 {
        return false
    }
    for i := 2; i*i <= num; i ++ {
        if num % i == 0 {
            return false
        }
    }
    return true
}
func maximumPrimeDifference(nums []int) int {
    minPos,maxPos := -1,-1
    primes := []int{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97}
    mp := make(map[int]struct{},len(primes))
    for _,elem := range primes {
        mp[elem] = struct{}{}
    }
    numsLen := len(nums)

    for idx := 0; idx < numsLen; idx ++ {
        if _,ok := mp[nums[idx]];ok {
            minPos = idx
            break
        }
    }
    
    for idx := numsLen - 1; idx >= 0; idx -- {
         if _,ok := mp[nums[idx]];ok {
            maxPos = idx
            break
        }
    }

    return maxPos - minPos
}

在这里插入图片描述

标签:遍历,minPos,idx,nums,int,质数,elem,maxPos,3115
From: https://blog.csdn.net/LightOfNight/article/details/140136624

相关文章

  • (nice!!!)LeetCode 3164. 优质数对的总数 II(数组、哈希表)
    3164.优质数对的总数II思路:先找出可以被k整除的nums[i].方法一:统计因子。1、找出数组nums1每个元素的因子,用哈希表来记录每个因子出现的次数。然后再遍历数组nums2进行累加即可。classSolution{public:constintN=1e6+10;longlongnumberOfPairs(vec......
  • PHP与js遍历的区别,PHP运行原理学习
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title><?phpecho'PHP的第一......
  • 算法笔记:模拟过程(螺旋遍历矩阵)
    1模拟过程“模拟过程题”通常指的是那些要求编程者通过编写代码来“模拟”或重现某个过程、系统或规则的题目。这类题目往往不涉及复杂的数据结构或高级算法,而是侧重于对给定规则的精确执行和逻辑的清晰表达。其中螺旋遍历矩阵的题目就是一类典型的模拟过程题,需要精心设......
  • 二叉树层序遍历
    题目描述给你二叉树的根节点 root ,返回其节点值的 层序遍历 。(即逐层地,从左到右访问所有节点)。假设有这样一棵二叉树,那么它经过层序遍历的结果就应该是:[[3],[9,20],[15,7]]解法我们可以用广度优先搜索解决这个问题。按层打印:题目要求的二叉树的从上至下打印(即......
  • 代码随想录63——二叉树4——迭代遍历
    ......
  • 代码随想录第13天 | 二叉树part01 基础和遍历
    二叉树基础知识二叉树种类满二叉树满二叉树:如果一棵二叉树只有度为0和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树(子节点要么为0,要么为2)若满二叉树的深度为k(即k层,从1开始),则其节点个数为:2^k-1完全二叉树完全二叉树:从上到下,从左到右,都是连续的。满二叉树一......
  • Map集合的特点以及遍历方式
    文章目录前言一、认识Map集合二、Map集合体系体系图Map集合体系的特点三、Map集合常用方法四、Map集合的遍历遍历方式一遍历方式二遍历方式三五、HashMap底层原理HashMap的特点六、LinkedHashMap底层原理LinkedHashMap的特点七、TreeMap底层原理TreeMap的特点总结......
  • Python优雅遍历字典删除元素的方法
    在Python中,直接遍历字典并在遍历过程中删除元素可能会导致运行时错误,因为字典在迭代时并不支持修改其大小。但是,我们可以通过一些方法间接地达到这个目的。1.方法一:字典推导式创建新字典(推荐)常见的方法是创建一个新的字典,其中不包含我们想要删除的元素。这可以通过字典推导式(dic......
  • 算法题---二叉树层序遍历
    二叉树层序遍历:classTreeNode{TreeNodeleft;TreeNoderight;intvalue;}voidlevelTraversal(TreeNoderoot){Queue<TreeNode>q=newLinkedList<>();q.add(root);while(!q.isEmpty()){......
  • DEMO_02:随机数获取;数组集合遍历;整型与字符串转换;字符串字符遍历;数组/集合排序
    /***考核点:随机数获取;数组集合遍历;整型与字符串转换;字符串字符遍历;数组/集合排序*<p>*题目:*1.使用while循环获取20个五位数随机数并打印;*2.遍历20个数,筛选出随机数中3的倍数,并统计个数;*3.符合2的数中,找出五位数中3的倍数和位置*4.符合2的数中,把这五位数......