LeetCode 專題:動態規劃

斐波拉契數列

“斐波拉契數列”問題是認識動態規劃非常好的例子。

LeetCode 第 70 題:Climbing Stairs

提示:斐波拉契數列,畫出樹形結構,發現大量重疊子問題,所以用動態規劃。

LeetCode 第 120 題:Triangle

提示:狀態 + 狀態轉移方程。

Python 代碼:

class Solution:
    def minimumTotal(self, triangle):
        """
        :type triangle: List[List[int]]
        :rtype: int
        """
        l = len(triangle)
        if l == 0:
            return 0
        dp = triangle[-1]
        for i in range(l - 2, -1, -1):
            for j in range(len(triangle[i])):
                dp[j] = min(dp[j], dp[j + 1]) + triangle[i][j]
        return dp[0]

LeetCode 第 64 題 :Minimum Path Sum 最小路徑和

給定一個包含非負整數的 m x n 網格,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和為最小。

提示:狀態 + 狀態轉移方程。也可以用組合數來做。

LeetCode 第 343 題: Integer Break

提示:關鍵在“將其拆分為至少兩個正整數的和”,還可以使用貪心算法。

Python 代碼:

class Solution:
    def integerBreak(self, n):
        """
        :type n: int
        :rtype: int
        """
        product = [0] * (n + 1)
        product[1] = 1
        for i in range(2, n + 1):
            product_max = 0
            for j in range(1, i):
                product_max = max(j * product[i - j], product_max, j * (i - j))
            product[i] = product_max
        return product[n]

LeetCode 第 279 題:完全平方數(特別重要)

傳送門:279. 完全平方數。提示:1、BFS;2、動態規劃,狀態就是題目中要問的問題。

Python 代碼:


LeetCode 第 91 題:解碼方法(特別重要)

提示:注意數組下標越界問題。

class Solution:

    def numDecodings(self, s):
        l = len(s)
        if l == 0:
            return 0

        if l == 1:
            return 1 if s[0] != '0' else 0
        dp = [0 for _ in range(l)]
        dp[0] = 1 if s[0] != '0' else 0
        for i in range(1, l):
            if s[i] != '0':
                # 如果不是 '0' ,那么 s[i] 就可以編碼,所以 cur 就至少是 dp[i-1]
                dp[i] += dp[i - 1]
            if 9 < int(s[i - 1:i + 1]) < 27:
                # 可以和前面的數字組成一個編碼
                if i - 2 < 0:
                    # 12
                    dp[i] += 1
                else:
                    dp[i] += dp[i - 2]
        return dp[l - 1]

不同的路徑

LeetCode 第 62 題:不同路徑

提示:還可以用組合數公式。

LeetCode 第 63 題:不同路徑 II

提示:分類討論。

打家劫舍系列

LeetCode 第 198 題:House Robber

提示:狀態轉移,如果使用“滾動數組”,可以把空間復雜度降到 O(1)。

Python 代碼:掌握“滾動數組”的寫法

class Solution:
    def rob(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        
        size = len(nums)
        
        if size == 0:
            return 0

        if size <= 2:
            return max(nums)
        
        pre = nums[0]
        cur = max(nums[0], nums[1])
        
        for i in range(2, size):
            temp = cur # 因為 cur 會被覆蓋,所以先把 cur 存一下,最后再賦值給 pre
            cur = max(cur, pre + nums[i])
            pre = temp
        return cur

LeetCode 第 213 題:House Robber II

提示:基于上一個問題。

LeetCode 第 337 題:House Robber III

提示:遞歸求解,先寫遞歸終止條件。

Java 代碼:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */

class Solution {
    public int rob(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int val = root.val;
        if(root.left != null){
            val+= rob(root.left.left) + rob(root.left.right);
        }

        if(root.right != null){
            val+= rob(root.right.left) + rob(root.right.right);
        }
        return Math.max(val, rob(root.left) + rob(root.right));
    }
}

LeetCode 第 309 題:Best Time to Buy and Sell Stock with Cooldown

(狀態機解決)

背包問題—— Knapsack01(6題)

LeetCode 第 416 題:分割等和子集

提示:0-1 背包問題,思路是:物品一個一個加進來。

狀態:dp[i][j]:考慮索引是 [0,i] 這個區間的物品是否能夠填充容量為 j 的背包。

Python 代碼:這是用二維數組的寫法,試試看能不能用一維數組寫出來

class Solution:

    def canPartition(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """

        l = len(nums)
        if l == 0:
            return False
        s = sum(nums)
        half = s // 2
        if s % 2 == 1:
            return False

        dp = [[0 for _ in range(half + 1)] for _ in range(l)]
        # 先填第 1 行,即 nums[0] 這個物品,是不是能夠填滿 0,1,。。。,half 的背包
        for i in range(half + 1):
            dp[0][i] = False if nums[0] != i else True
        # 再填后面幾行
        for i in range(1, l):
            for j in range(half + 1):
                # 不放這個物品 、 放這個物品
                dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]]

        return dp[-1][-1]

LeetCode 第 322 題 :Coin Change

提示:抓住“你可以認為每種硬幣的數量是無限的”。

Python 代碼:掌握 BFS 的寫法,關鍵是畫圖,還有里面的入隊邏輯不能錯,同類問題還有完全平方數

class Solution:
    def coinChange(self, coins, amount):
        """
        :type coins: List[int]
        :type amount: int
        :rtype: int
        """
        # 特判 [2147483647]
        # 2
        if amount <= 0:
            return 0
        coins.sort()
        if coins[0] > amount:
            return -1
        # 不要用數組,用哈希表
        # marked = [False for _ in range(amount)]
        marked = set()

        queue = [(0, amount)]
        while queue:
            # print(queue)
            level, top = queue.pop(0)
            level += 1
            for coin in coins:
                residue = top - coin
                if residue == 0:
                    return level
                if residue > 0 and residue not in marked:
                    queue.append((level, residue))
                    marked.add(residue)
                if residue < 0:
                    # 有了前面排序做保證
                    break
        return -1

Java 代碼:動態規劃

public class Solution {

    public int coinChange(int[] coins, int amount) {
        int len = coins.length;
        if (amount == 0) {
            return 0;
        }
        if (coins == null || len == 0) {
            return -1;
        }

        // 要把 0 算進去,這是基本情況,所以開辟 coins 這么多空間的
        // 組成當前的總價值需要的最少硬幣數
        int[] dp = new int[amount + 1];
        // dp 問題的套路,把初值先算出來,因為遞歸到底的時候,可能到這個地方
        dp[0] = 0;

        for (int i = 1; i <= amount; i++) {
            int minCount = Integer.MAX_VALUE;
            for (int j = 0; j < coins.length; j++) {
                if (i - coins[j] >= 0 && dp[i - coins[j]] != -1) {
                    minCount = Integer.min(dp[i - coins[j]] + 1, minCount);
                }
            }
            dp[i] = minCount == Integer.MAX_VALUE ? -1 : minCount;
        }
        return dp[amount];
    }

    public static void main(String[] args) {
        Solution solution = new Solution();

        int[] coins = {1};
        int amount = 0;
        int coinChange = solution.coinChange(coins, amount);
        System.out.println(coinChange);
    }
}

LeetCode 第 377 題 :Combination Sum IV

給定一個由正整數組成且不存在重復數字的數組,找出和為給定目標正整數的組合的個數。

Java 代碼:

class Solution {
    public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target + 1];
        // 這一步很關鍵,想想為什么 dp[0] 是 1
        // 因為 0 表示空集,空集和它"前面"的元素湊成一種解法,所以是 1
        // 這一步要加深體會
        dp[0] = 1;
        for (int i = 1; i < target + 1; i++) {
            for (int num : nums) {
                if (i >= num) {
                    dp[i] = dp[i] + dp[i - num];
                }
            }
        }
        return dp[target];
    }
}

Java 代碼:

public int combinationSum4(int[] nums, int target) {
    int[] memo = new int[target + 1];
    memo[0] = 1;
    for (int i = 0; i < target; i++) {
        for (int num : nums) {
            if (i + num <= target) {
                memo[i + num] += memo[i];
            }
        }
    }
    return memo[target];
}

LeetCode 第 474 題:Ones and Zeroes

提示:二維背包問題,數組有三維,可以降到一維。

LeetCode 第 139 題:Word Break(重點)

提示:用“記憶化遞歸”和“動態規劃”都做一下。先把單詞列表放到哈希表里,然后先判處。

LeetCode 第 494 題:Target Sum

傳送門:494. 目標和

給定一個非負整數數組,a1, a2, ..., an, 和一個目標數,S?,F在你有兩個符號 +-。對于數組中的任意一個整數,你都可以從 +-中選擇一個符號添加在前面。

返回可以使最終數組和為目標數 S 的所有添加符號的方法數。

示例 1:

輸入: nums: [1, 1, 1, 1, 1], S: 3
輸出: 5
解釋: 

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

一共有5種方法讓最終目標和為3。

注意:

  1. 數組的長度不會超過20,并且數組中的值全為正數。
  2. 初始的數組的和不會超過1000。
  3. 保證返回的最終結果為32位整數。

提示:1、回溯;2、0-1 背包問題。要處理一些細節。

例題1:LeetCode 第 300 題:Longest Increasing Subsequence

提示:1、動態規劃;2、使用二分法。

練習1:LeetCode 第 376 題:Wiggle Subsequence

提示:1、狀態機;2、貪心;

最長公共子序列問題(Longest Common Sequence)

地下城游戲(困難)

最長連續子數組的和

《編程之法》 P10 字典排序

看看哪里用到了“滾動數組”的技巧,典型的一維動態規劃問題

分割回文串問題

LeetCode 第 131 題:分割回文串1:dfs 或者動態規劃

LeetCode 第 132 題:分割回文串2(困難):動態規劃

LeetCode 第 62 題:最短的唯一路徑(1)

思路:典型的動態規劃問題。用一維的空間復雜度就可以寫出來。

class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        if m == 0:
            return 0
        dp = [1] * n
        for row in range(m - 1):
            for col in range(1, n):
                dp[col] += dp[col-1]
        return dp[-1]

LeetCode 第 63 題:最短的唯一路徑(2)

思路:典型的動態規劃問題。用一維的空間復雜度就可以寫出來。

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """

        m = len(obstacleGrid)
        if m == 0:
            return 0
        n = len(obstacleGrid[0])

        if obstacleGrid[0][0] == 1:
            return 0

        dp = [0] * n
        # 這一步不要忘記了
        dp[0] = 1
        # 再寫后面幾行
        for row in range(m):
            for col in range(n):
                # 【就分下面這兩種情況就可以了】
                if obstacleGrid[row][col] == 1:
                    dp[col] = 0
                elif col > 0:
                    dp[col] += dp[col - 1]
                else:
                    # 第 0 列不是 0 就是 1
                    # 0 的情況首先判斷了
                    # 什么都不做
                    pass
        return dp[-1]

LeetCode 第 64 題:路徑和最小

class Solution(object):
    def minPathSum(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        m = len(grid)
        if m == 0:
            return 0
        n = len(grid[0])

        for col in range(1, n):
            # 第 0 行特殊處理,不要忘記了
            grid[0][col] += grid[0][col - 1]
        for row in range(1, m):
            grid[row][0] += grid[row - 1][0]
            for col in range(1, n):
                grid[row][col] += min(grid[row - 1][col], grid[row][col - 1])
        return grid[-1][-1]

LeetCode 第 115 題:不同的子序列(困難)

傳送門:115. 不同的子序列

要求:給定一個字符串 S 和一個字符串 T,計算在 S 的子序列中 T 出現的個數。

一個字符串的一個子序列是指,通過刪除一些(也可以不刪除)字符且不干擾剩余字符相對位置所組成的新字符串。(例如,"ACE""ABCDE" 的一個子序列,而 "AEC" 不是)

分析:首先,空串就是 1 種情況。

使用二維動態規劃。狀態:到目前為止匹配的種數。

  • 這是一道典型的動態規劃問題,并且還是二維的動態規劃問題
  • 而且這個狀態還比較難想,要結合具體的例子才能找到狀態轉移方程
  • 首先要注意到最特殊的情況:當 t == "" 為 true 的時候,不論 s 是什么,s 都有一個子串(那就是空字符串)能和此時的 t 匹配。
  • 因此 dp[i][0] = 1,所以 0 這個位置就被占據了,這就是為什么 dp 要設置成 dp[slen + 1][tlen + 1] 的原因。

參考資料:
https://leetcode.com/problems/distinct-subsequences/discuss/37320/Java-.
https://www.cnblogs.com/higerzhang/p/4133793.html
http://www.lxweimin.com/p/510906b900ec

public class Solution2 {

    public int numDistinct(String s, String t) {
        int slen = s.length();
        int tlen = t.length();
        if (slen == 0 || tlen == 0) {
            return 0;
        }
        int[][] dp = new int[slen + 1][tlen + 1];
        for (int i = 0; i < slen; i++) {
            dp[i][0] = 1;
        }
        for (int i = 1; i < slen + 1; i++) {
            for (int j = 1; j < tlen + 1; j++) {
                dp[i][j] = dp[i - 1][j];
                if (s.charAt(i - 1) == t.charAt(j - 1)) {
                    dp[i][j] += dp[i - 1][j - 1];
                }
            }
        }
        return dp[slen][tlen];
    }

    public static void main(String[] args) {
        String s = "babgbag";
        String t = "bag";
        Solution2 solution = new Solution2();
        int numDistinct = solution.numDistinct(s, t);
        System.out.println(numDistinct);
    }
}

(本節完)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容