rust踩雷笔记(2)——一道hard带来的思考[哈希表、字符串、滑动窗口]

今天被一道hard恶心坏了,算法不难,用C++几分钟的事。用rust主要还是缺乏对语言的熟练度,记录一下,主要的坑在下面这个操作:

对String取其中某个位置的char。

可能你会有疑问:这不是直接nth()取就行了么。没错,我看有些题解也是这样做的,但是那样会在某些字符长度很长的样例中OOT。

本文就从代码角度讲下这个问题。此外还有对哈希表更新操作中,不同语句的效率对比。当然我还对一些语法加了详细注释,因为我是初学者,上周六接触到rust,感觉到很有意思,但rust在一些地方的画风的确是不太一样,所以在语法上我需要多花功夫,也希望能帮到有需要的人。

题目简介

https://leetcode.cn/problems/minimum-window-substring/submissions/
链接我复制下来了,题目可以自行去leetcode 76看,我也不讲详细解法,蛮简单的。主要讲讲实现的注意事项。

不得不说注释还是很有用的,一开始的方法我怎么也没找到问题所在,所以索性对所有代码写了注释,最后定位到了罪魁祸首:nth()

代码迭代

这部分就给出几个版本的代码,详细的内容都在注释里,我这里主要写一下注意事项:
给定String s,让你取出里面第i个char,你会怎么做?

// 取s中下标为i的字符,nth()返回的是Option<char>
let c = s.chars().nth(i).unwrap();

相信这种写法你一定能想到,但是它会有什么问题吗?
当然!害我找了一上午bug:如果要遍历其中的char,i的范围是0~s.len() - 1,这种情况下,s如果很长很长,可能会造成OOT。

那你可能会问了:你为啥不用for c in s.chars()
那是因为我要用下标啦;
那你又会问了:用for (idx, c) in s.chars().enumerate()不也可以吗?
的确是可以,但我想了解所有的修改方式,除了上面这两种常见的,就没有别的办法了吗?初学者(出血者)的探索精神是不可阻挡的。

当然有!可以把nth的方式改为as_bytes()[](注意添加as char):

// 重要操作:当只有英文字母的时候,用下面的方式访问
let c = s.as_bytes()[right] as char;
版本一——注意看我把nth改成了什么 & 代码末尾有一些注释记录unwrap、unwrap_or、unwrap_or_else的用法

注意看我把nth修改成什么了

use std::collections::HashMap;impl Solution {pub fn min_window(s: String, t: String) -> String {// 定义滑动窗口[left,right)let mut left = 0;let mut right = 0;// ht存储t所有字符的个数,hs存储s[left..right]的所有字符的个数// 可以省略掉类型的声明,这里为了好理解所以我写上let mut ht: HashMap<char, usize> = HashMap::new();let mut hs: HashMap<char, usize> = HashMap::new();// 遍历t的所有字符,将它们加入ht中。// 遍历字符串的方式是ch in string.chars(),ch就是char型for ch in t.chars() {// 重要知识:如何改变hashmap中value的值// 方式一:entry入参是key,or_insert/or_insert_with/or_default返回的是对value的可变引用// let v = ht.entry(ch).or_insert(0);// *v += 1;// 方式二:直接insert覆盖掉旧值。这里的解引用*不加也不会报错,暂不详原理ht.insert(ch, *(ht.get(&ch).unwrap_or(&0)) + 1);}// 给最小窗口赋予初始值。usize::MAX的方式可以赋一个很大的值let mut res = 0..usize::MAX;// 表示滑动窗口内,有多少字符是t中的let mut cnt = 0;// 遍历s中的字符,滑动窗口右端点向右移动while right < s.len() {// 重要操作:取s中下标为right的字符,nth()返回的是Option<char>// 最新:严禁采用这种方式,根据实践这种方式速度有问题,当s.len()范围可以特别大的时候会对某些样例超时// let c = s.chars().nth(right).unwrap();// 重要操作:当只有英文字母的时候,用下面的方式访问let c = s.as_bytes()[right] as char;right += 1; // 先将right移动,是因为滑动窗口是左闭右开区间[left, right)// 将c加入hs中。v绑定的是c这个key对应的value的可变引用。如果不存在c这个key,就插入并将对应的值设为0let v = hs.entry(c).or_insert(0);*v += 1;// 看一下插入的是否为有效字符// 下面*在!=那行省略会报错;在<=那行省略不会报错if *(hs.get(&c).unwrap_or(&0)) != 0 &&*(hs.get(&c).unwrap_or(&0)) <= *(ht.get(&c).unwrap_or(&0)) {cnt += 1; }// 最容易写错的地方,如果滑动窗口左端点是重复的,就从hs中去除,并移动左端点// 严禁采用这种方式// let mut left_char = s.chars().nth(left).unwrap();let mut left_char = s.as_bytes()[left] as char;// 这里只用unwrap会报错,编译器会认为有对None进行unwrap的风险// 查的到就返回&value,查不到就返回&0while *(hs.get(&left_char).unwrap_or(&0)) > *(ht.get(&left_char).unwrap_or(&0)) {// 这可以是一个万能的更新hashmap中value的方法,用覆盖的方式更新值// 不用担心unwrap_or(&0) - 1会不会导致扔个-1进去,能进while循环,就说明值是1起步,减一肯定不为负hs.insert(left_char, hs.get(&left_char).unwrap_or(&0) - 1);left += 1;// 这里对left范围检查,前文也用unwrap_or('0')检查过if left >= right {break;}// left_char = s.chars().nth(left).unwrap();left_char = s.as_bytes()[left] as char;}// res是a..b类型,可以用start和end来访问a和bif cnt == t.len() && res.end - res.start > right - left {// 更新一波结果res = left..right;}}// 如果没找到结果,那么res就还是初始值0..usize::MAXif res.end - res.start > s.len() {"".to_string()} else {s[res].to_string()}}
}/*
unwrap()有什么风险?
如果是对None调用unwrap是错误的。
如果我确保x不会是None,并对x调用unwrap,看上去就符合逻辑。
但是编译器可能认为这有风险,从而直接报错!
所以要用unwrap_or()或者unwrap_or_else()来代替它。unwrap_or()怎么用?
assert_eq!(Some("car").unwrap_or("bike"), "car");
assert_eq!(None.unwrap_or("bike"), "bike");
简记:如果是Some()就unwrap,如果是None就是unwrap_or()括号中的值
注意:unwrap_or()的入参类型是T,和Some(T)中的T保持一致,
所以如果是哈希表的get,查出来的是Option<&value>,那么unwrap_or()入参应该是&valueunwrap_or_else()怎么用?
let k = 10;
assert_eq!(Some(4).unwrap_or_else(|| 2 * k), 4);
assert_eq!(None.unwrap_or_else(|| 2 * k), 20);
简记:和unwrap_or类似,但如果括号内要设定一个表达式,建议用unwrap_or_else,因为它是lazily evaluated
*/
版本二——修改哈希表的不同方式

你可能注意到了,上面的代码片段中,我在注释里写了两种对hashmap的修改方式:entry和insert覆盖,它们两哪种快呢?上面代码是insert覆盖的方式,下面我用entry的方式(代码和版本一变不了多少,再看一次就当巩固了)。

实测这个稍微快一点点。也就是我们修改hashmap的时候可以主要考虑用entry的方式(保留此话修改可能)。

use std::collections::HashMap;impl Solution {pub fn min_window(s: String, t: String) -> String {// 定义滑动窗口[left,right)let mut left = 0;let mut right = 0;// ht存储t所有字符的个数,hs存储s[left..right]的所有字符的个数// 可以省略掉类型的声明,这里为了好理解所以我写上let mut ht: HashMap<char, usize> = HashMap::new();let mut hs: HashMap<char, usize> = HashMap::new();// 遍历t的所有字符,将它们加入ht中。// 遍历字符串的方式是ch in string.chars(),ch就是char型for ch in t.chars() {// 重要知识:如何改变hashmap中value的值// 方式一:entry入参是key,or_insert/or_insert_with/or_default返回的是对value的可变引用*ht.entry(ch).or_insert(0) += 1;// 方式二:直接insert覆盖掉旧值。这里的解引用*不加也不会报错,暂不详原理// ht.insert(ch, *(ht.get(&ch).unwrap_or(&0)) + 1);}// 给最小窗口赋予初始值。usize::MAX的方式可以赋一个很大的值let mut res = 0..usize::MAX;// 表示滑动窗口内,已经有valid个字符,数量等于t中该字符数量let mut valid = 0;// 遍历s中的字符,滑动窗口右端点向右移动while right < s.len() {// 重要操作:取s中下标为right的字符,nth()返回的是Option<char>// 严禁采用这种方式// let c = s.chars().nth(right).unwrap();let c = s.as_bytes()[right] as char;right += 1; // 先将right移动,是因为滑动窗口是左闭右开区间[left, right)// 判断字符是否在t中,只有在ht中,才能插入到hs中// contains_key的入参是&charif ht.contains_key(&c) {// 方式一:entry,在leetcode上测的时间方式一似乎比方式二快一些*hs.entry(c).or_insert(0) += 1;// 方式二:插入覆盖(这里不加*也可以,暂时不知道为什么)// hs.insert(c, hs.get(&c).unwrap_or(&0) + 1);if hs.get(&c) == ht.get(&c) {valid += 1;}}while valid == ht.len() {if right - left < res.end - res.start {res = left..right;}// 在while中不断将滑动窗口左端点移出,直到跳出这个while,那么再进入外层while// 严禁用这种方式// let mut left_char = s.chars().nth(left).unwrap();let mut left_char = s.as_bytes()[left] as char;left += 1;if ht.contains_key(&left_char) {// ht中有的才会加入hs,所以要先判断才将left_char从hs中移出// 如果移出之前窗口内left_char的数量等于t中left_char数量,那么valid要减少if hs.get(&left_char) == ht.get(&left_char) {valid -= 1;}// hs.insert(left_char, hs.get(&left_char).unwrap_or(&0) - 1);*hs.entry(left_char).or_insert(0) -= 1;}}}// 如果没找到结果,那么res就还是初始值0..usize::MAXif res.end - res.start > s.len() {"".to_string()} else {s[res].to_string()}}
}

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

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

相关文章

联想拯救者笔记本Win11系统键盘无法打字解决参考方法

一位好机友新购买的联想拯救者笔记本在使用过程中突然发现整个键盘都不能使用了、不能打字、按任何按键都没有反应&#xff0c;只有鼠标能正常操作&#xff1b;那么这是什么问题呢&#xff1f;能不能是笔记本的键盘坏了呢&#xff1f;还是笔记本出现了什么故障而引起键盘失灵呢…

LangChain手记 Evalutation评估

整理并翻译自DeepLearning.AILangChain的官方课程&#xff1a;Evaluation&#xff08;源代码可见&#xff09; 基于LLM的应用如何做评估是一个难点&#xff0c;本节介绍了一些思路和工具。 “从传统开发转换到基于prompt的开发&#xff0c;开发使用LLM的应用&#xff0c;整个工…

Linux 终端会话中,启动任务并放到后台运行

一、需求 linux要执行一个脚本&#xff0c;耗时很长&#xff0c;想要脚本在后台运行&#xff0c;用户注销或终端软件关闭时也可以继续运行。 二、实现 1、nohup命令 脚本在后台运行 nohup 是在 Linux 和类 Unix 系统中使用的一个命令&#xff0c;用于在后台运行程序&#x…

Python爬虫——scrapy_当当网图书管道封装

创建爬虫项目 srcapy startproject scrapy_dangdang进入到spider文件里创建爬虫文件&#xff08;这里爬取的是青春文学&#xff0c;仙侠玄幻分类&#xff09; srcapy genspider dang http://category.dangdang.com/cp01.01.07.00.00.00.html获取图片、名字和价格 # 所有的se…

c语言——查找特定字符在字符串中出现的次数

fgets 函数用于从标准输入&#xff08;stdin&#xff09;中读取一行字符串&#xff0c; 并将其存储在指定的字符数组 str 中。 sizeof str/sizeof str[0] 是用来计算字符数组 str 的大小。 这个表达式计算的结果是字符数组 str 可以容纳的元素个数&#xff08;包括…

【IMX6ULL驱动开发学习】07.驱动程序分离的思想之平台总线设备驱动模型和设备树

一、驱动程序分离的思想 【IMX6ULL驱动开发学习】05.字符设备驱动开发模板&#xff08;包括读写函数、poll机制、异步通知、定时器、中断、自动创建设备节点和环形缓冲区&#xff09;_阿龙还在写代码的博客-CSDN博客 之前编写驱动程序的代码存在不少弊端&#xff1a;移植性差…

数学建模之“聚类分析”原理详解

一、聚类分析的概念 1、聚类分析&#xff08;又称群分析&#xff09;是研究样品&#xff08;或指标&#xff09;分类问题的一种多元统计法。 2、主要方法&#xff1a;系统聚类法、有序样品聚类法、动态聚类法、模糊聚类法、图论聚类法、聚类预报法等。这里主要介绍系统聚类法…

神经网络基础-神经网络补充概念-25-深层神经网络

简介 深层神经网络&#xff08;Deep Neural Network&#xff0c;DNN&#xff09;是一种具有多个隐藏层的神经网络&#xff0c;它可以用来解决复杂的模式识别和特征学习任务。深层神经网络在近年来的机器学习和人工智能领域中取得了重大突破&#xff0c;如图像识别、自然语言处…

Windows环境下安装RabbitMQ

1.消息队列中间件简介 消息队列中间件是分布式系统中重要的组件&#xff0c;主要解决应用耦合&#xff0c;异步消息&#xff0c;流量削锋等问题实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性。 使用较多的消息队列有 ActiveMQ&#xff08;安全&#xff09;&…

【脚踢数据结构】队列(顺序和链式)

(꒪ꇴ꒪ )&#xff0c;Hello我是祐言QAQ我的博客主页&#xff1a;C/C语言,Linux基础,ARM开发板&#xff0c;软件配置等领域博主&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的攻城狮&#xff01;送给自己和读者的一句鸡汤&#x1f914;&…

Ant Design Vue 下拉框输入框 可以输入 可以查询

Ant Design Vue 下拉框 可以输入 可以查询 直接上代码 效果图 &#xff08;输入内容查询后端 返回下拉的值 &#xff0c;如何查询后端是空的直接 把输入的内容 赋值给 输入框&#xff09; 在这里插入图片描述 <template><div><a-selectv-model.lazy"i…

WPF CommunityToolkit.Mvvm

文章目录 前言ToolkitNuget安装简单使用SetProperty&#xff0c;通知更新RealyCommandCanExecute 新功能&#xff0c;代码生成器ObservablePropertyNotifyCanExecuteChangedForRelayCommand其他功能对应关系 NotifyPropertyChangedFor 前言 CommunityToolkit.Mvvm&#xff08;…

自适应AI chatgpt智能聊天创作官网html源码

我们致力于开发先进的自适应AI智能聊天技术&#xff0c;旨在为用户提供前所未有的聊天体验。通过融合自然语言处理、机器学习和深度学习等领域的顶尖技术&#xff0c;我们的智能聊天系统能够准确理解用户的需求并给出相应的回应。 我们的自适应AI智能聊天系统具备以下核心特点…

MySQL面试题二

1、关系型和非关系型数据库的区别&#xff1f; 关系型数据库的优点 容易理解&#xff0c;因为它采用了关系模型来组织数据。 可以保持数据的一致性。 数据更新的开销比较小。 支持复杂查询&#xff08;带 where 子句的查询&#xff09; 非关系型数据库&#xff08;NOSQL&#x…

fiddler抓包问题记录,支持https、解决 tunnel to 443

fiddler下载安装步骤及基本配置 fiddler抓包教程&#xff0c;如何抓取HTTPS请求&#xff0c;详细教程 可能遇到的问题及解决方案 1. 不能正常访问页面&#xff08;所有https都无法访问&#xff09; 解决方案&#xff1a;查看下面配置是否正确 Rules-customization 找到 OnB…

Vue中路由缓存问题及解决方法

一.问题 Vue Router 允许你在你的应用中创建多个视图&#xff0c;并根据路由来动态切换这些视图。默认情况下&#xff0c;当你从一个路由切换到另一个路由时&#xff0c;Vue Router 会销毁前一个路由的组件实例并创建新的组件实例。然而&#xff0c;有时候你可能希望保持一些页…

【推荐】深入浅出学习Spring框架【中】

目录 1.AOP是什么? 2.案列&#xff1a; 3.spring的aop的专业术语 4.代码模拟 4.1 前置通知 3.2.后置通知 3.3.环绕通知 3.4.异常通知 3.5.过滤通知 1.AOP是什么? 面向切面编程&#xff08;Aspect-Oriented Programming&#xff09;是一种编程范式&#xff0c;它的主要…

第十四届中国大学生服务外包大赛细品,上百支队伍与合合信息用AI共克“记账”难题

前言 熟悉我的小伙伴应该知道我在大学时期参与了很多竞赛&#xff0c;我向来对比赛是比较热枕的&#xff0c;以我个人观点&#xff0c;我认为可以通过竞赛激发学习激情和检验自己的技能水平掌握情况&#xff0c;大学生很少有机会能够了解到课堂之外市场的需求&#xff0c;外包…

P1123 取数游戏

取数游戏 题目描述 一个 N M N\times M NM 的由非负整数构成的数字矩阵&#xff0c;你需要在其中取出若干个数字&#xff0c;使得取出的任意两个数字不相邻&#xff08;若一个数字在另外一个数字相邻 8 8 8 个格子中的一个即认为这两个数字相邻&#xff09;&#xff0c;求…

JWT(JSON Web Token )令牌

1、介绍 jwt就是将原始的json数据格式进行了安全的封装&#xff0c;这样就可以直接基于jwt在通信双方安全的进行信息传输了。 2、jwt组成 第一部分&#xff1a;Header(头&#xff09;&#xff0c; 记录令牌类型、签名算法等。 例如&#xff1a;{"alg":"HS256…