484,打家劫舍 II
技術標籤:資料結構和演算法打家劫舍動態規劃LeetCode演算法
想了解更多資料結構以及演算法題,可以關注微信公眾號“資料結構和演算法”,每天一題為你精彩解答。也可以掃描下面的二維碼關注
問題描述
你是一個專業的小偷,計劃偷竊沿街的房屋,每間房內都藏有一定的現金。這個地方所有的房屋都圍成一圈 ,這意味著第一個房屋和最後一個房屋是緊挨著的。同時,相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警 。
給定一個代表每個房屋存放金額的非負整數陣列,計算你在不觸動警報裝置的情況下 ,能夠偷竊到的最高金額。
示例 1:
輸入:nums = [2,3,2]
輸出:3
解釋:你不能先偷竊 1 號房屋(金額 = 2),然後偷竊 3 號房屋(金額 = 2), 因為他們是相鄰的。
示例 2:
輸入:nums = [1,2,3,1]
輸出:4
解釋:你可以先偷竊 1 號房屋(金額 = 1),然後偷竊 3 號房屋(金額 = 3)。
偷竊到的最高金額 = 1 + 3 = 4 。
示例 3:
輸入:nums = [0]
輸出:0
提示:
- 1 <= nums.length <= 100
- 0 <= nums[i] <= 1000
動態規劃解決
我們先來考慮這樣一個問題,假如所有的房屋沒有圍成一個圈,也就是說第一個房屋和最後一個房屋不是挨著的,那麼這道題就回退到
這裡來定義一個二維陣列dp[length][2],其中length是房屋的數量。
- dp[i][0]表示不偷當前房屋時能偷到的最高金額
- dp[i][1]表示偷當前房屋時能偷到的最高金額
如果不偷當前房屋,那麼前一家偷不偷都是可以的,我們取最大值即可
dp[i][0]=max(dp[i-1][0],dp[i-1][1]);
如果偷當前房屋,那麼前一家肯定是不能偷的,所以
dp[i][1]=dp[i-1][0]+nums[i];
所以遞推公式很容易找出來,和第477題完全一樣。
邊界條件是
- dp[0][0]=0,第一家沒偷
- dp[0][1]=nums[0],第一家偷了
程式碼如下
public int robHelper(int[] nums) {
//邊界條件判斷
if (nums == null || nums.length == 0)
return 0;
int length = nums.length;
int[][] dp = new int[length][2];
dp[0][0] = 0;//第1家沒偷
dp[0][1] = nums[0];//第1家偷了
//從第2個開始判斷
for (int i = 1; i < length; i++) {
//下面兩行是遞推公式
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
dp[i][1] = dp[i - 1][0] + nums[i];
}
//最後取最大值即可
return Math.max(dp[length - 1][0], dp[length - 1][1]);
}
當然我們還可以進一步優化,因為上面的二維陣列中每次計算當前值的時候只和前面的兩個值有關,其他的都不會在用到了,所以這裡可以使用兩個變數即可,不需要申請一個二維陣列。
private int robHelper(int[] num) {
int steal = 0, noSteal = 0;
for (int j = 0; j < num.length; j++) {
int temp = steal;
steal = noSteal + num[j];
noSteal = Math.max(noSteal, temp);
}
return Math.max(steal, noSteal);
}
到這裡還沒完,上面我們假設所有的房屋沒有構成一個環,但這道題中所以的房屋是圍成一個環形的。
- 如果偷第1家,就不能偷最後一家,所以可偷的範圍是[0,length-2]。
- 如果不偷第1家,那麼就可以偷最後一家,可偷的範圍是[1,length-1]。
所以最終程式碼如下
public int rob(int[] nums) {
if (nums.length == 1)
return nums[0];
//可以偷第一家,但不能偷最後一家
int robFirst = robHelper(nums, 0, nums.length - 2);
//可以偷最後一家,但不能偷第一家
int robLast = robHelper(nums, 1, nums.length - 1);
//選擇偷第1家和不偷第1家結果的最大值
return Math.max(robFirst, robLast);
}
private int robHelper(int[] num, int start, int end) {
int steal = 0, noSteal = 0;
for (int j = start; j <= end; j++) {
int temp = steal;
steal = noSteal + num[j];
noSteal = Math.max(noSteal, temp);
}
return Math.max(steal, noSteal);
}
總結
這題與479,遞迴方式解打家劫舍和477,動態規劃解按摩師的最長預約時間其實很相似,程式碼完全可以照搬,然後再稍加修改即可。這裡稍微麻煩一點的是陣列是構成一個環,要避免第一個和最後一個同時選擇。