LeetCode 第38次双周赛

LeetCode 第38次双周赛

本次双周赛题目想要通过还是比较简单的,这里做一个简要的回顾。

No 1.按照频率将数组升序排序

给你一个整数数组 nums ,请你将数组按照每个值的频率 升序 排序。如果有多个值的频率相同,请你按照数值本身将它们 降序 排序。

请你返回排序后的数组。

示例 1:
输入:nums = [1,1,2,2,2,3]
输出:[3,1,1,2,2,2]
解释:‘3’ 频率为 1,‘1’ 频率为 2,‘2’ 频率为 3 。

示例 2:
输入:nums = [2,3,1,3,2]
输出:[1,3,3,2,2]
解释:‘2’ 和 ‘3’ 频率都为 2 ,所以它们之间按照数值本身降序排序。

解析

本题要求我们对输入的数组,按照元素的出现频率升序(出现越少越靠前)同频率按照数值降序(数字越大越靠前)的顺序输出,注意这里的输出使每个数字都要输出,比如实例1有2个1,有3个2,都要输出,而不是排好序返回3,1,2就行了。但显然,相同的数字都是放在一起的,所以我们还是只要知道数字的顺序,以及每个数字的数量即可。

本题最直接的方法,就是先记录每个数字及其出现次数,然后按照“频率小的优先,相同评率数字大的优先”的原则排序,可以借助vector<pair<int,int>>这样的数据结构实现。排序后将每个数字输出,每个数字的输出次数要与数组中真实出现次数一致。
C++代码如下:

//No 1
  vector<int> frequencySort(vector<int>& nums) {
    unordered_map<int, int> m;
    for (auto n : nums)m[n]++;
    vector<pair<int, int>> vp;
    for (auto mm : m) {
      vp.push_back({ mm.second,mm.first });
    }
    sort(vp.begin(), vp.end(), [](pair<int, int>& p1, pair<int, int>& p2) {return p1.first<p2.first || p1.first == p2.first && p1.second>p2.second; });
    vector<int>ans;
    for (auto p : vp) {
      for(int i=0;i<p.first;++i)
        ans.push_back(p.second);
    }
    return ans;
  }

No 2. . 两点之间不包含任何点的最宽垂直面积

给你 n 个二维平面上的点 points ,其中 points[i] = [xi, yi] ,请你返回两点之间内部不包含任何点的 最宽垂直面积 的宽度。

垂直面积 的定义是固定宽度,而 y 轴上无限延伸的一块区域(也就是高度为无穷大)。 最宽垂直面积 为宽度最大的一个垂直面积。

请注意,垂直区域 边上 的点 不在 区域内。

示例 1:
输入:points = [[8,7],[9,9],[7,4],[9,7]]
输出:1

示例 2:
输入:points = [[3,1],[9,0],[1,0],[1,4],[5,3],[8,8]]
输出:3

提示:

n == points.length
2 <= n <= 10^5
points[i].length == 2
0 <= xi, yi <= 109

解析

本题给定一个点集,分别记录这些点的横纵坐标,要求我们找到一个垂直区域,其中不包含任何点(但边界上要有点,否则就不是两点之间了)求这个最大宽度。
本题其实也很简单。首先垂直面积的定义就保证了两条边界的确定其实只跟x坐标有关,y坐标无所谓,如果我们按照x坐标对每个点进行排序,那么所有能取得的垂直面积只能是相邻的点中间区域,如果不相邻就意味着中间包含了其他点,不合题意。而且最左端的点也不能作为右边界向左无限延伸,不符合两点之间的题目要求,最右端点也是同理。

输入横坐标排序复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)对于本题 1 0 5 10^5 105的数据量完全可行。
代码如下:

//No 2
  int maxWidthOfVerticalArea(vector<vector<int>>& points) {
    vector<int>xs;
    for (auto p : points) {
      xs.push_back(p[0]);
    }
    sort(xs.begin(), xs.end());
    int len = xs.size(), maxL = 0;
    for (int i = 0; i < len - 1; ++i) {
      maxL = max(maxL, xs[i + 1] - xs[i]);
    }
    return maxL;
  }

No 3. 统计只差一个字符的子串数目

给你两个字符串 s 和 t ,请你找出 s 中的非空子串的数目,这些子串满足替换 一个不同字符 以后,是 t 串的子串。换言之,请你找到 s 和 t 串中 恰好 只有一个字符不同的子字符串对的数目。

比方说, “computer” 和 “computation” 加粗部分只有一个字符不同: ‘e’/‘a’ ,所以这一对子字符串会给答案加 1 。

请你返回满足上述条件的不同子字符串对数目。

一个 子字符串 是一个字符串中连续的字符。

示例 1:

输入:s = “aba”, t = “baba”
输出:6
s第一个a与t中的2个b,2
s第二个b与t中2个a,2
s第三个a与t中2个b,2
总计6

提示:

1 <= s.length, t.length <= 100
s 和 t 都只包含小写英文字母。

解析

本题给定我们两个字符串,分别找到两个字符串的非空子串中,只差一个字符的子串对的数目。换言之,找一个s的非空子串,一个t的非空子串,如果二者只差一个字符,就符合要求,那么这样符合要求的有多少对。
本题数据范围,两字符串长度为100,能够暴力求解。

首先我们考虑,给定两个字符串,判断二者是否只差一个字符,要如何判断。实际上也就是首先长度一致,其次从头到尾顺序遍历,记录不一样的字符个数,检查是否恰好一个不同即可。若字符串长度为N,则此处需要时间复杂度 O ( N ) O(N) O(N)

接下来如何找到所有的符合条件的子串对并记录数量呢?首先符合条件的子串长度一定相等,否则不可能之差一个。因此子串的长度取值范围,最小是1,最大是两个输入字符串中较短的长度。

然后我们可以枚举每一个长度,分别讨论长度为1,2,3…,min(length)时,有多少符合条件的子串。当长度一定时,我们只需要两重循环,分别确定s和t两个字符串中的子串起始位置,从而枚举当前长度下的两个子串,检验是否符合并记录即可。
C++代码如下:

 //No 3
  bool checkStrings(string s, string t) {
    if (s.size() != t.size()) return false;
    else {
      int count = 0,i=0;
      while (i < s.size() && count <= 1) {
        if (s[i] != t[i])++count;
        ++i;
      }
      return count == 1;
    }
  }
  int countSubstrings(string s, string t) {
    int lenS = s.size(), lenT = t.size(), len = min(lenS, lenT);
    int ans = 0;
    for (int i = 1; i <= len; ++i) {
      for (int s1 = 0; s1 <= lenS - i; ++s1) {
        for (int s2 = 0; s2 <= lenT - i; ++s2) {
          string ss = s.substr(s1, i), tt = t.substr(s2, i);
          if (checkStrings(ss, tt)) {
            ++ans;
          }
        }
      }
    }
    return ans;
  }

checkStrings 函数用于检验两个字符串是否只差一个字符,count用于记录同一位置不同字符的个数。跳出循环时,要么count达到2,要么遍历了全部字符串count为0或1,分别对应了不同字符超过1、完全相同、以及只差1,这三种情况。跳出后只要检验count是否为1即可。

countSubstrings中,首先根据两字符串长度确定子串长度取值范围,对每个长度,枚举所有子串对,检验是否满足条件。

整体而言,复杂度为 O ( N 4 ) O(N^4) O(N4),对于长度100能够不超时通过。

No 4. 通过给定词典构造目标字符串的方案数

给你一个字符串列表 words 和一个目标字符串 target 。words 中所有字符串都 长度相同 。

你的目标是使用给定的 words 字符串列表按照下述规则构造 target :

从左到右依次构造 target 的每一个字符。
为了得到 target 第 i 个字符(下标从 0 开始),当 target[i] = words[j][k] 时,你可以使用 words 列表中第 j 个字符串的第 k 个字符。
一旦你使用了 words 中第 j 个字符串的第 k 个字符,你不能再使用 words 字符串列表中任意单词的第 x 个字符(x <= k)。也就是说,所有单词下标小于等于 k 的字符都不能再被使用。
请你重复此过程直到得到目标字符串 target 。
请注意, 在构造目标字符串的过程中,你可以按照上述规定使用 words 列表中 同一个字符串 的 多个字符 。

请你返回使用 words 构造 target 的方案数。由于答案可能会很大,请对 109 + 7 取余 后返回。

(译者注:此题目求的是有多少个不同的 k 序列,详情请见示例。)

示例 1:
输入:words = [“acca”,“bbbb”,“caca”], target = “aba”
输出:6
解释:总共有 6 种方法构造目标串。
“aba” -> 下标为 0 (“acca”),下标为 1 (“bbbb”),下标为 3 (“caca”)
“aba” -> 下标为 0 (“acca”),下标为 2 (“bbbb”),下标为 3 (“caca”)
“aba” -> 下标为 0 (“acca”),下标为 1 (“bbbb”),下标为 3 (“acca”)
“aba” -> 下标为 0 (“acca”),下标为 2 (“bbbb”),下标为 3 (“acca”)
“aba” -> 下标为 1 (“caca”),下标为 2 (“bbbb”),下标为 3 (“acca”)
“aba” -> 下标为 1 (“caca”),下标为 2 (“bbbb”),下标为 3 (“caca”)

提示:

1 <= words.length <= 1000
1 <= words[i].length <= 1000
words 中所有单词长度相同。
1 <= target.length <= 1000
words[i] 和 target 都仅包含小写英文字母。

解析

本题给定一个包含相同长度字符串的字典,以及一个目标字符串target,要求我们从字典中选择字符,从左到右顺序的构造出target,但要求一旦使用了某单词的下标为k的字符,接下来就不能选择下标小于等于k的字符了。

本题相当于在一个二维字符串矩阵中,“走”出一条路径,只能向右,不能回头,不能从上到下直上直下的走,这条路径拼起来相当于target字符串。

但实际上如果我们真的按照路径进行DFS或BFS可能由于分支过多而造成超时。

那么我们通过动态规划能够解决此题呢。

我们讨论使用字典的前i列,构造出字符串前j个的情况,首先,我们完全可以使用前i-1列,跳过当前列不使用;其次,我们如果一定要使用第i列,那首先假设前i-1列构造字符串前j-1个一共有k中方案,接下来我们需要在第i列选择一个字符串的第j和字符,假设有p种选法,那么这种情况两部分可以任意组合,共有 k ∗ p k*p kp种方法。
同时,我们考虑空串(长度0)的情况,对于m列的字典和l长的目标串,我们定义(m+1)*(l+1)的二维动规数组。
因此,状态转移方程可以这样定义, d p [ i ] [ j ] dp[i][j] dp[i][j]表示到字典 i − 1 i-1 i1列,构造出目标串前 j − 1 j-1 j1个字符的方法数量,

d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − 1 ] ∗ n u m O f C h a r ( t a r g e t [ j − 1 ] ) i n ( w o r d s [ a l l ] [ i − 1 ] ) dp[i][j] = dp[i-1][j] +dp[i-1][j-1]*numOfChar(target[j-1]) in(words[all][i-1]) dp[i][j]=dp[i1][j]+dp[i1][j1]numOfChar(target[j1])in(words[all][i1])
简言之,到字典 i − 1 i-1 i1列,构造出目标串前 j − 1 j-1 j1个字符的方法数,就是字典 i − 2 i-2 i2列,构造出目标串前 j − 1 j-1 j1个字符的方法数(不用第i-1列),再加上到字典 i − 2 i-2 i2列,构造出目标串前 j − 2 j-2 j2个字符的方法数乘字典 i − 1 i-1 i1列有几个target[j-1]字符。

显然如果直接按照上述方法去做,二维动规本身是 O ( N 2 ) O(N^2) O(N2),检索每一列有几个特定的字符,又需要遍历一整列,整体复杂度是 O ( N 3 ) O(N^3) O(N3),对于长度为1000的数据范围肯定会超时。

这里我们选择空间换时间的策略,首线构造二维数组记录每一列包含的每个字符的数量,由于字符只包含小写字母,共26个,故构造26*列数的表就够了,先通过 O ( N 2 ) O(N^2) O(N2)的方法记录每个字符在每一列的出现次数,用到的时候直接查表 O ( 1 ) O(1) O(1)就得到了,这样整体算法复杂度就是 O ( N 2 ) O(N^2) O(N2),不会超时。

注意每次乘或加要取余,动规数组的数据类型可选64位整型避免溢出。

C++代码如下:

  //No 4
  int numWays(vector<string>& words, string target) {
    int64_t n = words.size(), m = words[0].size(),l=target.size(),M=1e9+7;
    vector<vector<int64_t>>charMap(26, vector<int64_t>(m, 0));
    for (int i = 0; i < n; ++i) {
      for (int j = 0; j < m; ++j) {
        charMap[words[i][j]-'a'][j]++;
      }
    }
    vector<vector<int64_t>>dp(m + 1, vector<int64_t>(l + 1, 0));
    for (int i = 0; i <= m; ++i) dp[i][0] = 1;
    for (int i = 1; i <= m; ++i) {
      for (int j = 1; j <= l; ++j) {
        if (j > i) dp[i][j] = 0;
        else {
          dp[i][j] = (dp[i-1][j] + (dp[i - 1][j - 1] * charMap[target[j - 1]-'a'][i - 1]) % M) % M;
        }
      }
    }
    return dp[m][l];
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值