LeetCode刷题day57

简介: LeetCode刷题day57

139. 单词拆分

题目描述

给你一个字符串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


思路分析

单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。


拆分时可以重复使用字典中的单词,说明就是一个完全背包!


动规五部曲 分析如下:


确定dp数组以及下标的含义

dp[i]:字符串长度为i的话,dp[i]为true,表示可有拆分为一个或多个在字典中出现的单词.为false表示无法拆分


确定递推公式

如果dp[j]是true,且[j,i]这个区间的子串出现在字典里,那么dp[i]一定是true. ( j < i )


所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true。


dp数组如何初始化

从递归公式可有看出,dp[i]的状态依靠dp[j]是否为true,那么dp[0]就是递归的根基,dp[0]一定要为true,否则递归下去后面都为false了.


那么dp[0]有没有意义呢? 由于题目说了 ‘给定一个非空字符串s’,所以测试数据中不会出现i为0情况,dp[0]为true完全是为了推导公式.


对于下标非0的dp[i]初始化为false,只要没有被覆盖说明都是不可拆分为一个或者多个在字典中出现的单词.


确定遍历顺序

题目中说是拆分为一个或多个在字典中出现的单词,所以这是完全背包。


还要讨论两层for循环的前后循序。


如果求组合数就是外层for循环遍历物品,内层for遍历背包。


如果求排列数就是外层for遍历背包,内层for循环遍历物品。


本题最终要求的是是否都出现过,所以对出现单词集合里的元素是组合还是排列,并不在意!


但是本题具有特殊性,因为是求子串,最好是遍历背包放在外循环,将遍历物品放在内循环.


如果要是外层for循环遍历物品,内层for遍历背包,就需要把所有的子串都预先放在一个容器里。


举例推导dp[i]

以输入: s = “leetcode”, wordDict = [“leet”, “code”]为例,dp状态如图:


7cab9bc145e4507f80ab5334a2d89887.jpg

参考代码

#include<bits/stdc++.h>
using namespace std;
bool wordBreak(string s, vector<string>& wordDict) {
  unordered_set<string> wordSet(wordDict.begin(),wordDict.end());
  vector<bool> dp(s.size()+1,false) ;
  dp[0] = true;//初始化,后面都是由前面推出来的..
  //完全背包 外层背包 内层物品,这样 
  for(int i = 1;i <= s.size();i++) {
    for(int j = 0; j <i; j++){//物品肯定是背包中的一小块. 所以j<i,不能等于,否则物品为空无意义. 
      string word = s.substr(j,i-j);  // i:5  j:4
      if(wordSet.find(word)!=wordSet.end() && dp[j]==true){//如果当前子串属于物品,并且之前的串也是物品. 那么从0到i就是个大物品. 
        dp[i] = true;
      }
    }
  }
  return dp[s.size()];
}


198. 打家劫舍

题目描述

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。


给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。


示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。


示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。


思路分析

打家劫舍是dp的经典问题.动规五部曲 分析如下:


确定dp数组以及下标的含义

dp[i]:下标为i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]


确定递推公式

决定dp[i]的因素就是第i间房间偷还是不偷.


如果偷第i间房间,那么i-1房间一定是不偷的.找出下标i-2以内的房屋,最多可以偷窃的金额dp[i-2]加上第i房间偷到的钱.

如果不偷第i房间,那么dp[i] = dp[i-1]

然后dp[i]取最大值,即dp[i] = max(dp[i-2] + nums[i], dp[i-1])


dp数组如何初始化

从递推公式dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);可以看出,递推公式的基础就是dp[0] 和 dp[1]


从dp[i]的定义上来讲,dp[0] 一定是 nums[0],dp[1]就是nums[0]和nums[1]的最大值即:dp[1] = max(nums[0], nums[1]);


确定遍历顺序

dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么一定是从前到后遍历!


举例推导dp数组

0b473b59c3f590da2c2fcfa9edcf163f.jpg

参考代码

int rob(vector<int>& nums) {
  if(nums.size()==0){
    return 0;
  }
  if(nums.size()==1){
    return nums[0];
  }
  vector<int> dp(nums.size(),0) ;
  dp[0]  = nums[0];
  dp[1] = max(nums[0],nums[1]);
  //就是简单的dp,一层循环就够... 
  for(int i = 2;i < nums.size(); i++){
    dp[i] = max(dp[i-2]+nums[i],dp[i-1]);//状态转移方程  dp[i]偷到的最大金额= 当前房子偷的金额 和 当前房子不偷的最大值. 
  }
  return dp[nums.size() - 1] ;
}

213. 打家劫舍 II

题目描述


你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。


给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 1:

输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:

输入:nums = [1,2,3]
输出:3

思路分析

这道题目和 198.打家劫舍 (opens new window) 是差不多的,唯一区别就是成环了。

对于一个数组,成环的话主要有如下三种情况:

  • 情况一:考虑不包含首尾元素

92453c65169837121f689266ce952965.jpg

  • 情况二:考虑包含首元素,不包含尾元素

f8a0c2f07c5ea701d1a6b25d1e0e4928.jpg

  • 情况三:考虑包含尾元素,不包含首元素

84dd5ce642abd7b4122f13a77abf354a.jpg

注意我这里用的是"考虑",例如情况三,虽然是考虑包含尾元素,但不一定要选尾部元素! 对于情况三,取nums[1] 和 nums[3]就是最大的。


而情况二 和 情况三 都包含了情况一了,所以只考虑情况二和情况三就可以了。(不理解的可以举一个例子)


分析到这里,本题其实比较简单了。 剩下的和 198.打家劫舍 (opens new window)就是一样的了。


参考代码

#include<bits/stdc++.h>
using namespace std;
int rob(vector<int>& nums) {
  if(nums.size()==0){
    return 0;
  }
  if(nums.size()==1) {
    return nums[0];
  }
  //包含头不包含尾 
  int result1 = robRange(nums,0,nums.size()-2) ;
  //包含尾不包含头
  int result2 = robRange(nums,1,nums.size()-1);
  return max(result1,result2);
}
//上个题中的逻辑 
int robRange(vector<int>& nums,int begin,int end){
  if(begin==end){//如果只有一个元素,则直接进行返回 
    return nums[begin];
  }
  vector<int> dp(nums.size(),0);//定义dp 
  //初始化 
  dp[begin] = nums[begin];
  dp[begin+1] = max(nums[begin],nums[begin+1]);
  for(int i = begin+2;i <= end;i++) {
    dp[i] = max(dp[i-2]+nums[i],dp[i-1]);// 进行递推 
  }
  return dp[end];
}

337. 打家劫舍 III

题目描述


小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。


除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。


给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额。


示例 1:


b4c08fdc5350ebc5ab6d3e592cf45d33.jpg

输入: root = [3,2,3,null,3,null,1]
输出: 7 
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7


示例 2:


570171cc1c6370513a6d72d63cd22045.jpg

输入: root = [3,4,5,1,3,null,1]
输出: 9
解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9

思路分析

动态规划其实就是使用状态转移容器来记录状态的变化,这里可以使用一个长度为2的数组,记录当前节点偷与不偷所得到的的最大金钱。


树形dp的入门题目,因为是在树上进行状态转移,我们在讲解二叉树的时候说过递归三部曲,下面我们以递归三部曲来分析本题


确定递归函数的参数和返回值

参数:当前节点


返回值:长度为2的dp数组:表示当前节点偷与不偷所得到的金钱


dp数组以及下标的含义:下标为0记录不偷该节点所得到的最大金钱,下标为1记录偷该节点所得到的最大金钱.


确定终止条件


当遇到空节点的时候,进行返回.


if (cur == NULL) return vector<int>{0, 0};


确定遍历顺序

首先明确要使用后序遍历,因为需要通过递归函数的返回值来做下一步计算.


通过递归左节点,得到左节点偷与不偷的金钱数.


通过递归右节点,得到右节点偷与不偷的金钱数.


确定单层递归逻辑

如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val+left[0]+right[0]

如果不偷当前节点,那么左右孩子就可以偷,至于偷不偷一定是选择其中最大的. val2 = max(left[0],left[1])+max(right[0],right[1])

最后当前节点的状态就是{val2, val1}; 即:{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}


举例推导dp数组

以示例1为例,dp数组状态如下:(注意用后序遍历的方式推导)


fa9f01e5ad59f4bacda2a0ab43445ab1.jpg

最后头结点就是 取下标0 和 下标1的最大值就是偷得的最大金钱

参考代码

#include<bits/stdc++.h>
using namespace std;
//Definition for a binary tree node.
struct TreeNode {
  int val;
  TreeNode *left;
  TreeNode *right;
  TreeNode() : val(0), left(nullptr), right(nullptr) {}
  TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
  TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
int rob(TreeNode* root) {
  vector<int> result = robTree(root);
  return max(result[0],result[1]);
}
vector<int> robTree(TreeNode* cur) {
  //结束条件
  if(cur==NULL) {
    return  vector<int> {0,0}; //另外一种方式定义 vector<int> v = {0,0}
  }
  vector<int> left = robTree(cur->left) ;//遍历左子树
  vector<int> right = robTree(cur->right);//遍历右子树
  //偷当前节点
  int val1 = cur->val+left[0]+right[0] ;
  //不偷当前节点
  int val2 = max(left[0],left[1])+max(right[0],right[1]);
  return {val2,val1};
}

如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。

创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客

相关文章
|
12月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
173 6
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
131 6
|
Python
【Leetcode刷题Python】剑指 Offer 26. 树的子结构
这篇文章提供了解决LeetCode上"剑指Offer 26. 树的子结构"问题的Python代码实现和解析,判断一棵树B是否是另一棵树A的子结构。
134 4
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
283 2
|
12月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
179 3
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
10月前
|
机器学习/深度学习 人工智能 自然语言处理
280页PDF,全方位评估OpenAI o1,Leetcode刷题准确率竟这么高
【10月更文挑战第24天】近年来,OpenAI的o1模型在大型语言模型(LLMs)中脱颖而出,展现出卓越的推理能力和知识整合能力。基于Transformer架构,o1模型采用了链式思维和强化学习等先进技术,显著提升了其在编程竞赛、医学影像报告生成、数学问题解决、自然语言推理和芯片设计等领域的表现。本文将全面评估o1模型的性能及其对AI研究和应用的潜在影响。
239 1
|
索引 Python
【Leetcode刷题Python】从列表list中创建一颗二叉树
本文介绍了如何使用Python递归函数从列表中创建二叉树,其中每个节点的左右子节点索引分别是当前节点索引的2倍加1和2倍加2。
243 7
|
Python
【Leetcode刷题Python】剑指 Offer 22. 链表中倒数第k个节点
Leetcode题目"剑指 Offer 22. 链表中倒数第k个节点"的Python解决方案,使用双指针法找到并返回链表中倒数第k个节点。
134 5
|
Python
【Leetcode刷题Python】剑指 Offer 30. 包含min函数的栈
本文提供了实现一个包含min函数的栈的Python代码,确保min、push和pop操作的时间复杂度为O(1)。
97 4
|
Python
【Leetcode刷题Python】剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
Leetcode题目"剑指 Offer 21. 调整数组顺序使奇数位于偶数前面"的两种Python解决方案,一种是使用双端队列调整数组顺序,另一种是使用双指针法将奇数移到数组前半部分,偶数移到后半部分。
91 4