【LeetCode 3440. 重新安排會議得到最多空余時間 II】解析

目錄

  • LeetCode中國站原文
  • 原始題目
    • 題目描述
    • 示例1:
    • 示例2:
    • 示例3:
    • 示例4:
  • 講解
    • 1. 新規則,新挑戰
    • 2. 收益從何而來?兩種可能性的誕生
    • 3. 我們的終極策略
    • 4. 當策略被壓縮到極致
      • 第一次遍歷:從左到右(向左看)
      • 第二次遍歷:從右到左(向右看)
      • 等價的但是我自己嘗試的代碼
    • 5. 結論

LeetCode中國站原文

https://leetcode.cn/problems/reschedule-meetings-for-maximum-free-time-ii/

原始題目

題目描述

給你一個整數 eventTime 表示一個活動的總時長,這個活動開始于 t = 0 ,結束于 t = eventTime

同時給你兩個長度為 n 的整數數組 startTimeendTime 。它們表示這次活動中 n 個時間 沒有重疊 的會議,其中第 i 個會議的時間為 [startTime[i], endTime[i]]

你可以重新安排 至多 一個會議,安排的規則是將會議時間平移,且保持原來的 會議時長 ,你的目的是移動會議后 最大化 相鄰兩個會議之間的 最長 連續空余時間。

請你返回重新安排會議以后,可以得到的 最大 空余時間。

注意,會議 不能 安排到整個活動的時間以外,且會議之間需要保持互不重疊。

注意:重新安排會議以后,會議之間的順序可以發生改變。

示例1:

輸入:eventTime = 5, startTime = [1,3], endTime = [2,5]
輸出:2
解釋:將 [1, 2] 的會議安排到 [2, 3] ,得到空余時間 [0, 2] 。

示例2:

輸入:eventTime = 10, startTime = [0,7,9], endTime = [1,8,10]
輸出:7
解釋:將 [0, 1] 的會議安排到 [8, 9] ,得到空余時間 [0, 7] 。

示例3:

輸入:eventTime = 10, startTime = [0,3,7,9], endTime = [1,4,8,10]
輸出:6
解釋:將 [3, 4] 的會議安排到 [8, 9] ,得到空余時間 [1, 7] 

示例4:

輸入:eventTime = 5, startTime = [0,1,2,3,4], endTime = [1,2,3,4,5]
輸出:0
解釋:活動中的所有時間都被會議安排滿了。

講解

如果你看過我們之前對 I 系列的討論(【LeetCode 3439. 重新安排會議得到最多空余時間 I】解析),你會發現這道題有一個關鍵性的變化:會議的相對順序可以改變了! 這個變化讓上一題精妙的“滑動窗口”解法瞬間失效,迫使我們必須尋找新的出路。

這篇文章將帶你從零開始,理清思路,看穿問題的本質,并最終理解一個堪稱“神仙”級別的解法。

1. 新規則,新挑戰

我們先來回顧一下規則:

  • 能力:可以平移最多 1 個會議。
  • 變化:平移后,會議的順序可以任意改變
  • 目標:創造出一個盡可能長的連續空閑時間

“順序可以改變”是這道題的“游戲規則改變者”。這意味著,我們可以從所有會議中,任意挑選一個“倒霉蛋”會議,把它塞進任意一個能容納它的空隙里。

那么,我們的決策過程就變成了:

  1. 選哪個會議去移動?
  2. 把它移動到哪里去?

我們的目標是讓這兩個決策的組合,能產生最大的收益。

2. 收益從何而來?兩種可能性的誕生

要獲得最長的空閑時間,最終的結果只可能從兩種情況中產生:

情況一:不移動任何會議

如果我們選擇不動,那么最長的空閑時間就是所有原始“空隙”中,最長的那一個。這是我們的保底收益。

情況一:不移動
原始狀態
最長空閑 = 空隙2的長度
會議1
空隙1
空隙2
最長
會議2
空隙3

情況二:移動 1 個會議

這是問題的精髓所在。當我們決定移動一個會議,比如「會議i」,會發生什么?

  • 「會議i」原本占用的空間 [startTime[i], endTime[i]] 被釋放了。
  • 這個被釋放的空間,會和它前后的空隙(「空隙i」和「空隙i+1」)合并,形成一個巨大的新空隙!
被移動的會議i
移動后
移動前
會議i
被塞到其他地方
新產生的巨大空隙
長度 = 空隙i + 會議i + 空隙i+1
會議i-1
會議i+1
空隙i
會議i-1
會議i
空隙i+1
會議i+1

這個新產生的巨大空隙,其長度為 startTime[i+1] - endTime[i-1]

但是,這個美好的情況有一個前提條件:被我們移動的「會議i」必須有地方可去!也就是說,它的時長 (endTime[i] - startTime[i]) 必須小于或等于我們場上存在的某一個原始空隙的長度。

3. 我們的終極策略

結合以上分析,一個清晰的,雖然可能比較慢的策略誕生了:

  1. 預處理

    • 計算出所有會議各自的時長。
    • 計算出所有原始空隙的長度,并找到其中的最大值 max_original_gap
  2. 遍歷與決策

    • 遍歷每一個會議 i (從 0 到 n-1),假裝要移動它。
    • 計算收益:如果移動會議 i,能創造出的新空隙長度為 startTime[i+1] - endTime[i-1]
    • 檢查條件:會議 i 的時長,是否 <= max_original_gap
      • 如果條件滿足:說明移動是可行的。我們就在這個“收益”和我們已知的“最大收益”之間取一個更大的。
      • 如果條件不滿足:說明會議 i 太長了,根本沒地方放。我們無法移動它,只能放棄這個方案。
  3. 綜合比較:最后,別忘了把“移動會議”能產生的最大收益,和“不移動會議”(即 max_original_gap)本身再比較一次,取最終的勝利者。

這個 O(N) 的預處理 + O(N) 的遍歷決策,總時間復雜度為 O(N),空間復雜度也是 O(N)。這是一個非常可靠且正確的解法。

4. 當策略被壓縮到極致

現在,讓我們來欣賞一下那段極其精煉的、只用兩個 for 循環就解決問題的代碼。它沒有顯式地預處理和存儲,而是將所有計算都“動態地”揉進了循環里。

這個解法的核心思想是:通過一次從左到右和一次從右到左的遍歷,來模擬“向前看”和“向后看”,從而解決移動條件檢查的問題。

第一次遍歷:從左到右(向左看)

這次遍歷,當我們站在會議 i 的位置時,我們只知道在它左邊發生的所有事。

// mx 在這里代表“截至目前,我左邊見過的最大空隙是多少”
int mx = 0;
for (int i = 0; i < n; i++) {int len = endTime[i] - startTime[i]; // 會議i的時長// 檢查移動條件:會議i能放進左邊的最大空隙嗎?if (len <= mx) {// 如果可以,就計算移動它能產生的收益,并更新全局最大值 resint potential_gap = (i == n-1 ? eventTime : startTime[i+1]) - (i == 0 ? 0 : endTime[i-1]);res = Math.max(res, potential_gap);}// 更新狀態,為下一次循環做準備// 1. 讓 mx 知道剛剛路過的這個空隙int current_gap = startTime[i] - (i == 0 ? 0 : endTime[i-1]);mx = Math.max(mx, current_gap);// 2. 同時,我們也要考慮不移動的情況,即原始空隙本身也是備選答案res = Math.max(res, current_gap);
}

注意:為了便于理解,我稍微修改了代碼邏輯,使其更符合直覺。官方題解的寫法更為精煉,但核心思想一致。

這次遍歷的盲點在于,它不知道會議 i 右邊是否有一個超級大的空隙可以容納它。

第二次遍歷:從右到左(向右看)

為了彌補這個盲點,我們需要一次完全對稱的、從右到左的遍歷。

// mx 重置,代表“截至目前,我右邊見過的最大空隙是多少”
mx = 0; 
for (int i = n - 1; i >= 0; i--) {int len = endTime[i] - startTime[i];// 檢查移動條件:會議i能放進右邊的最大空隙嗎?if (len <= mx) {int potential_gap = (i == n-1 ? eventTime : startTime[i+1]) - (i == 0 ? 0 : endTime[i-1]);res = Math.max(res, potential_gap);}// 更新狀態int current_gap = (i == n-1 ? eventTime : startTime[i+1]) - endTime[i];mx = Math.max(mx, current_gap);res = Math.max(res, current_gap);
}```### 最終代碼呈現將上述思想融合,并采用官方題解的精煉寫法,就得到了最終的答案:```java
class Solution {public int maxFreeTime(int eventTime, int[] startTime, int[] endTime) {int n = startTime.length;int res = 0;// 第一次遍歷:從左到右int mx_left_gap = 0; // 記錄左側的最大空隙int last_end = 0;for (int i = 0; i < n; i++) {int len = endTime[i] - startTime[i];int potential_new_gap = (i == n - 1 ? eventTime : startTime[i + 1]) - last_end;if (len <= mx_left_gap) {res = Math.max(res, potential_new_gap);} else {// 如果不能移動,我們能獲得的最大空隙,// 就是把當前會議的時長從 potential_new_gap 中挖掉res = Math.max(res, potential_new_gap - len);}mx_left_gap = Math.max(mx_left_gap, startTime[i] - last_end);last_end = endTime[i];}// 第二次遍歷:從右到左int mx_right_gap = 0; // 記錄右側的最大空隙int last_start = eventTime;for (int i = n - 1; i >= 0; i--) {int len = endTime[i] - startTime[i];int potential_new_gap = last_start - (i == 0 ? 0 : endTime[i - 1]);if (len <= mx_right_gap) {res = Math.max(res, potential_new_gap);} else {res = Math.max(res, potential_new_gap - len);}mx_right_gap = Math.max(mx_right_gap, last_start - startTime[i]);last_start = startTime[i];}return res;}
}

等價的但是我自己嘗試的代碼

由于本人的算法知識并不算非常強,在一開始寫左右分別看的代碼出現了很多問題,這里給出一個可能冗余但是能看懂邏輯的相同的“向左看”+向右看的代碼

class Solution {// time = O(n), space = O(n)public int maxFreeTime(int eventTime, int[] startTime, int[] endTime) {// 當前會議的長度,從startTime或者endTime任意一個數組的長度取值都可以。int meetingCount = startTime.length;// 記錄最終的答案int ans = 0;// 臨時的最大值int tempMaxGap = 0;// 接下來要從左向右看。// 記錄下來走過來最長的路int maxGap = 0;for(int i=0; i< meetingCount; i++){// 是否是第一個會議boolean isFirstMeeting = i==0;// 是否是最后一個會議boolean isLastMeeting = i == meetingCount - 1;// 左邊的空隙int leftGap = isFirstMeeting ? startTime[0] : startTime[i] - endTime[i-1];// 本次循環中會議的持續時間int meetingDuration = endTime[i] - startTime[i];if(maxGap >= meetingDuration){// 如果當前會議的時長比走過的最大間隔還要大,或者等于最大時長,代表可以插入// 上一個會議的結束點int lastMeetingEnd = isFirstMeeting ? 0 : endTime[i-1];// 下一個會議的開始時間int nextMeetingStart = isLastMeeting ? eventTime : startTime[i+1];// 新的巨大空隙tempMaxGap = nextMeetingStart - lastMeetingEnd;}else{// 右邊的空隙int rightGap = isLastMeeting ? eventTime - endTime[i]: startTime[i+1] - endTime[i];// 新的空隙(為左邊的空隙+右邊的空隙)tempMaxGap = leftGap + rightGap;}ans = Math.max(ans, tempMaxGap);// 更新記憶maxGap = Math.max(maxGap, leftGap);}// 更新ansans = Math.max(ans, maxGap);// 重置記憶maxGapmaxGap = 0;for(int i=meetingCount - 1; i>=0; i--){// 是否是第一個會議boolean isFirstMeeting = i==0;// 是否是最后一個會議boolean isLastMeeting = i == meetingCount - 1;// 右邊的空隙int rightGap = isLastMeeting ? eventTime - endTime[i]: startTime[i+1] - endTime[i];// 本次循環中會議的持續時間int meetingDuration = endTime[i] - startTime[i];if(maxGap >= meetingDuration){// 如果當前會議的時長比走過的最大間隔還要大,或者等于最大時長,代表可以插入// 上一個會議的結束點int lastMeetingEnd = isFirstMeeting ? 0 : endTime[i-1];// 下一個會議的開始時間int nextMeetingStart = isLastMeeting ? eventTime : startTime[i+1];// 新的巨大空隙tempMaxGap = nextMeetingStart - lastMeetingEnd;}else{// 左邊的空隙int leftGap = isFirstMeeting ? startTime[0] : startTime[i] - endTime[i-1];// 新的空隙(為左邊的空隙+右邊的空隙)tempMaxGap = leftGap + rightGap;}ans = Math.max(ans, tempMaxGap);// 更新記憶maxGap = Math.max(maxGap, rightGap );}return ans;}
}

5. 結論

這道題是對問題分析和抽象能力的絕佳考驗。它告訴我們:

  1. 明確規則變化:”相對順序可變“是解題的鑰匙。
  2. 分解問題:將復雜問題分解為“移動”和“不移動”兩種獨立的、可分析的情況。
  3. 優化實現:思考如何用更少的空間和時間復雜度來實現策略。兩次遍歷的解法正是這種極致優化的產物。

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

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

相關文章

C++卸載了會影響電腦正常使用嗎?解析C++運行庫的作用與卸載后果

卸載C運行庫可能導致常用軟件癱瘓&#xff01;這些不起眼的組件為Photoshop、游戲等提供關鍵支持&#xff0c;多個版本共存是正常現象&#xff0c;隨意清理會引發程序報錯甚至閃退。一、前言&#xff1a;C不是“編程語言”那么簡單很多用戶在電腦中看到“Microsoft Visual C Re…

前端vue對接海康攝像頭流程

1、拆包攝像頭、插電源2、下載SADP&#xff08;設備網絡搜索&#xff09;&#xff0c;連接設備&#xff0c;獲取ip地址 下載地址&#xff1a;https://partners.hikvision.com/tools 找到自己的設備類型DS開頭3、攝像頭鏈接wifi、網線 登錄設備預覽配置網頁-配置網絡-可預覽等 4…

org.casic.javafx.control.PaginationPicker用法

org.casic.javafx.control.PaginationPicker 是 CASIC&#xff08;或某位作者&#xff09;基于 JavaFX 自制的分頁控件&#xff0c;功能比官方 Pagination 更完整&#xff0c;支持&#xff1a;首頁 / 上一頁 / 下一頁 / 尾頁按鈕頁碼快速跳轉每頁條數自定義總數據量、當前頁碼、…

下載 | Win10 2021精簡版,預裝應用極少!(7月更新、Win 10 IoT LTSC 2021版、適合老電腦安裝)

? 【資源A047】Win10 IoT LTSC 2021精簡版 &#x1f536;Windows 10 IoT 企業版 LTSC 2021 正式版更新中。LTSC是長期服務渠道版本&#xff0c;網友俗稱“老壇酸菜版”&#xff0c;相當于精簡版Win10&#xff0c;精簡了很多預裝應用&#xff0c;同時更新頻率也更低&#xff0c…

Web3:Foundry使用指南

Foundry目錄1. 前言2. 什么是Foundry3. 安裝與環境配置1. 安裝工具2. 重新加載 .bashrc3. 檢查環境變量 PATH4. 手動運行 foundryup4. Foundry的基本使用1.創建一個新的Foundry項目2. 編寫智能合約3. 編譯智能合約4. foundry.toml 主要作用5.部署智能合約5. Cli參考1. forge2. …

uniapp+unipush推送配置

APP推送記錄 一、使用框架 Uniappunipush推送插件 二、需要提前準備的 1.準備自有證書 可以用這個網站—香蕉云編&#xff08;用于安卓 ios證書生成&#xff09;https://www.yunedit.com/update/androidzhengshu/list 安卓證書生成后&#xff0c;下載證書&#xff0c;除了原文…

CentOS系統哪些版本?分別適用于那些業務或網站類型?

CentOS&#xff08;Community ENTerprise Operating System&#xff09;是一款開源的企業級 Linux 操作系統&#xff0c;因其穩定性、安全性和長期支持周期&#xff0c;廣泛應用于服務器環境。以下是 CentOS 的主要版本及其適用場景的詳細介紹。1. CentOS 主要版本CentOS 的版本…

【前端】【Iconify圖標庫】【vben3】createIconifyIcon 實現圖標組件的自動封裝

&#x1f9e9; Vue 圖標管理全攻略&#xff1a;Iconify createIconifyIcon 封裝最佳實踐 在前端項目中&#xff0c;圖標無處不在。按鈕需要圖標&#xff0c;導航需要圖標&#xff0c;提示信息也少不了圖標。如何優雅、高效地使用圖標&#xff0c;是每個中大型 Vue 項目不可回…

數據可視化全流程設計指南

一、需求定義階段1. 明確核心目標回答關鍵問題&#xff1a;2. 確定數據特性import pandas as pd data pd.read_csv(your_data.csv) print(f""" 數據概覽: - 維度: {data.shape[1]}列 {data.shape[0]}行 - 類型分布: {data.dtypes.value_counts()} - 缺失值: …

Llama系列:Llama1, Llama2,Llama3內容概述

前言 參考視頻&#xff1a;大模型修煉之道(三): Llama系列講解 Llama1&#xff0c;Llama2, Llama3_嗶哩嗶哩_bilibili 本博客是基于視頻的學習筆記&#xff0c;以及相關知識點的擴充 Llama1 1. 動機 使用完全開源數據&#xff0c;性能媲美GPT3研究開源&#xff0c;禁止商用…

Docker 搭建本地Harbor私有鏡像倉庫

Docker 搭建本地Harbor私有鏡像倉庫 一、Harbor 核心價值與企業級特性解析 在容器化技術普及的背景下&#xff0c;鏡像倉庫作為容器生命周期的核心組件&#xff0c;其可靠性直接影響開發效率與生產穩定性。Docker 官方的 Registry 雖能實現基礎鏡像存儲&#xff0c;但存在明顯短…

AI 助力:如何批量提取 Word 表格字段并導出至 Excel

在日常辦公中&#xff0c;我們經常需要處理大量的 Word 文檔中的表格數據&#xff0c;如學生登記表、客戶信息表、報名表等。然而這些表格往往格式各異、字段命名不統一&#xff08;如“姓名”“名字”“Name”&#xff09;&#xff0c;甚至含有合并單元格或多余空白行&#xf…

在 Azure Linux 上安裝 RustFS

本文分享在 Azure Linux 上安裝并使用對象存儲 RustFS 的過程。 關于 RustFS RustFS 是一款用 Rust 語言編寫的分布式存儲系統&#xff0c;兼容 S3 協議&#xff0c;是 MinIO 的國產化平替。詳情可以前往 RustFS 官網。目前&#xff0c;RustFS 支持二進制、Docker 安裝方式&am…

實現在線預覽pdf功能,后臺下載PDF

<!-- PDF預覽模態框 --><n-modalv-model:show"pdfModalVisible"title"投訴統計報告預覽":closable"false":mask-closable"false"positive-click"closePdfModal"positive-text"關閉":width"900"…

華為VS格行VS中興VS波導隨身WIFI6怎么選?流量卡OR隨身WIFI,長期使用到底誰更香?

在移動互聯時代&#xff0c;流量焦慮成為現代人的通病。面對"辦流量卡還是隨身WiFi"的抉擇&#xff0c;許多人陷入兩難。本文從實際需求出發&#xff0c;用數據和場景幫你精準決策&#xff0c;尤其這五類人群建議直接選擇正規隨身WiFi。一、這五類人&#xff0c;隨身…

AI網絡搜索

作為AI應用程序開發人員在了解函數調用&#xff08;Function Calling&#xff09;特性調用本地函數時可能注意到列表型參數tools中每一個元素都攜帶有一個type值。而在大多數函數調用示例程序中&#xff0c;這個type值一直被設定為“function”&#xff0c;這意味著它還可能存在…

39.Sentinel微服務流量控制組件

雪崩問題 微服務調用鏈路中某個服務故障,引起整個鏈路中的所有微服務都不可用。 解決方案 1.超時處理:設置一個超時時間,請求超過一定時間沒有響應就返回錯誤信息,不會無休止的等待。(只能起到緩解作用,并不能從根本上解決問題) 2.艙壁模式:限定每個業務能使用的線程…

基于hadoop的競賽網站日志數據分析與可視化(下)

【基于hadoop的競賽網站日志數據分析與可視化&#xff08;上&#xff09;】講解了如何用hadoop對數據進行初步處理&#xff0c;本篇主要講解用python對結果數據進行可視化分析。 ------------------------------------------------------------------------------------------…

Python爬蟲打怪升級:數據獲取疑難全解析

一、引言 **??? 在大數據時代,數據就是價值的源泉。而 Python 爬蟲,作為數據獲取的得力助手,憑借 Python 簡潔的語法和豐富強大的庫,在眾多領域發揮著重要作用。無論是電商領域的價格監測、市場調研中的數據收集,還是學術研究里的文獻獲取,Python 爬蟲都能大顯身手。…

基于R語言的極值統計學及其在相關領域中的實踐技術應用

極值統計學就是專門研究自然界和人類社會中很少發生&#xff0c;然而發生之后有著巨大影響的極端現象的統計建模及分析方法&#xff1b;在水文、氣象、環境、生態、保險和金融等領域都有著廣泛的應用。一&#xff1a;獨立假設下的極值統計建模 1.廣義極值模型. 2.極小值的處理.…