面試必備算法題集之「動態規劃」Ⅰ

題目來源:LeetCode-騰訊-動態規劃
題庫鏈接:LeetCode傳送門

前言

這份題集來源於 LeetCode-騰訊-動態規劃 ,懷着想學習動態規劃的心(帶着害怕面試被問到的恐懼 )做了第一份DP題集,希望可以志同道合的夥伴可以共同進步。

1. 爬樓梯

假設你正在爬樓梯。需要 n 階你才能到達樓頂。

每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?

注意:給定 n 是一個正整數。

示例 1:
輸入: 2
輸出: 2

示例 2:

輸入: 3
輸出: 3

狀態轉移方程:dp[i]=dp[i1]+dp[i2]dp[i] = dp[i-1]+dp[i-2]

class Solution {
    public int climbStairs(int n) {
        if (n == 1) {
            return 1;
        } else if (n == 2) {
            return 2;
        }
        int a = 1, b = 2;
        for (int i = 2; i < n; i++) {
            b = a + b;
            a = b - a;
        }
        return b;
    }
}

2. 最大子序列和

給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。

示例:
輸入: [-2,1,-3,4,-1,2,1,-5,4],
輸出: 6

解釋: 連續子數組 [4,-1,2,1] 的和最大,爲 6。

狀態轉移方程:dp[i]=max(dp[i1]+nums[i],nums[i])dp[i]=max(dp[i-1]+nums[i], nums[i])
PS: dp[i]dp[i] 表示的是以nums[i]nums[i]結尾的子序列的最大值

class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        int dp = 0, ans = Integer.MIN_VALUE;
        
        for (int i = 0; i < n; i++) {
            dp = Math.max(dp + nums[i], nums[i]);
            ans = Math.max(dp, ans);
        }
        return ans;
    }
}

3. 買賣股票的最佳時機

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

如果你最多隻允許完成一筆交易(即買入和賣出一支股票一次),設計一個算法來計算你所能獲取的最大利潤。

注意:你不能在買入股票前賣出股票。

示例 1:
輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。

示例 2:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。

狀態轉移方程:dp[i]=max(dp[i1],prices[i]minPrice)dp[i] = max(dp[i-1],prices[i]-minPrice)
PS:其中 minPriceminPrice 表示 i 天前的最低價格。

class Solution {
    public int maxProfit(int[] prices) {
        int dp = 0, minPrice = Integer.MAX_VALUE;
        //計算狀態轉移方程,通過滾動變量的形式優化空間複雜度
        for (int i = 0; i < prices.length; i++) {
            minPrice = Math.min(prices[i], minPrice);
            dp = Math.max(dp, prices[i] - minPrice);
        }
        return dp;
    }
}

4. 買賣股票的最佳時機 II

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。

注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:
輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。

在這裏插入圖片描述
狀態轉移方程(根據上面的樹形圖推導):
dp[i][0]=max(dp[i1][0],dp[i1][1]+prices[i])dp[i][1]=max(dp[i1][1],dp[i1][0]prices[i]) dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])\\ dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])
PS:dp[i][0]dp[i][0]表示第 i 天持有的現金的最大利潤,dp[i][1]dp[i][1]表示第 i 天持有的股票的最大利潤。

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        if (n < 2) {
            return 0;
        }
        
        int[][] dp = new int[n][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        
        for (int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
        }
        return dp[n - 1][0];
    }
}

可以看一下這篇題解:暴力搜索、貪心算法、動態規劃
(明明貪心就很好做……)

5. 不同路徑

一個機器人位於一個 m x n 網格的左上角 (起始點在下圖中標記爲“Start” )。

機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記爲“Finish”)。

問總共有多少條不同的路徑?
在這裏插入圖片描述
示例 1:
輸入: m = 3, n = 2
輸出: 3
解釋:
從左上角開始,總共有 3 條路徑可以到達右下角。

  1. 向右 -> 向右 -> 向下
  2. 向右 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向右

示例 2:
輸入: m = 7, n = 3
輸出: 28

狀態轉移方程:dp[i][j]=dp[i1][j]+dp[i][j1]dp[i][j] = dp[i - 1][j] + dp[i][j-1]

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[n][m];
        for (int i = 0; i < n; i++) {
            dp[i][0] = 1;
        }
        for (int i = 0; i < m; i++) {
            dp[0][i] = 1;
        }
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < m; j++) {
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[n - 1][m - 1];
    }
}

(明明排列組合更加方便……)

6. 子集

給定一組不含重複元素的整數數組 nums,返回該數組所有可能的子集(冪集)。
說明:解集不能包含重複的子集。

示例:
輸入: nums = [1,2,3]
輸出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]

狀態轉移方程:set[k]=set[j]+nums[i]set[k] = set[j] + nums[i]
PS:其中 set[k]set[k] 表示新找的一個集合, set[j]set[j] 表示原先找到的集合, nums[i]nums[i] 表示新的數字。

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> ans = new ArrayList<>();
        // 首先添加空集
        ans.add(new ArrayList<>());
        for (int i = 0; i < nums.length; i++) {
            // 由於下面過程會改變容量,所以要先保存
            int tSize =  ans.size();
            // 遍歷已經找到的集合
            for (int j = 0; j < tSize; j++) {
                List<Integer> temp = new ArrayList<>(ans.get(j));
                // 在已經找到的集合的基礎上加上新的數字就是新的集合了
                temp.add(nums[i]);
                ans.add(temp);
            }
        }
        return ans;
    }
}

不知道爲啥這道題在動態規劃題單裏面……(大霧


總結

終於寫完了,感覺很多題就用不着去DP,直接貪心就好了,強行DP做到我裂開。由於時間緊迫,題解也沒有寫得很詳細,就將就着看吧,有問題可以留言提出來哦~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章