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.pswp.cn/news/40876.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/40876.shtml
英文地址,請注明出處:http://en.pswp.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…