力扣第190场周赛

力扣第190场周赛

作为我为数不多能全做出来的一次周赛,简单分析一下题目和思路吧。我还是太菜了!
本次周赛链接位于:weekly-contest-190

这次周赛跟往常一样,包括分数和难度递增的四道题:
力扣第190场周赛_第1张图片

1· 检查单词是否为句中其他单词的前缀

本题要求给一个字符串 sentence 作为句子并指定检索词为 searchWord ,其中句子由若干用 单个空格 分隔的单词组成
请你检查检索词 searchWord 是否为句子 sentence 中任意单词的前缀。
如果 searchWord 是某一个单词的前缀,则返回句子 sentence 中该单词所对应的下标(下标从 1 开始)。
如果 searchWord 是多个单词的前缀,则返回匹配的第一个单词的下标(最小下标)。
如果 searchWord 不是任何单词的前缀,则返回 -1 。
字符串 S 的 「前缀」是 S 的任何前导连续子字符串。

根据题意,我们需要判断给定的searchWord是sentence中第几个单词的前缀,返回编号,如果有多个就返回最小的,如果不是任何一个单词的下标就返回-1.

分析

本题是签到题,还是很简单的,不过对C++选手可能涉及到字符串空格分割。
C++代码如下:

 bool isFront(string tem, string s) {
    if (tem.size() < s.size()) return false;
    int i = 0;
    while (i < s.size()) {
      if (tem[i] == s[i])++i;
      else return false;
    }
    return true;
  }
  int isPrefixOfWord(string sentence, string searchWord) {
    stringstream ss(sentence);
    vector<string>vs;
    string tmp;
    int i = 0;
    while (ss >> tmp) {
      if (isFront(tmp, searchWord)) return i+1;
      else ++i;
    }
    return -1;
  }

这里采用stringstream的方式从sentence中依次读取被空格分隔的每个单词,并通过计数器记录这是第几个。注意题目给的例子中返回值下标从1开始,而不是0,所以从零计数要返回i+1。遍历所有单词都没有跳出并返回的那就是不存在,返回-1即可。
bool isFront(string tem, string s) 函数用于判断字符串 s 是否为字符串 tem 的前缀。首先s的长度如果超过tem那显然不可能是前缀;然后依次遍历s和tem,判断从头开始的字符是不是一一对应相同的,是的话就返回true,中间出现不同的情况就返回false。

这道题很简单,不过C++分词部分相比于其他语言的现成方法稍麻烦一点。stringstream的方式还是很好用的。

2· 定长子串中元音的最大数目

给你字符串 s 和整数 k 。
请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。
英文中的 元音字母 为(a, e, i, o, u)。
如:
输入:s = “abciiidef”, k = 3
输出:3
解释:子字符串 “iii” 包含 3 个元音字母。

分析

本题规定了k是小于等于s长度的,因此特殊情况就不讨论了。k>s.size()的情况也很简单,就找到s包含的全部元音字符数量就行了。不过不是题目要求的部分。
题目要求长度为k的子串所包含的最大元音字母数,其实就是遍历全部的长度为k的子串,并记录最大的元音字母数即可。当然也不需要真的对每个字串从头查找,这是比较经典的滑动窗口的应用。

C++代码如下:

int maxVowels(string s, int k) {
    int count = 0, maxC = 0, start = -1,end=start+k,len=s.size();
    set<char> v{ 'a','e','i','o','u' };
    for (int i = 0; i < k; ++i) {
      if (v.count(s[i])) ++count;
    }
    maxC = count;
    ++start;
    ++end;
    while (end < len) {
      if (v.count(s[end])) ++count;
      if (v.count(s[start])) --count;
      if (count > maxC) maxC = count;
      ++start; 
      ++end;
    }
    return maxC;
  }

首先集合v记录所有元音字母,避免一个个if判断,而且只有5个,对计算量增加不大。
这里用start和end标记滑窗,[start+1, end]是当前滑窗内容,每次start加一,end也加一,表示右侧新增一个而左侧减少一个,维护count作为当前窗口内的元音字母数量,并在窗口移动过程中记录最大值。
也是比较简单的题目。不过自己还是花了一些时间,主要是滑窗的初始化、计算、停止条件之类的,看来最近还是题做少了,害!

3· 二叉树中的伪回文路径

给你一棵二叉树,每个节点的值为 1 到 9 。我们称二叉树中的一条路径是 「伪回文」的,当它满足:路径经过的所有节点值的排列中,存在一个回文序列。
请你返回从根到叶子节点的所有路径中 伪回文 路径的数目。
力扣第190场周赛_第2张图片本题要求一颗二叉树的“伪回文”路径数目,伪回文定义为路径上的值存在一个回文的排列,而且节点值有限定在1-9之间。所以其实对于每条路径,统计出现的每个数字的数量,如果全是偶数或有一个奇数那一定可以排列成回文的序列;如果超过1个数字出现次数为奇数就排不成了,所以考虑遍历所有路径并统计每个数字出现数量,遇到叶节点就判断,如果满足数字的数量最多只有1个奇数的条件,就在结果上加一。
这道题我采用了递归的方法,遍历每条路径。

C++代码如下:

bool canPali(vector<int>&count) {
    int n = 0;
    for (auto c : count) {
      if (c % 2 == 1)++n;
      if (n >= 2) return false;
    }
    if (n <= 1) return true;
  }
  void pathRec(TreeNode* root, vector<int>&count,int*ans) {
    if (root == nullptr)
      return;
    count[root->val]++;
    if (root->left == nullptr&& root->right == nullptr) {
      if (canPali(count)) (*ans)++;
    }
    else {
      if (root->left) {
        pathRec(root->left, count,ans);
        count[root->left->val]--;
      }
      if (root->right) {
        pathRec(root->right, count, ans);
        count[root->right->val]--;
      }
    }
  }
  int pseudoPalindromicPaths(TreeNode* root) {
    vector<int>count(10, 0);
    int ans = 0;
    pathRec(root, count, &ans);
    return ans;
  }

count数组是一个长度为10的数组,分别记录0-9出现的次数,count[i] 就是i出现的次数。之所以长度为10,是为了方便的直接用 node->val作为index访问,如果长度为9下标范围是0-8还需要人为-1.尽管长度是10但是由于按照题意0这个数没出现过一直是0所以不影响能否回文的判断。
bool canPali(vector&count)函数用于判断count数组是不是能形成回文序列。就是简单粗暴的遍历并记录出现次数为奇数的数字有几个。
void pathRec(TreeNode* root, vector&count,int*ans)是核心的递归函数,对于传入的树节点root,首先记录其数值放入count,然后判断是不是叶节点(左右子节点都为空),如果是那说明找到了一条路径,判断是不是伪回文,是的话对整形指针ans指向的整数加一。这里没有用全局变量而是传入指针,因为之前听说LeetCode多个样例测试时全局变量可能出错(也就是全局变量定义时一定要初始化为0吧我猜),但其实效果是一样的。
如果不是叶节点,就分别将左右节点(若存在)加入路径并继续查找下去。这里要注意传入的count是引用,如果遍历了左子树那左子节点就加入count了,并且显然这不属于右子树的路径,所以遍历完左边,要先将左子节点的值踢出去在遍历右子树。

4· 两个子序列的最大点积

给你两个数组 nums1 和 nums2 。

请你返回 nums1 和 nums2 中两个长度相同的 非空 子序列的最大点积。

数组的非空子序列是通过删除原数组中某些元素(可能一个也不删除)后剩余数字组成的序列,但不能改变数字间相对顺序。比方说,[2,3,5] 是 [1,2,3,4,5] 的一个子序列而 [1,5,3] 不是。

力扣第190场周赛_第3张图片本题给定两个数组,分别选取等长的子序列并求两个子序列点积(也就是对应位置相乘再相加)的最大值。

分析

这道题一开始想了递归的路子,但是超时了,反过来一想,其实动态规划就可以搞定。根据点积定义,我们可以将题目转化为两个数组选取若干对点,这些点的乘积加起来最大,那么每个点都可以讨论要不要选取。

本题思路可以看做是二维的dp,两个数组长度分别是m和n,设计m*n的二维数组dp,其中 d p [ i ] [ j ] dp[i][j] dp[i][j]表示第一个数组0-i和第二个数组0-j两个子数组能得到的非空子序列最大点积。
那么对于 d p [ i ] [ j ] dp[i][j] dp[i][j],它的最大值一定出自一下几种情况:
d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j],也就是新增加的nums1[i]不足以加入所选取的子序列;
d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1],也就是新增加的nums2[j]不足以加入所选取的子序列;
d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1],新加入的两个数乘积是负的还比 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1]小,加进去反而减小了总的点积
n u m s 1 [ i ] ∗ n u m s 2 [ j ] nums1[i] * nums2[j] nums1[i]nums2[j],前面的是负的,加上反而变小,所以单独取新加入的2个数
d p [ i − 1 ] [ j − 1 ] + n u m s 1 [ i ] ∗ n u m s 2 [ j ] dp[i-1][j-1] + nums1[i] * nums2[j] dp[i1][j1]+nums1[i]nums2[j],新加入一对点有增加。
所以 d p [ i ] [ j ] dp[i][j] dp[i][j]取上述情况最大值即可。
C++代码如下:

int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
    vector<vector<int>>dp(nums1.size(),vector<int>(nums2.size()));
    dp[0][0] = nums1[0] * nums2[0];
    int maxC = dp[0][0];
    for (int i = 1; i < nums1.size(); ++i)dp[i][0] = max(dp[i - 1][0], nums1[i] * nums2[0]);
    for (int j = 1; j < nums2.size(); ++j)dp[0][j] = max(dp[0][j-1], nums1[0] * nums2[j]);
    for (int i = 1; i < nums1.size(); ++i) {
      for (int j = 1; j < nums2.size(); ++j) {
        int tmp = nums1[i] * nums2[j];
        dp[i][j] = max(max(dp[i - 1][j - 1]+tmp ,max(tmp, dp[i - 1][j - 1])), max(dp[i - 1][j], dp[i][j - 1]));
        if (dp[i][j] > maxC) maxC = dp[i][j];
      }
    }
    return maxC;
  }

初始化的时候对于i=0的情况,意味着只考虑数组1 的第一个数,那能得到的最大点积,就是这个数跟另一个数组讨论范围内所有能得到的最大乘积(此时子序列长度只能为1)。j=0的情况同理。之后就按照上述情况逐一更新 d p [ i ] [ j ] dp[i][j] dp[i][j]并记录最大值即可。

你可能感兴趣的:(LeetCode,算法与数据结构,C++)