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

编程题:递归与分治练习题3道(C语言实现)

编程题:递归与分治练习题3道(C语言实现)

编程题1.位 1 的个数(汉明重量):分治 vs 暴力的直观对比

问题:给一个正整数 n,求它二进制里 1 的个数(比如 n=11→二进制 1011→输出 3)。

  • 暴力法:逐位判断!用 n%2 取最低位,是 1 就计数,再 n=n/2 右移,直到 n=0。

    ✅ 优点:简单直接,时间复杂度 O (k)(k 是二进制位数,32 位整数就是 O (1));

    ❌ 缺点:没用到分治思想,适合小规模但体现不出算法思维。

  • 分治法:把二进制字符串 “一拆二”!

    1. 拆分:先转二进制字符串(比如 11→"1011"),从中间分成左 “10” 和右 “11”;

    2. 递归:分别算左右两部分的 1 的个数;

    3. 合并:左右计数相加就是结果;

    4. 终止条件:字符串长度≤1 时,直接返回 1 或 0。

      ✨ 关键发现:虽然时间复杂度也是 O (k),但处理超长二进制(比如 64 位以上)时,递归拆分的扩展性更强~

      #define _CRT_SECURE_NO_WARNINGS 1
      #include<stdio.h>
      #include<stdlib.h>
      #include<string.h>// 暴力算法:递归输出二进制并返回1的个数
      int bin(int n) {if (n == 0) return 0;int cnt = bin(n / 2);int bit = n % 2;printf("%d",bit);return cnt + bit;}
      // 分治函数:计算二进制字符串中1的个数
      int countOnes(char* str, int left, int right) {// 终止条件:字符串长度 <= 1 时,直接判断是否为'1'if (left == right) {return (str[left] == '1') ? 1 : 0;}// 拆分(Divide):二分中点int mid = (left + right) / 2;// 递归计算左半部分1的个数int leftCount = countOnes(str, left, mid);// 递归计算右半部分1的个数int rightCount = countOnes(str, mid + 1, right);// 合并(Combine):左右两部分之和return leftCount + rightCount;
      }//
      int main() {//暴力算法测试int n;scanf("%d", &n);if (n == 0) printf("0\n0"); // 特殊处理0else printf("\n%d\n", bin(n)); // 输出二进制后换行,再输出1的个数//分治算法测试// 将n转换为二进制字符串(跳过前2个字符"0b"),调用_itoa函数获取二进制数char* binaryStr = _itoa(n, malloc(33), 8); // 33是int最大二进制长度(含结束符)// 输出二进制字符串printf("%s\n", binaryStr);// 调用分治函数计算1的个数(参数:字符串、左边界0、右边界长度-1)int total = countOnes(binaryStr, 0, strlen(binaryStr) - 1);printf("%d", total);free(binaryStr); // 释放动态分配的内存return 0;
      }
      

2. 库存管理 II:找 “超半数” 的商品 ID(多数元素)

问题:仓库数组里某商品 ID 出现次数超数组长度一半,返回它(比如输入 [6,1,3,1,1,1]→输出 1)。

分治思路太妙了!核心是 “子数组的多数元素,可能也是全局的多数元素”:

  1. 拆分:把数组从中间分成左右两半;

  2. 递归:分别找左右子数组的多数元素(left_major/right_major);

  3. 合并:

    • 若左右多数元素相同,直接返回;

    • 不同就遍历当前数组,统计两者出现次数,返回多的那个(题目保证有多数元素,不用怕平局)。

      📊 效率:时间复杂度 O (n log n),虽然比摩尔投票法(O (n))稍慢,但逻辑更直观,适合刚学分治的同学理解~

      #define _CRT_SECURE_NO_WARNINGS 1
      #include<stdio.h>
      #include<stdlib.h>
      #include<string.h>// 分治函数:在数组stock的[left, right]下标范围内寻找多数元素(出现次数超过一半的元素)
      // 参数:stock-目标数组;left-当前范围左边界下标;right-当前范围右边界下标
      // 返回值:当前范围内的多数元素
      int findMajority(int* stock, int left, int right) {// 终止条件:当子数组范围左边界等于右边界时,说明子数组只有一个元素// 此时该元素就是这个子数组的多数元素,直接返回if (left == right) return stock[left];// 拆分:计算当前范围的中点下标,避免(left + right)可能导致的整数溢出int mid = left + (right - left)/2;// 递归处理左子数组[left, mid],获取左半部分的多数元素int leftMaj = findMajority(stock, left, mid);// 递归处理右子数组[mid+1, right],获取右半部分的多数元素int rightMaj = findMajority(stock, mid+1, right);// 合并:若左右两个子数组的多数元素相同,说明该元素也是当前范围的多数元素,直接返回if (leftMaj == rightMaj) return leftMaj;// 若左右多数元素不同,则需要统计两者在当前范围[left, right]内的出现次数int leftCnt = 0;  // 用于记录左多数元素leftMaj在当前范围的出现次数int rightCnt = 0; // 用于记录右多数元素rightMaj在当前范围的出现次数// 遍历当前范围的所有元素,统计两个候选元素的出现次数for (int i = left; i <= right; i++) {if (stock[i] == leftMaj) {  // 若当前元素等于左多数元素,左计数器加1leftCnt++;} else if (stock[i] == rightMaj) {  // 若当前元素等于右多数元素,右计数器加1rightCnt++;}}// 比较两个计数器,返回出现次数更多的元素(题目保证存在多数元素,无需处理次数相等的情况)return leftCnt > rightCnt ? leftMaj : rightMaj;
      }// 入口函数:寻找数组中的多数元素
      // 参数:stock-目标数组;stockSize-数组长度
      // 返回值:数组中的多数元素
      int majorityElement(int* stock, int stockSize) {// 调用分治函数,初始范围为整个数组[0, stockSize-1]return findMajority(stock, 0, stockSize - 1);
      }int main() {// 测试用例1:数组[1,2,3,2,2,2,5,4,2],预期多数元素为2(出现5次,超过长度9的一半)int stock1[] = { 1,2,3,2,2,2,5,4,2 };// 计算数组长度:总字节数 / 单个元素字节数int size1 = sizeof(stock1) / sizeof(stock1[0]);// 调用入口函数,打印测试结果printf("测试用例1结果:%d\n", majorityElement(stock1, size1)); // 输出2// 测试用例2:数组[3,2,3],预期多数元素为3(出现2次,超过长度3的一半)int stock2[] = { 3,2,3 };int size2 = sizeof(stock2) / sizeof(stock2[0]); // 计算数组长度(3)printf("测试用例2结果:%d\n", majorityElement(stock2, size2)); // 输出3// 测试用例3:数组[6,1,3,1,1,1],预期多数元素为1(出现4次,超过长度6的一半)int stock3[] = {6,1,3,1,1,1};int size3 = sizeof(stock3) / sizeof(stock3[0]); // 计算数组长度(6)printf("测试用例3结果:%d\n", majorityElement(stock3, size3)); // 输出1return 0;
      }
      

3. 库存管理 III:找库存最少的 k 个商品

问题:给库存数组,返回最少的 cnt 个余量(比如输入 [2,5,7,4],cnt=1→输出 [2])。

分治的 “拆分 - 筛选” 思路很实用:

  1. 终止条件:子数组长度≤cnt 时,直接返回子数组(排序后当候选);

  2. 拆分:数组拆成左右两半,分别找每半的 “最少 cnt 个元素”;

  3. 合并:把左右候选合并成有序数组,取前 cnt 个就是结果。

    💡 小技巧:合并时用双指针法,比直接排序更高效;当 cnt 很小时(比如 cnt=10),效率接近 O (n),内存占用也比全排序小~

// 比较函数(用于快速排序)
int compare(const void* a, const void* b) {return *(int*)a - *(int*)b; // 升序排列
}// 合并两个有序数组(左半部分和右半部分的最小元素候选集)
int* merge(int* a, int m, int* b, int n, int* mergedSize) {*mergedSize = m + n;int* merged = (int*)malloc(*mergedSize * sizeof(int));int i = 0, j = 0, k = 0;// 双指针合并两个有序数组(高效)while (i < m && j < n) {if (a[i] < b[j]) {merged[k++] = a[i++];}else {merged[k++] = b[j++];}}// 处理剩余元素while (i < m) merged[k++] = a[i++];while (j < n) merged[k++] = b[j++];return merged;
}// 分治函数:寻找[start, end]范围内的最小cnt个元素
int* findMinK(int* stock, int start, int end, int cnt, int* returnSize) {int len = end - start + 1; // 当前子数组长度// 终止条件:子数组长度 <= cnt,直接返回所有元素(排序后方便合并)if (len <= cnt) {*returnSize = len;int* res = (int*)malloc(len * sizeof(int));for (int i = 0; i < len; i++) {res[i] = stock[start + i]; // 复制子数组元素}qsort(res, len, sizeof(int), compare); // 排序,便于后续合并return res;}// 拆分:递归处理左右两部分int mid = start + (end - start) / 2; // 计算中点(避免溢出)int leftSize, rightSize;int* left = findMinK(stock, start, mid, cnt, &leftSize);   // 左半部分的最小cnt个元素int* right = findMinK(stock, mid + 1, end, cnt, &rightSize); // 右半部分的最小cnt个元素// 合并:将左右结果合并为一个有序数组int mergedSize;int* merged = merge(left, leftSize, right, rightSize, &mergedSize);// 释放中间数组内存(避免泄漏)free(left);free(right);// 从合并后的数组中选出最小的cnt个元素*returnSize = (mergedSize < cnt) ? mergedSize : cnt;int* res = (int*)malloc(*returnSize * sizeof(int));for (int i = 0; i < *returnSize; i++) {res[i] = merged[i]; // 合并后的数组是有序的,前cnt个即为最小}free(merged); // 释放合并数组内存return res;
}// 主函数:入口(符合LeetCode函数规范)
int* getLeastNumbers(int* stock, int stockSize, int cnt, int* returnSize) {if (cnt == 0) { // 特殊情况:cnt=0时返回空*returnSize = 0;return NULL;}return findMinK(stock, 0, stockSize - 1, cnt, returnSize);
}int main() {// 测试用例1:[3,2,1],cnt=2 → 预期输出[1,2](顺序不限)int stock1[] = { 3,2,1 };int size1 = sizeof(stock1) / sizeof(stock1[0]);int cnt1 = 2;int returnSize1;int* res1 = getLeastNumbers(stock1, size1, cnt1, &returnSize1);printf("测试用例1(最小2个元素):");for (int i = 0; i < returnSize1; i++) {printf("%d ", res1[i]); // 输出:1 2}free(res1);printf("\n");//// 测试用例2:[0,1,2,1],cnt=1 → 预期输出[0]int stock2[] = { 0,1,2,1 };int size2 = sizeof(stock2) / sizeof(stock2[0]);int cnt2 = 1;int returnSize2;int* res2 = getLeastNumbers(stock2, size2, cnt2, &returnSize2);printf("测试用例2(最小1个元素):");for (int i = 0; i < returnSize2; i++) {printf("%d ", res2[i]); // 输出:0}free(res2);printf("\n");return 0;
}
http://www.dtcms.com/a/465784.html

相关文章:

  • 龙海市建设局网站有什么公司做网站好
  • 【Day 74 】Ansible-playbook剧本-角色
  • 百度网站排名全掉专注微商推广的网站
  • wordpress payjs学seo如何入门
  • Neo4j查询计划完全指南:读懂数据库的“执行蓝图“
  • Kubernetes 1.20集群部署
  • PostgresWAL文件和序列号
  • 个人网页设计制作网站模板中国建设银行网站忘记密码怎么办
  • cms 官方网站网站建设团队管理怎么写
  • 什么是Ansible 清单
  • MySQL——数据库入门指南
  • 国外网站空间租用费用电商食品网站建设
  • 一级a做爰片免费网站短视频教程软件开发需要用什么软件
  • 机器人如何帮助工厂提升工作效率
  • 苹果软件混淆与 iOS 代码加固趋势,IPA 加密、应用防反编译与无源码保护的工程化演进
  • 将聚合工程的ssm项目部署到本地tomcat
  • 网站开发模块的需求网站搜索引擎优化建议
  • 方正宋体超大字符集
  • 网站和系统哪个好做网站开发的总结
  • 【大前端】 TypeScript vs JavaScript:全面对比与实践指南
  • wpf之MVVM中只读属性更新界面
  • 南通企业免费建站深圳网站开发运营公司
  • php微信商家转账回调通知数据解密
  • 使用Linux的read和write系统函数操作文件
  • 基于 PLC 的仓储管理系统设计
  • 企业网站建设计划内部局域网怎么搭建
  • elasticsearch索引多长时间刷新一次(智能刷新索引根据数据条数去更新)
  • 脑电模型实战系列(二):PyTorch实现简单DNN模型
  • 脑电模型实战系列(二):为什么从简单DNN开始脑电情绪识别?
  • 哪个网站做h5比较好看金华手机建站模板