LeetCode 1340. 跳躍游戲 V(困難)

題目描述

給你一個整數數組?arr?和一個整數?d?。每一步你可以從下標?i?跳到:

  • i + x?,其中?i + x < arr.length?且?0 < x <= d?。
  • i - x?,其中?i - x >= 0?且?0 < x <= d?。

除此以外,你從下標?i?跳到下標?j?需要滿足:arr[i] > arr[j]?且?arr[i] > arr[k]?,其中下標?k?是所有?i?到?j?之間的數字(更正式的,min(i, j) < k < max(i, j))。

你可以選擇數組的任意下標開始跳躍。請你返回你?最多?可以訪問多少個下標。

請注意,任何時刻你都不能跳到數組的外面。

示例 1:

輸入:arr = [6,4,14,6,8,13,9,7,10,6,12], d = 2
輸出:4
解釋:你可以從下標 10 出發,然后如上圖依次經過 10 --> 8 --> 6 --> 7 。
注意,如果你從下標 6 開始,你只能跳到下標 7 處。你不能跳到下標 5 處因為 13 > 9 。你也不能跳到下標 4 處,因為下標 5 在下標 4 和 6 之間且 13 > 9 。
類似的,你不能從下標 3 處跳到下標 2 或者下標 1 處。

示例 2:

輸入:arr = [3,3,3,3,3], d = 3
輸出:1
解釋:你可以從任意下標處開始且你永遠無法跳到任何其他坐標。

示例 3:

輸入:arr = [7,6,5,4,3,2,1], d = 1
輸出:7
解釋:從下標 0 處開始,你可以按照數值從大到小,訪問所有的下標。

示例 4:

輸入:arr = [7,1,7,1,7,1], d = 2
輸出:2

示例 5:

輸入:arr = [66], d = 1
輸出:1

提示:

  • 1 <= arr.length <= 1000
  • 1 <= arr[i] <= 10^5
  • 1 <= d <= arr.length

問題分析

這道題是跳躍游戲系列的第五題,有以下特點:

  • 跳躍規則:
    • 可以向左或向右跳躍,跳躍距離范圍是?[1, d]
    • 只能從高處往低處跳(arr[i] >?arr[j])
    • 跳躍路徑上的所有點都必須比起點低(arr[i] > arr[k],對于所有 i 到 j 之間的 k)
  • 目標:找出從任意起點出發,最多可以訪問多少個下標。
  • 關鍵點:
    • 可以從任意下標開始
    • 需要計算的是最大訪問點數,而不是是否能到達某個特定目標

這個問題適合使用動態規劃來解決,因為跳躍決策具有重疊子問題的性質。同時,由于跳躍的方向性(只能從高處跳到低處),我們可以按高度排序來確定解決問題的順序。


解題思路

動態規劃 + 記憶化搜索

我們可以定義?dp[i]?表示從下標?i?開始跳躍,最多可以訪問的下標數量(包括起點自身)。

遞推關系如下:

  • 對于每個下標?i,我們嘗試向左或向右跳躍距離?x(其中?1 <= x <= d)
  • 如果可以跳到下標?j,那么?dp[i] = max(dp[i],?1 + dp[j])

由于跳躍方向是從高到低,我們需要確保先計算出較低位置的 dp 值,再計算較高位置的 dp?值。一種方法是使用記憶化搜索(也稱為自頂向下的動態規劃)。

算法步驟

  • 創建一個?dp?數組,dp[i]?表示從下標?i?開始最多可以訪問的下標數量
  • 將所有?dp[i]?初始化為 1(至少可以訪問自身)
  • 使用記憶化搜索,對每個下標?i:
    • 嘗試向左或向右跳躍距離?x(1 <= x <=?d)
    • 檢查跳躍條件:目標位置在數組范圍內,且路徑上所有點都比起點低
    • 如果可以跳到下標?j,則?dp[i] = max(dp[i], 1 + dp[j])
  • 返回所有?dp[i]?中的最大值

算法過程

以示例1為例:arr = [6,4,14,6,8,13,9,7,10,6,12], d = 2

讓我們跟蹤從下標10開始的DFS過程(這是最優起點):

  • 初始化:
    • dp[10] = 1(至少可以訪問自身)
    • 當前下標:10,對應值:12
  • 嘗試向左跳:
    • 檢查下標10-1=9:arr[10] > arr[9]?(12 > 6) ?
      • 遞歸計算?dp[9]
      • dp[9] = 1(至少可以訪問自身)
      • 由于沒有可跳的地方,dp[9] =?1
    • 檢查下標10-2=8:arr[10] > arr[8]?(12 > 10)??
      • 遞歸計算?dp[8]
      • dp[8] = 1
      • 檢查下標8-1=7:arr[8] > arr[7]?(10 > 7)??
        • 遞歸計算?dp[7]
        • dp[7] = 1
        • 檢查下標7-1=6:arr[7] > arr[6]?(7?< 9) ? 不能跳
        • 檢查下標7-2=5:超出范圍(d=2)
      • 所以?dp[7] = 1
      • 更新?dp[8] = 1?+ dp[7] =?2
      • 檢查下標8-2=6:arr[8] >?arr[6]?(10 > 9)??
        • 已計算?dp[6]?= 1(沒有可跳的地方)
    • 更新?dp[8] = max(2, 1+1) = 2
    • 更新?dp[10] = max(1, 1+dp[8]) = 1+2 =?3
  • 最終路徑:
    • 下標10 -> 下標8 -> 下標7 -> 可能的終點(無法繼續跳躍)
    • 或者 下標10 -> 下標8 -> 下標6 -> 可能的終點
    • 最多訪問4個下標(包括起點10)

事實上,完整的最優路徑是:10 ->?8 -> 6 -> 7,總共訪問4個下標。


詳細代碼實現

Java 實現

class Solution {private int[] dp;private int[] arr;private int d;public int maxJumps(int[] arr, int d) {int n = arr.length;this.arr = arr;this.d = d;this.dp = new int[n];// 初始化dp數組,每個位置至少可以訪問自身Arrays.fill(dp, -1);int maxVisited = 0;for (int i = 0; i < n; i++) {maxVisited = Math.max(maxVisited, dfs(i));}return maxVisited;}private int dfs(int i) {// 如果已經計算過,直接返回if (dp[i] != -1) {return dp[i];}// 至少可以訪問自身dp[i] = 1;// 嘗試向右跳for (int j = i + 1; j <= Math.min(i + d, arr.length - 1); j++) {// 檢查跳躍條件if (arr[i] <= arr[j]) {break;}// 檢查路徑上的所有點boolean canJump = true;for (int k = i + 1; k < j; k++) {if (arr[k] >= arr[i]) {canJump = false;break;}}if (canJump) {dp[i] = Math.max(dp[i], 1 + dfs(j));}}// 嘗試向左跳for (int j = i - 1; j >= Math.max(i - d, 0); j--) {// 檢查跳躍條件if (arr[i] <= arr[j]) {break;}// 檢查路徑上的所有點boolean canJump = true;for (int k = i - 1; k > j; k--) {if (arr[k] >= arr[i]) {canJump = false;break;}}if (canJump) {dp[i] = Math.max(dp[i], 1 + dfs(j));}}return dp[i];}
}

C# 實現

public class Solution {private int[] dp;private int[] arr;private int d;public int MaxJumps(int[] arr, int d) {int n = arr.Length;this.arr = arr;this.d = d;this.dp = new int[n];// 初始化dp數組for (int i = 0; i < n; i++) {dp[i] = -1;}int maxVisited = 0;for (int i = 0; i < n; i++) {maxVisited = Math.Max(maxVisited, Dfs(i));}return maxVisited;}private int Dfs(int i) {// 如果已經計算過,直接返回if (dp[i] != -1) {return dp[i];}// 至少可以訪問自身dp[i] = 1;// 嘗試向右跳for (int j = i + 1; j <= Math.Min(i + d, arr.Length - 1); j++) {// 檢查跳躍條件if (arr[i] <= arr[j]) {break;}// 檢查路徑上的所有點bool canJump = true;for (int k = i + 1; k < j; k++) {if (arr[k] >= arr[i]) {canJump = false;break;}}if (canJump) {dp[i] = Math.Max(dp[i], 1 + Dfs(j));}}// 嘗試向左跳for (int j = i - 1; j >= Math.Max(i - d, 0); j--) {// 檢查跳躍條件if (arr[i] <= arr[j]) {break;}// 檢查路徑上的所有點bool canJump = true;for (int k = i - 1; k > j; k--) {if (arr[k] >= arr[i]) {canJump = false;break;}}if (canJump) {dp[i] = Math.Max(dp[i], 1 + Dfs(j));}}return dp[i];}
}

復雜度分析

  • 時間復雜度:O(n2),其中n是數組的長度。在最壞情況下,對于每個位置,我們需要檢查最多2d個可能的跳躍目標,總時間復雜度為O(n * d)。由于d最大可達n,所以最壞情況下時間復雜度是O(n2)。
  • 空間復雜度:O(n),主要用于存儲dp數組和遞歸調用棧。

優化:單調棧方法

上面的實現中,檢查路徑上的所有點是否都比起點低的時間復雜度是?O(d),我們可以使用單調棧來優化這一過程,降低時間復雜度。

Java 實現

class Solution {public int maxJumps(int[] arr, int d) {int n = arr.length;int[] dp = new int[n];Arrays.fill(dp, -1);// 將下標按照高度排序,從低到高處理Integer[] indices = new Integer[n];for (int i = 0; i < n; i++) {indices[i] = i;}Arrays.sort(indices, (a, b) -> arr[a] - arr[b]);int maxVisited = 0;for (int idx : indices) {maxVisited = Math.max(maxVisited, dfs(arr, d, idx, dp));}return maxVisited;}private int dfs(int[] arr, int d, int i, int[] dp) {if (dp[i] != -1) {return dp[i];}dp[i] = 1; // 至少可以訪問自身// 向右跳for (int j = i + 1; j <= Math.min(i + d, arr.length - 1); j++) {if (arr[i] > arr[j]) {dp[i] = Math.max(dp[i], 1 + dfs(arr, d, j, dp));}// 如果遇到更高或相等的點,則無法繼續向右if (arr[j] >= arr[i]) {break;}}// 向左跳for (int j = i - 1; j >= Math.max(i - d, 0); j--) {if (arr[i] > arr[j]) {dp[i] = Math.max(dp[i], 1 + dfs(arr, d, j, dp));}// 如果遇到更高或相等的點,則無法繼續向左if (arr[j] >= arr[i]) {break;}}return dp[i];}
}

C#實現

public class Solution {public int MaxJumps(int[] arr, int d) {int n = arr.Length;int[] dp = new int[n];// 初始化dp數組,所有值設為-1表示未計算for (int i = 0; i < n; i++) {dp[i] = -1;}// 將下標按照高度排序,從低到高處理int[] indices = new int[n];for (int i = 0; i < n; i++) {indices[i] = i;}Array.Sort(indices, (a, b) => arr[a] - arr[b]);int maxVisited = 0;foreach (int idx in indices) {maxVisited = Math.Max(maxVisited, Dfs(arr, d, idx, dp));}return maxVisited;}private int Dfs(int[] arr, int d, int i, int[] dp) {// 如果已經計算過,直接返回if (dp[i] != -1) {return dp[i];}// 至少可以訪問自身dp[i] = 1;// 向右跳for (int j = i + 1; j <= Math.Min(i + d, arr.Length - 1); j++) {if (arr[i] > arr[j]) {dp[i] = Math.Max(dp[i], 1 + Dfs(arr, d, j, dp));}// 如果遇到更高或相等的點,則無法繼續向右if (arr[j] >= arr[i]) {break;}}// 向左跳for (int j = i - 1; j >= Math.Max(i - d, 0); j--) {if (arr[i] > arr[j]) {dp[i] = Math.Max(dp[i], 1 + Dfs(arr, d, j, dp));}// 如果遇到更高或相等的點,則無法繼續向左if (arr[j] >= arr[i]) {break;}}return dp[i];}
}

復雜度分析

  • 時間復雜度:O(n log?n + n * d)
    • 排序下標需要O(n log n)時間
    • 對于每個位置,我們最多檢查2d個可能的跳躍目標,總共n個位置,所以是O(n * d)
    • 綜合起來就是O(n log?n + n * d)
  • 空間復雜度:O(n)
    • 主要用于存儲dp數組、排序后的下標數組和遞歸調用棧

優化與技巧

  1. 按高度排序處理:先計算高度較低的點的dp值,再計算高度較高的點的dp值,可以減少重復計算。
  2. 提前終止:如果遇到高度大于等于當前點的位置,可以提前終止搜索,因為無法跳過這個位置。
  3. 記憶化搜索:使用dp數組存儲已計算的結果,避免重復計算。
  4. 邊界檢查:確保跳躍不會超出數組范圍。
  5. 利用問題特性:由于只能從高處跳到低處,整個跳躍路徑形成了一個有向無環圖(DAG),這使得動態規劃可以正確解決此問題。

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

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

相關文章

三相電壓的優勢,應用場景,功率測量

三相系統概述 我國三相系統&#xff0c;由頻率相同&#xff0c;幅度類似的三個交流電壓組成&#xff0c;每個電壓相差120度。 三相系統的優勢 啟動電機&#xff1a;三個矢量間隔的電壓&#xff0c;在電機中產生旋轉磁場&#xff0c;不需要額外繞組就可以啟動電機。 減少線損…

[原創](計算機數學)(The Probability Lifesaver)(P14): 推導計算 In(1-u) 約等于 -u

[作者] 常用網名: 豬頭三 出生日期: 1981.XX.XX 企鵝交流: 643439947 個人網站: 80x86匯編小站 編程生涯: 2001年~至今[共24年] 職業生涯: 22年 開發語言: C/C++、80x86ASM、Object Pascal、Objective-C、C#、R、Python、PHP、Perl、 開發工具: Visual Studio、Delphi、XCode、…

Android12 Rom定制去掉剪貼板復制成功的Toast

Android12Rom定制去掉剪貼板復制成功的Toast提示 1.前言&#xff1a; 最近在rom定制化開發時&#xff0c;測試提了一個bug&#xff0c;在瀏覽器或者文本里面使用剪貼板復制成功后會有一個Toast提示&#xff0c;這種體驗不是很好&#xff0c;因為每次復制成功都有一個提示&…

SOC-ESP32S3部分:9-GPIO輸入按鍵狀態讀取

飛書文檔https://x509p6c8to.feishu.cn/wiki/L6IGwHKV6ikQ08kqwAwcAvhznBc 前面我們學習了GPIO的輸出&#xff0c;GPIO輸入部分其實也是一樣的&#xff0c;這里我們使用按鍵作為GPIO輸入例程講解&#xff0c;分三步走。 查看板卡原理圖&#xff0c;確定使用的是哪個GPIO查看G…

高可用集群keepalived

1.不同操作系統的安裝 1.1 不同系統編譯安裝 ubuntu環境 apt-get - y install libssl-dev libpopt-dev daemon build-essential libssl-dev openssl libpopt-dev libsnmp-dev libnl-3-dev libnl-genl-3-dev centos環境 &#xff08;其他的下同&#xff09; yum install - y…

SpringCloud - 整合MQ實現消息總線服務

一、背景介紹 每當修改配置文件內容&#xff0c;如果需要客戶端也同步更新&#xff0c;就需要手動調用/refresh接口&#xff0c;以便客戶端能獲取到最新的配置內容。 當客戶端越來越多的時候&#xff0c;通過人工進行處理顯然非常雞肋。有沒有一種更加高效的辦法&#xff0c;…

測試W5500的第3步_使用ioLibrary庫創建TCPServer

W5500是一款具有8個Socket的網絡芯片&#xff0c;支持TCP Server模式&#xff0c;最多可同時連接8個客戶端。本文介紹了基于STM32F10x和W5500的TCP Server實現&#xff0c;包括SPI初始化、W5500復位、網絡參數配置、Socket狀態管理等功能&#xff0c;適用于需要多客戶端連接的嵌…

Web攻防-SQL注入數據庫類型用戶權限架構分層符號干擾利用過程發現思路

知識點&#xff1a; 1、Web攻防-SQL注入-產生原理&應用因素 2、Web攻防-SQL注入-各類數據庫類型利用 演示案例-WEB攻防-SQL注入-數據庫類型&架構分層&符號干擾 一、數據庫知識 1、數據庫名&#xff0c;表名&#xff0c;列名&#xff0c;數據 2、自帶數據庫&…

手機合集(不定期更新)

一、華為手機&#xff1a; 1.華為手機自助維修的方法&#xff1a; https://blog.csdn.net/humors221/article/details/145946128 2.華為手機實用功能介紹&#xff1a; https://blog.csdn.net/humors221/article/details/132514011 3.華為手機清理大數據的方法&#xff1a;…

移動安全Android——ROOT檢測繞過

工具準備 Magisk GitHub - topjohnwu/Magisk: The Magic Mask for Android ZygisckNext GitHub - Dr-TSNG/ZygiskNext at v1.2.8 Shamiko Releases LSPosed/LSPosed.github.io 安卓ROOT教程 Magisk 安裝教程 - Magisk 中文網 問題 大多數手機在ROOT狀態下會出現APP閃…

Python高效網絡爬蟲開發指南

Python 網絡爬蟲入門與實戰 一、引言 隨著互聯網數據的爆炸性增長&#xff0c;獲取和分析這些數據變得越來越重要。網絡爬蟲作為數據采集的重要工具&#xff0c;在這其中扮演了不可或缺的角色。 二、環境搭建 首先我們需要安裝Python環境以及一些必要的庫&#xff1a; req…

wireshark: Display Filter Reference

https://www.wireshark.org/docs/dfref/// 這個里面的擴展功能還是很強大&#xff0c;可以幫著問題分析。支持大量的自定義化的字段讀取功能&#xff0c;支持很多的協議。 https://www.wireshark.org/docs/dfref///f/frame.html frame.time_delta Time delta from previous ca…

dify創建銀行客服系統例子

傳統的銀行客服系統&#xff0c;通常以會話管理的方式實現&#xff0c;配置繁瑣復雜&#xff0c;固定且不靈活。如&#xff1a; 智能體的出現&#xff0c;為實現銀行客服系統提供了想象空間&#xff0c;可以集知識庫和業務流程為一體實現靈活可控的智能客服系統&#xff0c;即能…

前端函數防抖(Debounce)完整講解 - 從原理、應用到完整實現

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有堅忍不拔之志 &#x1f390; 個人CSND主頁——Micro麥可樂的博客 &#x1f425;《Docker實操教程》專欄以最新的Centos版本為基礎進行Docker實操教程&#xff0c;入門到實戰 &#x1f33a;《RabbitMQ》…

服務接口鑒權與內部認證:自定義注解與AOP實現的企業級實踐

本文深入解析企業級系統中接口安全管控的核心需求&#xff0c;提出基于Spring AOP與自定義注解的輕量級鑒權方案。通過解構注解元數據定義、切面攔截邏輯、上下文傳遞機制等關鍵技術環節&#xff0c;系統闡述零侵入式鑒權體系的構建路徑。結合金融支付網關、多租戶SaaS平臺、物…

26考研|高等代數:線性變換

前言 線性變換這一章節是考頻較高的一部分&#xff0c;此部分涉及考點較多&#xff0c;涉及的考題也較多&#xff0c;學習線性變換時&#xff0c;應該注意搭建線性變換與矩陣之間的聯系&#xff0c;掌握如何利用矩陣表示一個線性變換結構&#xff0c;同時介紹了最簡單的線性變…

電磁兼容(EMC)仿真(精編版)

寫在前面 本系列文章主要講解電磁兼容(EMC)仿真的相關知識,希望能幫助更多的同學認識和了解電磁兼容(EMC)仿真。 若有相關問題,歡迎評論溝通,共同進步。(*^▽^*) 隨著產品復雜性和密集度的提高以及設計周期的不斷縮短,在設計周期的后期解決電磁兼容性(EMC)問題變得…

解決:dpkg: error: dpkg frontend lock is locked by another process

1、等待其他進程完成 如果后臺有其他包管理操作&#xff08;如自動更新、軟件安裝等&#xff09;&#xff0c;等待幾分鐘再重試。 可以通過以下命令查看是否有相關進程&#xff1a; ps aux | grep -E apt|apt-get|dpkg 2、強制終止占用鎖的進程 如果確認沒有其他包管理操作&…

LVGL(lv_textarea文本框控件)

文章目錄 一、lv_textarea 是什么&#xff1f;二、基本用法1. 創建 lv_textarea 對象2. 設置提示文字&#xff08;占位符&#xff09;3. 設置最大長度4. 設置密碼模式&#xff08;顯示為\*號&#xff09;5. 獲取和設置內容6. 配合虛擬鍵盤使用&#xff08;常用于觸摸屏&#xf…

【Java高階面經:數據庫篇】18、分布式事務:如何在分庫分表中實現高性能與一致性?

一、分布式事務核心挑戰:分庫分表下的一致性困境 在分布式系統架構中,分庫分表通過將數據分散存儲提升了擴展性和性能,但卻打破了傳統單庫事務的邊界,使得分布式事務成為保障數據一致性的核心難題。其挑戰主要體現在以下三方面: 1.1 ACID特性的分布式撕裂 原子性(Atomi…