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

用C语言搞定PTA L1-027出租题:从微博段子到编程实战的保姆级拆解

从微博段子到C语言实战:PTA L1-027出租题趣味解析

记得第一次在微博上看到那张"求救"图片时,我也忍不住笑出了声。图片里是一段看似神秘的代码,配文写着"谁能破解这个电话号码?"。评论区里网友们纷纷脑洞大开,有人说是密码学难题,有人猜测是某种加密算法。但真相往往比想象中简单——这其实是PTA平台上L1-027"出租"题的一个趣味变种。今天,就让我们从这段网络热梗出发,用C语言还原解题全过程,你会发现编程原来可以这么有趣!

1. 题目背后的故事:为什么这道题会火?

2018年,这张包含两行代码的图片在新浪微博突然走红。图片显示:

int[] arr = new int[]{8,3,2,1,0}; int[] index = new int[]{3,0,4,3,1,0,2,4,3,4,4};

配文只有简单一句:"谁能破解这个电话号码?"短短几天内,转发量突破10万+。网友们各显神通:

  • 数学爱好者尝试用排列组合破解
  • 程序员直接写脚本暴力破解
  • 甚至有人联系电信运营商朋友帮忙

最终揭晓的答案却出人意料地简单——电话号码就是18013820100。这个案例完美诠释了"外行看热闹,内行看门道":

"看似复杂的表象下,往往藏着简单的逻辑。这正是编程的魅力所在——化繁为简的能力。"

2. 题目解析:我们需要实现什么?

PTA原题要求我们为任意11位手机号码生成类似上面的两行代码。具体来说:

输入:一个11位数字组成的手机号码(如18013820100)

输出:两行代码

  1. 第一行:arr数组,包含手机号码中所有不重复数字,按降序排列
  2. 第二行:index数组,表示原号码每个数字在arr数组中的位置索引

示例

输入:18013820100 输出: int[] arr = new int[]{8,3,2,1,0}; int[] index = new int[]{3,0,4,3,1,0,2,4,3,4,4};

3. 解题思路拆解:三步走战略

3.1 第一步:数字提取与存储

处理手机号码时,我们面临第一个选择:如何存储这11位数字?

方案对比

存储方式优点缺点
整型变量直接存储数字难以逐位处理
字符数组方便逐位处理需要字符数字转换
整型数组直接存储数字需要处理输入

我们选择字符数组作为输入方式,因为:

  • gets()scanf()可以一次性读取11位数字
  • 每位数字字符可以通过-'0'转换为整型数字
char phone[12]; // 多一位存放'\0' gets(phone); int digits[11]; for(int i=0; i<11; i++){ digits[i] = phone[i] - '0'; }

3.2 第二步:数字去重与排序

这是本题的核心算法部分,我们需要:

  1. 提取不重复数字
  2. 按降序排列

实现方案

  1. 冒泡排序:虽然效率不高(O(n²)),但对初学者友好
  2. 快速排序:效率更高(O(nlogn)),但实现稍复杂
  3. 桶排序:针对0-9的数字特别高效(O(n))

我们选择冒泡排序+双指针去重,因为:

  • 代码直观,易于理解
  • 排序和去重可以在一个函数内完成
// 冒泡排序+去重函数 int sort_and_unique(int arr[], int size){ // 冒泡排序(降序) for(int i=0; i<size-1; i++){ for(int j=0; j<size-1-i; j++){ if(arr[j] < arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } // 双指针去重 if(size == 0) return 0; int slow = 1; for(int fast=1; fast<size; fast++){ if(arr[fast] != arr[fast-1]){ arr[slow++] = arr[fast]; } } return slow; // 返回唯一元素个数 }

3.3 第三步:索引映射与输出

现在我们有:

  • original[]:原始手机号码数字
  • unique[]:去重排序后的数字

需要为original中每个数字找到在unique中的位置:

int find_index(int num, int arr[], int size){ for(int i=0; i<size; i++){ if(arr[i] == num){ return i; } } return -1; // 理论上不会发生 }

4. 完整代码实现:从理论到实践

将上述思路整合,我们得到完整解决方案:

#include <stdio.h> // 冒泡排序+去重 int sort_and_unique(int arr[], int size){ for(int i=0; i<size-1; i++){ for(int j=0; j<size-1-i; j++){ if(arr[j] < arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } if(size == 0) return 0; int slow = 1; for(int fast=1; fast<size; fast++){ if(arr[fast] != arr[fast-1]){ arr[slow++] = arr[fast]; } } return slow; } // 查找索引 int find_index(int num, int arr[], int size){ for(int i=0; i<size; i++){ if(arr[i] == num){ return i; } } return -1; } int main(){ char phone[12]; gets(phone); // 转换为数字数组 int digits[11]; for(int i=0; i<11; i++){ digits[i] = phone[i] - '0'; } // 复制一份用于去重排序 int unique[11]; for(int i=0; i<11; i++){ unique[i] = digits[i]; } // 处理unique数组 int unique_size = sort_and_unique(unique, 11); // 输出arr行 printf("int[] arr = new int[]{"); for(int i=0; i<unique_size; i++){ printf("%d", unique[i]); if(i != unique_size-1){ printf(","); } } printf("};\n"); // 输出index行 printf("int[] index = new int[]{"); for(int i=0; i<11; i++){ printf("%d", find_index(digits[i], unique, unique_size)); if(i != 10){ printf(","); } } printf("};"); return 0; }

5. 优化与进阶:让代码更专业

虽然上面的代码已经能正确解题,但我们还可以做以下优化:

5.1 输入验证

增加对输入合法性的检查:

// 检查输入是否为11位数字 int is_valid = 1; for(int i=0; i<11; i++){ if(phone[i] < '0' || phone[i] > '9'){ is_valid = 0; break; } } if(!is_valid || phone[11] != '\0'){ printf("Invalid input!"); return 1; }

5.2 排序算法优化

用更高效的qsort替代冒泡排序:

#include <stdlib.h> // 比较函数(降序) int compare(const void *a, const void *b){ return (*(int*)b - *(int*)a); } // 在sort_and_unique中使用 qsort(arr, size, sizeof(int), compare);

5.3 使用更简洁的去重方法

对于已知范围(0-9)的数字,可以用桶排序思想:

int buckets[10] = {0}; for(int i=0; i<11; i++){ buckets[digits[i]] = 1; } int unique[10], count=0; for(int i=9; i>=0; i--){ if(buckets[i]){ unique[count++] = i; } }

6. 常见错误与调试技巧

在实现过程中,新手常会遇到这些问题:

  1. 数组越界:确保字符数组有足够空间(12字节:11数字+'\0')

    错误示例:char phone[11]; // 不够存放'\0'

  2. 数字转换错误:记得用-'0'将字符转为数字

  3. 排序方向错误:题目要求降序而非升序

  4. 去重不彻底:确保比较时检查前一个元素

  5. 输出格式错误:注意逗号的位置和分号的使用

调试时可以分阶段验证:

// 测试阶段1:打印原始数字 printf("Original digits: "); for(int i=0; i<11; i++){ printf("%d ", digits[i]); } printf("\n"); // 测试阶段2:打印去重排序后结果 printf("Unique & sorted: "); for(int i=0; i<unique_size; i++){ printf("%d ", unique[i]); } printf("\n");

7. 从题目到实战:编程思维的培养

这道看似简单的题目其实蕴含了多个编程核心概念:

  1. 数据表示:选择合适的数据结构(字符数组vs整型数组)
  2. 算法设计:排序和去重的多种实现方式
  3. 问题分解:将大问题拆解为小函数(sort_and_unique, find_index)
  4. 边界处理:考虑输入验证和异常情况
  5. 效率分析:不同算法的时间/空间复杂度比较

"编程不是关于写代码,而是关于解决问题。好的程序员不是记住语法的人,而是能把复杂问题分解成简单步骤的人。"

下次当你看到类似的"谜题"时,不妨用程序员的思维去分析:

  1. 输入是什么?输出是什么?
  2. 中间需要哪些处理步骤?
  3. 有哪些边界情况需要考虑?
  4. 如何验证我的解决方案是否正确?

这种思维训练,远比单纯记忆语法更有价值。

http://www.cnnetsun.cn/news/2026601.html

相关文章:

  • 终极文档下载神器:30+平台免费下载完整指南
  • TrollInstallerX技术深度解析:iOS越狱环境下的智能安装引擎
  • 3步掌握TTS游戏存档备份:保护你的桌游世界不丢失
  • STM32 HAL库中断配置避坑指南:从CubeMX生成代码到手动修改NVIC优先级(以F407的GPIO和TIM2为例)
  • 3分钟搞定:LinkSwift网盘直链下载助手终极使用手册
  • 抖音批量下载工具终极指南:告别手动操作,5分钟学会无水印视频采集
  • 用OpenCV4.0玩转图像像素:手把手教你实现亮度调节与颜色特效
  • 空洞骑士模组管理革命:Scarab如何让复杂安装变简单
  • 安卓ROM移植避坑指南:手把手教你识别和替换关键的lib/so文件(附常用so功能清单)
  • 从应力应变到本构矩阵:Voigt符号在材料力学仿真中的核心应用避坑指南
  • Adobe-GenP 3.0:Adobe全家桶通用补丁终极使用指南
  • 告别线束噩梦:用ADI的A2B音频总线,在车载系统里轻松搞定数字麦克风和ANC
  • 从C语言到RISC-V汇编:手把手教你用汇编重写冒泡排序(附完整代码)
  • 告别纯点SLAM的尴尬:手把手教你用PL-SLAM+LSD在弱纹理环境跑通第一个Demo
  • 别再到处找标注工具了!用MATLAB Image Labeler搞定语义分割数据集(附完整代码)
  • 用运放和H桥电路,我给自己DIY了一个能闭环控制的‘玩具’舵机
  • 使用长轮询技术实时接收服务器命令
  • 告别理论公式:用5个仿真案例直观理解MIMO如何提升你的无线网络性能
  • PyTorch环境配置:如何精准匹配你的NVIDIA驱动、CUDA 10.2和cuDNN版本(附自查清单)
  • 别再靠人眼打分了!用Python+Transformer(TRIQ)5分钟搞定图像质量自动评估
  • 告别‘玄学调参’:智能车越野组软件调试,从ICM2060滤波到GPS/陀螺仪融合实战
  • Elasticsearch核心:Token Filter工作原理全解析+内置过滤器实战
  • StreamCap:如何用Python构建40+平台直播录制系统?
  • UPF3.0实战:用VCS NLP跑通你的第一个低功耗仿真(附完整脚本)
  • 智能网盘直链解析工具:告别会员限制,轻松实现高速下载
  • 我的MobileViT训练翻车实录:从数据集坑到学习率调参,这些PyTorch细节新手一定要注意
  • 别再混淆了!一文讲透机器学习里的‘在线学习’、‘增量学习’和‘终身学习’到底啥区别
  • 手把手教你用Skyline健康检查辅助VSAN集群安全关机(附7.0U3新功能解读)
  • Kubernetes Pod 生命周期完全指南:阶段、探针与管理机制
  • 突破百度网盘限速:Python多线程下载脚本实战指南