LeetCode 第190场周赛 题解

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.题目

给你两个数组 nums1nums2
请你返回 nums1nums2 中两个长度相同的 非空 子序列的最大点积。
数组的非空子序列是通过删除原数组中某些元素(可能一个也不删除)后剩余数字组成的序列,但不能改变数字间相对顺序。比方说,[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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值