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