青蛙在数组荷叶上发射火箭推进器,双指针却在数据荒原上演钟表匠魔术——算法狂欢节的极限操作

                                            ---- 贪心引擎×时空折叠术:两道让时间复杂度原地后空翻的微操实验

        当跳跃游戏从青铜升级到王者段位,你的青蛙不再满足于笨拙的蹦跶——
        它突然学会用动态规划给自己装上火箭推进器,在数组荷叶上轰出最骚走位;
        而另一边,两个指针正在数据沙漠表演量子纠缠,以光年速度折叠有序数组,从时空裂缝里精准揪出中位数幽灵…
        今天这两道题,将用贪心算法的涡轮增压系统和双指针的时空扭曲刀法,带你体验把算法玩成星际穿越的快感。

题目一:动态规划——跳跃游戏 II

题目描述

给定一个非负整数数组 nums,每个元素 nums[i] 表示从位置 i 最多可以跳跃的步数。初始位于数组的第一个位置(nums[0]),求到达最后一个位置的最少跳跃次数。题目保证可以到达最后一个位置。

示例: 输入:[2, 3, 1, 1, 4] 输出:2(从下标 0 跳到 1,再跳到 4

算法分析
  1. 动态规划解法

    • 状态定义dp[i] 表示到达位置 i 的最小跳跃次数。
    • 状态转移:对于每个 i,遍历所有能跳到 i 的位置 j(即 j + nums[j] >= i),并更新 dp[i] = min(dp[i], dp[j] + 1)
    • 复杂度:时间复杂度为 O(n²),空间复杂度为 O(n)
       
  2. 贪心解法

    • 核心思想:每次在当前可跳跃范围内选择能跳到最远的位置,从而最小化跳跃次数。
    • 关键变量
      • end:当前跳跃的边界。
      • maxPos:当前能跳到的最远位置。
      • steps:跳跃次数。
    • 过程:遍历数组,更新 maxPos,当 i == end 时进行一次跳跃,并更新 end = maxPos
    • 复杂度:时间复杂度为 O(n),空间复杂度为 O(1) 
考察点
  • 动态规划:状态定义与转移方程的构建。
  • 贪心算法:局部最优选择(每次跳跃到最远可达位置)的证明与应用。
#include <stdio.h>   // 标准输入输出库,提供printf等函数
#include <limits.h>  // 定义整数类型极限值,如INT_MAX

/**
 * @brief 计算到达数组末尾的最小跳跃次数(动态规划解法)
 * 
 * @param nums 非负整数数组,每个元素表示该位置可跳跃的最大步数
 * @param numsSize 数组的长度(元素个数)
 * @return int 返回到达数组末尾所需的最小跳跃次数
 */
int jump(int* nums, int numsSize) {
    /* 边界条件检查:当数组长度小于等于1时 */
    if (numsSize <= 1) {
        return 0;  // 已经在末尾或空数组,不需要跳跃
    }

    /* 定义并初始化dp数组:dp[i]表示到达位置i的最小跳跃次数 */
    int dp[numsSize];  // 创建动态规划数组
    dp[0] = 0;         // 初始化起始位置,跳跃次数为0

    /* 初始化dp数组:将所有位置初始化为INT_MAX表示暂时不可达 */
    for (int i = 1; i < numsSize; i++) {
        dp[i] = INT_MAX;  // 使用最大整数值表示初始状态不可达
    }

    /* 动态规划主循环:填充dp数组 */
    for (int i = 1; i < numsSize; i++) {          // 遍历每个目标位置i
        for (int j = 0; j < i; j++) {             // 检查所有可能到达i的起点j
            /* 检查是否可以从位置j跳跃到位置i */
            if (j + nums[j] >= i) {               // j位置的最大跳跃距离是否足够到达i
                /* 如果当前路径更优,则更新dp[i] */
                if (dp[j] + 1 < dp[i]) {         // 比较并更新最小跳跃次数
                    dp[i] = dp[j] + 1;           // 更新为更小的跳跃次数
                }
            }
        }
    }

    return dp[numsSize - 1];  // 返回最后一个位置的最小跳跃次数
}

/* 主函数:程序入口 */
int main() {
    /* 定义并初始化测试用例数组 */
    int nums[] = {3, 4, 1, 9, 14, 6, 2, 8, 8};  // 题目给定的输入数组
    int numsSize = sizeof(nums) / sizeof(nums[0]);  // 计算数组长度

    /* 调用jump函数计算最小跳跃次数 */
    int minJumps = jump(nums, numsSize);  // 获取计算结果

    /* 输出最终结果 */
    printf("Minimum number of jumps to reach the end: %d\n", minJumps);  // 打印最小跳跃次数

    /* 额外验证:输出nums[2]的值(题目特殊要求) */
    printf("Value of nums[2]: %d\n", nums[2]);  // 打印数组第三个元素的值

    return 0;  // 程序正常退出,返回0
}

输出结果:

题目二:双指针找中位数

题目描述

给定两个已排序的数组 AB,长度分别为 mn,要求在不合并数组的情况下,使用双指针找到两个数组的中位数。

示例: 输入:A = [1, 3], B = [2] 输出:2.0(合并后数组为 [1, 2, 3],中位数为 2

算法分析
  1. 双指针解法

    • 核心思想:模拟合并过程,通过双指针遍历两个数组,找到第 k 小的元素(k = (m + n) / 2)。
    • 关键变量
      • i 和 j:分别指向 A 和 B 的当前元素。
      • current 和 previous:记录当前和上一个遍历到的值,用于偶数长度时的中位数计算。
    • 过程:依次比较 A[i] 和 B[j],移动较小的指针,直到遍历到中位数位置。
    • 复杂度:时间复杂度为 O(m + n),空间复杂度为 O(1) 

 

#include <stdio.h>   // 标准输入输出头文件,提供printf等函数

/**
 * @brief 使用双指针法查找两个有序数组的中位数
 * @param A 第一个有序数组(升序排列)
 * @param m 数组A的元素个数
 * @param B 第二个有序数组(升序排列)
 * @param n 数组B的元素个数
 * @return double 返回两个数组合并后的中位数(浮点型)
 */
double findMedianSortedArrays(int* A, int m, int* B, int n) {
    /* 计算两个数组的总长度 */
    int total = m + n;
    
    /* 定义并初始化变量:
       current - 当前遍历到的元素值
       previous - 前一个遍历到的元素值(用于偶数长度计算) */
    int current = 0, previous = 0;
    
    /* 初始化双指针:
       i - 指向数组A的当前位置(初始为0)
       j - 指向数组B的当前位置(初始为0) */
    int i = 0, j = 0;

    /* 主循环:遍历到中位数位置(总长度的一半+1) */
    for (int k = 0; k <= total / 2; k++) {
        /* 保存前一个值(在current被覆盖前) */
        previous = current;

        /* 指针移动规则:
           1. 如果A数组还有元素(i < m)且(B数组已遍历完或A当前元素较小)
           2. 否则移动B数组的指针 */
        if (i < m && (j >= n || A[i] <= B[j])) {
            current = A[i];  // 取A的当前元素
            i++;             // A指针后移
        } else {
            current = B[j];  // 取B的当前元素
            j++;             // B指针后移
        }
    }

    /* 根据总长度的奇偶性返回中位数 */
    if (total % 2 == 0) {
        /* 偶数长度:取中间两个数的平均值 */
        return (previous + current) / 2.0;  // 注意除以2.0保证浮点结果
    } else {
        /* 奇数长度:直接返回中间的数 */
        return current;
    }
}

/* 主函数:程序入口 */
int main() {
    /* 打印功能说明 */
    printf("Find the median of two sorted arrays using the two-pointer method:\n");

    /* 定义并初始化测试用例数组 */
     int A[] = {5, 6, 7, 8, 89};      // 第一个有序数组
     int B[] = {10, 15, 16, 17, 19, 67, 89, 254, 698};  // 第二个有序数组
    
    /* 计算数组长度:
       sizeof(A)获取数组总字节数
       sizeof(A[0])获取单个元素字节数
       相除得到元素个数 */
    int m = sizeof(A) / sizeof(A[0]);
    int n = sizeof(B) / sizeof(B[0]);

    /* 调用函数计算中位数 */
    double median = findMedianSortedArrays(A, m, B, n);
    
    /* 输出结果,保留1位小数 */
    printf("Median: %.1f\n", median);  // 预期输出16.5

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

输出结果:

  1. 二分查找优化

    • 核心思想:利用数组有序性,通过二分查找快速定位中位数。
    • 关键步骤
      1. 确保 A 是较短的数组。
      2. 在 A 和 B 中分别找分割点 i 和 j,使得 A[i-1] <= B[j] 且 B[j-1] <= A[i]
      3. 根据分割点计算中位数。
    • 复杂度:时间复杂度为 O(log(min(m, n))),空间复杂度为 O(1)
考察点
  • 双指针:有序数组的遍历与合并模拟。
  • 数学性质:中位数的定义与奇偶长度处理。
  • 二分查找:利用有序性优化查找效率。
#include <stdio.h>   // 标准输入输出头文件,提供printf等函数
#include <limits.h>  // 定义整数类型极限值,如INT_MIN和INT_MAX

/**
 * @brief 返回两个整数中的较小值
 * @param a 第一个整数
 * @param b 第二个整数
 * @return 较小的整数值
 */
int min(int a, int b) {
    return a < b ? a : b;  // 三元运算符实现最小值比较
}

/**
 * @brief 返回两个整数中的较大值
 * @param a 第一个整数
 * @param b 第二个整数
 * @return 较大的整数值
 */
int max(int a, int b) {
    return a > b ? a : b;  // 三元运算符实现最大值比较
}

/**
 * @brief 使用二分查找法查找两个有序数组的中位数(优化版)
 * @param A 第一个有序数组(需保证长度较短)
 * @param m 数组A的长度
 * @param B 第二个有序数组
 * @param n 数组B的长度
 * @return 中位数(double类型)
 */
double findMedianSortedArraysOptimized(int* A, int m, int* B, int n) {
    /* 确保A是较短的数组:如果A比B长,交换A和B的位置 */
    if (m > n) {
        return findMedianSortedArraysOptimized(B, n, A, m);
    }

    /* 计算总长度和中位数的分割点:
       total - 两个数组的总长度
       half - 中位数的左分割点位置 */
    int total = m + n;
    int half = (total + 1) / 2;  // +1确保奇数长度时正确

    /* 初始化二分查找的边界:
       left - 当前查找范围的左边界
       right - 当前查找范围的右边界 */
    int left = 0, right = m;

    /* 开始二分查找循环 */
    while (left <= right) {
        /* 计算当前分割点:
           i - 数组A的分割位置
           j - 数组B的分割位置(由half-i决定) */
        int i = (left + right) / 2;  // A的分割点(中点)
        int j = half - i;            // B的分割点(保证左半部分总数为half)

        /* 处理边界情况(当分割点在数组边界时):
           使用INT_MIN/INT_MAX表示虚拟的无限小/大值 */
        int A_left = (i == 0) ? INT_MIN : A[i - 1];  // A分割点左侧的值
        int A_right = (i == m) ? INT_MAX : A[i];      // A分割点右侧的值
        int B_left = (j == 0) ? INT_MIN : B[j - 1];  // B分割点左侧的值
        int B_right = (j == n) ? INT_MAX : B[j];     // B分割点右侧的值

        /* 检查分割条件是否满足:
           1. A的左半部分最大值 <= B的右半部分最小值
           2. B的左半部分最大值 <= A的右半部分最小值 */
        if (A_left <= B_right && B_left <= A_right) {
            /* 找到正确的分割点,根据总长度奇偶性返回中位数 */
            if (total % 2 == 0) {
                // 偶数长度:取左右两部分最大值的平均
                return (max(A_left, B_left) + min(A_right, B_right)) / 2.0;
            } else {
                // 奇数长度:取左半部分的最大值
                return max(A_left, B_left);
            }
        }
        /* 调整二分查找范围 */
        else if (A_left > B_right) {
            // A的分割点太靠右,需要左移
            right = i - 1;
        } else {
            // A的分割点太靠左,需要右移
            left = i + 1;
        }
    }

    /* 理论上不会执行到这里(题目保证输入有效) */
    return 0.0;
}

/* 主函数:程序入口 */
int main() {
    /* 打印功能说明 */
    printf("Finding the median of two sorted arrays using binary search:\n");

    /* 定义并初始化测试用例数组 */
    int A[] = {4, 5, 6, 7, 8, 89};          // 第一个有序数组
    int B[] = {10, 16, 17, 19, 89, 254};    // 第二个有序数组

    /* 计算数组长度 */
    int m = sizeof(A) / sizeof(A[0]);  // 数组A的元素个数
    int n = sizeof(B) / sizeof(B[0]);  // 数组B的元素个数

    /* 调用优化版的中位数查找函数 */
    double median = findMedianSortedArraysOptimized(A, m, B, n);

    /* 输出结果,保留1位小数 */
    printf("Median: %.1f\n", median);  // 预期输出13.0

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

 

输出结果:

1
对比维度跳跃游戏 II双指针找中位数
问题类型最优化问题(最小跳跃次数)查找问题(中位数)
核心算法动态规划或贪心双指针或二分查找
时间复杂度O(n)(贪心)或 O(n²)(动态规划)O(m + n)(双指针)或 O(log(min(m, n)))(二分)
空间复杂度O(1)(贪心)或 O(n)(动态规划)O(1)
关键技巧贪心的局部最优选择双指针的同步移动或二分分割
难点证明贪心选择的正确性处理奇偶长度与边界条件

总结

  1. 算法选择

    • 跳跃游戏 II 更适合用贪心算法,因其能高效利用局部最优性。
    • 双指针找中位数在数据规模较小时可用双指针,大规模时需用二分查找优化。
  2. 复杂度对比

    • 跳跃游戏 II 的贪心解法(O(n))优于动态规划(O(n²))。
    • 双指针找中位数的二分解法(O(log(min(m, n))))显著优于双指针(O(m + n))。
  3. 思想共通性

    • 两者均通过减少无效计算(贪心的跳跃选择、二分的快速排除)来优化效率。
    • 均需处理边界条件(如跳跃的终点、中位数的奇偶性)。
  4. 实际应用

    • 跳跃游戏 II 可用于路径规划或资源分配问题。
    • 双指针找中位数是数据库查询和数据分析中的常见操作

现在你已解锁:
① 跳跃游戏的『火箭燃料配给学』(青蛙看了都想改行当航天员);
② 中位数狩猎的『时空折叠许可证』(连爱因斯坦都想偷看的操作手册)。
如果你的大脑像刚经历完星际跃迁的飞船引擎般发烫,请速在评论区输入『冷却液告急』;
如果觉得还能承受更高维的算法风暴,试试用反向贪心破解跳跃游戏,用量子指针同时定位三个中位数

博主:再次求波三连!

下期预告:请关注博主,每天上午会发布,如果你有什么问题,可以私信我也可以评论区留言

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

司铭鸿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值