【Leecode 随笔】C语言版看了不后悔系列持续更新中。。。

在这里插入图片描述

🌈你好呀!我是 山顶风景独好
🎈欢迎踏入我的博客世界,能与您在此邂逅,真是缘分使然!😊
🌸愿您在此停留的每一刻,都沐浴在轻松愉悦的氛围中。
📖这里不仅有丰富的知识和趣味横生的内容等您来探索,更是一个自由交流的平台,期待您留下独特的思考与见解。🌟
🚀让我们一起踏上这段探索与成长的旅程,携手挖掘更多可能,共同进步!💪✨

第一题:赎金信

题目描述:

给定一个字符串S,通过将字符串中的每个字母转变成大写或小写,我们可以获得一个新的字符串。返回所有可能得到的字符串列表。返回的列表中的字符串可以按任意顺序排列。

示例输入与输出:

输入: “a1b2”
输出: [“a1b2”, “a1B2”, “A1b2”, “A1B2”]
输入: “3z4”
输出: [“3z4”, “3Z4”]

题目分析:

这是一道典型的回溯问题。我们需要遍历字符串中的每个字符,当遇到字母时,分别尝试将其转换为大写和小写,然后继续处理下一个字符。这样,我们就能生成所有可能的字符串组合。

解题思路:

初始化一个结果列表来存储所有可能的字符串。
使用递归函数来处理字符串的每个字符。
如果字符是数字,则直接添加到当前处理的字符串中。
如果字符是字母,则分别生成两个新字符串,一个将字母转为大写,一个转为小写,然后递归处理剩余的字符串。
当处理完所有字符时,将当前处理的字符串添加到结果列表中。

示例代码:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <ctype.h>  
  
// 定义结果列表的最大长度,根据输入字符串的长度动态调整  
#define MAX_RESULTS 1024  
  
// 存储结果的字符串数组  
char** results;  
int result_count;  
  
// 辅助函数,用于将当前处理的字符串添加到结果列表中  
void add_result(char* str) {  
    results[result_count] = strdup(str); // 使用strdup复制字符串,需要free  
    result_count++;  
}  
  
// 递归函数,生成所有可能的字符串组合  
void generate_permutations(char* s, char* current, int index) {  
    if (s[index] == '\0') {  
        // 处理完所有字符,添加当前字符串到结果列表中  
        add_result(current);  
        return;  
    }  
  
    // 添加当前字符到当前处理的字符串中  
    current[index] = s[index];  
  
    if (isalpha(s[index])) {  
        // 如果是字母,分别生成大写和小写的情况  
        generate_permutations(s, current, index + 1); // 保持当前字符不变  
        current[index] = toupper(s[index]); // 转为大写  
        generate_permutations(s, current, index + 1); // 递归处理  
        current[index] = tolower(s[index]); // 恢复为小写,为下一次递归做准备  
    } else {  
        // 如果是数字,直接递归处理下一个字符  
        generate_permutations(s, current, index + 1);  
    }  
}  
  
char** letterCasePermutation(char* S, int* returnSize) {  
    int len = strlen(S);  
    results = (char**)malloc(MAX_RESULTS * sizeof(char*)); // 分配存储空间  
    result_count = 0;  
  
    // 为当前处理的字符串分配空间,并初始化为空字符串  
    char* current = (char*)calloc(len + 1, sizeof(char));  
    generate_permutations(S, current, 0); // 生成所有可能的字符串组合  
  
    *returnSize = result_count; // 返回结果数量  
    free(current); // 释放临时字符串的空间  
    return results;  
}  
  
// 示例测试代码  
int main() {  
    char* S = "a1b2";  
    int returnSize;  
    char** result = letterCasePermutation(S, &returnSize);  
  
    for (int i = 0; i < returnSize; i++) {  
        printf("%s\n", result[i]);  
        free(result[i]); // 释放结果字符串的空间  
    }  
    free(result); // 释放结果数组的空间  
  
    return 0;  
}

深入剖析:

递归与回溯:本题使用了递归与回溯的思想。递归函数通过不断地处理字符串中的每个字符,生成所有可能的组合。当遇到字母时,通过递归生成两个新的分支,一个将字母转为大写,一个保持小写。
内存管理:在C语言中,内存管理是一个重要的考虑因素。我们使用malloc和calloc来动态分配存储空间,并在使用完毕后使用free来释放空间,以避免内存泄漏。
字符串处理:C语言中的字符串处理需要特别注意字符串的结束符\0。在生成新字符串时,我们需要确保正确地添加结束符,以避免字符串溢出或未正确结束的问题。

第二题:实现Trie(前缀树)

题目描述

实现一个Trie(前缀树),包含insert、search和startsWith这三个操作。
Trie的基本概念
Trie是一种树形数据结构,专门用于处理字符串的前缀匹配问题。Trie的每个节点代表一个字符,从根节点到某个节点的路径代表一个字符串的前缀。

解题思路

定义Trie节点的结构体:
包含指向子节点的指针数组(大小为字母表的大小)。
包含一个布尔值,表示节点是否是一个完整字符串的结束。
实现Trie树的结构体:
包含根节点的指针。
创建Trie树:
分配内存给Trie树和根节点。
初始化根节点的子节点指针为NULL。
设置根节点的结束标志为false。
插入字符串:
从根节点开始,遍历字符串的每个字符。
计算字符在字母表中的索引。
如果当前字符的子节点为空,则创建新节点。
移动到下一个字符,继续处理。
在字符串的最后一个字符处,标记节点为字符串结束。
搜索字符串:
从根节点开始,遍历字符串的每个字符。
计算字符在字母表中的索引。
如果当前字符的子节点为空,则字符串不存在。
移动到下一个字符,继续处理。
在字符串的最后一个字符处,检查节点是否标记为字符串结束。
检查前缀:
与搜索字符串类似,但不检查最后一个字符的结束标志。
释放Trie树的内存:
递归释放每个节点的内存。

示例代码

#include <stdio.h>    
#include <stdlib.h>    
#include <stdbool.h>    
#include <string.h>    
  
#define ALPHABET_SIZE 26    
  
// 定义Trie节点的结构体    
typedef struct TrieNode {    
    struct TrieNode* children[ALPHABET_SIZE];    
    bool isEndOfWord;    
} TrieNode;    
  
// 定义Trie树的结构体    
typedef struct {    
    TrieNode* root;    
} Trie;    
  
// 创建Trie树节点  
TrieNode* createTrieNode() {  
    TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode));  
    memset(node->children, 0, sizeof(node->children));  
    node->isEndOfWord = false;  
    return node;  
}  
  
// 创建Trie树    
Trie* trieCreate() {    
    Trie* obj = (Trie*)malloc(sizeof(Trie));    
    obj->root = createTrieNode();  
    return obj;    
}    
  
// 插入字符串到Trie树中    
void trieInsert(Trie* obj, char* word) {    
    TrieNode* node = obj->root;    
    while (*word) {    
        int index = *word - 'a';    
        if (node->children[index] == NULL) {    
            node->children[index] = createTrieNode();  
        }    
        node = node->children[index];    
        word++;    
    }    
    node->isEndOfWord = true;    
}    
  
// 搜索Trie树中的字符串    
bool trieSearch(Trie* obj, char* word) {    
    TrieNode* node = obj->root;    
    while (*word) {    
        int index = *word - 'a';    
        if (node->children[index] == NULL) {    
            return false;    
        }    
        node = node->children[index];    
        word++;    
    }    
    return node->isEndOfWord;    
}    
  
// 检查Trie树中是否包含以某个前缀开头的字符串    
bool trieStartsWith(Trie* obj, char* prefix) {    
    TrieNode* node = obj->root;    
    while (*prefix) {    
        int index = *prefix - 'a';    
        if (node->children[index] == NULL) {    
            return false;    
        }    
        node = node->children[index];    
        prefix++;    
    }    
    return true;    
}    
  
// 递归释放Trie树的内存  
void trieFreeHelper(TrieNode* node) {  
    for (int i = 0; i < ALPHABET_SIZE; i++) {  
        if (node->children[i] != NULL) {  
            trieFreeHelper(node->children[i]);  
        }  
    }  
    free(node);  
}  
  
// 释放Trie树的内存    
void trieFree(Trie* obj) {    
    if (obj != NULL && obj->root != NULL) {  
        trieFreeHelper(obj->root);  
    }  
    free(obj);  
}    
  
// 示例测试代码    
int main() {    
    Trie* obj = trieCreate();  
    trieInsert(obj, "apple");  
    printf("Search for 'apple': %s\n", trieSearch(obj, "apple") ? "true" : "false");  
    printf("Search for 'app': %s\n", trieSearch(obj, "app") ? "true" : "false");  
    printf("Starts with 'app': %s\n", trieStartsWith(obj, "app") ? "true" : "false");  
    trieFree(obj);  
  
    obj = trieCreate();  
    trieInsert(obj, "banana");  
    printf("Search for 'banana': %s\n", trieSearch(obj, "banana") ? "true" : "false");  
    printf("Search for 'ban': %s\n", trieSearch(obj, "ban") ? "true" : "false");  
    printf("Starts with 'ban': %s\n", trieStartsWith(obj, "ban") ? "true" : "false");  
    trieFree(obj);  
  
    return 0;    
}

代码解释

  1. 结构体定义:
    TrieNode结构体包含26个子节点指针和一个布尔值。
    Trie结构体包含一个根节点指针。
  2. 函数实现:
    createTrieNode函数用于创建并初始化一个新的Trie节点。
    trieCreate函数用于创建并初始化一个新的Trie树。
    trieInsert函数用于插入一个新的字符串到Trie树中。
    trieSearch函数用于搜索一个完整的字符串是否在Trie树中。
    trieStartsWith函数用于检查是否存在以某个前缀开头的字符串。
    trieFreeHelper函数是递归函数,用于释放Trie树的内存。
    trieFree函数释放Trie树的根节点和Trie树本身的内存。
  3. 测试代码:
    创建Trie树并插入字符串。
    测试搜索和前缀匹配功能。
    释放Trie树的内存。

第三题:买卖股票的最佳时机 II

题目描述:

你可以无限次地买卖一只股票,但必须在再次购买前卖出之前的股票。你的目标是最大化总收益。设计一个算法来计算可以获得的最大利润。

示例输入与输出:

输入:[7, 1, 5, 3, 6, 4]
输出:7
解释:在第 2 天(股票价格 = 1)买入,在第 3 天(股票价格 = 5)卖出,交易收益为 5-1 = 4。然后在第 4 天(股票价格 = 3)买入,在第 5 天(股票价格 = 6)卖出,交易收益为 6-3 = 3。总收益为 4 + 3 = 7。
输入:[1, 2, 3, 4, 5]
输出:4
解释:在第 1 天(股票价格 = 1)买入,在第 5 天(股票价格 = 5)卖出,交易收益为 5-1 = 4。总收益为 4。

解题思路:

这道题的关键在于找到所有股价上升的区间,并将这些区间的差值累加起来。具体实现时,我们可以遍历价格数组,如果当前价格比前一天高,就将差值加入总收益。

代码实现:

#include <stdio.h>  
  
int maxProfit(int* prices, int pricesSize) {  
    int maxProfit = 0;  
    for (int i = 1; i < pricesSize; i++) {  
        if (prices[i] > prices[i - 1]) {  
            maxProfit += prices[i] - prices[i - 1];  
        }  
    }  
    return maxProfit;  
}  
  
int main() {  
    int prices1[] = {7, 1, 5, 3, 6, 4};  
    int size1 = sizeof(prices1) / sizeof(prices1[0]);  
    printf("Max Profit: %d\n", maxProfit(prices1, size1)); // Output: 7  
  
    int prices2[] = {1, 2, 3, 4, 5};  
    int size2 = sizeof(prices2) / sizeof(prices2[0]);  
    printf("Max Profit: %d\n", maxProfit(prices2, size2)); // Output: 4  
  
    return 0;  
}

深入剖析:

这种贪心算法的时间复杂度为O(n),因为我们只需要遍历一次价格数组。这种方法简单且高效,适用于处理大规模数据。


✨ 这就是今天要分享给大家的全部内容了,我们下期再见!😊
🏠 我在CSDN等你哦!我的主页😍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

山顶风景独好

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

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

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

打赏作者

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

抵扣说明:

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

余额充值