別只知道暴力循環!我從用戶名校驗功能中領悟到的高效字符集判斷法(1684. 統計一致字符串的數目)

別只知道暴力循環!我從用戶名校驗功能中領悟到的高效字符集判斷法 😎

大家好,日常開發中,我們經常會遇到一些看似不起眼,卻能成為性能瓶頸的小模塊。今天,我想和大家分享一個我親身經歷的故事,關于如何從一個簡單的“用戶名合法性校驗”功能,一步步挖掘出三種不同層次的算法解法,最終get到性能優化的精髓。

我遇到了什么問題

事情是這樣的,我正在負責一個新項目的用戶注冊模塊。產品經理(PM)跑過來對我說:“為了系統的安全和統一,我們規定新注冊的用戶名,只能包含我們指定的一組字符。這組字符未來可能會在后臺調整。”

聽起來是個小需求嘛,不就是個字符串校驗?我當時想。

PM接著補充道:“后臺會有個功能,可以批量導入一批用戶名,我們需要快速地篩選出所有合法的用戶名。”

這個場景就變得有意思了。我需要一個規則集allowed 字符串)去校驗一大批待測字符串(words 數組)。如果每次校驗一個單詞,都去遍歷一次規則集,當單詞數量和長度上來后,性能開銷會非常大。

這讓我想起了 LeetCode 上的這道題,簡直就是對我這個場景的完美抽象:

1684. 統計一致字符串的數目

給你一個由不同字符組成的字符串 allowed 和一個字符串數組 words 。如果一個字符串的每一個字符都在 allowed 中,就稱這個字符串是 一致字符串 。請你返回 words 數組中 一致字符串 的數目。

在動手編碼前,我們先來仔細品味一下題目的“提示”:

  • 1 <= words.length <= 10^4, 1 <= words[i].length <= 10: 單詞數量不少,但每個單詞都不長。這告訴我們,算法的效率重點應該放在如何快速判斷“單個字符”是否合法上,而不是糾結于單詞長度。
  • 1 <= allowed.length <= 26, allowed 中的字符 互不相同, 只包含小寫英文字母: 這是最重要的三條信息! 它把問題從一個通用的字符串匹配,降維到了一個固定大小(26個字母)的集合問題上。這是我們施展優化“魔法”的關鍵。

好了,背景介紹完畢,讓我們看看我是如何用三套“組合拳”來解決這個問題的。

我是如何用算法“組合拳”解決的

解法一:哈希集合(HashSet),萬能的“存在性”檢查器

這是我腦海里冒出的第一個想法,也是最符合直覺的解法。要反復檢查一個字符在不在 allowed 里,如果每次都用 String.contains(),效率太低。更好的辦法是把 allowed 的字符先存到一個查詢飛快的數據結構里。HashSet 就是為此而生的!

先把所有“合法”的字符都請進一個 HashSet,它就像一個VIP俱樂部的門禁系統。然后,對于每一個待校驗的用戶名,我們檢查它的每個字符是不是這個俱樂部的會員。

import java.util.HashSet;
import java.util.Set;/** 思路:使用哈希集合。將 allowed 字符串的字符存入 HashSet,以實現 O(1) 的快速查找。* 然后遍歷每個單詞,檢查其所有字符是否都在 HashSet 中。* 時間復雜度:O(L + S),其中 L 是 allowed 的長度,S 是 words 中所有字符的總數。* 空間復雜度:O(L),由于 L<=26,可視為 O(1)。*/
class Solution {public int countConsistentStrings(String allowed, String[] words) {// 1. 預處理,構建“VIP俱樂部”// 為什么用 HashSet?因為它提供了平均時間復雜度為 O(1) 的 contains() 方法,// 對于需要頻繁檢查“某元素是否存在于集合中”的場景,它是最理想的選擇。// 比起 List 的 O(L) 查找,簡直是降維打擊。Set<Character> allowedSet = new HashSet<>();for (char c : allowed.toCharArray()) {allowedSet.add(c);}int consistentCount = 0;// 2. 遍歷所有待校驗的用戶名for (String word : words) {boolean isConsistent = true;// 3. 檢查用戶名的每個字符for (char c : word.toCharArray()) {// 如果在“VIP俱樂部”里查不到這個字符,說明是非法用戶,直接標記并“拉黑”if (!allowedSet.contains(c)) {isConsistent = false;break; // 重要的優化:一旦發現不合法的,立即停止對當前單詞的檢查!}}// 如果這個用戶名所有字符都通過了檢查,計數器加一if (isConsistent) {consistentCount++;}}return consistentCount;}
}

復雜度

時間復雜度:O(L + S)。L 是 allowed 的長度,S 是 words 中所有字符的總數。構建 HashSet 的時間是 O(L)。之后遍歷所有單詞的所有字符,總字符數為 S,每次查詢是 O(1),所以檢查部分是 O(S)。總計 O(L + S)。

空間復雜度:O(L)。HashSet 需要存儲 allowed 中的字符。因為題目限制 L <= 26,所以空間復雜度可以認為是常數級別的 O(1)。

這個解法又穩又準,代碼清晰,足以應對大多數場景。但當我看到提示里的“26個小寫字母”時,一個念頭閃過:我真的需要 HashSet 這個“重型武器”嗎?有沒有更輕量、更快的辦法?😉

解法二:布爾數組,小范圍數據的“降維打擊”

HashSet 很好,但它內部有哈希計算、沖突處理等機制,有一定開銷。既然我們已經知道所有字符都在’a’到’z’這個小范圍內,就可以用一個簡單的數組來創建一個更快的“門禁系統”。

我們可以創建一個長度為26的布爾數組,每個位置對應一個字母。array[0] 代表 ‘a’,array[1] 代表 ‘b’,以此類推。如果 allowed 里有 ‘c’,我們就把 array[2] 設為 true。檢查時,只需要看對應位置是不是 true 就行了,這是最純粹的 O(1) 操作!

/** 思路:使用布爾數組模擬哈希。利用字符集限定在26個小寫字母的特性,* 創建一個長度為26的布爾數組作為查找表,比 HashSet 更高效。* 時間復雜度:O(L + S),但常數時間更小。* 空間復雜度:O(1),數組大小固定為26。*/
class Solution {public int countConsistentStrings(String allowed, String[] words) {// 1. 預處理,構建一個超快的“字母通行證”// 為什么用 boolean 數組?因為當鍵的范圍已知且很小時(26個字母),// 數組提供了最快的直接尋址訪問(真正的 O(1)),避免了 HashSet 的哈希計算和潛在沖突開銷。// c - 'a' 這個操作是精髓,它能巧妙地把字符映射到 0-25 的索引上。boolean[] isAllowed = new boolean[26];for (char c : allowed.toCharArray()) {isAllowed[c - 'a'] = true;}int consistentCount = 0;outerLoop: // 使用標簽可以更清晰地跳出外層循環,直接檢查下一個單詞for (String word : words) {for (char c : word.toCharArray()) {// 如果“通行證”上這個字母沒蓋章,說明不合法if (!isAllowed[c - 'a']) {continue outerLoop; // 直接跳到下一個單詞的檢查}}// 如果一個單詞的內層循環能正常走完,說明它是合法的consistentCount++;}return consistentCount;}
}

復雜度

時間復雜度:O(L + S)。分析同上。但由于數組訪問是直接內存尋址,它的實際運行速度通常會比 HashSet 快。

空間復雜度:O(1)。isAllowed 數組的大小是固定的26,不隨輸入規模變化,是純粹的常數空間。

這個解法是我當時“恍然大悟”的瞬間!它讓我明白了,充分利用題目給定的約束條件,是算法優化的不二法門。這個方法已經非常高效了,但我還想挑戰一下自己,有沒有辦法把空間再壓榨一下?

解法三:位運算(Bitmask),高手的極簡主義

布爾數組 [true, true, false, ...] 本質上不就是一串二進制 110... 嗎?既然如此,我為什么不直接用一個整數來表示這26個狀態位呢?一個 int 類型有32位,足夠放下26個字母的“通行證”了!

這就是位運算的魅力。我們可以用一個整數的二進制位來代表集合。第0位是1,代表’a’合法;第1位是1,代表’b’合法……

  • 添加字符:用“按位或 |”操作。
  • 檢查字符:用“按位與 &”操作。
/** 思路:位運算。將 allowed 字符串表示為一個位掩碼(bitmask),然后檢查* 每個單詞的每個字符對應的位是否在該掩碼中為1。這是空間和時間效率都極高的做法。* 時間復雜度:O(L + S)。* 空間復雜度:O(1)。*/
class Solution {public int countConsistentStrings(String allowed, String[] words) {// 1. 預處理,構建一個整數版的“通行證”// 為什么用 int 做位掩碼?因為它緊湊、高效。26個字母的狀態剛好可以用一個32位整數表示。// `1 << (c - 'a')` 生成一個只有 c 對應位是 1 的數字(例如 c='b', 結果是二進制的10)。// `|=` (按位或賦值) 將這個位設置為1,而不影響其他位。int allowedMask = 0;for (char c : allowed.toCharArray()) {allowedMask |= (1 << (c - 'a'));}int consistentCount = 0;outerLoop:for (String word : words) {for (char c : word.toCharArray()) {// 檢查字符 c 的“通行證”位是否為1// `(allowedMask & (1 << (c - 'a')))` 會提取出 c 對應的位。// 如果結果是 0,說明 allowedMask 在這一位上是0,即字符不合法。if ((allowedMask & (1 << (c - 'a'))) == 0) {continue outerLoop; // 驗證失敗,檢查下一個單詞}}consistentCount++;}return consistentCount;}
}

復雜度

時間復雜度:O(L + S)。分析同上。位運算直接在CPU層面執行,通常是所有解法中實際運行最快的。

空間復雜度:O(1)。只用了一個額外的 int 變量,空間利用達到了極致。

這個解法雖然代碼看起來有點“黑話”,但它展示了對計算機底層工作原理的深刻理解。在追求極致性能的場景下,位運算是你的屠龍寶刀!

舉一反三:這個思想還能用在哪?

這個“預處理查詢集”的思想在開發中無處不在:

  • API網關:校驗請求參數是否在允許的枚舉值范圍內。
  • 文件上傳:檢查上傳文件的后綴名是否在白名單中(如 .jpg, .png, .gif)。
  • 敏感詞過濾:將敏感詞庫預處理成高效的查找結構(如Trie樹),快速判斷文本中是否包含敏感詞。

練練手,更熟練

如果你對這類問題產生了興趣,不妨挑戰一下這些“兄弟”題目,它們都能用上類似的思路:

  • 771. 寶石與石頭:本題的簡化版,絕佳的入門練習。
  • 242. 有效的字母異位詞:同樣使用數組作為哈希表來統計字符頻率。
  • 389. 找不同:位運算解法在此題中大放異彩。

希望我這次從真實項目到算法優化的心路歷程能給你帶來一些啟發。記住,優雅的解決方案,往往源于對問題本質和約束條件的深刻洞察。下次再遇到類似問題,別只知道暴力循環啦!😉

解法對比

特性解法一 (HashSet)解法二 (布爾數組)解法三 (位運算)
核心思想通用哈希集合查找數組直接尋址模擬哈希整數位表示集合
代碼簡潔度高,API直觀易用中,邏輯清晰低,需要理解位運算
執行效率高效非常高效(優于HashSet)極致高效(硬件級優化)
時間復雜度O(L + S)O(L + S)O(L + S)
空間復雜度O(L) (可視為O(1))O(1) (固定26)O(1) (固定1個int)
普適性,可用于任何類型元素,僅限于鍵可映射為連續整數,僅限于鍵空間極小

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/90380.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/90380.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/90380.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

力扣面試150題--在排序數組中查找元素的第一個和最后一個位置

Day 85 題目描述思路 當 nums[mid] < target 時&#xff0c;說明目標值在右側&#xff0c;移動左指針 left mid 1 當 nums[mid] > target 時&#xff0c;說明目標值可能在當前位置或左側&#xff0c;移動右指針 right mid - 1 循環結束后&#xff0c;left 指針會指向第…

C++實戰:人臉識別7大核心實例

計算機視覺實例應用 基于C++的人臉識別實例 以下是一些基于C++的人臉識別實例的示例和實現方法,涵蓋了多種技術和庫的應用。這些例子可以幫助開發者快速上手并實現人臉識別功能。 OpenCV 基礎人臉檢測 使用OpenCV的預訓練模型進行人臉檢測是入門級示例。OpenCV自帶Haar級聯…

Uniapp中使用vue3語法

在setup語法糖中調用uniapp的頁面生命周期 <script setup>import { onShow } from "dcloudio/uni-app"onShow(() > {//hanlder...}) </script>vue2混入在vue3中建議使用組合式API 新建baseHook.js import { ref } from "vue"; export fu…

C++vector(2)

2.vector深度剖析及模擬實現 2.1std::vector的核心框架接口的模擬實現bit::vector vector的模擬實現 2.2 使用memcpy拷貝問題 假設模擬實現的vector中的reserve接口中&#xff0c;使用memcpy進行的拷貝&#xff0c;以下代碼會發生什么問題&#xff1f; int main() {gxl::ve…

IPSec VPN -- 野蠻模式

一、野蠻模式簡介野蠻模式VPN是指IPsec VPN中IKE協商采用野蠻模式&#xff08;Aggressive Mode&#xff09;的虛擬專用網絡。它是IKE第一階段協商的一種方式&#xff0c;與主模式相對&#xff0c;具有協商速度快但安全性稍低的特點。以下是具體介紹&#xff1a;1、工作原理&…

rk3588開發板使用硬件編碼處理視頻

開發板默認下載的ffmpeg是通用版&#xff0c;無法調用rk3588的硬件編碼器&#xff0c;視頻編碼效率低。 nyanmisaka開發了用于jellyfin的ffmpeg&#xff0c;支持rk3588硬件編碼器&#xff0c;編譯方法&#xff1a; https://github.com/nyanmisaka/ffmpeg-rockchip/wiki/Compil…

`neutron router-gateway-set` 操作失敗的可能原因及解決方案

根據提供的錯誤信息和搜索結果&#xff0c;neutron router-gateway-set 操作失敗的可能原因及解決方案如下&#xff1a;一、常見錯誤原因數據庫字符集配置問題&#xff08;中文名支持&#xff09; 表現&#xff1a;若路由器名稱包含中文字符&#xff0c;可能因數據庫字符集非UT…

(一)ZooKeeper 發展歷史

?博客主頁&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客內容》&#xff1a;.NET、Java.測試開發、Python、Android、Go、Node、Android前端小程序等相關領域知識 &#x1f4e2;博客專欄&#xff1a; https://blog.csdn.net/m0_63815035/cat…

OpenCV快速入門之CV寶典

文章目錄OpenCV的基礎應用一、OpenCV簡介&#xff1a;1.1 OpenCV 優勢1.2 OpenCV-Python二、環境安裝2.1 環境導入三、圖像表示3.1 顏色空間&#xff08;Color Space&#xff09;3.2 具體說明3.3 圖像在計算機中的表示四、基本圖像操作4.1 創建窗口**1. 核心窗口行為控制**cv.W…

LangChain4j 兩種類型API

LangChain4j operates on two levels of abstraction: &#xfeff;LangChain4j 提供了兩種類型API抽象Low level. At this level, you have the most freedom and access to all the low-level components such as ChatModel, UserMessage, AiMessage, EmbeddingStore, Embedd…

CLI 與 IDE 編碼代理比較:提升開發效率的兩種路徑

引言 在當今快速發展的軟件開發領域&#xff0c;人工智能編碼助手已成為開發者工具箱中不可或缺的一部分。根據行業報告&#xff0c;使用AI編碼助手可以將開發速度提高55%以上&#xff0c;同時顯著提升代碼質量。目前市場上主要有兩種類型的編碼代理&#xff1a;集成在IDE中的代…

【STM32】FreeRTOS 任務的創建(二)

這篇文章在于 詳細解釋 FreeRTOS 中任務的創建過程&#xff0c;包括任務創建的本質過程、API 詳解、兩種創建方式&#xff08;動態/靜態&#xff09;、任務函數規范、常見錯誤及實踐建議。 這里參照&#xff1a;RTOS官方文檔&#xff1a;https://www.freertos.org/zh-cn-cmn-s…

軟考 系統架構設計師系列知識點之面向服務架構設計理論與實踐(9)

接前一篇文章:軟考 系統架構設計師系列知識點之面向服務架構設計理論與實踐(8) 所屬章節: 第15章. 面向服務架構設計理論與實踐 第3節 SOA的參考架構 15.3 SOA的參考架構 IBM的Websphere業務集成參考架構(如圖15-2所示,以下簡稱參考架構)是典型的以服務為中心的企業集…

分區域材料設計:主承重區 / 次承重區 / 足弓區的彈性參數與刺激強度匹配

你是否總在為足部酸痛、膝蓋不適或腰背僵硬煩惱&#xff1f;穿了昂貴的緩震跑鞋&#xff0c;用了定制矯形器&#xff0c;問題卻反復出現&#xff1f;今天&#xff0c;我們要顛覆一個流傳百年的“常識”——腳不是脆弱的“需要被保護的對象”&#xff0c;而是被錯誤的設計“慣壞…

使用Qt下QAudioOutput播放聲音

導讀本項目目的是使用QAudioOutput播放聲音 &#xff0c;音頻數據來源為ffmpeg解碼后的音頻數據。Qt音頻播放類說明 QAudioFormatQAudioFormat是Qt多媒體框架中用于定義音頻格式的核心類&#xff0c;用于設置音頻數據的參數&#xff0c;確保與硬件設備兼容。其主要功能和參數如…

日語學習-日語知識點小記-構建基礎-JLPT-N3階段(9):ようなN

日語學習-日語知識點小記-構建基礎-JLPT-N3階段&#xff08;9&#xff09;&#xff1a;ようなN 1、前言&#xff08;1&#xff09;情況說明&#xff08;2&#xff09;工程師的信仰2、知識點&#xff08;&#xff11;&#xff09;復習&#xff08;&#xff12;&#xff09;復習&…

洛谷P1512 伊甸園日歷游戲

一開始&#xff0c;我發現有“必勝策略”&#xff0c;就知道是博弈論&#xff0c;然后看了兩種操作&#xff08;月份1和天數1&#xff09;&#xff0c;于是想到用記憶化搜索找出所有的可能性 &#xff0c;但不知道怎么判斷當前是否為先手必勝/必敗態&#xff0c;使用了TJ方法后…

Kafka——消費者組到底是什么?

引言在分布式系統中&#xff0c;消息中間件的核心價值在于高效地連接生產者與消費者&#xff0c;實現數據的可靠傳遞。然而&#xff0c;傳統消息引擎面臨一個兩難困境&#xff1a;如何在“消息不重復消費”與“系統可擴展性”之間找到平衡&#xff1f;點對點模型&#xff08;如…

新mac電腦軟件安裝指南(前端開發用)

1. 下載git 未下載git直接下載homebrew也會提示你下載git 2. 下載homebrew 介紹&#xff1a; Homebrew 是 macOS 和 Linux 系統的開源包管理器?&#xff0c;通過命令行實現軟件的快速安裝、更新和管理&#xff0c;極大簡化了開發者及普通用戶的工作流程。 命令&#xff1a;…

【HarmonyOS】ArkUI 布局與容器組件

目錄前言一、線性布局(Column/Row)1.先布局后內容2.元素在主軸上的排列方式3.元素在交叉軸上的排列方式二、層疊布局(Stack)1.開發布局2.對齊方式三、彈性布局(Flex)四、創建列表(List)五、創建輪播(Swiper)1.基本用法2.常用屬性3.樣式自定義六、選項卡Tabs1.基本用法2.常用屬性…