【圖解動態規劃】打家劫舍 II(四種解法)
目錄
題目描述
你是一個專業的小偷,計劃偷竊沿街的房屋,每間房內都藏有一定的現金。這個地方所有的房屋都圍成一圈,這意味著第一個房屋和最後一個房屋是緊挨著的。同時,相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。
給定一個代表每個房屋存放金額的非負整數陣列,計算你在不觸動警報裝置的情況下,能夠偷竊到的最高金額。
示例 1:
輸入: [2,3,2]
輸出: 3
解釋: 你不能先偷竊1
號房屋(金額 = 2),然後偷竊3
號房屋(金額 = 2), 因為他們是相鄰的。
示例 2:
輸入: [1,2,3,1]
輸出: 4
解釋: 你可以先偷竊1
號房屋(金額 = 1),然後偷竊3
號房屋(金額 = 3)。
偷竊到的最高金額 = 1 + 3 = 4 。
遞迴
注意!!這道題的求解過程是完全基於【打家劫舍 I】來的,遞迴的解法、動態規劃解法、以及動態規劃+空間壓縮都是套用了【打家劫舍 I】。
如果沒有看過【打家劫舍 I】,請一定要先看一遍,點選【這裡】檢視。
【打家劫舍 I】本質是基於一維陣列的,而【打家劫舍 II】是一個環形陣列。
直接用【打家劫舍 I】的程式碼會有什麼問題呢?
因為是環形陣列,所以第二排的陣列的求解方式就不對了,1
和5
因為是首尾相連只能二選一。
而第三排陣列的求解是滿足要求的,只選了1
,沒有選5
所以我們可以改成這樣:
我們將原陣列拆成兩部分
1、nums[0],num[1]...num[end-1]
2、nums[1],nums[2]...nums[end]
然後我們分別對這兩個陣列求最大值,這個求解過程就完全套用了【打家劫舍 I】的邏輯。
我們只需要用同樣的邏輯,執行兩邊程式碼,最後求兩次的最大值即可。
遞迴的邏輯就是求兩遍陣列、動態規劃也是求兩遍、動態規劃+壓縮也一樣。
到這裡就很明確了,所謂的【打家劫舍 II】,其實就是執行了兩遍的【打家劫舍 I】而已。
java程式碼:
class Solution {
public int rob(int[] nums) {
if(nums==null || nums.length==0) {
return 0;
}
if(nums.length<=2) {
return (nums.length==2) ? Math.max(nums[0],nums[1]) : nums[0];
}
int n = nums.length;
//將原陣列分拆分成兩個陣列,然後對兩個陣列分別求一次最大值
int a = dfs(Arrays.copyOfRange(nums,0,n-1),0,0);
int b = dfs(Arrays.copyOfRange(nums,1,n),0,0);
return Math.max(a,b);
}
//直接套用了【打家劫舍 I】的程式碼
private int dfs(int[] arr,int index,int status) {
if(index>=arr.length) {
return 0;
}
int a=0,b=0,c=0;
a = dfs(arr,index+1,status);
if(status==1) {
b = dfs(arr,index+1,0);
}
else {
c = dfs(arr,index+1,1)+arr[index];
}
return Math.max(a, Math.max(b,c) );
}
}
python程式碼:
class Solution(object):
def rob(self, nums):
if not nums:
return 0
n = len(nums)
if n<=2:
return max(nums)
# 直接套用了【打家劫舍 I】的程式碼
def dfs(arr,index,status):
if index>=len(arr):
return 0
a,b,c = 0,0,0
a = dfs(arr,index+1,status)
if status:
b = dfs(arr,index+1,0)
else:
c = dfs(arr,index+1,1)+arr[index]
return max(a,b,c)
# 將原陣列分拆分成兩個陣列,然後對兩個陣列分別求一次最大值
a = dfs(nums[0:n-1],0,0)
b = dfs(nums[1:],0,0)
return max(a,b)
遞迴+記憶化
我們建立了兩套快取,然後分別呼叫了兩次遞迴函式,這兩個遞迴函式的邏輯跟【打家劫舍 I】幾乎是一樣的。
然後將兩次的返回結果求最大值即可。
java程式碼:
class Solution {
public int rob(int[] nums) {
if(nums==null || nums.length==0) {
return 0;
}
if(nums.length<=2) {
return (nums.length==2) ? Math.max(nums[0],nums[1]) : nums[0];
}
int n = nums.length;
int[][] cache1 = new int[n][2];
int[][] cache2 = new int[n][2];
for(int i=0;i<n;++i) {
Arrays.fill(cache1[i],-1);
Arrays.fill(cache2[i],-1);
}
//將原陣列分拆分成兩個陣列,然後對兩個陣列分別求一次最大值
int a = dfs(cache1,Arrays.copyOfRange(nums,0,n-1),0,0);
int b = dfs(cache2,Arrays.copyOfRange(nums,1,n),0,0);
return Math.max(a,b);
}
//套用了【打家劫舍 I】的程式碼
private int dfs(int[][] cache,int[] arr,int index,int status) {
if(index>=arr.length) {
return 0;
}
if(cache[index][status]>-1) {
return cache[index][status];
}
int a=0,b=0,c=0;
a = dfs(cache,arr,index+1,status);
if(status==1) {
b = dfs(cache,arr,index+1,0);
}
else {
c = dfs(cache,arr,index+1,1)+arr[index];
}
cache[index][status] = Math.max(a, Math.max(b,c) );
return cache[index][status];
}
}
python程式碼:
class Solution(object):
def rob(self, nums):
if not nums:
return 0
n = len(nums)
if n<=2:
return max(nums)
# 套用了【打家劫舍 I】的程式碼
def dfs(cache,arr,index,status):
if index>=len(arr):
return 0
if (index,status) in cache:
return cache[index,status]
a,b,c = 0,0,0
a = dfs(cache,arr,index+1,status)
if status:
b = dfs(cache,arr,index+1,0)
else:
c = dfs(cache,arr,index+1,1)+arr[index]
cache[index,status] = max(a,b,c)
return cache[index,status]
# 將原陣列分拆分成兩個陣列,然後對兩個陣列分別求一次最大值
a = dfs(dict(),nums[0:n-1],0,0)
b = dfs(dict(),nums[1:],0,0)
return max(a,b)
動態規劃
動態規劃也是基於【打家劫舍 I】的。
你會發現這題會有點無聊,每種解法都是執行兩遍【打家劫舍 I】。
這裡我們建立兩個dp
陣列dp1
計算的是nums[1],nums[2]...nums[end]
這個陣列
dp2
計算的是nums[0],nums[1]...nums[end-1]
這個陣列
dp1
是從nums[1]
開始的,所以初的前兩個值
-
dp1[0]
就等於0 -
dp1[1]
等於nums[1]
同理,dp2
是從nums[0]
開始的,所以初始化的前兩個值
-
dp2[0]
就等於nums[0]
-
dp2[1]
就等於max(nums[0],nums[1])
java程式碼:
class Solution {
public int rob(int[] nums) {
if(nums==null || nums.length==0) {
return 0;
}
if(nums.length<=2) {
return (nums.length==2) ? Math.max(nums[0],nums[1]) : nums[0];
}
int n = nums.length;
int[] dp1 = new int[n];
int[] dp2 = new int[n];
//初始化兩個dp陣列,dp1是計算的是[1,end],dp2計算的是[0,end-1]
dp1[0] = 0;
dp1[1] = nums[1];
dp2[0] = nums[0];
dp2[1] = Math.max(nums[0],nums[1]);
//按照【打家劫舍 I】的轉移方式執行兩遍
for(int i=2;i<n;++i) {
dp1[i] = Math.max(dp1[i-1],dp1[i-2]+nums[i]);
}
for(int i=2;i<n-1;++i) {
dp2[i] = Math.max(dp2[i-1],dp2[i-2]+nums[i]);
}
return Math.max(dp1[n-1],dp2[n-2]);
}
}
python程式碼:
class Solution(object):
def rob(self, nums):
if not nums:
return 0
n = len(nums)
if n<=2:
return max(nums)
dp1 = [0 for _ in xrange(n)]
dp2 = [0 for _ in xrange(n)]
# 初始化兩個dp陣列,dp1是計算的是[1,end],dp2計算的是[0,end-1]
dp1[0] = 0
dp1[1] = nums[1]
dp2[0] = nums[0]
dp2[1] = max(nums[0],nums[1])
# 按照【打家劫舍 I】的轉移方式執行兩遍
for i in xrange(2,n):
dp1[i] = max(dp1[i-1],dp1[i-2]+nums[i])
for i in xrange(2,n-1):
dp2[i] = max(dp2[i-1],dp2[i-2]+nums[i])
return max(dp1[-1],dp2[-2])
動態規劃+空間壓縮
【打家劫舍 I】中我們用兩個變數來優化空間,因為dp[i]
的值只需要用到dp[i-2]
和dp[i-1]
。
所以我們用兩個變數來代替dp[i-2]
,dp[i-1]
然後不斷滾動更新這兩個變數,這裡就是執行兩遍。
java程式碼:
class Solution {
public int rob(int[] nums) {
if(nums==null || nums.length==0) {
return 0;
}
if(nums.length<=2) {
return (nums.length==2) ? Math.max(nums[0],nums[1]) : nums[0];
}
int n = nums.length;
//執行兩遍,第一次求nums[0,n-1],第二次求nums[1,n],再返回兩次計算的最大值
int a = dp(nums,0,n-1);
int b = dp(nums,1,n);
return Math.max(a,b);
}
//用滾動陣列的方式優化空間
private int dp(int[] nums,int begin,int end) {
int pre = 0;
int cur = 0;
for(int i=begin;i<end;++i) {
int tmp = cur;
cur = Math.max(cur,pre+nums[i]);
pre = tmp;
}
return cur;
}
}
python程式碼:
class Solution(object):
def rob(self, nums):
if not nums:
return 0
n = len(nums)
if n<=2:
return max(nums)
# 用滾動陣列的方式優化空間
def dp(begin,end):
pre,cur = 0,0
for i in xrange(begin,end):
cur,pre = max(cur,pre+nums[i]),cur
return cur
# 執行兩遍,第一次求nums[0,n-1],第二次求nums[1,n],再返回兩次計算的最大值
return max( dp(0,n-1),dp(1,n) )
推薦閱讀:
歡迎掃描關注公眾號 ,有更多圖解的演算法面試題等你哦~
相關文章
- 四步模板解決動態規劃動態規劃
- 【力扣198-打家劫舍】動態規劃(python3)力扣動態規劃Python
- 【LeetCode】Word Break II 動態規劃LeetCode動態規劃
- 演算法(七):圖解動態規劃演算法圖解動態規劃
- 圖解 | 原來這就是動態規劃圖解動態規劃
- LeetCode 分割回文串II(動態規劃)LeetCode動態規劃
- 動態規劃專題之----213. House Robber II動態規劃
- 【每日演算法】動態規劃四演算法動態規劃
- 動態規劃解題方法動態規劃
- 【動態規劃(一)】動態規劃基礎動態規劃
- 乾貨:圖解演算法——動態規劃系列圖解演算法動態規劃
- 動態規劃動態規劃
- 動態規劃 擺花 題解動態規劃
- 力扣-動態規劃全解力扣動態規劃
- Day 42 | 198.打家劫舍 、213.打家劫舍II、337.打家劫舍III
- leetcode題解(動態規劃)LeetCode動態規劃
- 動態規劃分析動態規劃
- 動態規劃(DP)動態規劃
- 動態規劃初步動態規劃
- 模板 - 動態規劃動態規劃
- 動態規劃法動態規劃
- 【動態規劃】樹形DP完全詳解!動態規劃
- 動態規劃---求硬幣最優解動態規劃
- 有了四步解題法模板,再也不害怕動態規劃!動態規劃
- 演算法系列-動態規劃(1):初識動態規劃演算法動態規劃
- 淺談動態規劃動態規劃
- 有關動態規劃動態規劃
- 動態規劃小結動態規劃
- 動態規劃初級動態規劃
- 動態規劃講義動態規劃
- 好題——動態規劃動態規劃
- 3.動態規劃動態規劃
- 動態規劃-----線性動態規劃
- 動態規劃專題動態規劃
- 區間動態規劃動態規劃
- 動態規劃題單動態規劃
- 雙序列動態規劃動態規劃
- 動態規劃 總結動態規劃