当前位置: 首页 > news >正文

除自身以外数组的乘积与加油站问题:算法揭示数据中的隐藏关系与环路行驶的最优解

博客引言:

       在我们的日常生活中,数据处理和环路行驶优化是两个重要的领域。今天,我们将通过两个有趣的问题,探索如何用算法来优化这些场景。

首先,我们将探讨除自身以外数组的乘积问题,看看如何高效地计算每个元素的乘积,同时避免使用除法。接着,我们将分析加油站问题,探讨如何通过算法找到绕环路行驶的最优出发点。通过这两个案例,你将看到算法如何在实际数据处理和环路优化中发挥作用,帮助我们更高效地管理和分析数据。

让我们一起进入算法的世界,探索这些优化问题背后的奥秘!


博客正文:

一、除自身以外数组的乘积:乘积计算的高效实现

场景描述:
给定一个整数数组nums,要求返回一个新数组answer,其中answer[i]等于nums中除nums[i]之外其余各元素的乘积。不能使用除法,并且要在O(n)的时间复杂度内完成。

算法核心:两次遍历与左右乘积数组
这个问题可以通过两次遍历和左右乘积数组来解决。具体步骤如下:

  1. 初始化两个数组:left和right。left[i]表示从左到右到i的乘积,right[i]表示从右到左到i的乘积。
  2. 计算left数组:从左到右遍历数组,left[i] = left[i-1] * nums[i-1]。
  3. 计算right数组:从右到左遍历数组,right[i] = right[i+1] * nums[i+1]。
  4. 计算answer数组:answer[i] = left[i] * right[i]。

详细分析:

  1. 初始化:创建两个数组left和right,大小与nums相同。
  2. 计算left数组:left[0] = 1,然后从i=1到n-1,left[i] = left[i-1] * nums[i-1]。
  3. 计算right数组:right[n-1] = 1,然后从i=n-2到0,right[i] = right[i+1] * nums[i+1]。
  4. 计算answer数组:answer[i] = left[i] * right[i]。

题目验证示例:

  • 示例1:nums = [1,2,3,4],输出为[24,12,8,6]。计算过程如下:
    • left = [1,1,2,6]
    • right = [24,12,4,1]
    • answer = [124, 112, 24, 61] = [24,12,8,6]
  • 示例2:nums = [-1,1,0,-3,3],输出为[0,0,9,0,0]。计算过程如下:
    • left = [1, -1, -1, 0, 0]
    • right = [0, 0, 9, 3, 1]
    • answer = [10, -10, -19, 03, 0*1] = [0,0,9,0,0]
      #include <stdio.h>       // 标准输入输出头文件(printf等函数依赖)
      #include <stdlib.h>      // 标准库头文件(动态内存分配函数依赖)/*** 计算除自身外数组的乘积(核心算法函数)* @param nums 输入整数数组指针* @param numsSize 数组元素数量* @param returnSize 返回数组长度的指针(输出参数)* @return 动态分配的乘积数组指针(需调用者手动释放)*/
      int* productExceptSelf(int* nums, int numsSize, int* returnSize) {// 动态分配左累积数组(存储每个元素左侧所有元素的乘积)int *left = (int*)malloc(numsSize * sizeof(int));// 动态分配右累积数组(存储每个元素右侧所有元素的乘积)int *right = (int*)malloc(numsSize * sizeof(int));// 动态分配结果数组(最终需要返回的数组)int *answer = (int*)malloc(numsSize * sizeof(int));// 内存分配有效性检查(任一分配失败则释放已分配内存)if (!left || !right || !answer) {free(left);       // 释放左数组内存(如果分配成功)free(right);      // 释放右数组内存(如果分配成功)free(answer);     // 释放结果数组内存(如果分配成功)*returnSize = 0;  // 设置返回数组长度为0return NULL;      // 返回空指针表示错误}/* ========== 左累积数组计算 ========== */left[0] = 1;  // 初始化第一个元素的左累积(左侧无元素)for (int i = 1; i < numsSize; i++) {  // 从左到右遍历数组// 当前左累积 = 前一个左累积 × 前一个元素值left[i] = left[i - 1] * nums[i - 1];}/* ========== 右累积数组计算 ========== */right[numsSize - 1] = 1;  // 初始化最后一个元素的右累积(右侧无元素)for (int i = numsSize - 2; i >= 0; i--) {  // 从右到左遍历数组// 当前右累积 = 后一个右累积 × 后一个元素值right[i] = right[i + 1] * nums[i + 1];}/* ========== 最终结果计算 ========== */for (int i = 0; i < numsSize; i++) {  // 遍历所有元素// 最终结果 = 左累积 × 右累积(排除当前元素的乘积)answer[i] = left[i] * right[i];}// 释放临时使用的左右累积数组内存free(left);   // 释放左数组内存free(right);  // 释放右数组内存*returnSize = numsSize;  // 设置返回数组长度return answer;           // 返回结果数组指针
      }int main() {/* ========== 测试案例1 ========== */int nums1[] = {1, 2, 3, 4};                 // 输入数组int size1 = sizeof(nums1) / sizeof(nums1[0]); // 计算数组长度int returnSize1;                           // 存储返回数组长度// 调用核心算法函数int *result1 = productExceptSelf(nums1, size1, &returnSize1);printf("示例1输出: ");                     // 打印提示信息for (int i = 0; i < returnSize1; i++) {    // 遍历结果数组printf("%d ", result1[i]);            // 打印每个元素}printf("\n");                             // 换行free(result1);                            // 释放结果数组内存/* ========== 测试案例2 ========== */int nums2[] = {-1, 1, 0, -3, 3};           // 包含零值的测试数组int size2 = sizeof(nums2) / sizeof(nums2[0]);int returnSize2;int *result2 = productExceptSelf(nums2, size2, &returnSize2);printf("示例2输出: ");for (int i = 0; i < returnSize2; i++) {printf("%d ", result2[i]);}printf("\n");free(result2);  // 释放内存return 0;       // 主函数正常退出
      }

输出结果:

 


二、加油站问题:环路行驶的最优出发点

场景描述:
在一条环路上有n个加油站,每个加油站有一定量的汽油。汽车从其中一个加油站出发,油箱为空,每到下一个加油站需要消耗一定的汽油。目标是找到一个出发点,使得汽车能够按顺序绕环路行驶一周。如果不存在这样的出发点,就返回-1。

算法核心:净油量与累加法
这个问题可以通过计算每个加油站的净油量(加油量减去消耗量)和累加法来解决。具体步骤如下:

  1. 计算净油量:对于每个加油站i,计算其净油量为gas[i] - cost[i]。
  2. 累加法:从左到右遍历加油站,计算当前的油量和总的油量。如果当前油量小于0,就记录当前的位置作为可能的起点,并重置当前油量为0。如果总的油量小于0,就返回-1。否则,返回记录的起点。

详细分析:

  1. 初始化:total = 0,current = 0,start = 0。
  2. 遍历加油站:从i=0到n-1:
    • current += gas[i] - cost[i]
    • total += gas[i] - cost[i]
    • 如果current < 0,就记录start = i+1,current = 0。
  3. 判断结果:如果total < 0,返回-1。否则,返回start。

题目验证示例:

  • 示例1:gas = [1,2,3,4,5], cost = [3,4,5,1,2],输出为3。计算过程如下:
    • 净油量 = [-2, -2, -2, 3, 3]
    • current = 0, total = 0, start = 0
    • i=0: current = -2, total = -2 → current < 0 → start=1, current=0
    • i=1: current = -2, total = -4 → current < 0 → start=2, current=0
    • i=2: current = -2, total = -6 → current < 0 → start=3, current=0
    • i=3: current = 3, total = -3 → current >=0
    • i=4: current = 6, total = 3 → total >=0
    • 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
    • 因此,3 可为起始索引。
    • 返回start=3
  • 示例2:gas = [2,3,4], cost = [3,4,3],输出为-1。计算过程如下:
    • 净油量 = [-1, -1, 1]
    • current = 0, total = 0, start = 0
    • i=0: current = -1, total = -1 → current < 0 → start=1, current=0
    • i=1: current = -1, total = -2 → current < 0 → start=2, current=0
    • i=2: current = 1, total = -1 → total < 0 → 返回-1
    • 你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
      因此,无论怎样,你都不可能绕环路行驶一周。
      #include <stdio.h>  // 包含标准输入输出头文件(printf函数依赖)/*** 寻找可以环路行驶的加油站起点(贪心算法实现)* @param gas 汽油量数组指针,表示每个加油站可加油量* @param gasSize 汽油数组的长度* @param cost 消耗汽油数组指针,表示到下一站的油耗* @param costSize 消耗数组的长度* @return 可行起点的数组索引,不存在则返回-1*/
      int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize) {// 输入有效性检查:两个数组长度必须相等if (gasSize != costSize) return -1;int total = 0;      // 总累积净油量(全程油量总和)int current = 0;    // 当前累积油量(用于模拟行驶过程)int start = 0;      // 可能的起点索引(初始设为第一个加油站)// 遍历所有加油站(时间复杂度O(n))for (int i = 0; i < gasSize; i++) {int net = gas[i] - cost[i];  // 计算本站净油量(加油量-消耗量)current += net;              // 累加到当前油量total += net;                // 累加到总油量// 关键贪心策略:当前油量不足到达下一个站点时的处理if (current < 0) {start = i + 1;  // 重置起点为下一个站点(i+1可能超出数组范围,但后续逻辑会自动处理)current = 0;    // 重置当前油量为0(重新开始累积)}}// 最终判定:总油量>=0说明存在解,否则无解return (total >= 0) ? start : -1;  // 若返回start超过数组长度,系统会按C语言特性处理为-1
      }int main() {/* ========== 测试示例1 ========== */int gas1[] = {1, 2, 3, 4, 5};          // 汽油量数组int cost1[] = {3, 4, 5, 1, 2};         // 油耗数组int size1 = sizeof(gas1) / sizeof(gas1[0]); // 计算数组长度int result1 = canCompleteCircuit(gas1, size1, cost1, size1);printf("示例1输出: %d\n", result1);    // 预期输出3(对应算法验证结果)/* ========== 测试示例2 ========== */int gas2[] = {2, 3, 4};                // 包含无法完成环路的测试数据int cost2[] = {3, 4, 3};int size2 = sizeof(gas2) / sizeof(gas2[0]);int result2 = canCompleteCircuit(gas2, size2, cost2, size2);printf("示例2输出: %d\n", result2);    // 预期输出-1(无解)/* ========== 边界测试:单个加油站 ========== */int gas3[] = {5};                      // 边界场景测试int cost3[] = {3};int size3 = sizeof(gas3) / sizeof(gas3[0]);int result3 = canCompleteCircuit(gas3, size3, cost3, size3);printf("边界测试输出: %d\n", result3); // 预期输出0(单站满足条件)return 0;  // 主函数正常退出
      }

输出结果:

 


三、全方位对比:除自身以外数组的乘积 vs 加油站问题

对比维度除自身以外数组的乘积加油站问题
问题类型数组处理、乘积计算环路行驶优化、加油站出发点寻找
算法核心两次遍历、左右乘积数组净油量计算、累加法
复杂度时间O(n),空间O(n)时间O(n),空间O(1)
应用场景数据处理、乘积计算环路行驶优化、加油站出发点寻找
优化目标高效计算每个元素的乘积,避免除法找到绕环路行驶的最优出发点,确保油量非负

博客总结:

通过今天的分析,我们看到算法不仅仅是冰冷的代码,它还能帮助我们在实际数据处理和环路优化中实现高效和优化。无论是除自身以外数组的乘积,还是加油站问题,背后的算法都在默默发挥作用,帮助我们更高效地管理和分析数据。

希望这篇文章能让你对这些优化问题有更深入的了解,也期待你在生活中发现更多有趣的场景,用算法的视角去探索它们!


博客谢言:

感谢你的耐心阅读!如果你觉得这篇文章有趣,不妨在评论区分享你生活中遇到的数据处理或环路优化问题,或者你认为可以用算法优化的地方。让我们一起用算法的视角去探索数据的奥秘!

相关文章:

  • 图片批量压缩转换格式 JPG/PNG无损画质 本地运行
  • Java 可扩展状态系统设计:备忘录模式的工程化实践与架构演进
  • 6个跨境电商独立站平台
  • 理解 Redis 事务-21(使用事务实现原子操)
  • docker 镜像完整生成指南
  • 论文阅读笔记——Janus,Janus Pro
  • RabbitMQ 集群与高可用方案设计(一)
  • 嵌入式硬件---施密特触发器单稳态触发器多谐振荡器
  • Redis实战-缓存篇(万字总结)
  • uniapp报错mongo_cell_decision_not_found
  • TCP 和 UDP 的区别
  • Windows逆向工程提升之x86结构化异常SEH处理机制
  • 非接触式互连:当串扰是您的朋友时
  • npm修改镜像的教程,将npm镜像修改为国内地址增加下载速度
  • SpringBoot-11-基于注解和XML方式的SpringBoot应用场景对比
  • Kubernetes(k8s)全面解析:从入门到实践
  • 以前端的角度理解 Kubernetes(K8s)
  • xy坐标上如何判定两个矩形是否重合
  • 什么是ESLint?它有什么作用?
  • 指针、空间地址
  • 网站制作企业/建一个app平台的费用多少
  • 网站设计风格及色彩搭配技巧 -/腾讯广告投放平台官网
  • 任丘网站建设/北京seo优化诊断
  • 做网站好用的cms/百度的人工客服
  • app开发流程 网站开发/中国十大新闻网站排名
  • 网站建设需要哪些人员/seo优化教程培训