算法江湖的奇妙冒险:质数间隔寻宝 vs 等差数列探案

#王者杯·14天创作挑战营·第1期#

引言

        欢迎来到算法江湖!今天我们将化身两种不同的角色——“质数猎人”“等差数列侦探”,展开一场智慧与策略的较量。

        第一个任务,我们要在数字丛林中寻找相隔最远的质数宝藏,还要确保宝藏之间的路上没有其他质数埋伏;第二个任务,我们要化身侦探,在数列迷宫中用双指针魔法,揪出所有隐藏的等差数列三元组!

        这两个问题看似毫无关联,实则都暗藏数学规律算法设计的精妙博弈。究竟哪种解法更巧妙?哪种思路更高效?让我们拨开迷雾,一探究竟!


正文


一、质数猎人的寻宝挑战(排序+数学)

任务描述

在给定数组中,找到两个质数,它们之间的间隔最大,且这两个质数之间的所有数必须都是非质数。这就像在数字沙漠中寻找两座最远的绿洲,且中途不能有其他水源。

示例
[2,5,9,11,15] → 选择 2 和 11,间隔为 9,中间的数 9 和 15 都是非质数。

算法策略

  1. 质数筛选术

    • 快速判断一个数是否为质数(仅能被 1 和自身整除),优化方法:只需检查到其平方根。

  2. 排序魔法

    • 将数组排序后,质数的位置一目了然,方便寻找最大间隔。

  3. 双指针扫描

    • 从最左端(最小质数)和最右端(最大质数)开始,向中间收缩,验证中间数是否全为非质数。

核心难点

  • 必须确保两个质数之间的所有数都是“荒地”(非质数),否则宝藏会被中途拦截!

    
    #include <stdio.h> // 包含标准输入输出库,用于printf和scanf等函数
    #include <stdbool.h> // 包含布尔类型定义(true和false)
    #include <stdlib.h> // 包含标准库函数,如qsort
    
    /**
     * @brief 判断一个数是否为质数(优化版)
     * @param num 待判断的整数
     * @return true-是质数 false-不是质数
     */
    bool is_prime(int num) { // 定义一个函数,用于判断一个数是否为质数
        if (num <= 1) return false;    // 如果数字小于等于1,直接返回false,因为1及以下不是质数
        if (num == 2) return true;     // 2是唯一的偶质数,直接返回true
        if (num % 2 == 0) return false;// 如果数字是偶数(除了2),直接返回false
    
        // 只需检查到平方根,且跳过偶数因子
        for (int i = 3; i * i <= num; i += 2) { // 从3开始,每次加2(只检查奇数)
            if (num % i == 0) return false; // 如果能被i整除,则不是质数
        }
        return true; // 如果没有被任何数整除,则是质数
    }
    
    /**
     * @brief 用于qsort的比较函数(升序排列)
     * @param a 第一个元素的地址
     * @param b 第二个元素的地址
     * @return 两元素的差值
     */
    int compare(const void *a, const void *b) { // 定义比较函数,用于qsort排序
        return (*(int*)a - *(int*)b); // 将void指针强制转换为int指针,并返回两个值的差
    }
    
    /**
     * @brief 寻找最大质数间隔的核心算法
     * @param arr 原始数组
     * @param size 数组长度
     */
    void find_max_prime_gap(int arr[], int size) { // 定义函数,用于寻找最大质数间隔
        // 阶段1:数组排序
        qsort(arr, size, sizeof(int), compare); // 使用qsort对数组进行升序排序
    
        // 阶段2:质数索引提取(记录排序后的数组索引)
        int prime_indices[size];      // 定义一个数组,用于存储质数的索引
        int prime_count = 0;          // 定义一个计数器,用于记录质数的数量
    
        for (int i = 0; i < size; i++) { // 遍历排序后的数组
            if (is_prime(arr[i])) { // 如果当前元素是质数
                prime_indices[prime_count++] = i; // 将其索引存入prime_indices数组,并计数器加1
            }
        }
    
        // 边界条件处理
        if (prime_count < 2) { // 如果质数数量小于2,无法形成间隔
            printf("Error: Need at least 2 primes\n"); // 输出错误信息
            return; // 直接返回
        }
    
        // 阶段3:寻找最大合法间隔
        int max_gap = 0; // 定义变量,用于存储最大间隔
        int left = arr[prime_indices[0]], right = arr[prime_indices[0]]; // 初始化左右边界
    
        // 双重循环遍历所有质数对
        for (int i = 0; i < prime_count; i++) { // 外层循环,遍历每个质数
            for (int j = i + 1; j < prime_count; j++) { // 内层循环,遍历当前质数之后的质数
                int start = prime_indices[i];  // 获取左质数在排序数组中的索引
                int end = prime_indices[j];    // 获取右质数在排序数组中的索引
                int current_gap = arr[end] - arr[start]; // 计算当前质数对的间隔
    
                // 剪枝:如果当前间隔更小则跳过
                if (current_gap <= max_gap) continue; // 如果当前间隔不大于已知最大间隔,则跳过
    
                // 关键验证:检查排序数组中两质数之间的元素是否全为非质数
                bool all_non_prime = true; // 定义标志变量,假设所有元素都是非质数
                for (int k = start + 1; k < end; k++) { // 遍历两质数之间的元素
                    if (is_prime(arr[k])) { // 如果发现质数
                        all_non_prime = false; // 标志变量置为false
                        break; // 跳出循环
                    }
                }
    
                // 更新最大间隔
                if (all_non_prime && current_gap > max_gap) { // 如果验证通过且当前间隔更大
                    max_gap = current_gap; // 更新最大间隔
                    left = arr[start]; // 更新左边界
                    right = arr[end]; // 更新右边界
                }
            }
        }
    
        // 结果输出
        if (max_gap > 0) { // 如果找到合法的最大间隔
            printf("Max prime gap: %d (%d -> %d)\n", max_gap, left, right); // 输出最大间隔及其对应的质数对
        } else { // 如果没有找到合法的间隔
            printf("No valid prime pairs found\n"); // 输出提示信息
        }
    }
    
    int main() { // 主函数
        // 测试用例(包含乱序元素)
        int test_case[] = {5, 2, 9, 15, 11}; // 定义一个测试数组
        int size = sizeof(test_case) / sizeof(test_case[0]); // 计算数组长度
    
        // 运行算法
        printf("The initial array is: [");// 提示输出原始数组
        for (int i = 0; i < size; i++) {// 遍历数组并输出每个元素
            // 条件表达式控制逗号格式:首元素无前导逗号
            printf(i == 0 ? "%d" : ", %d", test_case[i]);
        }
        printf("]\n");
    
        find_max_prime_gap(test_case, size); // 调用函数,寻找最大质数间隔
    
        return 0; // 程序正常结束
    }
    
    
    

    输出结果;

二、等差数列侦探的破案之旅(双指针+数学)

任务描述

在有序数组中,找出所有满足条件的三元组 (i,j,k),其中 i<j<k,且 nums[j]-nums[i] = nums[k]-nums[j]。这就像在时间轴上寻找三个完美对齐的事件点,形成均匀的时间间隔。

示例
[2,4,6,8] 包含 2个有效三元组:

  • (0,1,2) → 2,4,6(公差 2

  • (1,2,3) → 4,6,8(公差 2

算法策略

  1. 固定中间点

    • 以每个元素 j 作为中间点,向左右两侧展开侦查。

  2. 双指针魔法

    • 左指针 i 从 j-1 向左移动,右指针 k 从 j+1 向右移动,寻找满足等差条件的组合。

  3. 差值平衡术

    • 比较 nums[j]-nums[i] 和 nums[k]-nums[j],动态调整指针,避免暴力枚举。

核心难点

  • 处理重复元素时需小心,避免漏判或重复计数!

#include <stdio.h>  // 标准输入输出头文件

/**
 * @brief 计算满足等差数列条件的三元组数量
 * @param nums 已排序的整数数组指针
 * @param numsSize 数组元素个数
 * @return 满足条件的三元组数量
 */
int countArithmeticTriplets(int* nums, int numsSize) {
    int count = 0;  // 初始化计数器为0

    // 遍历所有可能的中间点j(从第2个元素到倒数第2个元素)
    for (int j = 1; j < numsSize - 1; j++) {
        int i = j - 1;  // 左指针初始在j的前一个位置
        int k = j + 1;  // 右指针初始在j的后一个位置

        // 双指针向两端扩展寻找满足条件的三元组
        while (i >= 0 && k < numsSize) {
            int diff = nums[j] - nums[i];      // 计算左差值
            int target = nums[j] + diff;      // 计算期望的右值

            // 检查是否满足等差条件
            if (nums[k] == target) {
                count++;  // 找到一个有效三元组,计数器加1

                /* 处理左侧重复元素(虽然本题示例没有重复元素,
                   但这是为了代码的通用性) */
                int left = i - 1;
                while (left >= 0 && nums[left] == nums[i]) {
                    count++;    // 每个重复元素都构成新的三元组
                    left--;    // 继续向左检查
                }

                /* 处理右侧重复元素 */
                int right = k + 1;
                while (right < numsSize && nums[right] == nums[k]) {
                    count++;    // 每个重复元素都构成新的三元组
                    right++;   // 继续向右检查
                }

                // 移动双指针继续寻找其他可能的三元组
                i--;  // 左指针左移
                k++;  // 右指针右移
            }
            // 如果当前右值小于期望值,需要增大右值
            else if (nums[k] < target) {
                k++;  // 右指针右移
            }
            // 如果当前右值大于期望值,需要减小左值
            else {
                i--;  // 左指针左移
            }
        }
    }

    return count;  // 返回找到的三元组总数
}

/**
 * @brief 打印数组内容
 * @param arr 数组指针
 * @param size 数组大小
 */
void printArray(int* arr, int size) {
    printf("[");  // 打印左括号
    for (int i = 0; i < size; i++) {
        printf("%d", arr[i]);  // 打印当前元素
        if (i != size - 1) printf(", ");  // 如果不是最后一个元素,打印逗号
    }
    printf("]");  // 打印右括号
}

int main() {
    // 测试用例
    int nums[] = {2, 4, 6, 8};
    // 计算数组长度
    int numsSize = sizeof(nums) / sizeof(nums[0]);

    // 打印初始数组
    printf("Input array: ");
    printArray(nums, numsSize);
    printf("\n");

    // 计算并输出结果
    int result = countArithmeticTriplets(nums, numsSize);
    printf("Number of arithmetic triplets: %d\n", result);

    return 0;  // 程序正常退出
}

 输出结果:


三、终极对决:算法对比图

对比维度质数间隔最大和双指针等差子数组
核心思想排序+质数筛法+区间验证固定中间点+双指针差值平衡
时间复杂度O(n log n)(排序主导)O(n²)(双指针扫描)
空间复杂度O(n)(存储质数索引)O(1)(原地操作)
适用场景质数相关问题的极值查找有序数组的等差模式匹配
杀手锏质数快速判断+区间全非质数验证双指针高效跳转+差值动态平衡

四、总结:数学与算法的交响曲

  • 质数间隔问题像一场战略布局,需要先排序整理战场,再用质数筛法精准定位目标,最后用区间验证确保路径安全。

  • 等差子数组问题则像一场动态博弈,通过固定中间点稳住阵脚,再用双指针灵活调整,捕捉差值平衡的瞬间。

二者的共同灵魂在于:利用数学规律减少计算量,通过策略性扫描避免暴力枚举。无论是质数的平方根优化,还是双指针的差值平衡,都彰显了算法设计中“四两拨千斤”的智慧。


结语

今天的算法江湖之旅是否让你意犹未尽?无论是化身质数猎人还是等差数列侦探,算法世界的奇妙冒险永无止境。

互动话题

  • 如果你设计一个算法游戏,会加入怎样的数学挑战?

  • 在现实中,你觉得这两个算法能解决哪些有趣的问题?

欢迎在评论区分享你的脑洞!下次我们将解锁更多算法副本,敬请期待!

江湖路远,算法为伴,我们下期再见! 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

司铭鸿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值