算法打卡day40

文章介绍了如何使用动态规划方法解决LeetCode中的单词拆分问题(139题),以及如何在模拟笔试题目中(56题)计算宇航员携带矿石资源的最大价值。涉及完全背包和多重背包的理论应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今日任务:

1)139.单词拆分

2)多重背包理论基础(卡码网56携带矿石资源)

3)背包问题总结

4)复习day15

139单词拆分

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

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:
拆分时可以重复使用字典中的单词。你可以假设字典中没有重复的单词。

示例 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

文章讲解:代码随想录 (programmercarl.com)

视频讲解:动态规划之完全背包,你的背包如何装满?| LeetCode:139.单词拆分哔哩哔哩bilibili

思路:

字典里的单词是可以被重复的,这是一个完全背包问题。字符串中的单词是有序的,所以相当于是做排列问题,应该先遍历背包,再遍历物品

这里的s字符串就是背包:创建数组dp[j],不断更新dp[j]的值为True或False。也就是如果s[:j]能拆分,则dp[j] 为True

这里的单词就是物品:我们的目标是检查是否可以将字符串拆分为字典中的单词,这里有两种处理方法,一种是遍历字典中的单词,判断其在背包中能否拆分出来,有一个单词能被拆分都行;另一种是在s[:j]的每一个位置切分,判断s[i:j]是否存在字典中

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        dp = [False] * len(s)
        dp.insert(0, True)
        # print(dp)

        for j in range(1, len(s) + 1):
            for word in wordDict:
                lenth = len(word)
                if j >= lenth:
                    dp[j] = dp[j] or (dp[j-lenth] and word == s[j-lenth : j])
                # print(f'当物品为{word}时,dp[{j}]={dp[j]}')
            # print(f'此时j={j},dp={dp}')
        return dp[-1]

    def wordBreak2(self, s: str, wordDict: List[str]) -> bool:
        # 初始化动态规划数组,dp[j]表示s的前j个字符是否可以被拆分成字典中的单词
        dp = [False] * (len(s)+1)
        dp[0] = True

        # 将字典中的单词转换为集合,便于快速判断单词是否在字典中
        word_set = set(wordDict)

        # 遍历背包
        for j in range(1,len(s)+1):
            # print(f'j={j}')
            # 在背包大小的字符串s[:j]中寻找分割点(s[left:right]左闭右开),看是否存在以s[j]结尾的单词
            for i in range(j+1):
                # print(s[i:j])
                # 如果前i个字符可以被拆分且s[i:j]在字典中,则将dp[j]设置为True
                if s[i:j] in word_set and dp[i]:
                    dp[j] = True
                    break

        # 返回整个字符串s是否可以被拆分
        return dp[-1]

感想:

这题要注意i,j分别在dp和s中表示什么。很容易弄错,自己画图比较清晰
这里有一个优化,我们将字典中的单词转换为集合,便于快速判断单词是否在字典中

假设我们有一个单词列表 wordDict,其中包含了若干个单词。如果我们将这个列表转换为集合 wordSet,则每个单词就会成为 wordSet 中的一个元素。这样,当我们需要判断某个单词是否在字典中时,只需要使用集合的 in 操作符即可。这个操作会以常量时间(O(1))内完成,因为集合底层使用哈希表实现,可以快速地定位元素。

相比之下,如果我们使用列表来表示字典,那么在判断单词是否在字典中时,需要遍历整个列表,时间复杂度会是 O(n),其中 n 是字典中单词的数量。这样的时间复杂度在实际应用中可能会较高,尤其是当字典中包含大量单词时。

因此,将字典中的单词转换为集合后,能够以更高效的方式进行单词的查找,从而加快了判断字符串是否可以被拆分的速度。

多重背包理论基础(卡码网56携带矿石资源)

题目链接:56. 携带矿石资源(第八期模拟笔试) (kamacoder.com)

题目描述
你是一名宇航员,即将前往一个遥远的行星。在这个行星上,有许多不同类型的矿石资源,每种矿石都有不同的重要性和价值。你需要选择哪些矿石带回地球,但你的宇航舱有一定的容量限制。
给定一个宇航舱,最大容量为 C。现在有 N 种不同类型的矿石,每种矿石有一个重量 w[i],一个价值 v[i],以及最多 k[i] 个可用。不同类型的矿石在地球上的市场价值不同。你需要计算如何在不超过宇航舱容量的情况下,最大化你所能获取的总价值。

输入描述
输入共包括四行,第一行包含两个整数 C 和 N,分别表示宇航舱的容量和矿石的种类数量。
接下来的三行,每行包含 N 个正整数。具体如下:
第二行包含 N 个整数,表示 N 种矿石的重量。
第三行包含 N 个整数,表示 N 种矿石的价格。
第四行包含 N 个整数,表示 N 种矿石的可用数量上限。

输出描述
输出一个整数,代表获取的最大价值。

输入示例
10 3
1 3 4
15 20 30
2 3 2

输出示例
90

提示信息
数据范围:
1 <= C <= 10000;
1 <= N <= 10000;
1 <= w[i], v[i], k[i] <= 10000;

文章讲解:代码随想录 (programmercarl.com)

思路:

这个问题可以使用动态规划来解决。我们可以定义一个二维数组 dp[i][j],其中 dp[i][j] 表示前 i 种矿石,容量为 j 的情况下所能获取的最大价值。

具体的动态规划转移方程为:

dp[i][j] = \max(dp[i-1][j], dp[i-1][j - w[i]] + v[i],dp[i-1][j-2*w[i]]+2*v[i],...,dp[i-1][j-k[i]*w[i]]+k[i]*v[i])

其中,dp[i−1][j−w[i]]+v[i] 表示选择了第 i 种矿石,并且当前容量为 j,剩余容量为 j-w[i] 时的最大价值,加上选取当前矿石所获得的价值 v[i]。

根据这个方程,我们可以进行动态规划计算,最终得到dp[N][C],即前 N 种矿石,容量为 C 时所能获取的最大价值。

def max_value():
    # 从键盘上采集输入数据
    C, N = map(int, input().split())  # 宇航舱的容量和矿石的种类数量
    weights = list(map(int, input().split()))  # 矿石的重量
    values = list(map(int, input().split()))  # 矿石的价格
    limits = list(map(int, input().split()))  # 矿石的可用数量上限

    # 创建二维动态规划数组,初始化为0
    dp = [[0] * (C + 1) for _ in range(N + 1)]

    # 遍历每种矿石
    for i in range(1, N + 1):
        # 遍历容量
        for j in range(1, C + 1):
            # 当前容量 j 不足以装下当前矿石 i 时,不选择该矿石
            dp[i][j] = dp[i - 1][j]

            # 遍历当前矿石可用数量上限
            for k in range(1, min(j // weights[i - 1], limits[i - 1]) + 1):
                # 计算选择 k 个当前矿石 i 后的总价值
                dp[i][j] = max(dp[i][j], dp[i - 1][j - k * weights[i - 1]] + k * values[i - 1])

    # 返回前 N 种矿石,容量为 C 时的最大价值
    return dp[N][C]


# 调用函数并输出结果
print(max_value())

这个函数的时间复杂度为 O(N * C * k_max),其中 N 是矿石种类数量,C 是宇航舱的容量,k_max 是矿石的可用数量上限中的最大值。

采用一维dp数组解决,代码如下:

动态转移方程:dp[j] = \max(dp[j], dp[j - k \times weights[i - 1]] + k \times values[i - 1])

其中:

  • dp[j] 表示容量为 j 时的最大价值;
  • weights[i−1] 表示第 i 种矿石的重量;
  • values[i−1] 表示第 i 种矿石的价格;
  • k 表示选择的第 i 种矿石的数量,1\leq k\leq min(\frac{j}{weight[i-1]},limit[i-1])
def max_value2():
    # 从键盘上采集输入数据
    C, N = map(int, input().split())  # 宇航舱的容量和矿石的种类数量
    weights = list(map(int, input().split()))  # 矿石的重量
    values = list(map(int, input().split()))  # 矿石的价格
    limits = list(map(int, input().split()))  # 矿石的可用数量上限

    # 创建一维动态规划数组,初始化为0
    dp = [0] * (C + 1)

    # 遍历每种矿石
    for i in range(1, N + 1):
        # 逆序遍历容量,确保每个矿石只使用一次
        for j in range(C, 0, -1):
            # 遍历当前矿石可用数量上限
            for k in range(1, min(j // weights[i - 1], limits[i - 1]) + 1):
                # 计算选择 k 个当前矿石 i 后的总价值
                dp[j] = max(dp[j], dp[j - k * weights[i - 1]] + k * values[i - 1])

    # 返回容量为 C 时的最大价值
    return dp[C]

# 调用函数并输出结果
print(max_value2())
### 如何实现打卡日历组件 #### 组件需求分析 打卡日历组件通常用于展示用户的签到状态,例如已签到、未签到或补卡等情况。这种组件的核心功能包括但不限于:显示日期、标记特定日期的状态(如签到成功、缺卡、补卡)、提供交互能力(如点击某天进行操作)。以下是实现此类组件的关键技术点: 1. **基础日历结构** 使用 Vue.js 构建一个基本的日历网格布局,能够动态生成一个月中的每一天,并支持年份和月份的切换。 2. **状态标记** 需要一种机制来区分不同的日期状态,比如通过颜色编码表示已签到、未签到或补卡的情况。 3. **数据绑定与更新** 将后端返回的数据映射到对应的日期上,并实时反映用户的行为变化。 --- #### 技术实现方案 ##### 1. 基础日历构建 利用 JavaScript 的 `Date` 对象计算每个月的第一天是星期几以及该月有多少天,从而生成完整的日历表格。 ```javascript function getMonthData(year, month) { const firstDayOfMonth = new Date(year, month, 1).getDay(); // 获取当月第一天是星期几 const daysInMonth = new Date(year, month + 1, 0).getDate(); // 当前月总共有多少天 let calendar = []; for (let i = 0; i < Math.ceil((firstDayOfMonth + daysInMonth) / 7); i++) { // 计算需要几周 calendar.push([]); } let dayIndex = 0; for (let week of calendar) { while (week.length < 7 && dayIndex < daysInMonth) { if ((dayIndex + firstDayOfMonth) % 7 === 0 || (dayIndex + firstDayOfMonth) >= 7 * i) { week.push({ date: dayIndex + 1 }); dayIndex++; } else { week.push(null); } } } return calendar; } ``` 此函数会返回一个二维数组,代表每周的日期分布[^1]。 --- ##### 2. 数据绑定与状态管理 为了使日历具备签到功能,需引入额外的状态字段,例如 `isSigned`, `isMakeupCard` 等标志位。这些数据可以从服务器获取并存储在一个对象中,形如 `{ 'YYYY-MM-DD': { isSigned: true, isMakeupCard: false } }`。 在模板中遍历生成的日历数据时,结合上述状态信息应用 CSS 类名以改变视觉表现。 ```html <template> <div class="calendar"> <table> <thead> <tr><th v-for="(dayName, index) in ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']">{{ dayName }}</th></tr> </thead> <tbody> <tr v-for="(week, wIndex) in calendar" :key="'w' + wIndex"> <td v-for="(day, dIndex) in week" :key="'d' + dIndex" :class="{ signed: day?.date && checkStatus(day.date), makeup: day?.date && checkMakeup(day.date) }"> {{ day ? day.date : '' }} </td> </tr> </tbody> </table> </div> </template> <script> export default { data() { return { year: 2023, month: 9, signRecords: {}, // 后端传入的签到记录 calendar: [] }; }, methods: { generateCalendar() { this.calendar = getMonthData(this.year, this.month); }, checkStatus(date) { return !!this.signRecords[`${this.formatDate(this.year, this.month)}-${String(date).padStart(2, '0')}`]?.isSigned; }, checkMakeup(date) { return !!this.signRecords[`${this.formatDate(this.year, this.month)}-${String(date).padStart(2, '0')}`]?.isMakeupCard; }, formatDate(y, m) { return `${y}-${String(m + 1).padStart(2, '0')}`; } }, mounted() { this.generateCalendar(); } }; </script> <style scoped> .signed { background-color: green; color: white; } .makeup { background-color: yellow; } </style> ``` 以上代码片段展示了如何根据日期匹配相应的签到状态,并通过条件渲染调整单元格样式[^1]。 --- ##### 3. 动态交互设计 允许用户点击某个具体日期触发签到逻辑或者跳转至详情页面。可通过监听事件完成这一目标。 ```javascript methods: { handleCellClick(cellInfo) { if (!cellInfo) return; alert(`Clicked on ${this.year}/${this.month}/${cellInfo}`); // 可在此处调用 API 提交签到请求... } }, // 修改模板部分如下: <td @click="handleCellClick(day)" ... > ``` 这样即可赋予每个日期格子一定的功能性[^4]。 --- ### 总结 综上所述,创建一个具有签到功能的日历组件涉及多个方面的工作——从底层算法搭建框架,再到前端界面美化及行为定义。借助现代框架的优势可以使整个过程更加高效便捷。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值