精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

二分搜索算法運用的框架套路

算法與數據結構 ? 來源:labuladong ? 作者:labuladong ? 2021-08-25 16:06 ? 次閱讀

我們前文 我作了首詩,保你閉著眼睛也能寫對二分查找 詳細介紹了二分搜索的細節問題,探討了「搜索一個元素」,「搜索左側邊界」,「搜索右側邊界」這三個情況,教你如何寫出正確無 bug 的二分搜索算法

但是前文總結的二分搜索代碼框架僅僅局限于「在有序數組中搜索指定元素」這個基本場景,具體的算法問題沒有這么直接,可能你都很難看出這個問題能夠用到二分搜索。

對于二分搜索算法在具體問題中的運用,前文 二分搜索的運用(一) 和前文 二分搜索的運用(二) 有過介紹,但是還沒有抽象出來一個具體的套路框架。

所以本文就來總結一套二分搜索算法運用的框架套路,幫你在遇到二分搜索算法相關的實際問題時,能夠有條理地思考分析,步步為營,寫出答案。

警告:本文略長略硬核,建議清醒時學習。

原始的二分搜索代碼

二分搜索的原型就是在「有序數組」中搜索一個元素target,返回該元素對應的索引

如果該元素不存在,那可以返回一個什么特殊值,這種細節問題只要微調算法實現就可實現。

還有一個重要的問題,如果「有序數組」中存在多個target元素,那么這些元素肯定挨在一起,這里就涉及到算法應該返回最左側的那個target元素的索引還是最右側的那個target元素的索引,也就是所謂的「搜索左側邊界」和「搜索右側邊界」,這個也可以通過微調算法的代碼來實現。

我們前文 二分搜索算法框架詳解 詳細探討了上述問題,對這塊還不清楚的讀者建議復習前文,已經搞清楚基本二分搜索算法的讀者可以繼續看下去。

在具體的算法問題中,常用到的是「搜索左側邊界」和「搜索右側邊界」這兩種場景,很少有讓你單獨「搜索一個元素」。

因為算法題一般都讓你求最值,比如前文 二分搜索的運用(一) 中說的例題讓你求吃香蕉的「最小速度」,讓你求輪船的「最低運載能力」,前文 二分搜索的運用(二) 講的題就更魔幻了,讓你使每個子數組之和的「最大值最小」。

求最值的過程,必然是搜索一個邊界的過程,所以后面我們就詳細分析一下這兩種搜索邊界的二分算法代碼。

「搜索左側邊界」的二分搜索算法的具體代碼實現如下:

// 搜索左側邊界int left_bound(int[] nums, int target) {

if (nums.length == 0) return -1;

int left = 0, right = nums.length;

while (left 《 right) {

int mid = left + (right - left) / 2;

if (nums[mid] == target) {

// 當找到 target 時,收縮右側邊界

right = mid;

} else if (nums[mid] 《 target) {

left = mid + 1;

} else if (nums[mid] 》 target) {

right = mid;

}

}

return left;

}

假設輸入的數組nums = [1,2,3,3,3,5,7],想搜索的元素target = 3,那么算法就會返回索引 2。

「搜索右側邊界」的二分搜索算法的具體代碼實現如下:

// 搜索右側邊界int right_bound(int[] nums, int target) {

if (nums.length == 0) return -1;

int left = 0, right = nums.length;

while (left 《 right) {

int mid = left + (right - left) / 2;

if (nums[mid] == target) {

// 當找到 target 時,收縮左側邊界

left = mid + 1;

} else if (nums[mid] 《 target) {

left = mid + 1;

} else if (nums[mid] 》 target) {

right = mid;

}

}

return left - 1;

}

輸入同上,那么算法就會返回索引 4:

好,上述內容都屬于復習,我想讀到這里的讀者應該都能理解。記住上述的圖像,所有能夠抽象出上述圖像的問題,都可以使用二分搜索解決。

二分搜索問題的泛化

什么問題可以運用二分搜索算法技巧?

首先,你要從題目中抽象出一個自變量x,一個關于x的函數f(x),以及一個目標值target。

同時,x, f(x), target還要滿足以下條件:

1、f(x)必須是在x上的單調函數(單調增單調減都可以)。

2、題目是讓你計算滿足約束條件f(x) == target時的x的值。

上述規則聽起來有點抽象,來舉個具體的例子:

給你一個升序排列的有序數組nums以及一個目標元素target,請你計算target在數組中的索引位置,如果有多個目標元素,返回最小的索引。

這就是「搜索左側邊界」這個基本題型,解法代碼之前都寫了,但這里面x, f(x), target分別是什么呢?

我們可以把數組中元素的索引認為是自變量x,函數關系f(x)就可以這樣設定:

// 函數 f(x) 是關于自變量 x 的單調遞增函數// 入參 nums 是不會改變的,所以可以忽略,不算自變量int f(int x, int[] nums) {

return nums[x];

}

其實這個函數f就是在訪問數組nums,因為題目給我們的數組nums是升序排列的,所以函數f(x)就是在x上單調遞增的函數。

最后,題目讓我們求什么來著?是不是讓我們計算元素target的最左側索引?

是不是就相當于在問我們「滿足f(x) == target的x的最小值是多少」?

如果遇到一個算法問題,能夠把它抽象成這幅圖,就可以對它運用二分搜索算法。

算法代碼如下:

// 函數 f 是關于自變量 x 的單調遞增函數int f(int x, int[] nums) {

return nums[x];

}

int left_bound(int[] nums, int target) {

if (nums.length == 0) return -1;

int left = 0, right = nums.length;

while (left 《 right) {

int mid = left + (right - left) / 2;

if (f(mid, nums) == target) {

// 當找到 target 時,收縮右側邊界

right = mid;

} else if (f(mid, nums) 《 target) {

left = mid + 1;

} else if (f(mid, nums) 》 target) {

right = mid;

}

}

return left;

}

這段代碼把之前的代碼微調了一下,把直接訪問nums[mid]套了一層函數f,其實就是多此一舉,但是,這樣能抽象出二分搜索思想在具體算法問題中的框架。

運用二分搜索的套路框架

想要運用二分搜索解決具體的算法問題,可以從以下代碼框架著手思考:

// 函數 f 是關于自變量 x 的單調函數int f(int x) {

// ...

}

// 主函數,在 f(x) == target 的約束下求 x 的最值int solution(int[] nums, int target) {

if (nums.length == 0) return -1;

// 問自己:自變量 x 的最小值是多少?

int left = ...;

// 問自己:自變量 x 的最大值是多少?

int right = ... + 1;

while (left 《 right) {

int mid = left + (right - left) / 2;

if (f(mid) == target) {

// 問自己:題目是求左邊界還是右邊界?

// ...

} else if (f(mid) 《 target) {

// 問自己:怎么讓 f(x) 大一點?

// ...

} else if (f(mid) 》 target) {

// 問自己:怎么讓 f(x) 小一點?

// ...

}

}

return left;

}

具體來說,想要用二分搜索算法解決問題,分為以下幾步:

1、確定x, f(x), target分別是什么,并寫出函數f的代碼。

2、找到x的取值范圍作為二分搜索的搜索區間,初始化left和right變量。

3、根據題目的要求,確定應該使用搜索左側還是搜索右側的二分搜索算法,寫出解法代碼。

下面用幾道例題來講解這個流程。

例題一、珂珂吃香蕉

珂珂每小時最多只能吃一堆香蕉,如果吃不完的話留到下一小時再吃;如果吃完了這一堆還有胃口,也只會等到下一小時才會吃下一堆。

他想在警衛回來之前吃完所有香蕉,讓我們確定吃香蕉的最小速度K。函數簽名如下:

int minEatingSpeed(int[] piles, int H);

那么,對于這道題,如何運用剛才總結的套路,寫出二分搜索解法代碼?

按步驟思考即可:

1、確定x, f(x), target分別是什么,并寫出函數f的代碼。

自變量x是什么呢?回憶之前的函數圖像,二分搜索的本質就是在搜索自變量。

所以,題目讓求什么,就把什么設為自變量,珂珂吃香蕉的速度就是自變量x。

那么,在x上單調的函數關系f(x)是什么?

顯然,吃香蕉的速度越快,吃完所有香蕉堆所需的時間就越少,速度和時間就是一個單調函數關系。

所以,f(x)函數就可以這樣定義:

若吃香蕉的速度為x根/小時,則需要f(x)小時吃完所有香蕉。

代碼實現如下:

// 定義:速度為 x 時,需要 f(x) 小時吃完所有香蕉// f(x) 隨著 x 的增加單調遞減int f(int[] piles, int x) {

int hours = 0;

for (int i = 0; i 《 piles.length; i++) {

hours += piles[i] / x;

if (piles[i] % x 》 0) {

hours++;

}

}

return hours;

}

target就很明顯了,吃香蕉的時間限制H自然就是target,是對f(x)返回值的最大約束。

2、找到x的取值范圍作為二分搜索的搜索區間,初始化left和right變量。

珂珂吃香蕉的速度最小是多少?多大是多少?

顯然,最小速度應該是 1,最大速度是piles數組中元素的最大值,因為每小時最多吃一堆香蕉,胃口再大也白搭嘛。

這里可以有兩種選擇,要么你用一個 for 循環去遍歷piles數組,計算最大值,要么你看題目給的約束,piles中的元素取值范圍是多少,然后給right初始化一個取值范圍之外的值。

我選擇第二種,題目說了1 《= piles[i] 《= 10^9,那么我就可以確定二分搜索的區間邊界:

public int minEatingSpeed(int[] piles, int H) {

int left = 1;

// 注意,right 是開區間,所以再加一

int right = 1000000000 + 1;

// ...

}

3、根據題目的要求,確定應該使用搜索左側還是搜索右側的二分搜索算法,寫出解法代碼。

現在我們確定了自變量x是吃香蕉的速度,f(x)是單調遞減的函數,target就是吃香蕉的時間限制H,題目要我們計算最小速度,也就是x要盡可能小:

這就是搜索左側邊界的二分搜索嘛,不過注意f(x)是單調遞減的,不要閉眼睛套框架,需要結合上圖進行思考,寫出代碼:

public int minEatingSpeed(int[] piles, int H) {

int left = 1;

int right = 1000000000 + 1;

while (left 《 right) {

int mid = left + (right - left) / 2;

if (f(piles, mid) == H) {

// 搜索左側邊界,則需要收縮右側邊界

right = mid;

} else if (f(piles, mid) 《 H) {

// 需要讓 f(x) 的返回值大一些

right = mid;

} else if (f(piles, mid) 》 H) {

// 需要讓 f(x) 的返回值小一些

left = mid + 1;

}

}

return left;

}

PS:關于mid是否需要 + 1 的問題,前文 二分搜索算法詳解 進行了詳細分析,這里不展開了。

至此,這道題就解決了,現在可以把多余的 if 分支合并一下,最終代碼如下:

public int minEatingSpeed(int[] piles, int H) {

int left = 1;

int right = 1000000000 + 1;

while (left 《 right) {

int mid = left + (right - left) / 2;

if (f(piles, mid) 《= H) {

right = mid;

} else {

left = mid + 1;

}

}

return left;

}

// f(x) 隨著 x 的增加單調遞減int f(int[] piles, int x) {

// 見上文

}

PS:我們代碼框架中多余的 if 分支主要是幫助理解的,寫出正確解法后建議合并多余的分支,可以提高算法運行的效率。

例題二、運送貨物

再看看力扣第 1011 題「在 D 天內送達包裹的能力」:

要在D天內按順序運輸完所有貨物,貨物不可分割,如何確定運輸的最小載重呢?

函數簽名如下:

int shipWithinDays(int[] weights, int days);

和上一道題一樣的,我們按照流程來就行:

1、確定x, f(x), target分別是什么,并寫出函數f的代碼。

題目問什么,什么就是自變量,也就是說船的運載能力就是自變量x。

運輸天數和運載能力成反比,所以可以讓f(x)計算x的運載能力下需要的運輸天數,那么f(x)是單調遞減的。

函數f(x)的實現如下:

// 定義:當運載能力為 x 時,需要 f(x) 天運完所有貨物// f(x) 隨著 x 的增加單調遞減int f(int[] weights, int x) {

int days = 0;

for (int i = 0; i 《 weights.length; ) {

// 盡可能多裝貨物

int cap = x;

while (i 《 weights.length) {

if (cap 《 weights[i]) break;

else cap -= weights[i];

i++;

}

days++;

}

return days;

}

對于這道題,target顯然就是運輸天數D,我們要在f(x) == D的約束下,算出船的最小載重。

2、找到x的取值范圍作為二分搜索的搜索區間,初始化left和right變量。

船的最小載重是多少?最大載重是多少?

顯然,船的最小載重應該是weights數組中元素的最大值,因為每次至少得裝一件貨物走,不能說裝不下嘛。

最大載重顯然就是weights數組所有元素之和,也就是一次把所有貨物都裝走。

這樣就確定了搜索區間[left, right):

public int shipWithinDays(int[] weights, int days) {

int left = 0;

// 注意,right 是開區間,所以額外加一

int right = 1;

for (int w : weights) {

left = Math.max(left, w);

right += w;

}

// ...

}

3、需要根據題目的要求,確定應該使用搜索左側還是搜索右側的二分搜索算法,寫出解法代碼。

現在我們確定了自變量x是船的載重能力,f(x)是單調遞減的函數,target就是運輸總天數限制D,題目要我們計算船的最小載重,也就是x要盡可能小:

這就是搜索左側邊界的二分搜索嘛,結合上圖就可寫出二分搜索代碼:

public int shipWithinDays(int[] weights, int days) {

int left = 0;

// 注意,right 是開區間,所以額外加一

int right = 1;

for (int w : weights) {

left = Math.max(left, w);

right += w;

}

while (left 《 right) {

int mid = left + (right - left) / 2;

if (f(weights, mid) == days) {

// 搜索左側邊界,則需要收縮右側邊界

right = mid;

} else if (f(weights, mid) 《 days) {

// 需要讓 f(x) 的返回值大一些

right = mid;

} else if (f(weights, mid) 》 days) {

// 需要讓 f(x) 的返回值小一些

left = mid + 1;

}

}

return left;

}

到這里,這道題的解法也寫出來了,我們合并一下多余的 if 分支,提高代碼運行速度,最終代碼如下:

public int shipWithinDays(int[] weights, int days) {

int left = 0;

int right = 1;

for (int w : weights) {

left = Math.max(left, w);

right += w;

}

while (left 《 right) {

int mid = left + (right - left) / 2;

if (f(weights, mid) 《= days) {

right = mid;

} else {

left = mid + 1;

}

}

return left;

}

int f(int[] weights, int x) {

// 見上文

}

本文就到這里,總結來說,如果發現題目中存在單調關系,就可以嘗試使用二分搜索的思路來解決。搞清楚單調性和二分搜索的種類,通過分析和畫圖,就能夠寫出最終的代碼。

責任編輯:haq

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 框架
    +關注

    關注

    0

    文章

    399

    瀏覽量

    17437
  • 搜索
    +關注

    關注

    0

    文章

    69

    瀏覽量

    16651
  • 代碼
    +關注

    關注

    30

    文章

    4753

    瀏覽量

    68369

原文標題:我寫了一個套路,助你隨心所欲運用二分搜索

文章出處:【微信號:TheAlgorithm,微信公眾號:算法與數據結構】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    HarmonyOS NEXT應用元服務開發Intents Kit(意圖框架服務)綜述

    過程即智慧分發。其中系統入口包括:小藝對話、小藝搜索、小藝建議等。 系統入口、意圖框架、鴻蒙生態的關系如下: 、Intents Kit優勢 利用HarmonyOS的大模型、多維設備感知等AI能力
    發表于 11-28 10:43

    AFE5818的幀時鐘FCLK不等于clkin,是它的二分頻,請問是為什么?

    AFE5818的幀時鐘FCLK不等于clkin,是它的二分頻,請問是為什么?
    發表于 11-18 08:34

    HarmonyOS NEXT應用元服務開發Intents Kit(意圖框架服務)本地搜索方案概述

    一、概述 本地搜索是在HarmonyOS歸一化搜索特性,開發者將應用/元服務內的功能和內容通過意圖框架共享到HarmonyOS,即可實現“一步搜索,內容直達”。
    發表于 11-06 10:59

    UHF RFID自適應射頻干擾對消技術

    。針對目前有源干擾對消技術存在的抑制效果和實時性較差的缺點在分析有源干擾對消原理的基礎上提出了基于改進Powell 搜索算法的自適應射頻干擾對消方案。設計了有源對消電路通過改進的Powell 最優值搜索算法實現電路控制參數的自適應調節使電路工作在最優的抑制狀態。測試結果表
    發表于 11-05 10:22 ?0次下載

    怎樣使用模擬電路實現信號的二分頻呢?

    請問怎樣使用模擬電路實現信號的二分頻呢?
    發表于 09-10 08:06

    電商搜索革命:大模型如何重塑購物體驗?

    自我介紹:京東零售搜推算法算法工程師,專注于大模型技術以及在 AI 助手搜推等領域的應用探索和實踐。在 AI 助手,NLP 和搜索領域有十多年研發實踐經驗,在 AI/NLP 領域申請超過 15
    的頭像 發表于 08-19 15:09 ?247次閱讀

    FPGA-5G通信算法的基本套路

    ? 一個完整的通信系統,是十龐大的,沒有幾百上千人,在短時間內是做不好的。本文僅僅針對5G NR中的基帶算法部分,做一個簡單梳理。 對于5G通信系統, 站在基站側的角度,那么下行方向的整個處理
    發表于 08-15 17:34

    中國AI長卷():框架立基

    從AI框架可以看到,更強的產業化能力,就是中國AI的底色
    的頭像 發表于 07-24 12:27 ?2533次閱讀
    中國AI長卷(<b class='flag-5'>二</b>):<b class='flag-5'>框架</b>立基

    AI算法/模型/框架/模型庫的含義、區別與聯系

    在人工智能(Artificial Intelligence,簡稱AI)的廣闊領域中,算法、模型、框架和模型庫是構成其技術生態的重要基石。它們各自承擔著不同的角色,但又緊密相連,共同推動著AI技術的不斷發展。以下是對這四者含義、區別與聯系的詳細闡述。
    的頭像 發表于 07-17 17:11 ?3160次閱讀

    如何用C語言實現高效查找(二分法)

    今天給分享一下使用C語言實現二分算法,主要包含以下幾部分內容:二分查找算法介紹二分查找算法使用場
    的頭像 發表于 06-04 08:04 ?992次閱讀
    如何用C語言實現高效查找(<b class='flag-5'>二分</b>法)

    揭秘谷歌搜索算法工作原理,與官方聲明存在矛盾

    有著十多年搜索引擎優化經驗的蘭德·菲什金,近日透露他收到一份長達2500頁的文件,據稱這是對谷歌搜索算法工作原理的真實揭示,而非谷歌官方所聲稱的那樣。
    的頭像 發表于 05-29 16:00 ?578次閱讀

    二分頻電路總述 二分頻電路的功能實現

    分頻就是用同一個時鐘信號通過一定的電路結構轉變成不同頻率的時鐘信號。
    的頭像 發表于 03-06 17:13 ?2129次閱讀
    <b class='flag-5'>二分</b>頻電路總述 <b class='flag-5'>二分</b>頻電路的功能實現

    數據語料庫、算法框架和算力芯片在AI大模型中的作用和影響

    數據語料庫、算法框架和算力芯片的確是影響AI大模型發展的三大重要因素。
    的頭像 發表于 03-01 09:42 ?997次閱讀

    Redis官方搜索引擎來了,性能炸裂!

    RediSearch 是一個 Redis 模塊,為 Redis 提供查詢、級索引和全文搜索功能。
    的頭像 發表于 02-21 10:01 ?2218次閱讀
    Redis官方<b class='flag-5'>搜索</b>引擎來了,性能炸裂!

    語音數據集在智能語音搜索中的應用與挑戰

    揮著重要作用,為系統提供了豐富的語音數據和信息,提高了搜索的準確性和效率。本文將詳細介紹語音數據集在智能語音搜索中的應用、面臨的挑戰以及未來的發展趨勢。 、語音數據集在智能語音搜索
    的頭像 發表于 01-18 15:09 ?519次閱讀