贪心算法及Jump Game系列题详解
本博文所有的代碼均可在
https://github.com/Hongze-Wang/LeetCode_Java
https://github.com/Hongze-Wang/LeetCode_Python
中找到,歡迎star。
貪心算法屬于比較難的算法,一般用于求解最優解或者極限情況下判斷可能性。貪心和動態規劃的區別在于,貪心算法的解題過程中會展現出最優子結構,子問題的最優解組合構成了全局的最優解,而動態規劃則是考慮所有子問題,針對這些子結構求解,從中選取出最優解。
貪心算法有兩個重要組成部分:
詳見Greedy Analysis Strategies,摘自普林斯頓大學算法課件。
?
貪心算法和動態規劃的共同點在于分治思想,大問題拆解成小問題,從求解小問題過程中得到大問題的解。
貪心算法和動態規劃的不同點在于最優子結構,即貪心算法相當于認定了貪心策略下的解能得到最優解;而動態規劃則窮舉了所有的解的可能性,從中經過比較得到了最優解。
因此貪心算法不一定能保證最優解,但可以保證一個還不錯的解。動態規劃一定能保證最優解,但因為它要窮舉所有可能性,計算復雜度會很高,在很多場景下無法使用。
注:如果貪心策略是解決問題的唯一策略,那么貪心算法也一定能取得最優解,而且比動態規劃復雜度要小得多。
Jump Game
來自LeetCode的一組題,我們這里只介紹貪心算法,其中55題既可以使用貪心解也可以使用動態規劃,它是一個貪心算法復雜度低于動態規劃的一個實例。官方注釋是英文的,因此我加的注釋也是英文的,如果有看不懂的地方可以在評論區提問。
55. Jump Game (Medium)
Given an array of non-negative integers nums, you are initially positioned at the first index of the array.
Each element in the array represents your maximum jump length at that position.
Determine if you are able to reach the last index.
Example 1:
Input: nums = [2,3,1,1,4]
Output: true
Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index.
Example 2:
Input: nums = [3,2,1,0,4]
Output: false
Explanation: You will always arrive at index 3 no matter what. Its maximum jump length is 0, which makes it impossible to reach the last index.
Constraints
1 <= nums.length <= 3 * 104
0 <= nums[i] <= 105
貪心策略:選取最大的步數,從結束點逆推到起始點,看能不能回到起始點,如果能,自然也能從起始點到達結束點。選取最大步數在程序中體現的是i+nums[i],如果i+nums[i]>=lastPos,那么從i是能到達lastPos的。
// 55. Jump Game// The Solution of this question is very detail and inspring // I recommend you to read it for more info and idea: https://leetcode.com/problems/jump-game/solution/// Usually, solving and fully understanding a dynamic programming problem is a 4 step process: // Start with the recursive backtracking solution // Optimize by using a memoization table (top-down dynamic programming) // Remove the need for recursion (bottom-up dynamic programming) // Apply final tricks to reduce the time / memory complexity// Approach 1: Backtracking (Brute Force) 這里沒有回溯過程 所以我覺得不能稱作回溯法 只是單純的DFS而已 // check every pos from curr pos and all the reachable // Time Limit Exceeded class Solution {public boolean canJumpFromPosition(int pos, int[] nums) {if(pos == nums.length - 1) {return true;}int fur = Math.min(pos + nums[pos], nums.length-1);for(int next = pos+1; next<=fur; next++) {if(canJumpFromPosition(next, nums)) {return true;}}return false;}public boolean canJump(int[] nums) {return canJumpFromPosition(0, nums);} }// Approach 1: Backtracking (Brute Force) Optimized // check every pos from curr pos and all the reachable from right to left // this means we always try to make the biggest jump such that we reach the end as soon as possible (greedy policy) // Time Limit Exceeded class Solution {public boolean canJumpFromPosition(int pos, int[] nums) {if(pos == nums.length - 1) {return true;}int fur = Math.min(pos + nums[pos], nums.length-1);for(int next = fur; next>pos; next--) {if(canJumpFromPosition(next, nums)) {return true;}}return false;}public boolean canJump(int[] nums) {return canJumpFromPosition(0, nums);} }// Approach 2: Dynamic Programming Top-down // It relies on the observation that once we determine that a certain index is good / bad, this result will never change. // This means that we can store the result and not need to recompute it every time. // memoizationenum Index {GOOD, BAD, UNKNOW }public class Solution {Index[] memo;public boolean canJumpFromPosition(int pos, int[] nums) {if(memo[pos] != Index.UNKNOW) {return memo[pos] == Index.GOOD ? true : false;}int fur = Math.min(pos+nums[pos], nums.length-1);for(int next=pos+1; next<=fur; next++) {if(canJumpFromPosition(next, nums)) {memo[pos] = Index.GOOD;return true; }}memo[pos] = Index.BAD;return false;}public boolean canJump(int[ ] nums) {memo = new Index[nums.length];for(int i=0; i<memo.length; i++) {memo[i] = Index.UNKNOW;}memo[memo.length-1] = Index.GOOD;return canJumpFromPosition(0, nums);} }// Top down dp public class Solution {public boolean canJumpFromPostion(int pos, int[] nums, int[] memo) {if(memo[pos] != 0) {return memo[pos] == 1 ? true : false;}int fur = Math.min(pos+nums[pos], nums.length-1);for(int next=pos+1; next <= fur; next++) {if(canJumpFromPostion(next, nums, memo)) {memo[pos] = 1;return true;}}memo[pos] = 2;return false;}public boolean canJump(int[] nums) {int[] memo = new int[nums.length];memo[nums.length-1] = 1;return canJumpFromPostion(0, nums, memo);} }// Approach 3: Dynamic Programming Bottom-up // Top-down to bottom-up conversion is done by eliminating recursion // The recursion is usually eliminated by trying to reverse the order of the steps from the top-down approach.enum Index {GOOD, BAD, UNKNOW }public class Solution {public boolean canJump(int[] nums) {Index[] memo = new Index[nums.length];for(int i=0; i<nums.length; i++) {memo[i] = Index.UNKNOW;}memo[nums.length-1] = Index.GOOD;for(int i=nums.length-2; i>=0; i--) {int fur = Math.min(i+nums[i], nums.length-1);for(int j=i+1; j<=fur; j++) {if(memo[j] == Index.GOOD) {memo[i] = Index.GOOD;break;} }}return memo[0] == Index.GOOD;} }// Bottom up dp public class Solution {public boolean canJump(int[] nums) {int[] memo = new int[nums.length];memo[nums.length-1] = 1;for(int i=nums.length-2; i>=0; i--) {int fur = Math.min(i+nums[i], nums.length-1);for(int j=i+1; j<=fur; j++) {if(memo[j] == 1) {memo[i] = 1;break;}}}return memo[0] == 1;} }// Approach 4: Greedy // Greedy Policy // try to make the biggest jump such that we reach the start from the end as soon as possible public class Solution {public boolean canJump(int[] nums) {int lastPos = nums.length-1;for(int i=nums.length-1; i>=0; i--) {if(i+nums[i] >= lastPos) {lastPos = i;}}return lastPos == 0;} }動態規劃再怎么優化都是O(n^2),但貪心是O(n)復雜度確實要小得多。
45. Jump Game II (Hard)
Given an array of non-negative integers nums, you are initially positioned at the first index of the array.
Each element in the array represents your maximum jump length at that position.
Your goal is to reach the last index in the minimum number of jumps.
You can assume that you can always reach the last index.
Example 1:
Input: nums = [2,3,1,1,4]
Output: 2
Explanation: The minimum number of jumps to reach the last index is 2. Jump 1 step from index 0 to 1, then 3 steps to the last index.
Example 2:
Input: nums = [2,3,0,1,4]
Output: 2
Constraints:
1 <= nums.length <= 3 * 104
0 <= nums[i] <= 105
這道題比上一道考察貪心的傾向更明顯,因為它求的是最小步數,是個求最優解問題。貪心策略同上,也是每次都選取能走的最大步數,最后一次走的時候,如果i+nums[i] > nums.length-1而溢出,那么i == pos不會成立,因而不會再計次數。
// 45. Jump Game II // greedy: take the highest step as you can in every jumpclass Solution {public int jump(int[] nums) {if(nums.length == 1) return 0;int pos = 0, reachable = 0, count = 0;for(int i=0; i<nums.length-1; i++) { // last elem do not need considerreachable = Math.max(reachable, i+nums[i]);if(i == pos) {pos = reachable;count++;}}return count;} }// reachable store the current highest position it can reach // pos store the last hight postion it can reach // if i == pos means that i can be reach // update pos = reachable (currennt highest postion it can rech) greedy policy // count increase by one1306. Jump Game III (Medium)
Given an array of non-negative integers arr, you are initially positioned at start index of the array. When you are at index i, you can jump to i + arr[i] or i - arr[i], check if you can reach to any index with value 0.
Notice that you can not jump outside of the array at any time.
Example 1:
Input: arr = [4,2,3,0,3,1,2], start = 5
Output: true
Explanation:
All possible ways to reach at index 3 with value 0 are:
index 5 -> index 4 -> index 1 -> index 3
index 5 -> index 6 -> index 4 -> index 1 -> index 3
Example 2:
Input: arr = [4,2,3,0,3,1,2], start = 0
Output: true
Explanation:
One possible way to reach at index 3 with value 0 is:
index 0 -> index 4 -> index 1 -> index 3
Example 3:
Input: arr = [3,0,2,1,2], start = 2
Output: false
Explanation: There is no way to reach at index 1 with value 0.
Constraints:
1 <= arr.length <= 5 * 104
0 <= arr[i] < arr.length
0 <= start < arr.length
這道題雖然也放到這里了,但不是貪心,因為每次跳法只有兩種,這兩種跳法無法判斷誰最優,而且也不是求最優解問題。DFS或者BFS都可以做,下面的解法使用了一個trick,使用取負來標志一個元素被訪問過。
// 1306. Jump Game III// DFS class Solution {public boolean canReach(int[] arr, int start) {if(start < 0 || start >= arr.length || arr[start] < 0) return false;if(arr[start] == 0) return true;arr[start] = -arr[start]; // mark arr[start] as visitedreturn canReach(arr, start+arr[start]) || canReach(arr, start-arr[start]);} }// BFS class Solution {public boolean canReach(int[] arr, int start) {int n=arr.length;Queue<Integer> q = new LinkedList();q.add(start);while(!q.isEmpty()) {int node = q.poll();if(arr[node] == 0) return true;if(arr[node] < 0) continue;if(node + arr[node] < n) {q.offer(node + arr[node]);}if(node - arr[node] >=0) {q.offer(node - arr[node]);}arr[node] = -arr[node];}return false;} }總結
以上是生活随笔為你收集整理的贪心算法及Jump Game系列题详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 全面认识海思SDK及嵌入式层开发-第1/
- 下一篇: 安装wincc flexible等西门子