LeetCode - 34. 在排序數組中查找元素的第一個和最后一個位置

題目

34. 在排序數組中查找元素的第一個和最后一個位置 - 力扣(LeetCode)

思路

查找左邊界

初始化?left =?0,?right = nums.size() - 1

當?left?< right?時循環:

  • 計算中點?mid =?left + (right?- left) /?2
  • 如果?nums[mid]?< target,目標在右側,left = mid + 1
  • 否則,目標在左側或當前位置,right =?mid

循環結束后,left ==?right,檢查?nums[left]?是否等于?target

查找右邊界

初始化?left?= 0,?right = nums.size() -?1

當?left?< right?時循環:

  • 計算中點?mid = left?+ (right -?left +?1) /?2?(向上取整)
  • 如果?nums[mid] > target,目標在左側,right?= mid -?1
  • 否則,目標在右側或當前位置,left =?mid

循環結束后,left?== right,檢查?nums[left]?是否等于?target

圖解過程

以數組?[5,?7,?7, 8, 8,?10],目標值?target?= 8?為例:

查找左邊界

初始狀態:

索引:  0  1  2  3  4  5
數組: [5, 7, 7, 8, 8, 10]↑           ↑left        right

?第一次迭代:

  • mid =?0 + (5-0)/2 =?2
  • nums[mid] = 7?< 8,目標在右側
  • left?= mid +?1 =?3
索引:  0  1  2  3  4  5
數組: [5, 7, 7, 8, 8, 10]↑     ↑left  right

?第二次迭代:

  • mid = 3?+ (5-3)/2?= 4
  • nums[mid] =?8 ==?8,目標可能在左側
  • right?= mid =?4
索引:  0  1  2  3  4  5
數組: [5, 7, 7, 8, 8, 10]↑  ↑left right

?第三次迭代:

  • mid = 3?+ (4-3)/2?= 3
  • nums[mid]?= 8?== 8,目標可能在左側
  • right = mid = 3
索引:  0  1  2  3  4  5
數組: [5, 7, 7, 8, 8, 10]↑leftright

?循環結束:

  • left?==?right =?3,循環結束
  • nums[left] =?8 == target,找到左邊界?begin =?3

查找右邊界

初始狀態:

索引:  0  1  2  3  4  5
數組: [5, 7, 7, 8, 8, 10]↑           ↑left        right

?第一次迭代:

  • mid = 0?+ (5-0+1)/2?= 3?(向上取整)
  • nums[mid]?= 8?== 8,目標可能在右側
  • left = mid =?3
索引:  0  1  2  3  4  5
數組: [5, 7, 7, 8, 8, 10]↑     ↑left  right

?第二次迭代:

  • mid = 3?+ (5-3+1)/2 =?5?(向上取整)
  • nums[mid] =?10?>?8,目標在左側
  • right = mid -?1 =?4
索引:  0  1  2  3  4  5
數組: [5, 7, 7, 8, 8, 10]↑  ↑left right

?第三次迭代:

  • mid = 3?+ (4-3+1)/2 =?4?(向上取整)
  • nums[mid] =?8?== 8,目標可能在右側
  • left?= mid =?4
索引:  0  1  2  3  4  5
數組: [5, 7, 7, 8, 8, 10]↑leftright

循環結束:

  • left == right =?4,循環結束
  • nums[left] =?8 ==?target,找到右邊界?end = 4

最終結果

返回?[begin, end]?= [3,?4],表示目標值?8 的第一個位置是索引 3,最后一個位置是索引?4。

關鍵點

  • 使用?left?< right?作為循環條件,確保循環結束時?left ==?right
  • 查找左邊界時使用向下取整計算?mid,更新方式為?right?= mid
  • 查找右邊界時使用向上取整計算 mid,更新方式為?left = mid
  • 循環結束后檢查?nums[left]?是否等于目標值

讀者可能會出現的錯誤寫法

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if(nums.empty()){return {-1,-1};}int left = 0;int right = nums.size()-1;int begin = -1;int end =-1;while(left <= right){int mid = left + (right-left)/2;if(nums[mid]<target){left = mid + 1;}else{right = mid-1;}}if(nums[left] == target){begin = left;}left = 0;right = nums.size()-1;while(left <= right){int mid = left + (right-left)/2;if(nums[mid] > target){right = mid-1;}else{left = mid+1;}}if(nums[right] == target){end = right;}return {begin,end};}
};

在查找左邊界的時候,right = mid而不是right = mid-1;在查找右邊界的時候,left = mid而不是left = mid+1,并且left<right 而不是left<=right,并且當left=mid的條件下,mid = left + (right-left+1)/2,而不是mid = left + (right-left)/2

為什么要用right = mid而不是right = mid - 1?

  • 如果nums[mid] == target,mid可能就是我們要找的左端點,或者左端點在mid左側
  • 如果我們使用right =?mid - 1,就可能錯過正確答案
  • 使用right =?mid可以保留mid作為可能的答案,同時繼續向左搜索

為什么我們使用right =?mid - 1,就可能錯過正確答案

假設數組中有多個相同的目標值,例如[1, 3, 3,?3, 5],目標值target = 3。?

當我們找到一個nums[mid] == target時(例如中間的那個3),我們不知道這是不是第一個出現的3。有兩種可能:

  • 當前找到的這個3就是第一個3
  • 在mid左側還有其他的3

使用right = mid - 1的問題:

如果當前找到的nums[mid] == target恰好是第一個出現的目標值,那么使用right =?mid - 1會將搜索范圍縮小到mid左側,完全排除了mid這個位置。

具體例子:

  • 數組[1, 2, 3,?4, 5],target = 3
  • 初始:left = 0,?right = 4
  • 第一次迭代:mid = 2,?nums[mid] = 3 == target
  • 如果使用right = mid - 1,區間變為[0, 1]
  • 但索引2處的元素(值為3)是我們要找的答案!我們已經排除了它
  • 后續迭代會在[0, 1]范圍內搜索,但這個范圍內沒有值為3的元素
  • 最終會得出錯誤結論,認為數組中不存在目標值

使用right = mid的優勢:

當nums[mid]?== target時,使用right = mid:

  • 將右邊界移動到mid(而不是mid-1)
  • 保留了mid作為可能的答案
  • 同時繼續在左側搜索是否有更早出現的目標值

這樣,即使當前的mid就是第一個目標值,我們也不會錯過它,因為:

  • 如果左側沒有其他目標值,循環最終會收斂到這個位置
  • 如果左側有目標值,我們會找到那個更早的位置

總結:使用right = mid - 1在找到目標值時會完全排除當前位置,如果當前位置恰好是第一個目標值,就會錯過正確答案。而right = mid保留了當前位置作為候選答案,同時繼續向左搜索可能存在的更早出現的目標值。

為啥查找左端點的mid算法和查找右端點的mid算法不一樣

考慮當搜索區間縮小到只有兩個元素時,例如?[3, 4]:

  • left = 3, right = 4
  • mid = 3 + (4-3)/2 =?3(向下取整)
  • 如果?nums[mid] <=?target,執行?left = mid =?3
  • 新的搜索區間仍然是?[3, 4]
  • 下一次迭代,mid仍然是3
  • 如果?nums[mid] <=?target,又執行?left = mid = 3
  • 搜索區間永遠是?[3, 4],無法進一步縮小,陷入死循環

使用向上取整的解決方案

使用向上取整?mid = left + (right -?left + 1) / 2?可以解決這個問題:

  • left = 3, right = 4
  • mid =?3 + (4-3+1)/2 = 4(向上取整)
  • 如果?nums[mid] > target,執行?right?= mid - 1?= 3
  • 搜索區間變為?[3, 3],循環結束

或者,如果?nums[mid]?<= target,執行?left?= mid = 4,區間變為?[4, 4],循環也會結束。

為什么查找左端點不需要向上取整

關鍵在于更新策略的不同:

查找左端點時:

  • 當?nums[mid]?< target?時,使用?left?= mid +?1
  • 這個?+1?確保了即使 mid?= left,區間也會縮小

查找右端點時:

  • 當?nums[mid] <= target?時,使用?left = mid(沒有?+1)
  • 如果 mid = left,區間不會縮小,導致死循環

總結

  • 查找左端點時,即使使用向下取整,也不會導致死循環,因為:
  • 當?nums[mid] < target?時,left = mid +?1?會縮小區間
  • 當?nums[mid] >=?target?時,right?= mid?會縮小區間(因為 mid?≤ right)
  • 查找右端點時,使用向下取整可能導致死循環,因為:
  • 當?nums[mid] <=?target?且?mid = left 時,left = mid?不會縮小區間

當使用right = mid和left = mid這種更新方式時,循環條件應該是while(left < right)而不是while(left <= right)。為啥呀

原因如下:

主要原因:避免死循環

如果使用while(left <= right)作為循環條件,當left ==?right時循環仍會繼續執行:

當left == right時,無論使用什么mid計算方式,都會得到mid == left == right

此時:

  • 如果執行right = mid,結果是right?= left,區間不變
  • 如果執行left = mid,結果是left = right,區間不變

下一次循環,條件left <= right仍然滿足(因為left == right)

區間沒有縮小,算法陷入死循環

具體例子

假設數組[1, 3, 5],當搜索區間縮小到[1, 1](即left = right = 1):

如果使用while(left <=?right):

  • mid = 1
  • 如果執行right =?mid,區間仍為[1, 1]
  • 如果執行left = mid,區間仍為[1, 1]
  • 下一次循環,條件仍然滿足,陷入死循環

正確的寫法

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if(nums.empty()){return {-1,-1};}int left = 0;int right = nums.size()-1;int begin = -1;int end =-1;while(left < right){int mid = left + (right-left)/2;if(nums[mid]<target){left = mid + 1;}else{right = mid;}}if(nums[left] == target){begin = left;}left = 0;right = nums.size()-1;while(left < right){int mid = left + (right-left + 1)/2;if(nums[mid] > target){right = mid-1;}else{left = mid;}}if(nums[right] == target){end = right;}return {begin,end};}
};

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

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

相關文章

Tesollo四指靈巧手DG-4F:18自由度與多種抓取模式結合實現高精度操作

Tesollo四指靈巧手 DG-4F 是一款具備 18 自由度的多模態末端執行器&#xff0c;采用模塊化結構設計&#xff0c;融合人手靈活性與夾爪高效性特點。該產品兼容 Universal Robots、Techman、Doosan Robotics、Rainbow Robotics 等主流機器人平臺&#xff0c;適用于工業自動化、科…

深入淺出JavaScript 原型鏈:對象繼承的“隱形鏈條”

深入淺出JavaScript 原型鏈&#xff1a;對象繼承的“隱形鏈條” 在 JavaScript 的世界里&#xff0c;原型鏈&#xff08;Prototype Chain&#xff09;是一個核心概念。它如同一條隱形的鏈條&#xff0c;連接著所有對象&#xff0c;使得代碼能夠高效地共享屬性和方法。理解原型…

LINUX中MYSQL的使用

LINUX中MYSQL的使用 MYSQL的數據類型 bool&#xff1a; 布爾類型 0 或者 1 CHAR&#xff1a; 單字符的字符 CHAR&#xff08;n&#xff09;:多字節字符 VARCHAR&#xff08;n&#xff09;&#xff1a;可變長度的字符型 TINYINT &#xff1a; 單字節整型 SMALLINT&#x…

打卡第48天:隨機函數與廣播機制

知識點回顧&#xff1a; 隨機張量的生成&#xff1a;torch.randn函數卷積和池化的計算公式&#xff08;可以不掌握&#xff0c;會自動計算的&#xff09;pytorch的廣播機制&#xff1a;加法和乘法的廣播機制 ps&#xff1a;numpy運算也有類似的廣播機制&#xff0c;基本一致 …

學習昇騰開發的第四天--基本指令

1、查看npu當前狀態信息 npu-smi info 2、查看NPU的ID npu-smi info -l3、調用python python3 4、修改用戶名 su - HwHiAiUser 5、查看cann版本 cat /usr/local/Ascend/ascend-toolkit/latest/compiler/version.info 6、刪除文件夾 sudo rm -rf HelloWorld7、在本地環…

vue3 - 自定義hook

自定義hook 簡單點來說就是將人物或者訂單的所有數據和方法放在一個ts文件里面 這樣便于維護 假如一個人只需要管 人物的模塊 那他只需要操作usePerson.ts文件就可以了 //useDog.ts import { ref,reactive} from vue; import axios from axios;export default function(){…

【python】bash: !‘: event not found

報錯 # 2. 測試smplx是否工作&#xff08;可能不需要chumpy&#xff09; python -c "import smplx; print(? smplx works!)"bash: !: event not found 分析 這是bash的歷史擴展問題&#xff0c;感嘆號被解釋為歷史命令。用這些方法解決&#xff1a; &#x1f680…

【Python打卡Day47】注意力熱力圖可視化@浙大疏錦行

可視化空間注意力熱力圖的意義&#xff1a; 提升模型可解釋性 熱力圖能直觀展示模型決策的依據區域&#xff0c;破除深度學習"黑箱"困境。例如在圖像識別中&#xff0c;可以看到模型識別"貓"是因為關注了貓耳和胡須區域&#xff0c;識別"禁止通行&qu…

樹狀數組 2

L - 樹狀數組 2 洛谷 - P3368 Description 如題&#xff0c;已知一個數列&#xff0c;你需要進行下面兩種操作&#xff1a; 將某區間每一個數加上 x&#xff1b; 求出某一個數的值。 Input 第一行包含兩個整數 N、M&#xff0c;分別表示該數列數字的個數和操作的總個數。…

YOLOv2 技術詳解:目標檢測的又一次飛躍

&#x1f9e0; YOLOv2 技術詳解&#xff1a;目標檢測的又一次飛躍 一、前言 在 YOLOv1 提出后&#xff0c;雖然實現了“實時性 單階段”的突破&#xff0c;但其在精度和小物體檢測方面仍有明顯不足。為了彌補這些缺陷&#xff0c;Joseph Redmon 等人在 2017 年提出了 YOLOv2…

JAFAR Jack up Any Feature at Any Resolution

GitHub PaPer JAFAR: Jack up Any Feature at Any Resolution 摘要 基礎視覺編碼器已成為各種密集視覺任務的核心組件。然而&#xff0c;它們的低分辨率空間特征輸出需要特征上采樣以產生下游任務所需的高分辨率模式。在這項工作中&#xff0c;我們介紹了 JAFAR——一種輕量級…

SamWaf 開源輕量級網站防火墻源碼(源碼下載)

SamWaf網站防火墻是一款適用于小公司、工作室和個人網站的開源輕量級網站防火墻&#xff0c;完全私有化部署&#xff0c;數據加密且僅保存本地&#xff0c;一鍵啟動&#xff0c;支持Linux&#xff0c;Windows 64位,Arm64。 主要功能&#xff1a; 代碼完全開源 支持私有化部署…

79Qt窗口_QDockWidget的基本使用

目錄 4.1 浮動窗?的創建 4.2 設置停靠的位置 浮動窗? 在 Qt 中&#xff0c;浮動窗?也稱之為鉚接部件。浮動窗?是通過 QDockWidget類 來實現浮動的功能。浮動窗 ??般是位于核?部件的周圍&#xff0c;可以有多個。 4.1 浮動窗?的創建 浮動窗?的創建是通過 QDockWidget…

UE/Unity/Webgl云渲染推流網址,如何與外部網頁嵌套和交互?

需求分析&#xff1a;用threejs開發的數字孿生模型&#xff0c; 但是通過webgl技術網頁中使用&#xff0c;因為模型數據量大&#xff0c;加載比較慢&#xff0c;且需要和其他的業務系統進行網頁嵌套和交互&#xff0c;使用云渲染技術形成的推流網址&#xff0c;如何與外部網頁嵌…

在Termux中搭建完整Python環境(Ubuntu+Miniconda)

蹲坑也能寫python? ?? 環境準備?? 詳細搭建步驟步驟1:安裝Linux容器工具步驟2:查看可用Linux發行版步驟3:安裝Ubuntu系統步驟4:登錄Ubuntu環境步驟5:下載Miniconda安裝包步驟6:安裝Miniconda? 環境驗證?? 使用技巧?? 注意事項前言:想在吃飯、通勤甚至休息間隙…

EventSourcing.NetCore:基于事件溯源模式的 .NET Core 庫

在現代軟件架構中&#xff0c;事件溯源&#xff08;Event Sourcing&#xff09;已經成為一種非常流行的模式&#xff0c;尤其適用于需要高可用性和數據一致性的場景。EventSourcing.NetCore 是一個基于事件溯源模式的 .NET Core 庫&#xff0c;旨在幫助開發者更加高效地實現這一…

Linux下的第一個程序——進度條(命令行版本)

文章目錄 編寫Linux下的第一個小程序——進度條進度條的樣式前置知識回車和換行緩沖區對回車、換行、緩沖區、輸出的測試代碼簡單的測試樣例倒計時程序 進度條程序理論版本基本框架代碼實現 真實版本基礎框架 代碼實現 編寫Linux下的第一個小程序——進度條 在前面的基礎開發工…

【項目】仿muduo庫one thread one loop式并發服務器前置知識準備

&#x1f4da; 博主的專欄 &#x1f427; Linux | &#x1f5a5;? C | &#x1f4ca; 數據結構 | &#x1f4a1;C 算法 | &#x1f152; C 語言 | &#x1f310; 計算機網絡 |&#x1f5c3;? mysql 本文介紹了一種基于muduo庫實現的主從Reactor模型高并發服務器框架…

steam報網絡錯誤,但電腦是網絡連接的

steam報網絡錯誤&#xff0c;但電腦是網絡連接的 如&#xff1a; 解決辦法&#xff1a; 關閉電腦防火墻和所有殺毒軟件&#xff0c;然后重新打開steam開代理&#xff0c;可能國內有時候訪問不了 首選1進行嘗試 steam安裝路徑一定要在純英文路徑下 已ok

Vue 組合式 API 與 選項式 API 全面對比教程

一、前言&#xff1a;Vue 的兩種 API 風格 Vue 提供了兩種編寫組件邏輯的方式&#xff1a;組合式 API (Composition API) 和 選項式 API (Options API)。理解這兩種方式的區別和適用場景&#xff0c;對于 Vue 開發者至關重要。 為什么會有兩種 API&#xff1f; 選項式 API&a…