LeetCode Hot100 刷题笔记(8)—— 动态规划(一维、二维)

目录

前言

一、动态规划

1. 爬楼梯

2. 杨辉三角

3. 打家劫舍

4. 完全平方数

5. 零钱兑换

6. 单词拆分

7. 最长递增子序列

8. 乘积最大子数组

9. 分割等和子集

10. 最长有效括号

二、多维动态规划

11. 不同路径

12. 最小路径和

13. 最长回文子串

14. 最长公共子序列

15. 编辑距离


前言

一、动态规划:爬楼梯,杨辉三角,打家劫舍,完全平方数,零钱兑换,单词拆分,最长递增子序列,乘积最大子数组,分割等和子集,最长有效括号。

二、多维动态规划:不同路径,最小路径和,最长回文子串,最长公共子序列,编辑距离。

*** Trick

动态规划步骤:1. 确定初始状态;2. 确定边界条件;3. 确定状态转移方程。

*** 常考:0-1背包问题(对应题目:9、14、15)

class Solution(object):
    def canPartition(self, nums):
        # 0-1背包问题
        sums = sum(nums)
        if sums % 2 != 0:
            return False
        target = sums // 2
        n = len(nums)
        dp = [[False] * (target+1) for _ in range(n)]
        dp[0][0] = True
        for i in range(1, n):
            dp[i][0] = True
        for j in range(1, target+1):
            dp[0][j] = False
        for i in range(1, n):
            for j in range(1, target+1):
                if j >= nums[i]:
                    dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i]]
                else:
                    dp[i][j] = dp[i-1][j]
        return dp[n-1][target]

一、动态规划

1. 爬楼梯

原题链接:70. 爬楼梯 - 力扣(LeetCode)

# 1. 初始状态:爬到第1阶、第2届台阶的方法分别有1、2种
# 2. 确定边界条件
# 3. 动态转移方程:s(n) = s(n-1) + s(n-2)
class Solution(object):
    def climbStairs(self, n):
        s = [1, 2]   
        if n <=2:
            return s[n-1]
        else:
            for i in range(2, n):
                s.append(s[-1]+s[-2])
            return s[-1]

2. 杨辉三角

原题链接:118. 杨辉三角 - 力扣(LeetCode)

# 1. 初始状态:dp = [[1] * i for i in range(1, numRows+1)]
# 2. 边界条件:2<=i<numRows, 1<=j<numRows-2
# 3. 动态转移方程:dp[i][j] = dp[i][j-1] + dp[i][j]
class Solution(object):
    def generate(self, numRows):
        dp = [[1]*i for i in range(1, numRows+1)]
        if i<2:
            return dp
        for i in range(2, numRows):
            for j in range(1, i):
                dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
        return dp

3. 打家劫舍

原题链接:198. 打家劫舍 - 力扣(LeetCode)

class Solution(object):
    def rob(self, nums):
        nums.insert(0, 0)
        nums.insert(1, 0)
        dp = [0 for i in range(len(nums))]

        for i in range(2, len(nums)):
            dp[i] = max(dp[i-1], dp[i-2]+nums[i])
        return dp[-1]

4. 完全平方数

原题链接:279. 完全平方数 - 力扣(LeetCode)

# 1. 初始状态:dp = [i for i in range(n+1)]
# 2. 边界条件: wihle i-j*j >=0
# 3. 动态转移方程:dp[i] = min(dp[i-j*j]+1, dp[i])
class Solution(object):
    def numSquares(self, n):
        dp = [i for i in range(n+1)]
        for i in range(1, n+1):
            j = 1
            while i-j*j >= 0:
                dp[i] = min(dp[i-j*j]+1, dp[i])
                j+=1
        return dp[-1]

5. 零钱兑换

原题链接:322. 零钱兑换 - 力扣(LeetCode)

# 1. 初始状态:dp = [float("inf")] * (amount+1)
# 2. 边界条件:i-coin[j] >=0
# 3. 状态方程:dp[i] = min(dp[i-coin[j]] + 1, dp[i]), for j in len(coins)
class Solution(object):
    def coinChange(self, coins, amount):
        dp = [float("inf")] * (amount+1)
        dp[0] = 0
        for i in range(1, amount+1):
            for j in range(len(coins)):
                if i-coins[j] >= 0:
                    dp[i] = min(dp[i-coins[j]] + 1, dp[i])
        return dp[-1] if dp[-1]!=float("inf") else -1

6. 单词拆分

原题链接:139. 单词拆分 - 力扣(LeetCode)

class Solution(object):
    def wordBreak(self, s, wordDict):
        dp = [0] + [False for i in range(1, len(s)+1)]
        for i in range(len(s)+1):
            if dp[i] == True:
                for j in range(i+1, len(s)+1):
                    if s[i:j] in wordDict:
                        dp[j] =  True
        return dp[-1]

7. 最长递增子序列

原题链接:300. 最长递增子序列 - 力扣(LeetCode)

class Solution(object):
    def lengthOfLIS(self, nums):
        dp = [1 for i in range(len(nums))]
        for i in range(len(nums)):
            for j in range(i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[j]+1, dp[i])
        return max(dp)

8. 乘积最大子数组

原题链接:152. 乘积最大子数组 - 力扣(LeetCode)

class Solution(object):
    def maxProduct(self, nums):
        # 初始状态:max_pre = min_pre = num[0]
        # 状态转移方程:max_pre = max(max_pre*nums[i]), min_pre*nums[i], nums[i])
        res = nums[0]
        max_pre = min_pre = nums[0]
        for i in range(1, len(nums)):
            max_pre_ = max_pre * nums[i]
            min_pre_ = min_pre * nums[i]

            max_pre = max(max_pre_, min_pre_, nums[i])
            min_pre = min(max_pre_, min_pre_, nums[i])

            res = max(res, max_pre)
        return res

9. 分割等和子集

原题链接:416. 分割等和子集 - 力扣(LeetCode)

class Solution(object):
    def canPartition(self, nums):
        # 0-1背包问题
        sums = sum(nums)
        if sums % 2 != 0:
            return False
        target = sums // 2
        n = len(nums)
        dp = [[False] * (target+1) for _ in range(n)]
        dp[0][0] = True
        for i in range(1, n):
            dp[i][0] = True
        for j in range(1, target+1):
            dp[0][j] = False
        for i in range(1, n):
            for j in range(1, target+1):
                if j >= nums[i]:
                    dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i]]
                else:
                    dp[i][j] = dp[i-1][j]
        return dp[n-1][target]

10. 最长有效括号

原题链接:32. 最长有效括号 - 力扣(LeetCode)

class Solution(object):
    def longestValidParentheses(self, s):
        stack = [-1]
        res = 0
        for i in range(len(s)):
            if s[i] == '(':
                stack.append(i)
            else:
                stack.pop()
                if not stack:
                    stack.append(i)
                else:
                    res = max(res, i-stack[-1])
        return res

二、多维动态规划

11. 不同路径

原题链接:62. 不同路径 - 力扣(LeetCode)

class Solution(object):
    def uniquePaths(self, m, n):
        # 初始状态dp[i][0] = 1, dp[0][j] = 1
        # 状态转移方程:dp[i][j] = dp[i-1][j] + dp[i][j-1]
        dp = [[0] * n for _ in range(m)]
        for i in range(m):
            dp[i][0] = 1
        for j in range(n):
            dp[0][j] = 1
        for i in range(1, m):
            for j in range(1, n):
                dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[-1][-1]

12. 最小路径和

原题链接:64. 最小路径和 - 力扣(LeetCode)

class Solution(object):
    def minPathSum(self, grid):
        # dp[i][0] = dp[i-1] + grid[i][0], dp[0][j] = dp[0][j-1] + grid[0][j]
        # dp[i][j] = min(dp[i-1][j] + grid[i][j], dp[i][j-1]+ grid[i][j])

        m, n =  len(grid), len(grid[0])
        dp = [[0] * n for _ in range(m)]
        dp[0][0] = grid[0][0]
        for i in range(1, m):
            dp[i][0] = dp[i-1][0] + grid[i][0]
        for j in range(1, n):
            dp[0][j] = dp[0][j-1] + grid[0][j]
        for i in range(1, m):
            for j in range(1, n):
                dp[i][j] = min(dp[i-1][j] + grid[i][j], dp[i][j-1]+ grid[i][j])
        return dp[-1][-1]

13. 最长回文子串

原题链接:5. 最长回文子串 - 力扣(LeetCode)

class Solution(object):
    def longestPalindrome(self, s):
        n = len(s)
        dp = [[False] * n for _ in range(n)]
        start, max_len = 0, 0
        for right in range(n):
            for left in range(right+1):
                span = right - left + 1
                if span == 1:
                    dp[left][right] = True
                elif span == 2:
                    dp[left][right] = s[left] == s[right]
                else:
                    dp[left][right] = dp[left+1][right-1] and s[left] == s[right]
                
                if dp[left][right]:
                    if span > max_len:
                        max_len = span
                        start = left
        return s[start: start+max_len]

14. 最长公共子序列

原题链接:1143. 最长公共子序列 - 力扣(LeetCode)

class Solution(object):
    def longestCommonSubsequence(self, text1, text2):
        # 0-1 背包问题
        # if t1 == t2: dp[i+1][j+1] = dp[i][j] + 1
        # else: dp[i+1][j+1] = max(dp[i+1][j], dp[i][j+1])
        m, n = len(text1), len(text2)
        dp = [[0]* (n+1) for _ in range(m+1)]
        for i, t1 in enumerate(text1):
            for j, t2 in enumerate(text2):
                if t1 == t2:
                    dp[i+1][j+1] = dp[i][j] + 1
                else:
                    dp[i+1][j+1] = max(dp[i+1][j], dp[i][j+1])
        return dp[-1][-1]

15. 编辑距离

原题链接:72. 编辑距离 - 力扣(LeetCode)

class Solution(object):
    def minDistance(self, word1, word2):
        # 0-1 背包问题
        # 动态转移方程:dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1分别对应插入、删除、替换操作
        """
            0 r o s
        0   0 1 2 3
        h   1
        o   2
        r   3
        s   4
        e   5
        """
        m, n = len(word1), len(word2)
        dp = [[0] * (n+1) for _ in range(m+1)]
        for i in range(1, m+1):
            dp[i][0] = i
        for j in range(1, n+1):
            dp[0][j] = j
        for i, w1 in enumerate(word1):
            for j, w2 in enumerate(word2):
                if w1 == w2:
                    dp[i+1][j+1] = dp[i][j]
                else:
                    dp[i+1][j+1] = min(dp[i+1][j], dp[i][j+1], dp[i][j]) + 1 
        return dp[-1][-1]
### LeetCode Hot100 技巧与策略 LeetCodeHot100 是许多程序员提升算法能力的重要资源之一。以下是针对该部分的一些技巧和策略: #### 1. **制定合理的计划** - 推荐按照一定的顺序进行,例如优先完成热 HOT 100 和精选 TOP 面试[^1]。这些目覆盖了大部分常见的算法知识点和技术面试考点。 - 将目按难度分类(简单、中等、困难),逐步攻克不同层次的挑战。 #### 2. **利用辅助工具提高效率** - 借助公子龙大佬开发的网站及其插件功能[^2],可以更高效地查找解思路并学习经典算法框架。 - 插件能够帮助用户快速定位到某类问的核心解决方法,减少无谓的时间浪费。 #### 3. **注重总结归纳** - 对于每一道做过的目,都应该尝试分析其背后的通用模式以及可能的变化形式[^4]。比如环形链表的相关变种问(如回文链表)往往具有相似逻辑结构。 - 定期整理笔记,记录下自己容易遗忘的知识点或者巧妙的解决方案。 #### 4. **模拟真实场景训练** - 如果目标是为了准备技术面试,则建议模仿实际考试环境来进行限时答练习[^5]。这样不仅可以锻炼思维敏捷度还能增强抗压能力。 - 同时也要关注时间复杂度空间复杂度优化等方面的要求。 #### 5. **积极参与社区讨论** - 加入像寸铁这样的优质创作者所创建的学习小组或论坛,与其他志同道合者共同探讨难解答方案。 - 学习他人优秀的思维方式有助于拓宽视野开阔眼界。 下面提供一段简单的 Python 实现代码作为例子展示如何处理其中一类常见数据结构——链表的操作: ```python class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next def hasCycle(head: ListNode) -> bool: slow = fast = head while fast and fast.next: slow = slow.next # Move one step at a time. fast = fast.next.next # Move two steps at once. if slow == fast: # If there's an intersection point found. return True return False # No cycle detected after traversal completes. ``` 此函数用于检测单向链表是否存在闭环现象,采用双指针法实现。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值