跳躍遊戲系列題目【動態規劃&貪心算法&DFS&BFS】

Leetcode 55. 跳躍遊戲

問題描述

給定一個非負整數數組,你最初位於數組的第一個位置。

數組中的每個元素代表你在該位置可以跳躍的最大長度。

判斷你是否能夠到達最後一個位置。

示例 1:

輸入: [2,3,1,1,4]
輸出: true
解釋: 我們可以先跳 1 步,從位置 0 到達 位置 1, 然後再從位置 1 跳 3 步到達最後一個位置。[1]^{[1]}

解題報告

有兩種方法

動態規劃

dp[i]dp[i] 表示 第 i 個位置是否可達。
轉移方程爲:
dp[i]={0  if  nums[0:i1]dp[j]使dp[j]+j>=ij;1  otherwise;dp[i]= \left\{\begin{matrix} 0\;if\;nums[0:i-1]中能找到dp[j]使得dp[j]+j>=i且第 j 個位置可達;\\ 1\; otherwise; \end{matrix}\right.
時間複雜度 O(N2)O(N^2)

貪心算法

持續更新變量 MAXX,其表示當前能跳的最遠位置。
當遍歷到某個位置 i 時,如果 i>MAXX,則表示從 i 這個地方斷掉了,所以無法跳到最後位置,直接返回 false 即可。

實現代碼

動態規劃實現
class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n=nums.size();
        if(n==1){
            return 1;
        }
        vector<int>dp(n,0);
        dp[0]=1;
        for(int i=0;i<n;i++){
            for(int j=i-1;j>=0;j--){
                if(dp[j]&&j+nums[j]>=i){
                    dp[i]=1;
                    break;
                }
            }
        }
    return dp[n-1];
    }
};
貪心算法實現
class Solution {
public:
    bool canJump(vector<int>&nums){
        int MAXX=0;
        for(int i=0;i<nums.size()&&MAXX<nums.size();i++){
            if(i>MAXX) return false;
            MAXX=max(MAXX,i+nums[i]);
        }
        return true;
    }
};

Leetcode 45. 跳躍遊戲 II

問題描述

給定一個非負整數數組,你最初位於數組的第一個位置。

數組中的每個元素代表你在該位置可以跳躍的最大長度。

你的目標是使用最少的跳躍次數到達數組的最後一個位置。

示例:

輸入: [2,3,1,1,4]
輸出: 2
解釋: 跳到最後一個位置的最小跳躍數是 2。 從下標爲 0 跳到下標爲 1 的位置,跳 1 步,然後跳 3 步到達數組的最後一個位置。

說明:

假設你總是可以到達數組的最後一個位置。[2]^{[2]}

解題報告

動態規劃

見代碼註釋。

貪心算法

維持一個變量 MAXX 來記錄當前位置所能跳的最遠距離,並不是每一次更新最遠跳躍距離實際上是要跳的。例如在位置 0,我們能到達的最遠距離是 0+nums[0],但是在到達 0+nums[0] 前我們會多次更新最遠跳,而我們不知道哪個位置是實現最少跳躍的局部位置。[3]^{[3]}

\qquad實際上,我們只需要知道一點:在到達位置 0+nums[0] 後,我們必然需要再跳一次,這樣我們才能繼續前進。 所以我們另外維持一個變量 pos,它記錄的是在我們在執行跳躍動作時能到達的最遠位置,即爲當前時刻的 MAXX。每當我們到達邊界時,跳躍步數加 1 ,更新邊界。[4]^{[4]}

實現代碼

動態規劃實現
#include<stdio.h>
#define maxn 105
int dp[maxn];
int aa[maxn];
///dp[i]中i表示從第0號位置達到i號位置所需要的最小跳躍數
void dg(int a[],int n)
{
    dp[0]=0;///終點是0號位置時,不需要跳躍
    for(int i=1; i<n; i++)
    {
        for(int j=0; j<i; j++)
        {
///j從小變到大,當發現從j號位置能跳到i號位置時,判斷dp[j]+1是否小於dp[i],從而決定是否更新dp[i]的值
            if(aa[j]+j>=i)
            {
                if(dp[j]+1<dp[i])
                {
                    dp[i]=dp[j]+1;
                }
            }
        }
    }
}
int main()
{
    int n;
    for(int i=0; i<maxn; i++)
        dp[i]=99999999;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=0; i<n; i++)
            scanf("%d",&aa[i]);

        dg(aa,n);
        printf("%d\n",dp[n-1]);
    }
    return 0;
}
貪心算法實現
class Solution {
public:
    int jump(vector<int>&nums){
        int ans=0, pos=0, MAXX=0;
        for(int i=0;i<nums.size()-1;i++){
            MAXX=max(MAXX, i+nums[i]);
            if(i==pos){
                pos=MAXX;
                ans++;
            }
        }
        return ans;
    }
};

Leetcode 1306. 跳躍遊戲 III

問題描述

這裏有一個非負整數數組 arr,你最開始位於該數組的起始下標 start 處。當你位於下標 i 處時,你可以跳到 i + arr[i] 或者 i - arr[i]。

請你判斷自己是否能夠跳到對應元素值爲 0 的 任意 下標處。

注意,不管是什麼情況下,你都無法跳到數組之外。

示例 1:

輸入:arr = [4,2,3,0,3,1,2], start = 5
輸出:true
解釋: 到達值爲 0 的下標 3 有以下可能方案: 下標 5 -> 下標 4 -> 下標 1 -> 下標 3 下標 5 -> 下標 6 -> 下標 4 -> 下標 1 -> 下標 3

[5]^{[5]}

解題報告

DFS判斷是否可達。從某個位置出發,判斷是否能到達目標點。每個位置的出發的方向有兩個,由此可建一棵規模一定的搜索樹,使用深度優先搜索即可。

實現代碼

class Solution {
public:
    bool dfs(vector<bool> visited,int idx){
        if(idx<0||idx>=temp.size()) return false;
        if(temp[idx]==0) return true;
        if (visited[idx]) return false;
        visited[idx]=true;
        return dfs(visited,idx+temp[idx])||dfs(visited,idx-temp[idx]);
    }
    bool canReach(vector<int>& _arr, int start) {
        temp = _arr;
        int size = temp.size();
        vector<bool> visited(size,false);
        return dfs(visited,start);
    }
private:
    vector<int> temp;
};

Leetcode 1345. 跳躍遊戲 IV

問題描述

給你一個整數數組 arr ,你一開始在數組的第一個元素處(下標爲 0)。

每一步,你可以從下標 i 跳到下標:

i + 1 滿足:i + 1 < arr.length
i - 1 滿足:i - 1 >= 0
j 滿足:arr[i] == arr[j] 且 i != j
請你返回到達數組最後一個元素的下標處所需的 最少操作次數 。

注意:任何時候你都不能跳到數組外面。

示例 1:

輸入:arr = [100,-23,-23,404,100,23,23,23,3,404]
輸出:3
解釋:那你需要跳躍 3 次,下標依次爲 0 --> 4 --> 3 --> 9 。下標 9 爲數組的最後一個元素的下標。

[6]^{[6]}

解題報告

BFS求最短距離。
需要注意的是:

 if (m.find(arr[u]) != m.end()) {
   for (int v: m[arr[u]]) {
        if (!vis[v]) {
            vis[v] = 1;
            dis[v] = min(dis[u]+1, dis[v]);
            q.push(v);
        }
    }
    m.erase(arr[u]); // 訪問過的值直接清理掉
}

在更新完某一元素 arr[i]時,當又遇到元素 arr[i] 時,我們不需要重新更新了。所以需要區分某重複元素【值相同,但是索引不同,所以 vis[] 無法限制】是否已經被更新。[7]^{[7]}

實現代碼

class Solution {
public:
    int minJumps(vector<int>& arr) {
        int n = arr.size();
      
        vector<int> dis(n, INT_MAX); // 距離
        vector<int> vis(n, 0); // 訪問標記
        unordered_map<int, vector<int>> m; // 倒排加速(m既起到了倒排加速作用,又起到了記錄值是否被訪問的作用,如果有一個值被訪問過了,刪除該值對應的鍵)
        for (int i = 0; i < n-1; i++) 
            m[arr[i]].push_back(i);
      
        dis[n-1] = 0; // 最後一個點入隊
        vis[n-1]=1;
        queue<int> q;
        q.push(n-1);
      
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            if (u-1 >= 0 && !vis[u-1]) { // 左跳
                dis[u-1] = min(dis[u-1], dis[u]+1);
                vis[u-1] = 1;
                q.push(u-1);
            }
            if (u+1 < n && !vis[u+1]) { // 右跳
                dis[u+1] = min(dis[u+1], dis[u]+1);
                vis[u+1] = 1;
                q.push(u+1);
            }
            if (m.find(arr[u]) != m.end()) {
                for (int v: m[arr[u]]) {
                    if (!vis[v]) {
                        vis[v] = 1;
                        dis[v] = min(dis[u]+1, dis[v]);
                        q.push(v);
                    }
                }
                m.erase(arr[u]); // 訪問過的值直接清理掉
            }
        }
        return dis[0];
    }
};

// 作者:inszva-2
// 鏈接:https://leetcode-cn.com/problems/jump-game-iv/solution/onti-jie-by-inszva-2/
// 來源:力扣(LeetCode)
// 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

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))。

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

請注意,任何時刻你都不能跳到數組的外面。
在這裏插入圖片描述

輸入: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 處。

[8]^{[8]}

解題報告

對於任意的位置 i,根據第二個條件,我們只需要在其左右兩側最多掃描 d 個元素,就可以找出所有滿足條件的位置 j。隨後我們通過這些 j 的 dp 值對位置 i 進行狀態轉移,就可以得到 dp[i] 的值。

此時出現了一個需要解決的問題,如何保證在處理到位置 i 時,所有滿足條件的位置 j 都已經被處理過了呢?換句話說,如何保證這些位置 j 對應的 dp[j] 都已經計算過了?如果我們用常規的動態規劃方法(例如根據位置從小到大或者從大到小進行動態規劃),那麼並不能保證這一點,因爲 j 分佈在位置 i 的兩側。

因此我們需要藉助記憶化搜索的方法,即當我們需要 dp[j] 的值時,如果我們之前已經計算過,就直接返回這個值(記憶);如果我們之前沒有計算過,就先將 dp[i] 擱在一邊,轉而去計算 dp[j](搜索),當 dp[j] 計算完成後,再用其對 dp[i] 進行狀態轉移。

記憶化搜索一定能在有限的時間內停止嗎?如果它不能在有限的時間內停止,說明在搜索的過程中出現了環。即當我們需要計算 dp[i] 時,我們發現某個 dp[j] 沒有計算過,接着在計算 dp[j] 時,又發現某個 dp[k] 沒有計算過,以此類推,直到某次搜索時發現當前位置的 dp 值需要 dp[i] 的值才能得到,這樣就出現了環。在本題中,根據第三個條件,arr[j] 是一定小於 arr[i] 的,即我們的搜索每深入一層,就跳到了高度更小的位置。因此在搜索的過程中不會出現環。這樣以來,我們通過記憶化搜索,就可以在與常規的動態規劃相同的時間複雜度內得到所有的 dp 值。[9]^{[9]}

注意:深搜時,可以藉助記憶化來解決 【如何保證在處理到位置 i 時,所有滿足條件的位置 j 都已經被處理過了呢?】,當時用動態規劃時,則需要提前對數組中元素進行從低到高排序。

實現代碼

class Solution {
public:
	int maxJumps(vector<int>& arr, int d) {
		int n = arr.size();
		vector<vector<int>> temp;
		vector<int> dp(n, 0);
		int res = 1;
		for (int i = 0; i < arr.size(); i++)
			temp.push_back({ arr[i],i });
		sort(temp.begin(), temp.end());

		for (int i = 0; i < n; i++) {
			int index = temp[i][1]; //編號;
			dp[index] = 1;
			//向左找
			for (int j = index - 1; j >= index - d && j >= 0; j--) {
				if (arr[j] >= arr[index]) break;
				if (dp[j] != 0) dp[index] = max(dp[index], dp[j ] + 1);
			}
			//向右找
			for (int j = index + 1; j <= index + d && j < n; j++) {
				if (arr[j] >= arr[index]) break;
				if (dp[j] != 0) dp[index] = max(dp[index], dp[j] + 1);
			}
			res = max(dp[index], res);
		}
		return res;

	}
};
// 作者:wu-bin-cong
// 鏈接:https://leetcode-cn.com/problems/jump-game-v/solution/dp-dong-tai-gui-hua-fei-chang-hao-li-jie-by-wu-bin/
// 來源:力扣(LeetCode)
// 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

[10]^{[10]}

參考資料

[1] Leetcode 55. 跳躍遊戲
[2] Leetcode 45. 跳躍遊戲 II
[3] 跳躍遊戲二——動態規劃
[4] Leetcode 45. 跳躍遊戲 II【貪心算法O(n)時間複雜度,解釋非常詳細】
[5] Leetcode 1306. 跳躍遊戲 III
[6] Leetcode 1345. 跳躍遊戲 IV
[7] Leetcode 1345. 跳躍遊戲 IV 題解區:inszva-2
[8] Leetcode 1340. 跳躍遊戲 V
[9] Leetcode 1340. 跳躍遊戲 V 官方題解
[10] Leetcode 1340. 跳躍遊戲 V 題解區:wu-bin-cong

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章