關于二分法的邊界問題及兩種寫法

關于二分法的邊界問題及兩種寫法

二分查找法大家很熟悉了,對于一個有序序列,我們可以通過二分查找法在 O(logN)O(logN)O(logN) 的時間內找到想要的元素。但是,在代碼實現的過程中,如果沒有仔細理解清楚,二分法的邊界條件有時會讓人很頭疼,而對邊界條件的妥善處理是很能體現一個人的代碼功底的,也通常是面試官會很關注的一個點。另外,大佬的題解中的二分法代碼也總有幾處小細節不同,但是大佬的代碼都是怎么測都沒問題的,自己卻總因為某處細節沒有處理好而出現問題。

實際上,二分法通常有兩種細節略有不同的實現方式:左閉右閉和左閉右開,本文將簡單介紹這兩種實現方式,并指出他們之間的不同及適用情況,希望也能夠幫助大家徹底理解二分法,從此不再會因為邊界條件問題出錯。

題目

我們先給定題目要求,就通過 LeetCode 上的一道二分法的題目為例:704. 二分查找。

給定一個 n 個元素有序的(升序)整型數組 nums 和一個目標值 target ,寫一個函數搜索 nums 中的 target,如果目標值存在返回下標,否則返回 -1。

示例 1:

輸入: nums = [-1,0,3,5,9,12], target = 9
輸出: 4
解釋: 9 出現在 nums 中并且下標為 4

示例 2:

輸入: nums = [-1,0,3,5,9,12], target = 2
輸出: -1
解釋: 2 不存在 nums 中因此返回 -1

提示

  • 你可以假設 nums 中的所有元素是不重復的。
  • n 將在 [1, 10000]之間。
  • nums 的每個元素都將在 [-9999, 9999]之間。

給定的函數簽名 (C++) 是這樣的:

class Solution {
public:int search(vector<int>& nums, int target) {}
};

說明:以下兩節講解參考自代碼隨想錄。

左閉右閉

第一種寫法,我們定義 target 是在一個在左閉右閉的區間里,也就是 [left, right] (這個很重要非常重要)。

區間的定義這就決定了二分法的代碼應該如何寫,因為定義target在[left, right]區間,所以有如下兩點:

  • while (left <= right) 要使用 <= ,因為left == right是有意義的,所以使用 <=
  • if (nums[middle] > target) right 要賦值為 middle - 1,因為當前這個nums[middle]一定不是target,那么接下來要查找的左區間結束下標位置就是 middle - 1

例如在數組:1,2,3,4,7,9,10中查找元素2,如圖所示:

在這里插入圖片描述

代碼:

class Solution {
public:int search(vector<int>& nums, int target) {int left = 0;int right = nums.size() - 1; // 定義target在左閉右閉的區間里,[left, right]while (left <= right) { // 當left==right,區間[left, right]依然有效,所以用 <=int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2if (nums[middle] > target) {right = middle - 1; // target 在左區間,所以[left, middle - 1]} else if (nums[middle] < target) {left = middle + 1; // target 在右區間,所以[middle + 1, right]} else { // nums[middle] == targetreturn middle; // 數組中找到目標值,直接返回下標}}// 未找到目標值return -1;}
};

左閉右開

如果說定義 target 是在一個在左閉右開的區間里,也就是[left, right) ,那么二分法的邊界處理方式則截然不同。

有如下兩點:

  • while (left < right),這里使用 < ,因為left == right在區間[left, right)是沒有意義的
  • if (nums[middle] > target) right 更新為 middle,因為當前nums[middle]不等于target,去左區間繼續尋找,而尋找區間是左閉右開區間,所以right更新為middle,即:下一個查詢區間不會去比較nums[middle]

在數組:1,2,3,4,7,9,10中查找元素2,如圖所示:(注意和方法一的區別

在這里插入圖片描述

代碼:

class Solution {
public:int search(vector<int>& nums, int target) {int left = 0;int right = nums.size(); // 定義target在左閉右開的區間里,即:[left, right)while (left < right) { // 因為left == right的時候,在[left, right)是無效的空間,所以使用 <int middle = left + ((right - left) >> 1);if (nums[middle] > target) {right = middle; // target 在左區間,在[left, middle)中} else if (nums[middle] < target) {left = middle + 1; // target 在右區間,在[middle + 1, right)中} else { // nums[middle] == targetreturn middle; // 數組中找到目標值,直接返回下標}}// 未找到目標值return -1;}
};

搜索插入的位置:不大于target的最大索引

完整功能的二分法除了查找之外,還應該在沒有查找到 target 元素時返回 target 應該插入的位置,為接下來可能的插入操作提供便利。

35. 搜索插入位置

注意本題要求保證給定數組是嚴格單增的,即不存在相等的元素,如果存在相等的元素如 [1,2,3,3,5] 這種情況在插入 3 時就會有多個合理的插入位置。

版本一:左閉右閉

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

版本二:左閉右開

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

在排序數組中搜索元素的第一個和最后一個位置

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

由于數組已經排序,因此整個數組是單調遞增的,我們可以利用二分法來加速查找的過程。

考慮 targettargettarget 開始和結束位置,其實我們要找的就是數組中「第一個等于 targettargettarget 的位置」(記為 leftIdxleftIdxleftIdx )和「第一個大于 targettargettarget 的位置減一」(記為 rightIdxrightIdxrightIdx )。

二分查找中,尋找 leftIdxleftIdxleftIdx 即為在數組中尋找第一個大于等于 targettargettarget 的下標,尋找 rightIdxrightIdxrightIdx 即為在數組中尋找第一個大于 targettargettarget 的下標,然后將下標減一。兩者的判斷條件不同,為了代碼的復用,我們定義 binarySearch(nums, target, lower) 表示在 numsnumsnums 數組中二分查找 targettargettarget 的位置,如果 lowerlowerlowertruetruetrue,則查找第一個大于等于 targettargettarget 的下標,否則查找第一個大于 targettargettarget 的下標。

最后,因為 targettargettarget 可能不存在數組中,因此我們需要重新校驗我們得到的兩個下標 leftIdxleftIdxleftIdxrightIdxrightIdxrightIdx,看是否符合條件,如果符合條件就返回 [leftIdx,rightIdx][leftIdx,rightIdx][leftIdx,rightIdx],不符合就返回 [?1,?1][-1,-1][?1,?1]

class Solution {
private:int binSearch(vector<int>& nums, int target, bool lower) {int left = 0, right = nums.size() - 1;int res = nums.size();while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] > target || (lower && nums[mid] >= target)) {right = mid - 1;res = mid;}else left = mid + 1;}return res;}
public:vector<int> searchRange(vector<int>& nums, int target) {int left = binSearch(nums, target, true);int right = binSearch(nums, target, false) - 1;if (left <= right && nums[left] == target && nums[right] == target && right <= nums.size()) return {left, right};else return {-1, -1};}
};

Ref:

https://leetcode-cn.com/problems/binary-search

https://leetcode-cn.com/problems/search-insert-position/

https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html#%E6%80%9D%E8%B7%AF

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

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

相關文章

LeetCode上的各種股票最大收益

LeetCode上的各種股票最大收益 對于力扣平臺上的股票類型的題目&#xff1a; 121 買賣股票的最佳時機 122 買賣股票的最佳時機 II 123 買賣股票的最佳時機 III 124 買賣股票的最佳時機 IV 309 最佳買賣股票時機含冷凍期 714 買賣股票的最佳時機含手續費 劍指 Offer 63. …

建設專業化運維服務團隊必要性

信息系統的生命周期涵蓋&#xff1a;設計、開發、測試、部署上線、運行維護。其中&#xff0c;運行維護階段是信息系統生命周期中的關鍵環節&#xff0c;其執行效果直接影響系統是否能達到預期的運行目標。為了實現這個目標&#xff0c;我們必須建立一個以業務服務為導向的專業…

docker初探

docker初探 本文旨在介紹 docker 基本的安裝、常用命令和常見概念的辨析&#xff0c;方便新手入門和筆者日后查閱&#xff0c;大部分內容整理自互聯網&#xff0c;原出處在文中注明。 文章目錄docker初探docker安裝&#xff08;mac&#xff09;版本、信息相關命令version/info…

ubuntu安裝zsh、oh-my-zsh及常用配置

ubuntu安裝zsh、oh-my-zsh及常用配置 目前&#xff0c;ubuntu默認的shell是bash&#xff0c;但還有一種shell&#xff0c;叫做zsh它比bash更加強大&#xff0c;功能也更加完善&#xff0c;zsh雖說功能強大&#xff0c;但是配置比較復雜導致流行度不是很高 但是好東西終究是好…

Segmentaion標簽的三種表示:poly、mask、rle

Segmentaion標簽的三種表示&#xff1a;poly、mask、rle 不同于圖像分類這樣比較簡單直接的計算機視覺任務&#xff0c;圖像分割任務&#xff08;又分為語義分割、實例分割、全景分割&#xff09;的標簽形式稍為復雜。在分割任務中&#xff0c;我們需要在像素級上表達的是一張…

tensorboard報錯:ValueError Duplicate plugins for name projector 問題的出現及解決過程

tensorboard報錯&#xff1a;ValueError: Duplicate plugins for name projector 問題的出現及解決過程 記錄如題問題的出現及解決過程。 報錯命令及信息 筆者在終端調用 tensorboard 時&#xff1a; tensorboard --logdirruns/ --bind_all報錯&#xff1a; raise ValueEr…

發布自己的Python包(Pypi)

發布自己的Python包(Pypi) 我們經常使用 Pypi 來安裝包&#xff0c;但是有時候我們也想要發布自己的 Pypi 包&#xff0c;有可能我們寫了一個特別牛的包&#xff0c;也有可能我們只是想使用自己常用的一些輪子&#xff0c;可能這是我們日常編碼中很常用的一些輪子&#xff0c;…

Ubuntu PPA 使用指南

Ubuntu PPA 使用指南 轉自&#xff1a;https://zhuanlan.zhihu.com/p/55250294 一篇涵蓋了在 Ubuntu 和其他 Linux 發行版中使用 PPA 的幾乎所有問題的深入的文章。 如果你一直在使用 Ubuntu 或基于 Ubuntu 的其他 Linux 發行版&#xff0c;例如 Linux Mint、Linux Lite、Zorin…

如何在 Linux 中快速地通過 HTTP 提供文件訪問服務

如何在 Linux 中快速地通過 HTTP 提供文件訪問服務 轉自&#xff1a;https://linux.cn/article-10205-1.html 如今&#xff0c;我有很多方法來通過 Web 瀏覽器為局域網中的其他系統提供單個文件或整個目錄的訪問。我在我的 Ubuntu 測試機上測試了這些方法&#xff0c;它們如下面…

Linux apt命令

Linux apt命令及其與apt-get的關系 轉自&#xff1a;https://blog.csdn.net/taotongning/article/details/82320472、https://www.runoob.com/linux/linux-comm-apt.html apt&#xff08;Advanced Packaging Tool&#xff09;是一個在 Debian 和 Ubuntu 中的 Shell 前端軟件包管…

楊宏宇:騰訊多模態內容理解技術及應用

楊宏宇&#xff1a;騰訊多模態內容理解技術及應用 分享嘉賓&#xff1a;楊宇鴻 騰訊 內容理解高級工程師 編輯整理&#xff1a;吳祺堯 出品平臺&#xff1a;DataFunTalk 導讀&#xff1a; 搜索內容的理解貫穿了整個搜索系統。我們需要從多個粒度理解搜索內容&#xff0c;包括語…

git登錄相關操作梳理

git登錄相關操作梳理 本文主要基于 Linux/Mac &#xff0c;Windows下未經測試&#xff0c;不過估計差不多&#xff0c;在 git bash 內操作即可。 創建ssh key并關聯github等賬號 因為本地Git倉庫和GitHub倉庫之間的傳輸是通過SSH加密傳輸的&#xff0c;GitHub需要識別是否是…

關于mmdetection上手的幾點說明

關于mmdetection上手的幾點說明 官方的文檔很有參考價值&#xff0c;并且也有中文版&#xff0c;應當是大家上手 mmdetection 的第一參考&#xff0c;本文是記錄一些筆者在小白階段上手 mmdetection 時的一些心得&#xff0c;這些東西沒有人提&#xff0c;可能是大佬們覺得這些…

docker gpu報錯Error response from daemon: could not select device driver ““ with capabilities: [[gpu]]

Docker容器中使用Nvidia GPU報錯 docker: Error response from daemon: could not select device driver “” with capabilities: [[gpu]]. 問題出現 我們知道&#xff0c;想要在 docker19 及之后的版本中使用 nvidia gpu 已經不需要單獨安裝 nvidia-docker 了&#xff0c;這…

CUDA環境詳解

CUDA環境詳解 本文主要介紹 CUDA 環境&#xff0c;這一堆東西網上有很多博客介紹過了&#xff0c;我再來一篇:)&#xff0c;參考前輩們的文章&#xff0c;看能不能寫的更清楚一點。讀后仍有問題&#xff0c;歡迎留言交流。 CUDA APIs CUDA是由NVIDIA推出的通用并行計算架構&…

共享內存簡介及docker容器的shm設置與修改

共享內存簡介及docker容器的shm設置與修改 共享內存簡介 共享內存指 (shared memory)在多處理器的計算機系統中&#xff0c;可以被不同中央處理器&#xff08;CPU&#xff09;訪問的大容量內存。由于多個CPU需要快速訪問存儲器&#xff0c;這樣就要對存儲器進行緩存&#xff…

對Docker鏡像layer的理解

對Docker鏡像layer的理解 轉自&#xff1a;https://blog.csdn.net/u011069294/article/details/105583522 FROM python:3.6.1-alpine RUN pip install flask CMD [“python”,“app.py”] COPY app.py /app.py上面是一個Dockerfile的例子&#xff0c;每一行都會生成一個新的l…

ssh免密登錄配置方法及配置

ssh免密登錄配置方法及配置 直接上步驟&#xff0c;記我們本機為機器A&#xff0c;而機器B、機器C等是我們的服務器&#xff0c;我們要配置的是A到B、C等的 ssh 免密登錄。 1 在機器A上生成秘鑰對 ssh-keygen會得到輸出&#xff1a; Generating public/private rsa key pai…

機器學習系統:設計與實現 計算圖

機器學習系統:設計與實現 計算圖 轉自&#xff1a;https://openmlsys.github.io/chapter_computational_graph/index.html 在上一章節中&#xff0c;我們展示了用戶利用機器學習框架所編寫的程序。這些用戶程序包含了對于訓練數據&#xff0c;模型和訓練過程的定義。然而為了…

常見浮點數格式梳理

常見浮點數格式梳理 IEEE 754 標準 浮點數轉換網站&#xff1a;https://www.h-schmidt.net/FloatConverter/IEEE754.html IEEE二進制浮點數算術標準&#xff0c;為許多CPU與浮點運算器所采用。這個標準定義了表示浮點數的格式&#xff08;包括負零-0&#xff09;與反常值&am…