常见面试题-leetcode

这里写自定义目录标题

小行星碰撞 问题

问题分析

题目描述:

给定一个整数数组 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,44,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;
}
相关推荐
Smilecoc1 小时前
决策树(一):决策树基本原理
算法·决策树·机器学习
weixin_307779131 小时前
从工具到协作者:AI在后端研发中的流程重构与组织赋能
人工智能·后端·python·算法·自动化
沉下去,苦磨练!1 小时前
深度学习神经网络的搭建
人工智能·算法
Lsk_Smion1 小时前
力扣实训 _ [207].课程表/图论
数据结构·leetcode·图论
孬甭_1 小时前
深入剖析快速排序:原理、实现与性能优化
数据结构·算法·排序算法
San813_LDD2 小时前
[数据结构]共享栈与双端队列:算法思想分析及C语言实现
java·开发语言·数据结构
阿正的梦工坊2 小时前
【Rust】06-函数、控制流与模块组织
开发语言·算法·rust
阿正的梦工坊2 小时前
【Rust】16-async/await、Future 与执行器模型
网络·算法·rust
阿正的梦工坊2 小时前
【Rust】11-Rust 所有权模型的编译期推理机制
开发语言·算法·rust