news 2026/8/28 7:35:58

从暴力到最优:三道 C 语言入门题的解法思路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从暴力到最优:三道 C 语言入门题的解法思路

做算法题有个挺有意思的规律:题目越简单,越能看出思路的差距。暴力解法往往很快就能写出来,但想出更优雅的解法,可能需要多琢磨一会儿。下面这三道题都是 LeetCode 上的简单题,我把自己第一次做时的思路和后来学到的更好写法整理了一下。


题一:存在重复元素

题目:给一个整数数组,判断里面有没有重复的数字。

我最先想到的写法

看到"有没有重复",第一反应肯定是拿每个数跟后面的数都比一遍:

#include <stdbool.h> bool containsDuplicate(int nums[], int numsSize) { for (int i = 0; i < numsSize; i++) { for (int j = i + 1; j < numsSize; j++) { if (nums[i] == nums[j]) { return true; } } } return false; }

这代码写起来很顺,但问题也很明显:两层循环,时间复杂度是O(n²)。数组稍长一点,LeetCode 就会报超时。

更好的思路:排个序再看

其实有个特别简单的转化:如果数组是有序的,相同的数字一定会挨在一起。那我们先排序,然后只需要扫一遍,看看相邻的两个数相不相等就行了。

为了代码好懂,这里用冒泡排序(纯数组下标操作,不涉及指针):

#include <stdbool.h> bool containsDuplicate(int nums[], int numsSize) { // 冒泡排序 for (int i = 0; i < numsSize - 1; i++) { for (int j = 0; j < numsSize - 1 - i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; } } } // 排序后检查相邻元素 for (int i = 0; i < numsSize - 1; i++) { if (nums[i] == nums[i + 1]) { return true; } } return false; }

关键点:排序把时间复杂度降到了O(n log n)(虽然教学代码里写的是冒泡,实际工程中换成快速排序就行),后面的扫描是O(n)。整体比暴力解法快太多了。


题二:罗马数字转整数

题目:给一个罗马数字字符串,转成对应的整数。

我最先想到的写法

一开始我直接把六种减法特例全写成了if-else,比如看到I就判断后面是不是VX,是的话就减 1,否则加 1:

int romanToInt(char s[]) { int result = 0; int len = 0; while (s[len] != '\0') len++; for (int i = 0; i < len; i++) { if (s[i] == 'I') { if (i + 1 < len && (s[i+1] == 'V' || s[i+1] == 'X')) { result -= 1; } else { result += 1; } } else if (s[i] == 'X') { if (i + 1 < len && (s[i+1] == 'L' || s[i+1] == 'C')) { result -= 10; } else { result += 10; } } // ... 后面还有 C、V、L、D、M 的一大堆判断 // 代码太长,这里省略了 } return result; }

这代码能跑,但写起来特别繁琐,而且逻辑分散。万一规则再多几条,代码还会继续膨胀。

更好的思路:其实规律只有一条

仔细观察会发现,罗马数字的减法规则可以归纳成一句话:

如果当前字符的值 < 右边字符的值,当前字符做减法;否则做加法。

比如IV:I(1) < V(5),所以 I 减;VI:V(5) >= I(1),所以 V 加。

利用这个规律,我们建一个"字符到数值"的映射表,然后从左到右扫一遍就行:

int romanToInt(char s[]) { int map[256] = {0}; map['I'] = 1; map['V'] = 5; map['X'] = 10; map['L'] = 50; map['C'] = 100; map['D'] = 500; map['M'] = 1000; int result = 0; int len = 0; while (s[len] != '\0') len++; for (int i = 0; i < len; i++) { int current = map[(unsigned char)s[i]]; int next = (i + 1 < len) ? map[(unsigned char)s[i + 1]] : 0; if (current < next) { result -= current; } else { result += current; } } return result; }

关键点:把一堆特例压缩成了一个统一的判断条件,代码简洁了很多。时间复杂度O(n),空间复杂度O(1),已经是这道题的最优解。


题三:最长公共前缀

题目:给一个字符串数组,找出所有字符串的最长公共前缀。

我最先想到的写法

我当时的做法是:先求第 0 个和第 1 个字符串的公共前缀,再用这个结果和第 2 个字符串求公共前缀,依此类推。

// 辅助函数:求两个字符串的公共前缀长度 int commonPrefix(char a[], char b[]) { int i = 0; while (a[i] != '\0' && b[i] != '\0' && a[i] == b[i]) { i++; } return i; } char* longestCommonPrefix(char** strs, int strsSize) { if (strsSize == 0) return ""; int minLen = commonPrefix(strs[0], strs[1]); for (int i = 2; i < strsSize; i++) { int len = commonPrefix(strs[0], strs[i]); if (len < minLen) minLen = len; } strs[0][minLen] = '\0'; return strs[0]; }

这个思路没问题,但写起来比较繁琐,需要额外的辅助函数,而且每次两两比较时,前面的字符会被反复扫描。

更好的思路:纵向扫描

换个角度看问题:公共前缀其实就是所有字符串在相同位置上的字符都一样。那我们可以一列一列地检查,而不是一个字符串一个字符串地比较。

char* longestCommonPrefix(char** strs, int strsSize) { if (strsSize == 0) return ""; int col = 0; // 当前检查第几列 while (1) { char c = strs[0][col]; // 拿第一个字符串的第 col 个字符当基准 if (c == '\0') break; // 第一个字符串到头了 int match = 1; for (int row = 1; row < strsSize; row++) { if (strs[row][col] != c) { match = 0; break; } } if (!match) break; // 这一列有不匹配的,前缀到此为止 col++; } strs[0][col] = '\0'; // 截断,剩下的就是最长公共前缀 return strs[0]; }

关键点:把"字符串之间的比较"转化成了"矩阵按列的检查"。一旦发现某一列不匹配,立刻停止,不需要再往后看。代码更紧凑,而且避免了重复比较。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/28 7:35:14

AI工程化落地指南:从RAG知识库到可验证的问答系统

在讨论“Silicon Valley sees AI as the solution – for everyone else”这类话题时&#xff0c;一个很容易被忽略的事实是&#xff1a;硅谷把 AI 当作基础设施来投资&#xff0c;是因为它拥有同时解决算力、数据、人才和试错成本四件事的条件。而硅谷之外的普通团队所面对的现…

作者头像 李华
网站建设 2026/8/28 7:32:57

600W电源模块OVC III设计:从爬电距离到冲击耐压的实践指南

做电源设计的同行应该都有这种体会&#xff1a;模块能不能进工业控制柜、能不能装在楼层配电箱下游、能不能扛住一次雷击浪涌&#xff0c;最后看的不是标称效率也不是纹波&#xff0c;而是认证栏里那串字符。最近我评估了一批600W电源模块&#xff0c;核心看点就是标题里那句&q…

作者头像 李华
网站建设 2026/8/28 7:32:51

CTF Web安全实战:文件包含漏洞原理、绕过与利用链分析

1. 项目概述&#xff1a;一次典型的中职CTF赛题复盘最近在整理过去的竞赛资料&#xff0c;翻到了2022年中职网络空间安全国赛的一道题目&#xff0c;编号是试题7。这道题在当时赛场上给不少选手制造了麻烦&#xff0c;但它的设计思路非常经典&#xff0c;涵盖了Web安全中几个核…

作者头像 李华
网站建设 2026/8/28 7:30:27

蓝桥杯Java选手如何高效利用C++题单:算法迁移与实战解析

1. 从C到Java&#xff1a;一份国二选手的蓝桥杯AB组课题单实战解析拿到一份标注着“C AB组辅导课题单”的资料&#xff0c;但你的主力语言是Java&#xff0c;这感觉就像拿到一本武功秘籍&#xff0c;但文字是梵文写的。别慌&#xff0c;这种情况在算法竞赛的跨语言学习中太常见…

作者头像 李华
网站建设 2026/8/28 7:30:18

VersaLogic推Android评估套件,工业嵌入式开发迎来新拐点

看到VersaLogic推出Android Demo/Eval Kit并附带赢取活动的消息&#xff0c;说实话我第一反应不是"又一块开发板"&#xff0c;而是"嵌入式行业确实到了一个拐点"。VersaLogic在我印象里一直是医疗、军工、工业自动化这些领域的"老面孔"&#xff…

作者头像 李华
网站建设 2026/8/28 7:25:11

蓝桥杯国赛题解:从扩散模型到多源BFS的算法实践

1. 从“扩散”到“BFS”&#xff1a;一道蓝桥杯国赛题的解题心路最近在复盘蓝桥杯国赛的历年真题&#xff0c;翻到了那道经典的“扩散”题。这道题初看之下&#xff0c;题干可能只有寥寥数语&#xff0c;甚至有些抽象&#xff0c;但正是这种简洁背后&#xff0c;藏着对算法基本…

作者头像 李华