【Hot100算法刷题集】哈希-02-字母异位词分组(含排序构造键、自定义键、自定义哈希函数法)

news2025/1/23 9:09:02

在这里插入图片描述

🏠关于专栏:专栏用于记录LeetCode中Hot100专题的所有题目
🎯每日努力一点点,技术变化看得见

题目转载

题目描述

🔒link->题目跳转链接
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

题目示例

示例 1:
输入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出: [[“bat”],[“nat”,“tan”],[“ate”,“eat”,“tea”]]

示例 2:
输入: strs = [“”]
输出: [[“”]]

示例 3:
输入: strs = [“a”]
输出: [[“a”]]

题目提示

1 1 1 <= strs.length <= 1 0 4 10^4 104
0 0 0 <= strs[i].length <= 100 100 100
● strs[i] 仅包含小写字母

解题思路及代码

整理题意

题目中给出了异位字母词的概念,其指的是,如果两个单词的26个英文字母数相同,但位于的位置不同,则称为异位字母词。如eat和ate就是异位字母词,它们都有1个a、1个e、1个t;如queue和queen就不是异位字母词,因为他们的u和n字母的数量不同。

[1]排序

从异位字母词的概念我们可以知道,如果对两个互为异位字母词的字母串进行排序,则它们都会得到相同的字符串。如eat和ate排序后均为aet。那么我们可以使用哈希表进行存储,键域(key)保存异位字母词排序后的字符串,值域(value)保存一个vector<string>类型,用于保存所有排序后为键(key)的字符串。

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> m;
        for(auto str : strs)
        {
            string tmp = str;
            sort(tmp.begin(), tmp.end());
            m[tmp].push_back(str);
        }
        
        vector<vector<string>> ret;
        for(auto member : m)
        {
            ret.push_back(member.second);
        }
        return ret;
    }
};

[2]计数

既然互为异位字母词的字符串的各个字母数量相等,我们可不可以将上面哈希表中的键(key)改为26个字母的计数数组呢?在C++中,unordered_map无法直接将数组作为键(key),需要将数组转换为unordered_map支持的类型,如string、int等;或借助于仿函数,实现数组的直接比较。

自主定义键(key)

以纯数字字符串为键

从题目的提示可知,每个字母最多出现10000次,如果使用数字字符表示,需要5个;而26个字母,每个用5个数字字符表示,即需要 26 × 5 26×5 26×5,即130个字符表示,由这个字符串作为哈希表的键(key)。
在这里插入图片描述

class Solution {
public:
    string arrToSting(vector<int>& arr)
    {
        string ret;
        for(auto elem : arr)
        {
            string tmp; 
            tmp.push_back(elem);
            while(tmp.size() < 5) tmp.insert(tmp.begin(), '0');
            ret.append(tmp);
        }
        return ret;
    }
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> m;
        for(auto& str : strs)
        {
            vector<int> count(26);
            for(auto e : str) ++count[e - 'a'];
            m[arrToSting(count)].push_back(str);
        }

        vector<vector<string>> ret;
        for(auto elem : m)
        {
            ret.push_back(elem.second);
        }
        return ret;
    }
};
以数字、字母交替字符串为键

除了上面的方式,我们可以使用“字母+字母数量”组合而成的字符串作为键(key),如下图所示。
在这里插入图片描述

class Solution {
public:
    string arrToSting(vector<int>& arr)
    {
        string ret;
        for(int i = 0; i < arr.size(); ++i)
        {
            ret.push_back(i + '1');
            ret.push_back(arr[i]);
        }
        return ret;
    }
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string, vector<string>> m;
        for(auto& str : strs)
        {
            vector<int> count(26);
            for(auto e : str) ++count[e - 'a'];
            m[arrToSting(count)].push_back(str);
        }

        vector<vector<string>> ret;
        for(auto elem : m)
        {
            ret.push_back(elem.second);
        }
        return ret;
    }
};

自定义哈希函数

在介绍该方法前,先对一些C++中的操作进行介绍并给出相关示例。首先介绍std::hash,该哈希函数对象位于functional库中,它可用于为不同的类型生成哈希值,下方是关于std::hash的示例:

#include <iostream>
#include <functional>

int main()
{
	int num = 666;
	std::hash<int> hasher;
	size_t hashValue = hasher(num);
	std::cout << num << "'s hashValue is " << hashValue << std::endl;
	return 0;
}

🔍注意:C++中规定,哈希值为size_t类型

下面再认识一下std::accumulate,它位于numeric库中,默认情况下,它所实现的就是将数组中的所有数据累加。第一个参数为待计算区间的起始迭代器,第二个参数是待计算区间的终止迭代器,第三个参数是起始值,代码示例如下(下方输出结果为10):

#include <iostream>
#include <vector>
#include <numeric>

int main()
{
	std::vector<int> arr = {1, 2, 3, 4};
	std::cout << std::accumulate(arr.begin(), arr.end(), 0) << std::endl;
	return 0;
}

我们可以通过lambda表达式,自定义accumulate的累加操作。下方的acc表示当前所累加的数字综合,num表示当前数字,由accumulate函数自动传入。

#include <iostream>
#include <vector>
#include <numeric>

int main()
{
	std::vector<int> arr = {1, 2, 3, 4};
	int ret = std::accumulate(arr.begin(), arr.end(), 0, [&](int acc, int num){
		std::cout << "before add num, acc is " << acc << std::endl;
		acc += num;
		std::cout << "after add num values " << num << " acc is " << acc << std::endl;
		retrun acc;
	});
	std::cout << "final ret is " << ret << std::endl;
	return 0;
}

在这里插入图片描述
介绍完上述的操作后,下面开始介绍自定义哈希函数的方法。unordered_map在存储值域(value)时,先使用哈希函数对键域(key)进行映射操作,找到对应的映射位置后才能存储值(value)。而unordered_map之所以无法使用数组作为键域(key),就是因为缺少对应的哈希映射函数,那我们只要提供对应的哈希映射函数即可。下面提供了一个哈希映射函数。

auto arrayHash = [fn = hash<int>{}](const array<int, 26>& arr) -> size_t {
    return accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num){
        return (acc << 1) ^ fn(num);
    });
};

这里的哈希映射函数是将累加的数值总和acc<<2,即将acc乘以2,再与生成的哈希值做异或运算。下面我们将哈希映射函数提供给unordered_map,它就可以实现对数组作为键域(key)的位置映射。

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        auto arrayHash = [fn = hash<int>{}](const array<int, 26>& arr) -> size_t {
            return accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num) {
                return (acc << 1);
            });
        };

        unordered_map<array<int, 26>, vector<string>, decltype(arrayHash)> mp(0, arrayHash);
        for(string& str : strs)
        {
            array<int, 26> counts{};
            int length = str.length();
            for(int i = 0; i < length; i++)
            {
                counts[str[i] - 'a']++;
            }
            mp[counts].emplace_back(str);
        }
        vector<vector<string>> ans;
        for (auto it = mp.begin(); it != mp.end(); ++it) {
            ans.emplace_back(it->second);
        }
        return ans;
    }
};

这里的思路和哈希算法均为官方给出的题解,我们可能会有疑惑,这里的哈希映射函数,我们可以修改吗?当然可以,只要我们保证不同的值映射到的位置尽可能不同,尽量避免哈希冲突,这个哈希映射函数就是相对成功的。

对于queen的累次计算结果如下:

计算次序/对应字母acc原值acc<<2后数值num原值fn(num)值acc << 2 ^ fn(num)数值
0/a00000
1/b00000
2/c00000
3/d00000
4/e00222
5/f28008
6/g8320032
7/h3212800128
8/i12851200512
9/j5122048002048
10/k20488192008192
11/l8192327680032768
12/m3276813107200131072
13/n13107252428811524289
14/o5242892097156002097156
15/p20971568388624008388624
16/q8388624335544961133554497
17/r3355449713421798800134217988
18/s13421798853687195200536871952
19/t5368719522147487808002147487808
20/u21474878088589951232118589951233
21/v8589951233343598049320034359804932
22/w3435980493213743921972800137439219728
23/x13743921972854975687891200549756878912
24/y5497568789122199027515648002199027515648
25/z21990275156488796110062592008796110062592

这里的<<(左移)操作本质是扩大acc的数值。不断扩大结果集有助于降低哈希冲突的概率,但这却并不表明我们可以完全避免哈希冲突。由于每个字母至多出现10000次,10000至多需要13个比特位表示,若对acc每次左移13位,可完全避免哈希冲突。但左移位数越多,键域(key)所占的比特数越大。这里通过^(异或)操作尽量打乱二进制位,而不是增加<<(左移)数量的方式来减少哈希冲突概率,可以避免键(key)占用的二进制位过多。至于如何设计函数需要根据不同题目给出,这里不再讨论。这个方法建议作为了解即可,哈希函数的构造需要的数学理论和难度相对较高,这个方法也不容易想到。

刷题使我快乐😭
文章如有错误,请私信或在下方留言😀

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

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

相关文章

【解压即玩】PC版《最终幻想9》重制版 高清宽屏,怀旧经典

《最终幻想IX》&#xff08;日语&#xff1a;ファイナルファンタジーIX&#xff0c;英语&#xff1a;Final Fantasy IX&#xff0c;台港澳旧译为“太空战士IX”&#xff09;是由史克威尔&#xff08;现为史克威尔艾尼克斯&#xff09;为索尼PlayStation游戏机开发并发行的一款角…

元学习与机器学习

如图1 所示&#xff0c;机器学习的目标是要找一个函数f&#xff0c;这个函数可以是一个分类器&#xff0c;把几百张图 片输入进去&#xff0c;分类器就告诉我们分类的结果。元学习一样是找一个函数&#xff0c;但它要找的是一个学 习算法Fϕ∗&#xff0c;这个学习算法可以接受…

Java并发编程实战 06 | 为什么不建议使用线程优先级?

什么是线程优先级&#xff1f; 线程优先级是一个标识&#xff0c;用来提示操作系统或线程调度器哪个线程更重要、更需要优先执行。优先级通常是一个整数值。 在 Java 中&#xff0c;线程优先级的设置范围从 1 到 10&#xff0c;其中 1 是最低优先级&#xff0c;10 是最高优先…

2024 年高教社杯全国大学生数学建模竞赛C题—农作物的种植策略(讲解+代码+成品论文助攻)

2024数学建模国赛选题建议团队助攻资料-CSDN博客文章浏览阅读1k次&#xff0c;点赞20次&#xff0c;收藏24次。通过分析5个题目的特点&#xff0c;可知数学建模常用的模型大概可以分为五大类——https://blog.csdn.net/qq_41489047/article/details/141925859 本次国赛white学长…

WebAPI(四) BOM;延时函数;JS执行机制(同步异步);location对象;history对象;navigation对象

文章目录 BOM1. 定时器-延时函数2. JS执行机制(1)、同步与异步(2)、事件循环 3. location对象(1)、href属性获取完整的url地址(2)search属性获取地址中携带的参数(3) hash&#xff1a;获取地址中的#后边的部分(3) reload&#xff1a; 刷新当前页面&#xff0c;传入参数true表示…

025集—— 转义字符\、字符串详解(对比、分割、日期、数字等)——C#学习笔记

本文讲解字符串的比较&#xff1a;忽略大小写与不忽略大小写&#xff0c;内存地址是否相同。 当需要对两个字符串的值进行比较和排序而不需要考虑语言惯例时&#xff0c;请使用基本的序号比较。基本的序号比较 (Ordinal) 是区分大小写的&#xff0c;这意味着两个字符串的字符必…

纳米材料咋设计?蛋白质模块咋用?看这里就知道啦!

大家好&#xff0c;今天我们来了解一项关于蛋白质纳米材料设计的研究——《Blueprinting extendable nanomaterials with standardized protein blocks》发表于《Nature》。蛋白质结构复杂&#xff0c;其组装体的设计颇具挑战。但近期的研究取得了新突破&#xff0c;通过设计标…

高精度治具加工的重要性和创新性

在现代制造业中&#xff0c;高精度治具加工扮演着至关重要的角色。它不仅是生产过程中的关键环节&#xff0c;更是推动行业不断创新和发展的重要力量。时利和将解析高精度治具加工的重要性和创新性。 一、高精度治具加工的重要性 1.确保产品质量 高精度治具能够为生产过程提供准…

API安全 | 发现API的5个小tips

在安全测试目标时&#xff0c;最有趣的测试部分是它的 API。API 是动态的&#xff0c;它们比应用程序的其他部分更新得更频繁&#xff0c;并且负责许多后端繁重的工作。在现代应用程序中&#xff0c;我们通常会看到 REST API&#xff0c;但也会看到其他形式&#xff0c;例如 Gr…

基于yolov8的包装盒纸板破损缺陷测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的包装盒纸板破损缺陷检测系统是一种高效、智能的解决方案&#xff0c;旨在提高生产线上包装盒纸板的质量检测效率与准确性。该系统利用YOLOv8这一前沿的深度学习模型&#xff0c;通过其强大的目标检测能力&#xff0c;能够实时识别并标记出包装盒纸板…

HyperLogLog简介

基数估算 基数估算(Cardinality Estimation)&#xff0c;也称为 count-distinct problem&#xff0c;一直是大数据领域的重要问题之一&#xff0c;顾名思义&#xff0c;基数估算就是为了估算在一批超级大的数据中&#xff0c;它的不重复元素有多少个。常见的基数估算算法包括L…

HTML 基础,尚优选网站设计开发(二)

最近在恶补HTML相关知识点&#xff0c;本人是后端程序员&#xff0c;看到周围很多人都被裁员了&#xff0c;突然想尽早转变成全栈程序员变成独立开发者&#xff0c;有空余接接私单、商单的 尚优选网站设计开发&#xff0c;HTMLCSSJavaScript实际使用 尚优选网站设计开发页面分析…

《Web性能权威指南》-HTTP-读书笔记

HTTP简史 HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;。 HTTP 0.9&#xff1a;只有一行的协议 Tim Berners-Lee罗列HTTP协议的几条宏观设计目标&#xff1a;支持文件传输、能够请求对超文本文档的索引搜索、格式化协商机制&#xf…

全季恒温,网球爱好者的理想运动场馆—轻空间

气膜网球馆内配备了先进的恒温恒压系统&#xff0c;不论四季如何变化&#xff0c;都能为运动员们提供一个稳定、舒适的运动环境。凉爽的空气流通&#xff0c;配合无障碍的视觉体验&#xff0c;打造了一个极致的训练与比赛场所。 大人挥拍竞技&#xff0c;孩子们快乐训练 馆内不…

第四届摩纳哥智能化可持续发展码头交流会

第四届摩纳哥智能化可持续发展码头交流会 摩纳哥游艇码头顾问公司&#xff08;M3&#xff09;认为游艇行业的绿色转型需要做到从游艇本身到游艇码头的360度全方位可持续化发展&#xff0c;因此&#xff0c;继今年3月的摩纳哥智能游艇交流会后&#xff0c;他们将于2024年9月22日…

[第三篇 运维与安全管理] ==> 第8章 数据库安全管理与审计

MongoDB 数据库安全管理与审计 8.1 权限管理简介8.2 用户管理8.2.1 创建用户与登录8.2.2 查询用户8.2.3 修改用户8.2.4 删除用户8.2.5 授予用户权限8.2.6 撤销用户权限 8.3 角色管理8.3.1 内建角色8.3.2 创建自定义角色8.3.3 查询自定义角色8.3.4 修改自定义角色8.3.5 删除自定…

day44-测试平台搭建之前端vue学习-基础3

目录 一、条件渲染 二、列表渲染 三、收集表单数据 四、内置指令 五、自定义指令 六、今日学习思维导图 一、条件渲染 1.1.v-if 1).写法 1.1).v-if"表达式" 1.2).v-else-if"表达式" 1.3).v-else"表达式‘ 2).适用于&#xff1a;切换频率较低的场…

Kafka 实战演练:创建、配置与测试 Kafka全面教程

文章目录 1.配置文件2.消费者1.注解方式2.KafkaConsumer 3.依赖1.注解依赖2.KafkaConsumer依赖 本文档只是为了留档方便以后工作运维&#xff0c;或者给同事分享文档内容比较简陋命令也不是特别全&#xff0c;不适合小白观看&#xff0c;如有不懂可以私信&#xff0c;上班期间都…

腾讯地图SDK Android版开发 10 InfoWindow

腾讯地图SDK Android版开发 10 InfoWindow 前言介绍默认风格自定义样式实现方式交互操作播放信息窗口的动画开启多窗口模式 相关类和接口默认样式MarkerOptions 类Marker 类TencentMap类TencentMap.OnInfoWindowClickListener 接口类 自定义样式TencentMap 类TencentMap.InfoWi…

6.2高斯滤波

目录 实验原理 示例代码1 运行结果1 示例代码2 运行结果2 实验代码3 运行结果3 实验原理 在OpenCV中&#xff0c;高斯滤波&#xff08;Gaussian Filtering&#xff09;是一种非常常用的图像平滑处理方法。它通过使用一个高斯核&#xff08;即高斯分布函数&#xff09;对…