删除有序数组重复项与反转字符串单词:算法优化数据处理效率

#新星杯·14天创作挑战营·第11期#

博客引言:

在我们的日常生活中,数据处理是无处不在的。无论是清理杂乱的文件,还是重新排列书架上的书籍,都需要高效的算法来优化我们的工作流程。今天,我们将通过两个有趣的问题,探索如何用算法来优化数据处理的效率。

首先,我们将探讨删除有序数组中重复项问题,看看如何在原地删除重复项,使得每个元素最多出现两次,并返回新的数组长度。接着,我们将分析反转字符串中的单词问题,探讨如何高效地反转单词顺序,同时处理多余的空格。通过这两个案例,你将看到算法如何在实际数据处理中发挥作用,帮助我们更高效地管理和操作数据。

让我们一起进入算法的世界,探索这些优化问题背后的奥秘!


博客正文:

一、删除有序数组中重复项:原地优化数据

场景描述:
给定一个有序数组nums,要求原地删除重复出现的元素,使得出现次数超过两次的元素只出现两次,并返回删除后数组的新长度。必须在原地修改输入数组,并且使用O(1)额外空间。

算法核心:双指针法
这个问题可以通过双指针法来解决。维护一个慢指针slow和一个快指针fast。slow用于记录当前处理的位置,fast用于遍历数组。当nums[fast]与nums[slow]相等时,检查是否已经出现了两次,如果未出现两次,则slow前进并赋值;如果已经出现两次,则fast继续前进。这样,最终slow的位置即为新数组的长度。

详细分析:

  1. 初始化:slow = 0,fast = 0。
  2. 遍历数组:当fast遍历到数组末尾时,结束。
    • 如果nums[fast] != nums[slow],则slow前进,并赋值为nums[fast]。
    • 如果nums[fast] == nums[slow],则检查是否已经出现两次。如果未出现两次,则slow前进,并赋值为nums[fast]。
  3. 返回结果:slow + 1即为新数组的长度。

题目验证示例:

  • 示例1:nums = [1,1,1,2,2,3],输出为5,新数组为[1,1,2,2,3]。
  • 示例2:nums = [0,0,1,1,1,1,2,3,3],输出为7,新数组为[0,0,1,1,2,3,3]。
#include <stdio.h>  // 包含标准输入输出库,用于printf等函数

/**
 * 移除有序数组中超过两次重复的元素,返回新数组的长度(使用双指针法)。
 * @param nums 有序数组(原地修改)
 * @param numsSize 数组原始长度
 * @return 删除重复元素后的新数组长度
 */
int removeDuplicates(int* nums, int numsSize) {
    // 处理空数组情况
    if (numsSize == 0) return 0;  // 若数组长度为0直接返回0

    int slow = 0;  // 初始化慢指针,用于标记有效元素位置(最终长度=slow)
    // 快指针遍历整个数组
    for (int fast = 0; fast < numsSize; ++fast) {  // fast从0到numsSize-1遍历每个元素
        /* 核心逻辑:当满足以下任一条件时保留当前元素
           1. slow处于前两个位置(允许最多两个相同元素)
           2. 当前元素不等于slow前两位的值(保证最多重复两次) */
        if (slow < 2 || nums[fast] != nums[slow - 2]) {
            nums[slow] = nums[fast];  // 将快指针元素复制到慢指针位置
            slow++;                   // 慢指针前进,标记下一个有效位
        }
    }
    return slow;  // 返回新数组长度(slow最后的值即为有效元素总数)
}

int main() {
    // --------------- 示例1验证 ---------------
    int nums1[] = {1, 1, 1, 2, 2, 3};  // 原始测试数据
    int numsSize1 = sizeof(nums1) / sizeof(nums1[0]);  // 计算数组长度:总字节数/单个元素字节数
    int newLength1 = removeDuplicates(nums1, numsSize1);  // 调用处理函数
    printf("Example 1 Output Length: %d\n", newLength1);  // 打印新数组长度
    printf("New array: ");
    for (int i = 0; i < newLength1; ++i) {  // 遍历输出新数组元素
        printf("%d ", nums1[i]);
    }
    printf("\n\n");  // 换行分隔示例

    // --------------- 示例2验证 ---------------
    int nums2[] = {0, 0, 1, 1, 1, 1, 2, 3, 3};  // 含多个重复段的测试数据
    int numsSize2 = sizeof(nums2) / sizeof(nums2[0]);  // 计算数组长度
    int newLength2 = removeDuplicates(nums2, numsSize2);  // 处理数组
    printf("Example 2 Output Length: %d\n", newLength2);
    printf("New array:  ");
    for (int i = 0; i < newLength2; ++i) {  // 输出结果验证
        printf("%d ", nums2[i]);
    }
    printf("\n");  // 换行结束输出

    return 0;  // 程序正常退出
}

 

输出结果:

 


二、反转字符串中的单词:高效处理字符串

场景描述:
给定一个字符串s,要求反转字符串中单词的顺序。单词是由非空格字符组成的字符串,单词之间使用至少一个空格分隔。返回反转后的字符串,单词之间用单个空格连接,并且不包含额外的空格。

算法核心:分割、反转、重组
这个问题可以通过以下步骤解决:

  1. 分割单词:将字符串分割成单词列表,忽略多余的空格。
  2. 反转列表:将单词列表反转。
  3. 重组字符串:将反转后的单词列表重新组合成字符串,单词之间用单个空格分隔。

详细分析:

  1. 分割单词
    • 使用指针遍历字符串,记录单词的起始和结束位置。
    • 忽略前导空格、尾随空格以及单词间的多个空格。
  2. 反转列表
    • 将单词列表反转,使得第一个单词变为最后一个,依此类推。
  3. 重组字符串
    • 将反转后的单词列表重新组合成字符串,确保单词之间只有一个空格。

题目验证示例:

  • 示例1:s = "the sky is blue",输出为"blue is sky the"。
  • 示例2:s = " hello world ",输出为"world hello"。
  • 示例3:s = "a good example",输出为"example good a"。

 

#include <stdio.h>   // 引入标准输入输出库,用于printf等函数
#include <string.h>  // 引入字符串处理库,用于strlen等函数

/**
 * 去除字符串中多余空格(前导、中间多空格、尾随)
 * @param s 待处理字符串(原地修改)
 * @return 处理后字符串长度
 */
int removeExtraSpaces(char *s) {
    int slow = 0, fast = 0;  // 创建双指针:slow写指针,fast读指针
    int len = strlen(s);      // 获取原始字符串长度

    // 阶段一:去除前导空格
    while (fast < len && s[fast] == ' ') fast++;  // fast跳过所有起始空格

    // 阶段二:处理中间空格(快指针遍历剩余字符)
    for (; fast < len; fast++) {                 // 遍历剩余字符
        // 当检测到连续空格时跳过(保留第一个空格)
        if (fast > 1 && s[fast] == ' ' && s[fast - 1] == ' ') continue;
        s[slow++] = s[fast];  // 将有效字符写入slow位置并移动指针
    }

    // 阶段三:去除尾随空格
    if (slow > 0 && s[slow - 1] == ' ') slow--;  // 若末尾有空格则回退slow
    s[slow] = '\0';          // 添加字符串终止符

    return slow;  // 返回新字符串长度(slow值即为有效长度)
}

/**
 * 反转字符串指定区间
 * @param s 字符串指针(允许原地修改)
 * @param start 起始位置(包含)
 * @param end 结束位置(包含)
 */
void reverseSubstring(char *s, int start, int end) {
    while (start < end) {           // 双指针向中间移动直到相遇
        char tmp = s[start];        // 经典三变量交换法
        s[start] = s[end];          // 交换首尾字符
        s[end] = tmp;               // 完成字符位置调换
        start++;                    // 左指针右移
        end--;                      // 右指针左移
    }
}

/**
 * 反转字符串中的单词顺序(主功能函数)
 * @param s 输入字符串(原地修改)
 */
void reverseWords(char *s) {
    int len = removeExtraSpaces(s);  // 预处理:去除所有多余空格
    if (len == 0) return;           // 处理空字符串特殊情况

    // 步骤一:整体反转字符串(如"hello world" -> "dlrow olleh")
    reverseSubstring(s, 0, len - 1);

    // 步骤二:逐个单词反转(恢复单词原始顺序)
    int wordStart = 0;              // 单词起始位置标记
    for (int i = 0; i <= len; i++) {  // 包含len位置处理终止符
        // 检测单词边界(空格或字符串结尾)
        if (s[i] == ' ' || s[i] == '\0') {
            reverseSubstring(s, wordStart, i - 1);  // 反转单个单词
            wordStart = i + 1;       // 重置单词起始为下一个字符位置
        }
    }
}

// 验证用例
int main() {
    // ------------------- 示例1验证 -------------------
    char str1[] = "the sky is blue";  // 标准无多余空格用例
    reverseWords(str1);               // 执行核心处理
    printf("Example 1 output: \"%s\"\n", str1);  // 输出:"blue is sky the"

    // ------------------- 示例2验证 -------------------
    char str2[] = "  hello  world  ";  // 含多空格用例
    reverseWords(str2);                // 处理含前后导空格情况
    printf("Example 2 output: \"%s\"\n", str2);  // 输出:"world hello"

    // ------------------- 示例3验证 -------------------
    char str3[] = "a good   example";  // 中间含多空格用例
    reverseWords(str3);                // 验证多空格压缩能力
    printf("Example 3 output: \"%s\"\n", str3);  // 输出:"example good a"

    return 0;  // 程序正常退出
}

输出结果:

 


三、全方位对比:删除重复项 vs 反转单词

对比维度删除有序数组中重复项反转字符串中的单词
问题类型数组操作、原地修改字符串操作、单词反转
算法核心双指针法分割、反转、重组
复杂度时间O(n),空间O(1)时间O(n),空间O(n)
应用场景数据清洗、去重文本处理、字符串反转
优化目标最小化空间使用,高效删除重复项高效处理字符串,确保单词顺序正确反转

博客总结:

通过今天的分析,我们看到算法不仅仅是冰冷的代码,它还能帮助我们在实际数据处理中实现高效和优化。无论是删除有序数组中的重复项,还是反转字符串中的单词,背后的算法都在默默发挥作用,帮助我们更高效地管理和操作数据。

希望这篇文章能让你对这些优化问题有更深入的了解,也期待你在生活中发现更多有趣的场景,用算法的视角去探索它们!


博客谢言:

感谢你的耐心阅读!如果你觉得这篇文章有趣,不妨在评论区分享你生活中遇到的数据处理问题,或者你认为可以用算法优化的地方。让我们一起用算法的视角去探索数据的奥秘!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

司铭鸿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值