LeetCode 打家劫舍问题
LeetCode 打家劫舍問題
一:House Robber1
你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。
給定一個代表每個房屋存放金額的非負整數數組,計算你在不觸動警報裝置的情況下,能夠偷竊到的最高金額。
示例 1:
輸入: [1,2,3,1] 輸出: 4 解釋: 偷竊 1 號房屋 (金額 = 1) ,然后偷竊 3 號房屋 (金額 = 3)。偷竊到的最高金額 = 1 + 3 = 4 。示例 2:
輸入: [2,7,9,3,1] 輸出: 12 解釋: 偷竊 1 號房屋 (金額 = 2), 偷竊 3 號房屋 (金額 = 9),接著偷竊 5 號房屋 (金額 = 1)。偷竊到的最高金額 = 2 + 9 + 1 = 12 。題?很容易理解, ?且動態規劃的特征很明顯。 解決動態規劃問題就是找「狀態」 和「選擇」 , 僅此?已。
假想你就是這個專業強盜, 從左到右?過這?排房?, 在每間房?前都有兩
種選擇: 搶或者不搶。
如果你搶了這間房?, 那么你肯定不能搶相鄰的下?間房?了, 只能從下下
間房?開始做選擇。
如果你不搶這件房?, 那么你可以?到下?間房?前, 繼續做選擇。
當你?過了最后?間房?后, 你就沒得搶了, 能搶到的錢顯然是 0(base
case) 。
以上的邏輯很簡單吧, 其實已經明確了「狀態」 和「選擇」 : 你?前房?的
索引就是狀態, 搶和不搶就是選擇。
在兩個選擇中, 每次都選更?的結果, 最后得到的就是最多能搶到的money:
明確了狀態轉移, 就可以發現對于同? start 位置, 是存在重疊?問題的, ?如下圖:
如果每次到這都進?遞歸, 豈不是浪費時間? 所以說存在重疊?問題, 可以?備忘錄進?優化:
private int[] memo; // 主函數 public int rob(int[] nums) {// 初始化備忘錄memo = new int[nums.length];Arrays.fill(memo, -1);// 強盜從第 0 間房?開始搶劫 return dp(nums, 0); } // 返回 dp[start..] 能搶到的最?值 private int dp(int[] nums, int start) {if (start >= nums.length) {return 0;} // 避免重復計算if (memo[start] != -1) return memo[start];int res = Math.max(dp(nums, start + 1),nums[start] + dp(nums, start + 2));// 記?備忘錄memo[start] = res;return res; }這就是?頂向下的動態規劃解法, 我們也可以略作修改, 寫出?底向上的解
法:
我們?發現狀態轉移只和 dp[i] 最近的兩個狀態有關, 所以可以進?步優化, 將空間復雜度降低到 O(1)O(1)O(1)。
int rob(int[] nums) {int n = nums.length;// 記錄 dp[i+1] 和 dp[i+2]int dp_i_1 = 0, dp_i_2 = 0;// 記錄 dp[i]int dp_i = 0;for (int i = n - 1; i >= 0; i--) {dp_i = Math.max(dp_i_1, nums[i] + dp_i_2);dp_i_2 = dp_i_1;dp_i_1 = dp_i;} return dp_i; }C++代碼:
class Solution { public:int rob(vector<int>& nums) {int n = nums.size();if(n == 0){return n;}// 記錄 dp[i+1] 和 dp[i+2]int dp_i_1 = 0, dp_i_2 = 0;// 記錄 dp[i]int dp_i = 0;for (int i = n - 1; i >= 0; i--) {//dp_i代表當前在i位置開始搶劫所能獲得的最大momery,dp_i = max(dp_i_1, nums[i] + dp_i_2);//記錄在i - 2和 i - 1位置(反向遍歷的)處開始搶劫所能獲得的最大memorydp_i_2 = dp_i_1;dp_i_1 = dp_i;} return dp_i;} };二:House Robber II
你是一個專業的小偷,計劃偷竊沿街的房屋,每間房內都藏有一定的現金。這個地方所有的房屋都圍成一圈,這意味著第一個房屋和最后一個房屋是緊挨著的。同時,相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。
給定一個代表每個房屋存放金額的非負整數數組,計算你在不觸動警報裝置的情況下,能夠偷竊到的最高金額。
示例 1:
輸入: [2,3,2] 輸出: 3 解釋: 你不能先偷竊 1 號房屋(金額 = 2),然后偷竊 3 號房屋(金額 = 2), 因為他們是相鄰的。示例 2:
輸入: [1,2,3,1] 輸出: 4 解釋: 你可以先偷竊 1 號房屋(金額 = 1),然后偷竊 3 號房屋(金額 = 3)。偷竊到的最高金額 = 1 + 3 = 4 。這道題?和第?道描述基本?樣, 強盜依然不能搶劫相鄰的房?, 輸?依然是?個數組, 但是告訴你這些房?不是?排, ?是圍成了?個圈。也就是說, 現在第?間房?和最后?間房?也相當于是相鄰的, 不能同時搶。
這個約束條件看起來應該不難解決, 那么在這個問題上怎么處理呢?
?先, ?尾房間不能同時被搶, 那么只可能有三種不同情況: 要么都不被搶; 要么第?間房?被搶最后?間不搶; 要么最后?間房?被搶第?間不搶
那就簡單了啊, 這三種情況, 那種的結果最?, 就是最終答案唄! 不過, 其實我們不需要?較三種情況, 只要?較情況?和情況三就?了, 因為這兩種情況對于房?的選擇余地?情況??呀, 房??的錢數都是?負數, 所以選擇余地?, 最優決策結果肯定不會?。
代碼:
public int rob(int[] nums) {int n = nums.length;if (n == 1) return nums[0];return Math.max(robRange(nums, 0, n - 2),robRange(nums, 1, n - 1)); } // 僅計算閉區間 [start,end] 的最優結果 int robRange(int[] nums, int start, int end) {int n = nums.length;int dp_i_1 = 0, dp_i_2 = 0;int dp_i = 0;for (int i = end; i >= start; i--) {dp_i = Math.max(dp_i_1, nums[i] + dp_i_2);dp_i_2 = dp_i_1;dp_i_1 = dp_i;} return dp_i; }C++代碼:
class Solution { public:int robRange(vector<int>& nums, int start, int end) {int n = nums.size();int dp_i_1 = 0, dp_i_2 = 0;int dp_i = 0;for (int i = end; i >= start; i--) {dp_i = max(dp_i_1, nums[i] + dp_i_2);dp_i_2 = dp_i_1;dp_i_1 = dp_i;} return dp_i;}int rob(vector<int>& nums) {int n = nums.size();if (n == 1) return nums[0];//[0][n - 2]區間代表不搶最后一間房子;[1][n - 1]:代表不搶第一間房子return max(robRange(nums, 0, n - 2),robRange(nums, 1, n - 1));} };三:House Robber III
在上次打劫完一條街道之后和一圈房屋后,小偷又發現了一個新的可行竊的地區。這個地區只有一個入口,我們稱之為“根”。 除了“根”之外,每棟房子有且只有一個“父“房子與之相連。一番偵察之后,聰明的小偷意識到“這個地方的所有房屋的排列類似于一棵二叉樹”。 如果兩個直接相連的房子在同一天晚上被打劫,房屋將自動報警。
計算在不觸動警報的情況下,小偷一晚能夠盜取的最高金額。
示例 1:
輸入: [3,2,3,null,3,null,1]3/ \2 3\ \ 3 1輸出: 7 解釋: 小偷一晚能夠盜取的最高金額 = 3 + 3 + 1 = 7.示例 2:
輸入: [3,4,5,1,3,null,1]3/ \4 5/ \ \ 1 3 1輸出: 9 解釋: 小偷一晚能夠盜取的最高金額 = 4 + 5 = 9.你們說這個小偷是不是有點過分!!!竟然是傳說中的?智商犯罪
整體的思路完全沒變, 還是做搶或者不搶的選擇, 去收益較?的選擇。 甚?
我們可以直接按這個套路寫出代碼:
這道題就解決了, 時間復雜度 O(N)O(N)O(N), N 為數的節點數。
還有更漂亮的解法。
int rob(TreeNode root) {int[] res = dp(root);return Math.max(res[0], res[1]); } /* 返回?個??為 2 的數組 arr arr[0] 表?不搶 root 的話, 得到的最?錢數 arr[1] 表?搶 root 的話, 得到的最?錢數 */ int[] dp(TreeNode root) {if (root == null)return new int[]{0, 0};int[] left = dp(root.left);int[] right = dp(root.right);// 搶, 下家就不能搶了int rob = root.val + left[0] + right[0];// 不搶, 下家可搶可不搶, 取決于收益??int not_rob = Math.max(left[0], left[1])+ Math.max(right[0], right[1]);return new int[]{not_rob, rob}; }時間復雜度 O(N)O(N)O(N), 空間復雜度只有遞歸函數堆棧所需的空間, 不需要備忘
錄的額外空間
C++代碼:
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/ class Solution { public:/* 返回?個??為 2 的數組 arrarr[0] 表?不搶 root 的話, 得到的最?錢數arr[1] 表?搶 root 的話, 得到的最?錢數 */vector<int> dp(TreeNode* root) {if (root == nullptr){return vector<int>(2,0);}vector<int> left = dp(root->left);vector<int> right = dp(root->right);// 搶, 下家就不能搶了int rob = root->val + left[0] + right[0];// 不搶, 下家可搶可不搶, 取決于收益??,注意他是二叉樹的結構,所以還要返回兩種情況之和int not_rob = max(left[0], left[1])+ max(right[0], right[1]);return vector<int>{not_rob,rob};}int rob(TreeNode* root) {vector<int> ret = dp(root);return max(ret[0], ret[1]);} };總結
以上是生活随笔為你收集整理的LeetCode 打家劫舍问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LeetCode 股票买卖问题
- 下一篇: 动态规划之四键键盘