力扣题解2306

news2024/9/25 13:26:24

大家好,欢迎来到无限大的频道。

今日继续给大家带来力扣题解。

题目描述(困难):

公司命名

给你一个字符串数组 ideas 表示在公司命名过程中使用的名字列表。公司命名流程如下:

  1. 从 ideas 中选择 2 个 不同 名字,称为 ideaA 和 ideaB 。

  2. 交换 ideaA 和 ideaB 的首字母。

  3. 如果得到的两个新名字 都 不在 ideas 中,那么 ideaA ideaB(串联 ideaA 和 ideaB ,中间用一个空格分隔)是一个有效的公司名字。

  4. 否则,不是一个有效的名字。

返回 不同 且有效的公司名字的数目。

解题思路:

分析

  1. 目标:从给定的字符串数组 ideas 中选择两个不同的名字 ideaA 和 ideaB,交换它们的首字母,如果生成的新名字都不在 ideas 中,则计数为有效的公司名字。

  2. 有效性条件:对于两个名字 ideaA 和 ideaB,如果交换首字母后得到的新名字 newIdeaA 和 newIdeaB 都不在 ideas 中,则它们是有效的。

  3. 思路:

    • 我们可以将 ideas 按首字母进行分组。

    • 对于每个组中的名字,我们尝试用其他组的首字母来生成新名字。

    • 使用哈希表来快速检查生成的新名字是否已经存在于 ideas 中。

思路

  1. 分组:将所有名字按首字母分组。例如,ideas 中的名字可以分成以 'c' 开头的,'d' 开头的等。

  2. 生成新名字:对于每个组中的名字,尝试用其他组的首字母交换生成新名字。

  3. 检查有效性:使用哈希表存储 ideas,以便快速检查生成的新名字是否存在。

  4. 计数:如果生成的新名字都不在 ideas 中,则计数为有效。

参考代码如下:

#define ALPHABET_SIZE 26
​
typedef struct Node {
    char *word;
    struct Node *next;
} Node;
​
typedef struct {
    Node *buckets[ALPHABET_SIZE];
} HashSet;
​
unsigned int hash(const char *str) {
    return str[0] - 'a';
}
​
HashSet* createHashSet() {
    HashSet *set = (HashSet *)malloc(sizeof(HashSet));
    for (int i = 0; i < ALPHABET_SIZE; i++) {
        set->buckets[i] = NULL;
    }
    return set;
}
​
void addWord(HashSet *set, const char *word) {
    unsigned int index = hash(word);
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->word = strdup(word);
    newNode->next = set->buckets[index];
    set->buckets[index] = newNode;
}
​
bool containsWord(HashSet *set, const char *word) {
    unsigned int index = hash(word);
    Node *current = set->buckets[index];
    while (current != NULL) {
        if (strcmp(current->word, word) == 0) {
            return true;
        }
        current = current->next;
    }
    return false;
}
​
void freeHashSet(HashSet *set) {
    for (int i = 0; i < ALPHABET_SIZE; i++) {
        Node *current = set->buckets[i];
        while (current != NULL) {
            Node *temp = current;
            current = current->next;
            free(temp->word);
            free(temp);
        }
    }
    free(set);
}
​
long long distinctNames(char **ideas, int ideasSize) {
    HashSet *set = createHashSet();
    for (int i = 0; i < ideasSize; i++) {
        addWord(set, ideas[i]);
    }
​
    long long groups[ALPHABET_SIZE][ALPHABET_SIZE] = {0};
​
    for (int i = 0; i < ideasSize; i++) {
        char firstChar = ideas[i][0];
        for (char c = 'a'; c <= 'z'; c++) {
            if (c != firstChar) {
                ideas[i][0] = c;
                if (!containsWord(set, ideas[i])) {
                    groups[firstChar - 'a'][c - 'a']++;
                }
            }
        }
        ideas[i][0] = firstChar;
    }
​
    long long count = 0;
    for (int i = 0; i < ALPHABET_SIZE; i++) {
        for (int j = 0; j < ALPHABET_SIZE; j++) {
            count += groups[i][j] * groups[j][i];
        }
    }
​
    freeHashSet(set);
    return count;
}

代码结构和功能概述

    这个代码的目的是计算可以通过交换候选名字的首字母生成的有效公司名字的数量。它使用了哈希表来高效地存储候选名字,并通过双重循环来计算有效的名字组合。

主要结构

  1. 哈希表结构:

    • Node:链表的节点,存储一个单词和指向下一个节点的指针。

    • HashSet:哈希表,包含一个数组 buckets,每个元素是一个链表,用于处理哈希冲突。

函数分析

1. unsigned int hash(const char *str)
  • 功能:计算给定字符串的哈希值。这里的哈希值是通过字符的 ASCII 值减去字符 'a' 来得到的,确保值在 0 到 25 之间(对应于字母表的 26 个字母)。

  • 返回值:返回字符串首字母的哈希值,用于确定在哈希表中的桶索引​。

2. HashSet* createHashSet()
  • 功能:创建一个新的哈希表实例,并初始化所有桶为 NULL。

  • 返回值:返回一个指向新创建的 HashSet 的指针。

3. void addWord(HashSet *set, const char *word)
  • 功能:向哈希表中添加一个单词。首先计算该单词的哈希索引,然后将新节点插入到对应的桶中。

  • 参数:

    • set:指向哈希表的指针。

    • word:要添加的单词。

  • 实现细节:

    • 创建一个新的 Node,存储单词并将其插入到对应的桶的头部(链表的前端)。

4. bool containsWord(HashSet *set, const char *word)
  • 功能:检查哈希表中是否包含指定的单词。

  • 参数:

    • set:指向哈希表的指针。

    • word:要查找的单词。

  • 返回值:如果找到单词,返回 true,否则返回 false。

  • 实现细节:

    • 根据单词计算哈希索引,然后遍历该桶的链表,查看是否有匹配的单词。

5. void freeHashSet(HashSet *set)
  • 功能:释放哈希表的内存,清理所有存储的单词和节点。

  • 参数:

    • set:指向哈希表的指针。

  • 实现细节:

    • 遍历每个桶,释放桶中的每个节点和它们存储的单词,最后释放哈希表本身。

6. long long distinctNames(char **ideas, int ideasSize)
  • 功能:计算可以通过交换首字母生成的有效公司名字的数量。

  • 参数:

    • ideas:指向候选名字数组的指针。

    • ideasSize:候选名字的数量。

  • 返回值:返回有效公司名字的数量。

  • 实现细节:

    • 创建哈希表 set 并将所有候选名字添加到哈希表中。

    • 使用一个二维数组 groups 来记录每对不同首字母的有效名字组合的数量。

    • 对于每个候选名字,尝试将其首字母替换为其他字母,并检查替换后的名字是否存在于哈希表中。如果不存在,则增加对应的计数。

    • 最后,计算有效名字的总数,返回结果。

总体逻辑

  1. 哈希表创建:首先创建一个哈希表,用于存储所有候选名字。

  2. 名字处理:遍历所有候选名字,尝试用其他字母替换首字母,并检查替换后的名字是否已经存在于哈希表中。

  3. 结果计算:通过计算有效组合的数量,最终得到可以生成的有效公司名字的总数。

时间复杂度和空间复杂度分析

  • 时间复杂度:O(n * m),其中 n 是 ideas 中的名字数量,m 是每个名字的平均长度。我们需要遍历每个名字,并对每个名字尝试 25 次不同的首字母替换检查。

  • 空间复杂度:O(n * m),用于存储所有名字和哈希表。哈希表的存储需求与 ideas 的大小成正比。

理论上来说这个代码的实现没有问题,包括测试单个用例的时候也完全符合,但是对于力扣的测试用例却超出了时间限制,所以,我们必须想办法优化这个代码。

为了更高效地解决这个问题,我们可以按照交换 candidate idea 名字首字母后的方案计算有效的公司名字。

解题思路

  1. 分组:首先,我们按照首字母将所有的候选名字分组。

  2. 哈希映射:对于每一个首字母,我们维护一个哈希映射 names,其中键是首字母,而值是去除首字母后的每个候选名字的所有可能的剩余部分(即 suffix)。

  3. 双重循环:我们对于每一个不同的首字母组合 (preA, preB) 进行评估,检查哪些名字可以交换得到有效的组合。对于任意两个不同的首字母,我们检查其对应的姓名后缀集合,识别哪些后缀是唯一的(只在一个首字母组中存在)。

  4. 计算差异集合:使用集合的交集和差集来计算集合 names[preA] 和 names[preB] 的差异,并由此计算可生成的有效公司名字对的数量。

  5. 计算结果:每次通过集合操作,我们根据公式计算有效名字组合的数量,公式为:

参考代码如下:

#define MAX_STR_SIZE 16
​
typedef struct {
    char key[MAX_STR_SIZE];
    UT_hash_handle hh;
} HashItem; 
​
HashItem *hashFindItem(HashItem **obj, const char* key) {
    HashItem *pEntry = NULL;
    HASH_FIND_STR(*obj, key, pEntry);
    return pEntry;
}
​
bool hashAddItem(HashItem **obj, const char* key) {
    if (hashFindItem(obj, key)) {
        return false;
    }
    HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));
    sprintf(pEntry->key, "%s", key);
    HASH_ADD_STR(*obj, key, pEntry);
    return true;
}
​
void hashFree(HashItem **obj) {
    HashItem *curr = NULL, *tmp = NULL;
    HASH_ITER(hh, *obj, curr, tmp) {
        HASH_DEL(*obj, curr);  
        free(curr);
    }
}
​
size_t get_intersect_size(const HashItem *a,  const HashItem *b) {
    size_t ans = 0;
    for (HashItem *pEntry = a; pEntry; pEntry = pEntry->hh.next) {
        if (hashFindItem(&b, pEntry->key)) {
            ++ans;
        }
    }
    return ans;
}
​
long long distinctNames(char** ideas, int ideasSize) {
    HashItem *names[26] = {NULL};
    for (int i = 0; i < ideasSize; i++) {
        hashAddItem(&names[ideas[i][0] - 'a'], ideas[i] + 1);
    }
​
    long long ans = 0;
    for (int i = 0; i < 26; i++) {
        if (names[i] == NULL) {
            continue;
        }
        int lenA = HASH_COUNT(names[i]);
        for (int j = i + 1; j < 26; j++) {
            if (names[j] == NULL) {
                continue;
            }
            int lenB = HASH_COUNT(names[j]);           
            int intersect = get_intersect_size(names[i], names[j]);
            ans += 2 * (lenA - intersect) * (lenB - intersect);
        }
    }
    for (int i = 0; i < 26; i++) {
        hashFree(&names[i]);
    }
    return ans;
}

关于两种思路的分析

两种思路大差不差,只是第一种使用了手动链表哈希表,第二种使用了uthash库来实现,分析一下优缺点:

手动链表哈希表

优点
  1. 自包含:

    • 不依赖外部库,所有代码都是自包含的,便于在任何环境下编译和运行。

  2. 灵活性:

    • 手动实现的哈希表提供了更大的灵活性,可以根据具体需求进行调整和优化。

缺点

  1. 效率较低:

    • 查找和插入操作的时间复杂度为 O(n),因为需要遍历链表来查找元素,效率不如使用 uthash 的实现。

  2. 代码复杂性:

    • 手动管理链表和哈希冲突增加了代码复杂性,容易引入错误。

  3. 内存使用:

    • 需要为每个节点分配额外的内存来存储指针,这可能会导致更高的内存使用。

使用 uthash

优点
  1. 高效的哈希操作:

    • 使用 uthash 库提供的哈希表实现,哈希查找和插入操作的时间复杂度平均为 O(1),这使得查找和插入操作非常高效。

  2. 简洁的代码:

    • uthash 提供了一组简单的宏来处理哈希表操作,如 HASH_ADD_STR 和 HASH_FIND_STR,使得代码简洁且易于维护。

  3. 内存管理:

    • uthash 自动管理哈希表的大小调整,不需要手动处理哈希冲突或链表管理。

  4. 空间效率:

    • 哈希表仅存储必要的键值对,不需要像链表一样存储指针,节省了一定的内存。

缺点
  1. 外部依赖:

    • 需要依赖 uthash 库,如果在某些环境中没有安装该库,可能会导致编译问题。

  2. 复杂性:

    • 对于不熟悉 uthash 的开发者,可能需要额外学习如何使用这个库。

总结

  • 第一段代码 更适合在对外部库依赖敏感的环境中使用,或者在需要完全控制哈希表实现细节的情况下。

  • 第二段代码 更适合在需要高效查找和插入操作的场景下使用,尤其是在处理大量数据时。uthash 提供的高效哈希表实现能够显著提高性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2163653.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多校园信息付费发布顶置自定义表单小程序开源版开发

多校园信息付费发布顶置自定义表单小程序开源版开发 为校园管理和互动提供了强大的支持&#xff0c;包括用户端和运营后台两大部分。用户端允许学生和教职工方便地访问各种功能模块&#xff0c;而运营后台则使管理员能够高效地管理和配置系统。产品支持自定义模块和表单&#…

VSCode/VS2019#include头文件时找不到头文件:我的解决方法

0.前言 1.在学习了Linux之后&#xff0c;我平常大部分都使用本地的XShell或者VSCode连接远程云服务器写代码&#xff0c;CentOS的包管理器为我省去了不少繁琐的事情&#xff0c;今天使用vscode打开本地目录想写点代码发现#include头文件后&#xff0c;下方出现了波浪线&#…

批量发送邮件:性能优化与错误处理深度解析

目录 一、批量发送邮件的基础概述 1.1 批量发送邮件的定义 1.2 邮件发送流程 二、性能优化策略 2.1 发送速率控制 2.2 队列管理 2.3 动态IP池管理 2.4 智能调度 三、错误处理机制 3.1 暂时性发送错误处理 3.2 永久性发送错误处理 3.3 邮件反馈收集与分析 四、案例…

[C语言]--自定义类型: 结构体

目录 前言 一、结构体类型的声明 1.结构的声明 2.结构体变量的创建和初始化 3.结构的特殊声明 4.结构的自引用 二、结构体内存对齐 1.对齐规则 2.为什么存在内存对齐? 三、结构体传参 四、结构体实现位段 1.什么是位段 2.位段的内存分配 3.位段的跨平台问题 4.…

【JAVA开源】基于Vue和SpringBoot的影城管理系统

本文项目编号 T 045 &#xff0c;文末自助获取源码 \color{red}{T045&#xff0c;文末自助获取源码} T045&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 用…

通信工程高级职称评审条件详细解读

通信工程只有正高和副高级别的职称&#xff0c;中级通信工程的职称是需要自己参加考试的&#xff0c;并不是评审获得&#xff0c;这个大家需要注意一下&#xff0c;先要考取中级通信工程师之后才能评审副高和正高级通信工程的职称。 下面跟甘建二一起来看看通信专业职称评审条件…

C++ 9.24

作业一&#xff1a;将昨天的My_string类中的所有能重载的运算符全部进行重载、[] 、>、、<、、>、<、!、&#xff08;可以加等一个字符串&#xff0c;也可以加等一个字符&#xff09;、输入输出>>、<<。 main.cpp #include <iostream> #include…

华为昇腾系列-jupyter安装torch_npu

使用背景 国产算力的兴起&#xff0c;异构算力成为各大厂商的选择&#xff0c;以摆脱对英伟达算力过大的依赖&#xff0c;保障算力安全。本文将会讲解如何使用昇腾算力卡来制作一个镜像&#xff0c;然后交给k8s进行算力调度&#xff0c;显示国产算力的真正应用落地。 安装步骤…

微服务配置管理——动态路由

动态路由 网关的路由配置全部是在项目启动时由org.springframework.cloud.gateway.route.CompositeRouteDefinitionLocator在项目启动的时候加载&#xff0c;并且一经加载就会缓存到内存中的路由表内&#xff08;一个Map&#xff09;&#xff0c;不会改变。也不会监听路由变更新…

创建游戏暂停菜单

创建用户控件 设置样式 , 加一层 背景模糊 提升UI菜单界面质感 , 按钮用 灰色调 编写菜单逻辑 转到第三人称蓝图 推荐用 Set Input Mode Game And UI , 只用仅UI的话 增强输入响应不了 让游戏暂停的话也可以用 Set Game Paused , 打勾就是暂停 , 不打勾就是继续游戏 , 然后…

0基础如何转行IT

这是一个学习为王的时代&#xff0c;你没有超强的主动学习能力&#xff0c;很容易在千军万马的竞争中落后&#xff0c;甚至被优秀的替代者淘汰。 小白如何转行IT 正所谓业精于专&#xff0c;相较于科班生&#xff0c;非科班转行的在基础方面确实比较薄弱&#xff0c;因此必须…

VMWare虚拟机键盘卡顿

文章目录 环境问题解决办法参考 环境 Windows 11 家庭中文版VMware Workstation 17 ProUbuntu 24.04.1 问题 最近新入手了一台电脑台式机&#xff0c;型号是联想拯救者刃7000K&#xff0c;自带Win11家庭版。主机的CPU是第14代英特尔酷睿i9处理器&#xff0c;异构24核32线程。…

ubuntu 安装minikube,并拉取k8s镜像

虚拟机是vmware17, 系统是ubuntu20.4&#xff0c; minikube是1.23.1&#xff0c; docker是24.0.7&#xff0c; 为什么要装minikube&#xff0c;通常k8s集群是要3台机子以上&#xff0c;而通过minikube&#xff0c;可以在一台机子上搭建出k8s集群&#xff0c;minikube采用的是D…

unraid使用docker安装redis并创建密码

unraid使用docker安装redis并创建密码 一、redis简单介绍 redis基于K-V思路&#xff0c;数据存储在内存中&#xff0c;速度快&#xff0c;高效。 使用时会结合其他数据库如mysql。 二、redis安装 应用市场搜索redis&#xff0c;找下载量最高的一个即可&#xff0c;其中参数只…

5--SpringBoot项目中菜品管理 详解(一)

目录 公共字段自动填充 问题分析 实现思路 代码开发 步骤一 步骤二 功能测试 新增菜品 需求分析和设计 代码开发 文件上传接口 功能测试 公共字段自动填充 问题分析 后台系统的员工管理功能和菜品分类功能的开发&#xff0c;在新增员工或者新增菜品分类时需要设置…

C语言特殊字符串函数和字符函数

特殊字符串函数 strtok(字符串切割函数) 重点&#xff1a;1.delimiters 参数是个字符串&#xff0c;定义了用作分割符的字符集合 2.第一个参数指定一个字符串&#xff0c;里面包含0个或者多个分隔符 3.strtok函数找到str中的分隔符&#xff0c;会把它改成\0&#xff0c;然后…

内衣洗衣机哪个牌子好用?五款业内口碑爆棚产品汇总

内衣裤洗衣机是一种非常实用的洗衣机&#xff0c;可以有效地保护内衣和贴身衣物的质量和卫生&#xff0c;相比于普通的家用大型洗衣机&#xff0c;内衣裤洗衣机在容量、洗涤方式、控制方式和价格等方面有很大的不同之处&#xff0c;如果您经常需要清洗内衣和贴身衣物&#xff0…

无人机蜂群作战会成为未来战争的主要形式吗,该如何反制呢?

无人机蜂群作战在未来战争中确实有可能成为一种重要的作战形式&#xff0c;但是否会成为“主要形式”则取决于多种因素&#xff0c;包括技术发展、战术创新、战略需求以及国际政治和军事格局的变化等。以下是对无人机蜂群作战及其反制措施的详细分析&#xff1a; 一、无人机蜂…

图神经网络(GNN)简单介绍

参考文章:A Gentle Introduction to Graph Neural Networks 仅作为自己学习的笔记 GNN应用领域&#xff1a; 芯片设计 场景分析与问题分析 推荐系统&#xff08;类似抖音&#xff09; 欺诈检测&#xff0c;风控相关 知识图谱 道路交通&#xff0c;动态流量预测 自动驾驶&…

程序员的得力助手:Kimi AI的实战体验引言

引言 作为一名程序员&#xff0c;我们经常需要处理大量信息&#xff0c;从代码调试到文档编写&#xff0c;再到团队协作&#xff0c;每一项任务都需要我们保持高度的专注和效率。在这个过程中&#xff0c;一个得力的助手可以极大地提升我们的工作效率。今天&#xff0c;我想和…