当前位置: 首页 > news >正文

初学python的我开始Leetcode题15-2

提示:100道LeetCode热题15-2主要是动态规划相关,包括三题:完全平方数、零钱兑换、单词拆分。由于初学,所以我的代码部分仅供参考。


前言

上一次的三道动态规划是常见基本练习,这次学习三个新的题目~

粘贴题目有点黑乎乎的,试图在csdn更改格式,后来发现还不如直接粘贴方便易看,毕竟改成正文格式的话次方数还不会展示出LaTeX结果出来/(ㄒoㄒ)/~~


提示:以下是本篇文章正文内容,下面结果代码仅供参考

题目1:完全平方数

1.题目要求:

题目如下:

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,149 和 16 都是完全平方数,而 3 和 11 不是。

示例 1:

输入:n = 12
输出:3 
解释:12 = 4 + 4 + 4

示例 2:

输入:n = 13
输出:2
解释:13 = 4 + 9

提示:

  • 1 <= n <= 10^{4}

代码框架已经提供如下:

class Solution(object):

    def numSquares(self, n):

        """

        :type n: int

        :rtype: int

        """

2.结果代码:

class Solution(object):def numSquares(self, n):""":type n: int:rtype: int"""dp = [float('inf')] * (n + 1)dp[0] = 0max_square = int(n ** 0.5)          # 兼容低版本squares = [j * j for j in range(1, max_square + 1)]for i in range(1, n + 1):for s in squares:if s > i:breakdp[i] = min(dp[i], dp[i - s] + 1)return dp[n]

说明:

1. 题目到底让我们干什么?

给你一个数字 n,问“最少”需要几个完全平方数(1、4、9、16…)加起来正好等于 n
举例:

  • n = 12 → 最少 3 个数:4 + 4 + 4

  • n = 13 → 最少 2 个数:4 + 9

2. 为什么用“动态规划”?

想象你有一张表,第 i 格写着“和为 i 的最少平方数个数”。
我们从 i = 0 开始,一格一格填到 i = n,最后第 n 格里的数字就是答案。

3. 先搭好“表”

dp = [float('inf')] * (n + 1)   # 先全部写成“无穷大”,表示暂时不知道
dp[0] = 0                       # 和为 0 当然用 0 个数
  • dp[i] 就是上面说的“第 i 格”。

  • 先把它们都设成“无穷”,后面再慢慢改小。

4. 把所有“可能用到的平方数”列出来

max_square = int(n ** 0.5)      # 计算最大平方根,向下取整
squares = [j * j for j in range(1, max_square + 1)]
  • 如果 n = 12max_square = 3,所以 squares = [1, 4, 9]

  • 这些就是后面能用的“砖块”。

5. 开始填表(核心!)

for i in range(1, n + 1):       # 从 1 填到 nfor s in squares:           # 每个 i 都试一下所有平方数if s > i:               # 平方数太大了,没必要再试breakdp[i] = min(dp[i], dp[i - s] + 1)

这句 dp[i] = min(...) 最关键:

  • 含义:如果我用了一块 s(比如 4),那剩下的 i - s(比如 12 - 4 = 8)早就算过,用了 dp[8] 个数;再加上刚用的这块 4,就是 dp[8] + 1 个数。

  • 把所有可能的 s 都试一遍,取最小值,就能更新 dp[i]

6. 答案在哪?

填完表后,dp[n] 就是和为 n 的最少平方数个数,直接返回即可:

return dp[n]

题目2:零钱兑换

1.题目要求:

题目如下:

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1

示例 2:

输入:coins = [2], amount = 3
输出:-1

示例 3:

输入:coins = [1], amount = 0
输出:0

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 2^{31} - 1
  • 0 <= amount <= 10^{4}

代码框架已经提供如下:

class Solution(object):

    def coinChange(self, coins, amount):

        """

        :type coins: List[int]

        :type amount: int

        :rtype: int

        """

2.结果代码:

class Solution(object):def coinChange(self, coins, amount):""":type coins: List[int]:type amount: int:rtype: int"""# dp[i] 表示凑出金额 i 所需的最少硬币数dp = [float('inf')] * (amount + 1)dp[0] = 0  # 初始条件# 从小到大枚举金额for i in range(1, amount + 1):for c in coins:if c <= i:dp[i] = min(dp[i], dp[i - c] + 1)return dp[amount] if dp[amount] != float('inf') else -1

说明:

dp[i] 表示凑出金额 i 所需的最少硬币数。
状态转移:对于每个金额 i,枚举所有硬币 cc ≤ i),则

dp[i] = min(dp[i], dp[i - c] + 1)

初始条件:dp[0] = 0(金额 0 需要 0 枚硬币),其余置为无穷大。
若最后 dp[amount] 仍是无穷大,说明无解,返回 -1

但是,完成后发现别人有用时、内存利用更好的方法,如下:

class Solution:def coinChange(self, coins, amount) :dp = 1 << amountresult = 0while dp & 1 == 0:t = dpfor c in coins:dp |= t >> cif dp == t:return -1result += 1return result

啊w(゚Д゚)w!

这段代码其实用的是 位运算 + BFS 的思想捏...
它把“金额”映射到 一个整数的二进制位 上,从而用 一个 32/64 位整数(甚至 Python 的“大整数”)来同时记录 所有金额 的可达性,因此:

  1. 空间 极致压缩
    只用一个整数变量 dp(外加少量辅助变量),而传统数组 DP 需要 O(amount) 的整型数组。

  2. 位运算并行:
    一条 t >> c 就能一次性把 所有已可达的金额 再“向后平移 c”,相当于一次 批量 BFS
    在 CPU 的 64 位寄存器里,一次移位 / 或运算就能处理 64 个状态,常数极小。

拆解一下代码,试图用笨脑子理解中...

dp = 1 << amount        # 1. 把第 amount 位设为 1
result = 0
while dp & 1 == 0:      # 2. 只要第 0 位还是 0(金额 0 还没到达)t = dp              # 3. 记录上一轮所有可达的金额(位图)for c in coins:     # 4. 对于每种硬币dp |= t >> c    # 5. 把“当前所有可达金额 - c” 也设为可达if dp == t:         # 6. 一轮下来没有任何新金额可达 → 无解return -1result += 1         # 7. 步数 +1
return result           # 8. 第 0 位已置 1,返回步数

举个例子(amount = 11, coins = [1,2,5])

  • 初始
    dp = 1<<11 = 0b100000000000(只有 11 这一位是 1)

  • 第 1 轮(步数 1)
    用硬币 1:右移 1 → 把 10 置 1
    用硬币 2:右移 2 → 把 9 置 1
    用硬币 5:右移 5 → 把 6 置 1
    得到 dp = 0b100100100100(11,10,9,6 可达)

  • … 依次类推
    当第 0 位第一次变成 1 时,说明凑出了 0 元,此时 result 就是答案。

的确快哈:

  • cache 友好:只需要几个寄存器级操作,没有随机内存访问。

  • 并行度:一次位运算同时更新 32/64 个状态,相当于一次 向量化 BFS

  • 常数极小:没有 Python 层级的数组下标读写,移位和或运算都是 C 级指令

但是也有缺点:

  • amount 很大(> 10^5 甚至 10^6)时,Python 的大整数会变慢,内存也会膨胀。

  • 可读性较差,不如数组 DP 直观。所以懂动态规划方法也很好啦~

题目3:单词拆分

1.题目要求:

题目如下:

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例 1:

输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。

示例 2:

输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。注意,你可以重复使用字典中的单词。

示例 3:

输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

提示:

  • 1 <= s.length <= 300
  • 1 <= wordDict.length <= 1000
  • 1 <= wordDict[i].length <= 20
  • s 和 wordDict[i] 仅由小写英文字母组成
  • wordDict 中的所有字符串 互不相同

代码框架已经提供如下:

class Solution(object):

    def wordBreak(self, s, wordDict):

        """

        :type s: str

        :type wordDict: List[str]

        :rtype: bool

        """

2.结果代码:

class Solution(object):def wordBreak(self, s, wordDict):""":type s: str:type wordDict: List[str]:rtype: bool"""word_set = set(wordDict)          # 用 set 加速查找n = len(s)dp = [False] * (n + 1)            # dp[i] 表示 s[:i] 能否被拼出dp[0] = True                      # 空串一定为 True# 依次考察子串结束位置 ifor i in range(1, n + 1):# 枚举最后一个单词的起始位置 jfor j in range(i):if dp[j] and s[j:i] in word_set:dp[i] = Truebreak                 # 找到一个即可提前跳出return dp[n]

说明:

dp[i] 表示 字符串 s 的前 i 个字符(即 s[:i])能否被字典中的单词拼接而成。
状态转移:枚举最后一个单词 word 的起始下标 j,只要

  • s[j:i] == word

  • dp[j] 为真,
    那么 dp[i] 就为真。

时间复杂度:O(n² * m),其中 n = len(s)m 为单词平均长度;
空间复杂度:O(n)


总结

针对动态规划的三种题型进行了学习,了解了部分有关动态规划与python的相关知识,大家加油!

http://www.xdnf.cn/news/1268587.html

相关文章:

  • 【Python 工具人快餐 · 第 2 份】
  • TensorFlow深度学习实战(29)——强化学习(Reinforcement learning,RL)
  • Android 开发问题:The specified child already has a parent.
  • Visual Studio Code (v1.103) 中 GitHub Copilot 最新更新!
  • LLM表征的提取方式
  • n8n飞书webhook配置(飞书机器人、飞书bot、feishu bot)Crypto节点、js timestamp代码、Crypto node
  • 电机控制器母线电压采样芯片有哪些
  • 机器学习——模型的简单优化
  • 如何判断一个数是 2 的幂 / 3 的幂 / 4 的幂 / n 的幂 位运算 总结和思考 每日一题 C++的题解与思路
  • 机器翻译:需要了解的数学基础详解
  • 客服Agent革命:智能客服系统的技术实现与效果评估
  • Java Stream流详解:用法与常用API实战
  • Tob大客户销售面试经验
  • 数据安全与隐私保护:企业级防护策略与技术实现
  • DBSCAN聚类算法实战全解析
  • 时序分解 | MATLAB实现SAO-VMD雪消融算法优化变分模态分解
  • Python 属性描述符(描述符用法建议)
  • 词向量可视化:用TensorBoard或PCA探索词向量空间
  • RecyclerView 中 ViewHolder
  • Datawhale+AI夏令营_让AI读懂财报PDF task2深入赛题笔记
  • 学习Java的Day28
  • 常用信号深度解析(SIGINT、SIGPIPE、SIGALRM、SIGTERM等)
  • Android 锁屏图标的大小修改
  • 线上排查问题的一般流程是怎么样的?
  • [激光原理与应用-207]:光学器件 - 光纤种子源激光器常用元器件
  • python---类型别名
  • 新手小白使用jQuery在实际开发中常用到的经验
  • ABP VNext + Akka.NET:高并发处理与分布式计算
  • 从 AI 到实时视频通道:基于模块化架构的低延迟直播全链路实践
  • Vuex与Pinia对比,以及技术选型