70、 爬樓梯(爬一爬二)
假設你正在爬樓梯。需要 n 步你才能到達樓頂。
每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?
注意:給定 n 是一個正整數。
示例 1:
輸入: 2
輸出: 2
解釋: 有兩種方法可以爬到樓頂。
1. 1 步 + 1 步
2. 2 步
示例 2:
輸入: 3
輸出: 3
解釋: 有三種方法可以爬到樓頂。
1. 1 步 + 1 步 + 1 步
2. 1 步 + 2 步
3. 2 步 + 1 步
到達n的“前一步”走法可以是:從n-1處爬1階樓梯,或者從n-2處爬2階樓梯。
必須從頭走到尾纔算一種走法,每一步的選擇只是走法的一部分,而不是一個新的走法。從後向前思考比較正確。
S(n) = S(n-1) + S(n-2)
class Solution {
public int climbStairs(int n) {
if(n <= 0)
return 0;
if(n == 1)
return 1;
if(n == 2)
return 2;
int a = 1;
int b = 2;
int res = 0;
for(int i=3;i<=n;i++){
res = a + b;
a = b;
b = res;
}
return res;
}
}
下面遞歸會超時!!!
class Solution {
public int climbStairs(int n) {
if(n <= 0)
return 0;
if(n == 1)
return 1;
if(n == 2)
return 2;
return climbStairs(n-1) + climbStairs(n-2);
}
}
198、打家劫舍(搶不不搶)
你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。
給定一個代表每個房屋存放金額的非負整數數組,計算你在不觸動警報裝置的情況下,能夠偷竊到的最高金額。
示例 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 。
class Solution {
public int rob(int[] nums) {
int len = nums.length;
if(len <= 0)
return 0;
if(len == 1)
return nums[0];
if(len == 2)
return Math.max(nums[0],nums[1]);
int norob = nums[0];//不搶第二家(那就搶第一家)
int rob = nums[1];//搶第二家(那就不搶第一家)
for(int i=2;i<len;i++){
int tmp = norob;
norob = Math.max(norob,rob);//不搶第三家 (i+1家)
rob = nums[i] + tmp;//搶第三家 (i+1家)
}
return Math.max(norob,rob);
}
}
不搶第三家 ==》就是搶和不搶第二家的最大值。
搶第三家===》就是第三家的值加上不搶第二家的值。需要臨時變量保存一個變量。
213.、打家劫舍 II
你是一個專業的小偷,計劃偷竊沿街的房屋,每間房內都藏有一定的現金。這個地方所有的房屋都圍成一圈,這意味着第一個房屋和最後一個房屋是緊挨着的。同時,相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。
給定一個代表每個房屋存放金額的非負整數數組,計算你在不觸動警報裝置的情況下,能夠偷竊到的最高金額。
示例 1:
輸入: [2,3,2]
輸出: 3
解釋: 你不能先偷竊 1 號房屋(金額 = 2),然後偷竊 3 號房屋(金額 = 2), 因爲他們是相鄰的。
示例 2:
輸入: [1,2,3,1]
輸出: 4
解釋: 你可以先偷竊 1 號房屋(金額 = 1),然後偷竊 3 號房屋(金額 = 3)。
偷竊到的最高金額 = 1 + 3 = 4 。
class Solution {
public int robCycle(int[] nums,int low,int high){
int len = high - low + 1;
if(len == 1)
return nums[low];
if(len == 2)
return Math.max(nums[low],nums[high]);
int norob = nums[low];
int rob = nums[low+1];
for(int i=low+2;i<=high;i++){//注意:i的初始值和最後的值
int tmp = norob;
norob = Math.max(norob,rob);
rob = nums[i] + tmp;//注意:這裏是tmp
}
return Math.max(norob,rob);
}
public int rob(int[] nums) {
if(nums.length == 0)
return 0;
if(nums.length == 1)
return nums[0];
int res1 = robCycle(nums,0,nums.length-2);
int res2 = robCycle(nums,1,nums.length-1);
return Math.max(res1,res2);
}
}
母牛生產(今年的數量,去年的數量,新增的數量)
程序員代碼面試指南-P181
題目描述:假設農場中成熟的母牛每年都會生 1 頭小母牛,並且永遠不會死。第一年有 1 只小母牛,從第二年開始,母牛開始生小母牛。每隻小母牛 3 年之後成熟又可以生小母牛。給定整數 N,求 N 年後牛的數量。
新增的數量是距今3年前的牛的數量
f[i]代表第i年母牛的數量,那麼f[i]=f[i-1]+f[i-3],意思是:
第i年的母牛頭數 = 去年的木牛頭數 + 新生的小牛頭數(只有距第i年3年前的牛才能生小牛)。
寫代碼的時候i=1開始比較方便!
信件錯排(考慮三封信件之間的關係)
題目描述:有 N 個 信 和 信封,它們被打亂,求錯誤裝信方式的數量。
定義一個數組 dp 存儲錯誤方式數量,dp[i] 表示前 i 個信和信封的錯誤方式數量。假設第 i 個信裝到第 j 個信封裏面,而第 j 個信裝到第 k 個信封裏面。根據 i 和 k 是否相等,有兩種情況:
i==k,交換 i 和 k 的信後,它們的信和信封在正確的位置,但是其餘 i-2 封信有 dp[i-2] 種錯誤裝信的方式。由於 j 有 i-1 種取值,因此共有 (i-1)*dp[i-2] 種錯誤裝信方式。
i != k,交換 i 和 j 的信後,第 i 個信和信封在正確的位置,其餘 i-1 封信有 dp[i-1] 種錯誤裝信方式。由於 j 有 i-1 種取值,因此共有 (i-1)*dp[i-1] 種錯誤裝信方式。
綜上所述,錯誤裝信數量方式數量爲:
dp[ i ] = (i - 1) * dp[ i - 2] + (i - 1) * dp[ i - 1];
dp[N] 即爲所求。
64、最小路徑和
給定一個包含非負整數的 m x n 網格,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和爲最小。
說明:每次只能向下或者向右移動一步。
示例:
輸入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
輸出: 7
解釋: 因爲路徑 1→3→1→1→1 的總和最小。
class Solution {
public int minPathSum(int[][] grid) {
if(grid == null)
return 0;
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
for(int i=1;i<m;i++){
dp[i][0] = dp[i-1][0] + grid[i][0];
}
for(int j=1;j<n;j++){
dp[0][j] = dp[0][j-1] + grid[0][j];
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j] = Math.min(dp[i][j-1],dp[i-1][j]) + grid[i][j];
}
}
return dp[m-1][n-1];
}
}
注意:先把第0行和第0列進行初始化(第一行只能從左一直往右走,而第一列只能從上一直往下走,並且將經過的點累加起來。)
狀態轉移方程爲dp[i][j] = min{dp[i - 1][j], dp[i][j - 1]} + m[i][j].可以用一個二維的dp矩陣來求解.對於dp矩陣,第一行和第一列只會有一種走法,就是從左到右或者從上到下的累加,所以可以先進行初始化,然後其他元素就可以用過轉移方程一個一個填充.
邊界條件:if(m==null||m.length==0||m[0]==null||m[0].length==0)
優化:使用一個一維數組在進行這個迭代過程。
class Solution {
public int minPathSum(int[][] grid) {
if(grid == null)
return 0;
int row = grid.length;
int col = grid[0].length;
int[] dp = new int[col];
dp[0] = grid[0][0];
//計算第一行的最短路徑
for(int j=1;j<col;j++){
dp[j] = dp[j-1] + grid[0][j];
}
//計算除了第一行的其他最小路徑和
for(int i=1;i<row;i++){
//dp[0]代表每一行最左邊的dp,後一行的dp覆蓋前一行的dp
dp[0] = dp[0] + grid[i][0];
for(int j=1;j<col;j++){
dp[j] = Math.min(dp[j-1],dp[j]) + grid[i][j];
}
}
return dp[col-1];
}
}
本題中的壓縮空間的方法幾乎可以應用到所有的二維動態規劃的題目,通過一個數組滾動更新的方式無疑節省了大量的空間。沒有優化之前,求某個位置動態規劃值的過程是在矩陣中進行兩次尋址,程序的常數時間也得到了一定程度的加速,但是空間壓縮的方法是有侷限性的,如果本題改成打印具有最小路徑和的路徑,就不能使用空間壓縮的方法。