文章目录
a.检查单词是否为句中其他单词的前缀
a.题目
给你一个字符串 sentence
作为句子并指定检索词为 searchWord
,其中句子由若干用 单个空格 分隔的单词组成。
请你检查检索词 searchWord
是否为句子 sentence
中任意单词的前缀。
- 如果 searchWord 是某一个单词的前缀,则返回句子 sentence 中该单词所对应的下标**(下标从 1 开始)**。
- 如果 searchWord 是多个单词的前缀,则返回匹配的第一个单词的下标**(最小下标)**。
- 如果 searchWord 不是任何单词的前缀,则返回 -1 。
字符串 S 的 「前缀」是 S 的任何前导连续子字符串。
示例 1
输入:sentence = “i love eating burger”, searchWord = “burg”
输出:4
解释:“burg” 是 “burger” 的前缀,而 “burger” 是句子中第 4 个单词。
示例 2
输入:sentence = “this problem is an easy problem”, searchWord = “pro”
输出:2
解释:“pro” 是 “problem” 的前缀,而 “problem” 是句子中第 2 个也是第 6 个单词,但是应该返回最小下标 2 。
示例 3
输入:sentence = “i am tired”, searchWord = “you”
输出:-1
解释:“you” 不是句子中任何单词的前缀。
示例 4
输入:sentence = “i use triple pillow”, searchWord = “pill”
输出:4
示例 5
输入:sentence = “hello from the other side”, searchWord = “they”
输出:-1
提示
- 1 <= sentence.length <= 100
- 1 <= searchWord.length <= 10
- sentence 由小写英文字母和空格组成。
- searchWord 由小写英文字母组成。
- 前缀就是紧密附着于词根的语素,中间不能插入其它成分,并且它的位置是固定的——-位于词根之前。(引用自 前缀_百度百科 )
a.分析
显然 直接按照题意模拟就行了
拆分出全部单词保存或不保存也行
然后对每个单词去看下是不是前缀
如果是匹配前缀那么 STL的find应该返回下标0
当然暴力写匹配match也不是事
总的复杂度为遍历的O(n)和匹配的O(m)相乘为O(nm)
a.参考代码
class Solution {
public:
int isPrefixOfWord(string sentence, string searchWord) {
vector<string> words;
string tmp;
for(int i=0;i<sentence.size();i++) //拆分单词
if(sentence[i]==' '){
words.push_back(tmp);
tmp="";
}
else tmp.push_back(sentence[i]);
words.push_back(tmp);
for(int i=0;i<words.size();i++)
if(words[i].find(searchWord)==0)return i+1; //偷懒用stl 记得题目下标从1开始
return -1;
}
};
b.定长子串中元音的最大数目
b.题目
给你字符串 s
和整数 k
。
请返回字符串 s
中长度为 k
的单个子字符串中可能包含的最大元音字母数。
英文中的 元音字母 为(a, e, i, o, u)。
示例 1
输入:s = “abciiidef”, k = 3
输出:3
解释:子字符串 “iii” 包含 3 个元音字母。
示例 2
输入:s = “aeiou”, k = 2
输出:2
解释:任意长度为 2 的子字符串都包含 2 个元音字母。
示例 3
输入:s = “leetcode”, k = 3
输出:2
解释:“lee”、“eet” 和 “ode” 都包含 2 个元音字母。
示例 4
输入:s = “rhythms”, k = 4
输出:0
解释:字符串 s 中不含任何元音字母。
示例 5
输入:s = “tryhard”, k = 4
输出:1
提示
- 1 <= s.length <= 10^5
- s 由小写英文字母组成
- 1 <= k <= s.length
b.分析
分析题目可知这是个定长的滑动窗口问题
即在固定的窗口大小 维护里面的元音字母个数
然后再在途中求最大值
一般来说滑动窗口都可以用队列来维护
- 先建立长度为k的队列
- 窗口开始往后移动
- 先pop掉队头 如果是元音字母 那么维护个数先减掉
- 再判断新push进来的 如果是原因字母 维护个数就增加
总的时间复杂度只有遍历的 O(n)
b.参考代码
class Solution {
public:
int maxVowels(string s, int k) {
queue<char> q;
int cnt=0;
for(int i=0;i<k;i++){ //把大小为k的队列预处理先
q.push(s[i]);
if(check(s[i]))cnt++;
}
int ans=cnt;
for(int i=k;i<s.size();i++) //按照分析来维护
{
char front=q.front();
q.pop();
if(check(front))cnt--;
q.push(s[i]);
if(check(s[i]))cnt++;
ans=max(ans,cnt);
}
return ans;
}
inline bool check(char c) //判断元音字母
{
if(c=='a'||c=='e'||c=='i'||c=='o'||c=='u')return true;
return false;
}
};
c.二叉树中的伪回文路径
c.题目
给你一棵二叉树,每个节点的值为 1 到 9 。我们称二叉树中的一条路径是 **「伪回文」**的,当它满足:路径经过的所有节点值的排列中,存在一个回文序列。
请你返回从根到叶子节点的所有路径中 伪回文 路径的数目。
示例 1
输入:root = [2,3,1,3,1,null,1]
输出:2
解释:上图为给定的二叉树。总共有 3 条从根到叶子的路径:红色路径 [2,3,3] ,绿色路径 [2,1,1] 和路径 [2,3,1] 。
在这些路径中,只有红色和绿色的路径是伪回文路径,因为红色路径 [2,3,3] 存在回文排列 [3,2,3] ,绿色路径 [2,1,1] 存在回文排列 [1,2,1] 。
示例 2
输入:root = [2,1,1,1,3,null,null,null,null,null,1]
输出:1
解释:上图为给定二叉树。总共有 3 条从根到叶子的路径:绿色路径 [2,1,1] ,路径 [2,1,3,1] 和路径 [2,1] 。
这些路径中只有绿色路径是伪回文路径,因为 [2,1,1] 存在回文排列 [1,2,1] 。
示例 3
输入:root = [9]
输出:1
提示
- 给定二叉树的节点数目在 1 到 10^5 之间。
- 节点值在 1 到 9 之间。
c.分析
这里思考了一下
叶子路径是不是伪回文无法通过贪心判断 所以无法剪枝
跑完整棵树复杂度也不大
用dfs序跑出每条路径 由于数字只有1-9
而且路径是否是伪回文和顺序无关 所以直接用一个数组存出现的次数就行了
- 进入节点先把节点上的值在计数标记
- 离开节点要把标记减掉
- 把路径长度也顺便记录下 (直觉告诉我是否回文和路径长度奇偶有关
- 碰到叶子就判断一下是不是伪回文
然后就是最重要判断部分了
在纸上列出十几个回文串观察 得出:
- 如果串长度为奇数 那么贪心思想 把一个出现奇数次的数字放在最中间 其他偶数次的两边排布 且奇数长度串必定有一个奇数次的数字 分析之后得出这个数字当且仅当只有一个奇数次数字时候是回文串
- 偶数长度的话 显然没有办法把奇数次的数字调整好 所以就是只能出现偶数次数字
总的复杂度是遍历整棵树的O(n)乘上check时候的10
c.参考代码
class Solution {
public:
int cnt[10]={0}; //数的路径计数
int ans=0;
int len=0; //路径长度
int pseudoPalindromicPaths (TreeNode* root) {
dfs(root);
return ans;
}
void dfs(TreeNode* t) //常规的dfs
{
len++;
cnt[t->val]++;
if(!t->left&&!t->right){
if(check())ans++;
len--;
cnt[t->val]--;
return;
}
if(t->left)dfs(t->left);
if(t->right)dfs(t->right);
cnt[t->val]--;
len--;
}
bool check() //判断是否是伪回文
{
int odd=0,even=0;
for(int i=1;i<=9;i++)
if(cnt[i]&1)odd++; //奇数次
else even++;
if(len&1)return odd<=1; //奇数长度 其实这里只能是1 没有0的情况 比赛懒得考虑
else return odd==0; //偶数长度 没有奇数次的
}
};
d.两个子序列的最大点积
d.题目
给你两个数组 nums1 和 nums2 。
请你返回 nums1 和 nums2 中两个长度相同的 非空 子序列的最大点积。
数组的非空子序列是通过删除原数组中某些元素(可能一个也不删除)后剩余数字组成的序列,但不能改变数字间相对顺序。比方说,[2,3,5] 是 [1,2,3,4,5] 的一个子序列而 [1,5,3] 不是。
示例 1
输入:nums1 = [2,1,-2,5], nums2 = [3,0,-6]
输出:18
解释:从 nums1 中得到子序列 [2,-2] ,从 nums2 中得到子序列 [3,-6] 。
它们的点积为 (23 + (-2)(-6)) = 18 。
示例 2
输入:nums1 = [3,-2], nums2 = [2,-6,7]
输出:21
解释:从 nums1 中得到子序列 [3] ,从 nums2 中得到子序列 [7] 。
它们的点积为 (3*7) = 21 。
示例 3
输入:nums1 = [-1,-1], nums2 = [1,1]
输出:-1
解释:从 nums1 中得到子序列 [-1] ,从 nums2 中得到子序列 [1] 。
它们的点积为 -1 。
提示
- 1 <= nums1.length, nums2.length <= 500
- -1000 <= nums1[i], nums2[i] <= 100
d.分析
不管三七二十一 上来先给他预处理一下两两的乘积
然后问题就变成了选哪些乘积相加最大
对于预处理出来的二维矩阵:
如样例 nums1 = [2,1,-2,5], nums2 = [3,0,-6]
6 3 -6 15
0 0 0 0
-12 -6 12 -30
答案的18是选取了1,1的6和3,3的12相加得来的
思考之后发现 不可能在同一行或同一列选取乘积然后相加
因为同一行或同一列的是由同一个数与其他相乘得来的 违背了点积一一对应
然后再次思考发现 行和列只能不断增加进行选取
因为点积的顺序是固定的 不可能回头再找其他相乘的
先考虑暴力 假设n为行数 m为列数
- 对于第一行有m+1种选择 (你可以不选这一行的任何数
- 对于之后的行有m-i种选法 i表示最后一次选的第i列 (不是上一行 行是可以跳过的 正如列可以跳过
- 碰到负数就肯定不会要的了
以上暴力思路是dfs爆搜就能解决
但是时间复杂度是指数级别的 不可行
考虑优化:
想到遍历的方向一直是往矩阵右下角的 那么很快就能想到二位矩阵的动态规划
是否可以dp 就要看下是否能成功转移
设dp(i,j)为选到(i,j)位置为止的最大答案
对于(i,j)位置有以下可能:
- 这个位置不选 那么就代表这一行或这一列仍然是可以选的 那么就看下(i-1,j)和(i,j-1)哪个大就好了 当然这两个位置也有可能没有选到 没选到就表示整行跳过或整列跳过
- 这个位置选上 那么就代表这一行和这一列之前都不能选 因为选了这个了 所以就从(i-1,j-1)加上选的这个
从上述再选个大的 就是转移方程了
dp(i,j)=max(dp(i-1,j-1)+mul(i,j),max(dp(i-1,j,dp(i,j-1)))
另外要注意特判
题目要求最少选一个 但是转移方程会考虑不选的更优(为0
因此当答案为0时候 就要去看下是不是真的有这个0 否则把最大的负数选上 (选一个就行了 负数越多越小
总的时间复杂度为二维矩阵的大小O(nm)
d.参考代码
int mul[505][505],dp[505][505];
class Solution {
public:
int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
int n=nums1.size(),m=nums2.size();
for(int i=0;i<n;i++) //预处理出乘积
for(int j=0;j<m;j++)
mul[i+1][j+1]=nums1[i]*nums2[j];
int ans=INT_MIN;
for(int i=1;i<=n;i++) //状态枚举
for(int j=1;j<=m;j++)
{
dp[i][j]=max(max(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]+mul[i][j]);
ans=max(ans,dp[i][j]);
}
if(!ans){ //为0时候的特判
ans=INT_MIN;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
ans=max(ans,mul[i][j]);
}
return ans;
}
};