这里写自定义目录标题
- [小行星碰撞 问题](#小行星碰撞 问题)
- 删除一个元素使数组严格递增
- 合并区间
- 最长无重复字符的子串
-
- 问题描述:
- [解题思路:滑动窗口 + 哈希表](#解题思路:滑动窗口 + 哈希表)
- 分析
- 实现
- 变形一:找出字符串中最长的连续数字串
- 找出字符串中最长连续重复子串
- 找出字符串中最长连续不重复子串
- 进制转换
- 数字字符串组合倒序
小行星碰撞 问题
问题分析
题目描述:
给定一个整数数组 asteroids,表示在同一行的小行星。每个元素的绝对值表示小行星的大小,正负表示方向(正数向右,负数向左)。所有小行星以相同的速度移动。
碰撞规则:
两个小行星相遇时,较小的会爆炸
如果大小相同,两者都爆炸
同向移动的小行星永远不会相遇
关键点:
只有方向相反(右向左,左向右)且右边的向左(负)、左边的向右(正)时才会碰撞
需要考虑连锁碰撞
解题思路
使用栈结构:
1、遍历每个小行星
2、将当前小行星与栈顶元素比较
3、只有栈顶为正(向右)且当前为负(向左)时才可能碰撞
4、根据大小关系决定:
-
栈顶更小:栈顶爆炸(弹出),继续比较
-
相等:两者都爆炸(弹出栈顶,当前不加入)
-
当前更小:当前爆炸(不加入栈)
5、其他情况直接入栈
实现
c
#include <stdio.h>
#include <stdlib.h>
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* asteroidCollision(int* asteroids, int asteroidsSize, int* returnSize) {
// 分配栈空间
int* stack = (int*)malloc(asteroidsSize * sizeof(int));
int top = -1; // 栈顶指针
for (int i = 0; i < asteroidsSize; i++) {
int current = asteroids[i];
int alive = 1; // 当前小行星是否存活
// 当栈不为空,栈顶向右,当前向左,且当前还存活时,发生碰撞
while (top >= 0 && stack[top] > 0 && current < 0 && alive) {
int top_val = stack[top];
if (top_val > -current) {
// 栈顶更大,当前爆炸
alive = 0;
}
else if (top_val < -current) {
// 当前更大,栈顶爆炸
top--;
// 继续循环,当前继续与新的栈顶比较
}
else {
// 大小相等,两者都爆炸
top--;
alive = 0;
}
}
// 如果当前小行星存活,压入栈中
if (alive) {
stack[++top] = current;
}
}
// 返回结果
*returnSize = top + 1;
int* result = (int*)malloc((*returnSize) * sizeof(int));
for (int i = 0; i < *returnSize; i++) {
result[i] = stack[i];
}
free(stack);
return result;
}
// 测试函数
void printResult(int* result, int size) {
printf("[");
for (int i = 0; i < size; i++) {
printf("%d", result[i]);
if (i < size - 1) printf(", ");
}
printf("]\n");
}
int main() {
// 测试用例1
int asteroids1[] = {5, 10, -5};
int size1 = sizeof(asteroids1) / sizeof(asteroids1[0]);
int returnSize1;
int* result1 = asteroidCollision(asteroids1, size1, &returnSize1);
printf("测试1: ");
printResult(result1, returnSize1);
free(result1);
// 测试用例2
int asteroids2[] = {8, -8};
int size2 = sizeof(asteroids2) / sizeof(asteroids2[0]);
int returnSize2;
int* result2 = asteroidCollision(asteroids2, size2, &returnSize2);
printf("测试2: ");
printResult(result2, returnSize2);
free(result2);
// 测试用例3
int asteroids3[] = {10, 2, -5};
int size3 = sizeof(asteroids3) / sizeof(asteroids3[0]);
int returnSize3;
int* result3 = asteroidCollision(asteroids3, size3, &returnSize3);
printf("测试3: ");
printResult(result3, returnSize3);
free(result3);
// 测试用例4
int asteroids4[] = {-2, -1, 1, 2};
int size4 = sizeof(asteroids4) / sizeof(asteroids4[0]);
int returnSize4;
int* result4 = asteroidCollision(asteroids4, size4, &returnSize4);
printf("测试4: ");
printResult(result4, returnSize4);
free(result4);
// 测试用例5
int asteroids5[] = {1, -2, -2, -2};
int size5 = sizeof(asteroids5) / sizeof(asteroids5[0]);
int returnSize5;
int* result5 = asteroidCollision(asteroids5, size5, &returnSize5);
printf("测试5: ");
printResult(result5, returnSize5);
free(result5);
return 0;
}
删除一个元素使数组严格递增
问题分析与解题思路
题目核心
给定一个整数数组,判断是否可以通过恰好删除一个元素(或数组本身已经满足条件),使得剩下的数组是严格递增的。
为什么不能用暴力法?
最直观的方法是遍历数组,每次删除一个元素生成新数组,再验证新数组是否严格递增。这种方法的时间复杂度高达O(n2),在数组长度较大时极易超时。
贪心与单次遍历策略
严格递增意味着对于任意相邻元素,必须满足 numsi-1 < numsi。如果在遍历过程中发现了第一个破坏递增条件的"逆序对"(即 numsi >= numsi+1),我们必须删除其中一个元素来修复它。此时只有两种选择:
- 选择 A:删除 numsi(较大的前一个元素)
修复后,需要保证新的相邻关系成立,即 numsi-1 < numsi+1。 - 选择 B:删除 numsi+1(较小的后一个元素)
修复后,需要保证新的相邻关系成立,即 numsi < numsi+2。
核心逻辑(遍历数组)
1、从头开始遍历,寻找第一个满足 numsi >= numsi+1 的位置。
2、如果整个数组都没有这样的逆序对,说明原数组已经是严格递增的,直接返回 true。
3、如果找到了逆序对,由于题目限制最多只能删除一次,因此我们只需针对这个逆序对,分别尝试上述的"选择 A"和"选择 B":
- 如果选择 A 成立,且从 i+1 到末尾的剩余元素是严格递增的,返回 true。
- 如果选择 B 成立,且从 i+2 到末尾的剩余元素是严格递增的,返回 true。
- 如果两者都不行,或者遍历中发现了第二个逆序对,直接返回 false。
实现
c
#include <stdio.h>
#include <stdbool.h>
// 辅助函数:检查数组从指定下标 start 开始到末尾是否严格递增
bool isIncreasing(int* nums, int numsSize, int start) {
for (int i = start; i < numsSize - 1; i++) {
if (nums[i] >= nums[i + 1]) {
return false;
}
}
return true;
}
bool canBeIncreasing(int* nums, int numsSize) {
// 遍历寻找第一个不满足严格递增的逆序对
for (int i = 0; i < numsSize - 1; i++) {
if (nums[i] >= nums[i + 1]) {
// 找到第一个逆序对,尝试删除 nums[i] 或 nums[i+1]
// 选择A:删除 nums[i],需要检查 nums[i-1] < nums[i+1]
// 如果 i==0,说明逆序对在数组开头,删除 nums[0] 必然合法
bool removeCurr = (i == 0 || nums[i - 1] < nums[i + 1]);
// 选择B:删除 nums[i+1],需要检查 nums[i] < nums[i+2]
// 如果 i+1 是最后一个元素,删除末尾元素必然合法
bool removeNext = (i + 1 == numsSize - 1 || nums[i] < nums[i + 2]);
// 只要其中一种删除方式合法,且删除后剩余部分保持递增,即可返回 true
if (removeCurr && isIncreasing(nums, numsSize, i + 1)) {
return true;
}
if (removeNext && isIncreasing(nums, numsSize, i + 2)) {
return true;
}
// 如果两种删除方式都无法使数组保持严格递增,直接返回 false
return false;
}
}
// 遍历结束都没有发现逆序对,说明原数组已经是严格递增的
return true;
}
// 测试主函数
int main() {
int nums[] = {1, 2, 10, 5, 7};
int size = sizeof(nums) / sizeof(nums[0]);
if (canBeIncreasing(nums, size)) {
printf("True\n");
} else {
printf("False\n");
}
return 0;
}
合并区间
题目描述:
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervalsi = start_i, end_i。请合并所有重叠的区间,并返回一个不重叠的区间数组。
关键点:
区间可能无序
需要合并所有重叠的区间
返回合并后的区间列表
示例:
输入:\[1,3,2,6,8,10,15,18] → 输出:\[1,6,8,10,15,18]
输入:\[1,4,4,5] → 输出:\[1,5]
分析
(1)排序:按区间起点排序
(2)合并:遍历排序后的区间,合并重叠部分
如果当前区间与上一个合并后的区间重叠,更新结束点
否则,将当前区间加入结果
为什么需要排序?
如果不排序,我们需要将每个区间与所有已发现的区间进行重叠判断,时间复杂度高达 O(n2)。如果我们将所有区间按照起始位置(start)升序排列,那么可能重叠的区间在数组中一定是连续的。这样只需一次线性扫描即可完成合并,将时间复杂度降至 O(nlgn)。
遍历合并
排序后,我们维护一个当前正在合并的区间 start, end,依次遍历后续区间:
(1) 重叠情况:如果当前区间的起点 intervalsi0 <= end,说明两者有交集(注意:像 1,4 和 4,5 这种端点相接的也算重叠,因此必须包含等号 <=)。此时不需要新增区间,只需将 end 更新为 max(end, intervalsi1)。
(2) 不重叠情况:如果 intervalsi0 > end,说明当前区间与之前的区间完全独立。此时需要将之前合并好的 start, end 存入结果集,并将当前区间作为新的 start, end。
(3) 收尾工作:循环结束后,千万不要忘记把最后一个维护的 start, end 加入结果集中。
复杂度
时间复杂度: O(nlogn) 。主要开销在于 qsort 排序,排序后的线性扫描仅需 O(n) 。
空间复杂度: O(n) 。除了返回的结果数组外,qsort 内部递归栈大约需要
O(logn) 的额外空间。
实现
c
#include <stdio.h>
#include <stdlib.h>
// 区间结构体
typedef struct {
int start;
int end;
} Interval;
// 比较函数
int compareIntervals(const void* a, const void* b) {
Interval* ia = (Interval*)a;
Interval* ib = (Interval*)b;
return ia->start - ib->start;
}
// 合并区间 - 返回动态分配的二维数组
Interval* mergeIntervals(Interval* intervals, int size, int* returnSize) {
if (size == 0) {
*returnSize = 0;
return NULL;
}
// 排序
qsort(intervals, size, sizeof(Interval), compareIntervals);
// 分配结果数组
Interval* result = (Interval*)malloc(size * sizeof(Interval));
int idx = 0;
// 合并
for (int i = 0; i < size; i++) {
if (idx == 0 || intervals[i].start > result[idx-1].end) {
result[idx++] = intervals[i];
} else {
if (intervals[i].end > result[idx-1].end) {
result[idx-1].end = intervals[i].end;
}
}
}
*returnSize = idx;
return result;
}
// 打印结果
void printResult(Interval* intervals, int size) {
printf("[");
for (int i = 0; i < size; i++) {
printf("[%d,%d]", intervals[i].start, intervals[i].end);
if (i < size - 1) printf(", ");
}
printf("]\n");
}
int main() {
// 测试用例
Interval test1[] = {{1,3}, {2,6}, {8,10}, {15,18}};
int size1 = sizeof(test1) / sizeof(test1[0]);
int returnSize1;
Interval* result1 = mergeIntervals(test1, size1, &returnSize1);
printf("测试1: ");
printResult(result1, returnSize1);
free(result1);
Interval test2[] = {{1,4}, {4,5}};
int size2 = sizeof(test2) / sizeof(test2[0]);
int returnSize2;
Interval* result2 = mergeIntervals(test2, size2, &returnSize2);
printf("测试2: ");
printResult(result2, returnSize2);
free(result2);
Interval test3[] = {{1,4}, {0,2}, {3,5}};
int size3 = sizeof(test3) / sizeof(test3[0]);
int returnSize3;
Interval* result3 = mergeIntervals(test3, size3, &returnSize3);
printf("测试3: ");
printResult(result3, returnSize3);
free(result3);
return 0;
}
最长无重复字符的子串
问题描述:
给定一个字符串,找出其中最长的、不包含重复字符的连续子串的长度。
示例:
输入:"abcabcbb" → 输出:3 (对应子串 "abc")
输入:"bbbbb" → 输出:1 ("b")
输入:"pwwkew" → 输出:3 ("wke" 或 "kew")
解题思路:滑动窗口 + 哈希表
这是最高效的解法,核心是维护一个动态窗口,确保窗口内的字符永远不重复。
定义指针:使用 left 表示窗口左边界,right 表示右边界。
移动右指针:不断尝试向右移动 right 来扩大窗口。
记录位置:用一个数组 lastIndex(哈希表)记录每个字符最近一次出现的位置。
处理重复:当遇到重复字符时(即 lastIndex当前字符 的位置在窗口内),直接将 left 指针移动到该重复字符的下一个位置,从而瞬间跳过所有重复的可能。
更新长度:每次窗口调整后,计算当前窗口长度 right - left + 1,并更新最大值。
这种方法只需遍历一次字符串,非常高效。
注意:关于边界条件(如 abba 的情况),必须判断 lastIndexs\[right] >= left 才能确保跳过的是窗口内的重复字符,而不是窗口外历史的重复记录
分析
lastIndex128:这里用一个数组代替了复杂的哈希表,因为字符的 ASCII 码范围是 0-127,直接用字符做下标就能 O(1) 存取,非常高效。
if (lastIndexcurrentChar >= left):这是 abba 这类用例能正确运行的关键。它确保了只有当重复字符出现在当前窗口内部时,我们才移动 left;如果重复字符在 left 左边(已被排除出窗口),则不影响。
复杂度:时间复杂度 O(n),空间复杂度 O(1)(哈希表大小固定为 128)。
实现
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 函数:最长无重复字符的子串
// 参数:s - 输入字符串
// 返回值:最长子串的长度
int lengthOfLongestSubstring(char *s) {
int n = strlen(s);
if (n == 0) return 0;
// 初始化哈希表,记录每个字符最后一次出现的位置
// ASCII 共有 128 个常用字符,用 128 大小足够
int lastIndex[128];
for (int i = 0; i < 128; i++) {
lastIndex[i] = -1;
}
int left = 0; // 滑动窗口左边界
int maxLen = 0; // 最长子串长度
// right 为滑动窗口右边界
for (int right = 0; right < n; right++) {
char currentChar = s[right];
// 关键:如果当前字符之前出现过,并且在当前窗口内
// 则直接将左边界移动到重复字符的下一个位置
if (lastIndex[currentChar] >= left) {
left = lastIndex[currentChar] + 1;
}
// 更新当前字符的最新位置
lastIndex[currentChar] = right;
// 计算当前窗口长度并更新最大值
int currentLen = right - left + 1;
if (currentLen > maxLen) {
maxLen = currentLen;
}
}
return maxLen;
}
// 测试用例
int main() {
char str1[] = "abcabcbb";
char str2[] = "bbbbb";
char str3[] = "pwwkew";
char str4[] = "abba";
printf("输入: %s, 最长无重复子串长度: %d\n", str1, lengthOfLongestSubstring(str1));
printf("输入: %s, 最长无重复子串长度: %d\n", str2, lengthOfLongestSubstring(str2));
printf("输入: %s, 最长无重复子串长度: %d\n", str3, lengthOfLongestSubstring(str3));
printf("输入: %s, 最长无重复子串长度: %d\n", str4, lengthOfLongestSubstring(str4));
return 0;
}
变形一:找出字符串中最长的连续数字串
问题分析
给定一个字符串,找出其中最长的连续数字子串。如果有多个相同长度,返回第一个。
示例:
输入:"abcd12345ed125ss123456789" → 输出:"123456789"
实现
c
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
/**
* 找出字符串中最长的连续数字子串
* @param str 输入字符串
* @return 返回最长数字子串(需要调用者free)
*/
char* findLongestDigitSubstring(const char* str) {
if (str == NULL || *str == '\0') {
return NULL;
}
int len = strlen(str);
int maxLen = 0;
int start = 0; // 最长子串的起始位置
int currentStart = 0;
int currentLen = 0;
int i = 0;
while (i <= len) {
// 如果当前字符是数字,计数加1
if (i < len && isdigit(str[i])) {
if (currentLen == 0) {
currentStart = i;
}
currentLen++;
} else {
// 遇到非数字或结尾,检查是否需要更新
if (currentLen > maxLen) {
maxLen = currentLen;
start = currentStart;
}
currentLen = 0;
}
i++;
}
// 分配内存并复制结果
if (maxLen > 0) {
char* result = (char*)malloc((maxLen + 1) * sizeof(char));
strncpy(result, str + start, maxLen);
result[maxLen] = '\0';
return result;
}
return NULL;
}
/**
* 找出字符串中最长的连续数字子串(返回所有信息)
*/
void findLongestDigitSubstringEx(const char* str, char* result, int* length) {
if (str == NULL || *str == '\0') {
if (result) result[0] = '\0';
if (length) *length = 0;
return;
}
int len = strlen(str);
int maxLen = 0;
int start = 0;
int currentStart = 0;
int currentLen = 0;
for (int i = 0; i <= len; i++) {
if (i < len && isdigit(str[i])) {
if (currentLen == 0) {
currentStart = i;
}
currentLen++;
} else {
if (currentLen > maxLen) {
maxLen = currentLen;
start = currentStart;
}
currentLen = 0;
}
}
if (length) *length = maxLen;
if (result && maxLen > 0) {
strncpy(result, str + start, maxLen);
result[maxLen] = '\0';
} else if (result) {
result[0] = '\0';
}
}
int main() {
// 测试用例
const char* test1 = "abcd12345ed125ss123456789";
const char* test2 = "abc123def4567ghi89";
const char* test3 = "no digits here";
const char* test4 = "123456";
const char* test5 = "a1b22c333d4444e55555";
printf("测试1: \"%s\"\n", test1);
char* result1 = findLongestDigitSubstring(test1);
printf("最长数字子串: %s\n\n", result1 ? result1 : "无");
free(result1);
printf("测试2: \"%s\"\n", test2);
char* result2 = findLongestDigitSubstring(test2);
printf("最长数字子串: %s\n\n", result2 ? result2 : "无");
free(result2);
printf("测试3: \"%s\"\n", test3);
char* result3 = findLongestDigitSubstring(test3);
printf("最长数字子串: %s\n\n", result3 ? result3 : "无");
free(result3);
printf("测试4: \"%s\"\n", test4);
char* result4 = findLongestDigitSubstring(test4);
printf("最长数字子串: %s\n\n", result4 ? result4 : "无");
free(result4);
printf("测试5: \"%s\"\n", test5);
char* result5 = findLongestDigitSubstring(test5);
printf("最长数字子串: %s\n\n", result5 ? result5 : "无");
free(result5);
return 0;
}
找出字符串中最长连续重复子串
问题分析
找出字符串中连续重复最长的子串。
示例:
输入:"aabbbccccdd" → 输出:"cccc"
实现
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/**
* 找出字符串中最长连续重复子串
*/
char* findLongestRepeatedSubstring(const char* str) {
if (str == NULL || *str == '\0') {
return NULL;
}
int len = strlen(str);
int maxLen = 1;
int start = 0;
int currentLen = 1;
int currentStart = 0;
for (int i = 1; i <= len; i++) {
if (i < len && str[i] == str[i - 1]) {
currentLen++;
} else {
if (currentLen > maxLen) {
maxLen = currentLen;
start = currentStart;
}
currentStart = i;
currentLen = 1;
}
}
char* result = (char*)malloc((maxLen + 1) * sizeof(char));
strncpy(result, str + start, maxLen);
result[maxLen] = '\0';
return result;
}
int main() {
const char* test = "aabbbccccdd";
char* result = findLongestRepeatedSubstring(test);
printf("字符串: %s\n", test);
printf("最长连续重复子串: %s\n", result);
free(result);
return 0;
}
找出字符串中最长连续不重复子串
问题分析
找出字符串中最长的没有重复字符的连续子串。
示例:
输入:"abcabcbb" → 输出:"abc"(长度3)
实现
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define ASCII_SIZE 256
/**
* 找出字符串中最长无重复字符子串
*/
char* findLongestUniqueSubstring(const char* str) {
if (str == NULL || *str == '\0') {
return NULL;
}
int len = strlen(str);
int lastIndex[ASCII_SIZE];
for (int i = 0; i < ASCII_SIZE; i++) {
lastIndex[i] = -1;
}
int maxLen = 0;
int start = 0;
int currentStart = 0;
for (int i = 0; i < len; i++) {
unsigned char ch = (unsigned char)str[i];
// 如果字符已经出现过且在窗口内,移动窗口
if (lastIndex[ch] >= currentStart) {
currentStart = lastIndex[ch] + 1;
}
lastIndex[ch] = i;
int currentLen = i - currentStart + 1;
if (currentLen > maxLen) {
maxLen = currentLen;
start = currentStart;
}
}
char* result = (char*)malloc((maxLen + 1) * sizeof(char));
strncpy(result, str + start, maxLen);
result[maxLen] = '\0';
return result;
}
int main() {
const char* test = "abcabcbb";
char* result = findLongestUniqueSubstring(test);
printf("字符串: %s\n", test);
printf("最长无重复字符子串: %s (长度: %lu)\n", result, strlen(result));
free(result);
return 0;
}
进制转换
分析
核心原理:辗转相除法
将一个十进制数转换为 N 进制数,可以遵循一个非常简单的步骤:
取余:用原数除以 N,记录得到的余数,这就是 N 进制下的最低位。
更新:将原数更新为除以 N 得到的商。
重复:重复步骤 1 和 2,直到商为 0。
逆序:将记录下来的所有余数逆序排列,就得到了最终的 N 进制表示。
一个直观的例子:把十进制 10 转成二进制 1010
10 ÷ 2 = 5 ... 0 (最低位)
5 ÷ 2 = 2 ... 1
2 ÷ 2 = 1 ... 0
1 ÷ 2 = 0 ... 1 (最高位)
逆序取余:从最后一个余数读到第一个,得到 1010。
注意
在 C 语言中实现时,需要注意两个关键点:处理零和处理负数。
下面这个 convertBase 函数是一个强大的通用模板,几乎可以处理所有正数进制(如二进制、七进制、八进制等)的转换
进制转换
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* 将一个十进制整数转换为任意进制(2-16)的字符串表示
* @param num 要转换的十进制整数
* @param base 目标进制(2-16)
* @return 动态分配的字符串,需调用者手动 free()
*/
char* convertBase(int num, int base) {
// 1. 特殊值 0 的处理
if (num == 0) {
char* result = (char*)malloc(2);
strcpy(result, "0");
return result;
}
// 2. 定义进制字符表,用于处理 10-15 的字母表示(A-F)
const char table[] = "0123456789ABCDEF";
// 3. 处理负数:记录符号,并将数字转为正数进行计算
int isNegative = (num < 0);
unsigned int n = isNegative ? -num : num;
// 4. 分配临时空间。int 类型最多32位,二进制表示也只需33字节(含结束符)
char* buffer = (char*)malloc(33 * sizeof(char));
int index = 0;
// 5. 核心循环:辗转相除法
while (n > 0) {
// 取余数,并根据表找到对应的字符
buffer[index++] = table[n % base];
// 更新为商
n /= base;
}
// 6. 添加负号
if (isNegative) {
buffer[index++] = '-';
}
// 7. 逆序字符串(因为上述步骤得到的是低位到高位的顺序)
buffer[index] = '\0';
for (int i = 0; i < index / 2; i++) {
char temp = buffer[i];
buffer[i] = buffer[index - 1 - i];
buffer[index - 1 - i] = temp;
}
// 8. 将结果复制到恰好大小的内存中返回
char* result = (char*)malloc((index + 1) * sizeof(char));
strcpy(result, buffer);
free(buffer);
return result;
}
数字字符串组合倒序
题目说明
题目要求将一行字符串中的"单词"提取出来,然后反转它们的排列顺序。关键在于如何定义"单词",规则如下:
(1) 普通单词:由连续字母(a-z, A-Z)组成的字符串。
(2) 数字串:由连续数字(0-9)组成的字符串。
(3) 组合单词:
-
如果连接在字母或数字之间,则视为单词的一部分,例如 "20-years" 是一个整体。
-
连续两个或以上的 - (--),则作为分隔符。
(4) 非法字符:除了字母、数字、以及作为连接符的单横杠 - 之外的所有字符(如 @ * 等),都视为单词的间隔符。
(5) 输出格式:倒排后的单词之间用一个空格隔开,所有间隔符统一替换成一个空格,且首尾没有多余空格。
(6) 范例
输入:I am an 20-years out--standing @ * -stu- dent
预期输出:dent stu standing out 20-years an am I
分析
根据规则,我们可以将解题过程分为三个清晰的步骤:
(1)单词切分(Tokenization):
-
遍历输入的字符串。
-
维护一个状态机,区分当前是否处于一个"有效单词"的内部。
-
当遇到字母或数字时,开始一个新的单词。如果遇到 -,需要查看下一个字符,如果下一个字符也是 -,则 - 作为分隔符,否则 - 作为连接符追加到当前单词。
-
遇到其他非法字符,直接作为分隔符,结束当前单词。
(2)存储单词:
将切分出来的每一个有效单词按顺序存入一个字符串数组中。
(3)反转输出:
将存储单词的数组倒序输出,单词之间加一个空格。
实现
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdbool.h>
#define MAX_WORDS 1000 // 最大单词数
#define MAX_LEN 100 // 每个单词的最大长度
/**
* 判断字符是否可以作为单词的组成部分
* 规则:字母、数字、或者作为连接符的单横杠
* @param c 当前字符
* @param next_char 下一个字符(用于判断横杠是否为连接符)
* @param has_next 是否有下一个字符
*/
bool isWordChar(char c, char next_char, bool has_next) {
// 字母或数字,直接是单词组成部分
if (isalnum(c)) {
return true;
}
// 横杠作为单词组成部分的条件:
// 1. 当前字符是横杠
// 2. 有下一个字符(不是字符串结尾)
// 3. 下一个字符不是横杠(避免"--"作为连接符)
if (c == '-' && has_next && next_char != '-') {
return true;
}
return false;
}
int main() {
char input[1000];
char words[MAX_WORDS][MAX_LEN];
int wordCount = 0;
// 读取整行输入
printf("请输入字符串: ");
fgets(input, sizeof(input), stdin);
// 去除末尾的换行符
int len = strlen(input);
if (len > 0 && input[len - 1] == '\n') {
input[len - 1] = '\0';
len--; // 更新长度
}
printf("\n原始输入: %s\n", input);
printf("字符串长度: %d\n\n", len);
int i = 0;
while (i < len) {
// 1. 跳过无效字符(分隔符)
char current = input[i];
// 跳过既不是单词组成部分,也不是横杠分隔符的字符
if (!isalnum(current) && current != '-') {
i++;
continue;
}
// 2. 跳过连续横杠"--"作为分隔符
char next = (i + 1 < len) ? input[i + 1] : '\0';
bool has_next = (i + 1 < len);
if (current == '-' && has_next && next == '-') {
i++;
continue;
}
// 3. 开始提取一个完整的单词
char currentWord[MAX_LEN] = {0};
int idx = 0;
// 循环提取单词的每个字符
while (i < len) {
char ch = input[i];
char next_ch = (i + 1 < len) ? input[i + 1] : '\0';
bool has_next_ch = (i + 1 < len);
// 判断当前字符是否是单词的一部分
if (isWordChar(ch, next_ch, has_next_ch)) {
// 防止缓冲区溢出
if (idx < MAX_LEN - 1) {
currentWord[idx++] = ch;
}
i++;
} else {
// 不是单词组成部分,停止提取
break;
}
}
// 4. 存储提取到的单词
if (idx > 0) {
currentWord[idx] = '\0';
strcpy(words[wordCount], currentWord);
wordCount++;
// 调试输出:显示提取到的单词
printf("提取单词[%d]: %s\n", wordCount - 1, currentWord);
}
}
// 5. 倒序输出结果
printf("\n=== 倒序输出 ===\n");
for (int j = wordCount - 1; j >= 0; j--) {
printf("%s", words[j]);
if (j > 0) {
printf(" ");
}
}
printf("\n");
return 0;
}