第47场 LeetCode 双周赛

第47场 LeetCode 双周赛

本次周赛链接

No.1 找到最近的有相同 X 或 Y 坐标的点

给你两个整数 x 和 y ,表示你在一个笛卡尔坐标系下的 (x, y) 处。同时,在同一个坐标系下给你一个数组 points ,其中 points[i] = [ai, bi] 表示在 (ai, bi) 处有一个点。当一个点与你所在的位置有相同的 x 坐标或者相同的 y 坐标时,我们称这个点是 有效的 。

请返回距离你当前位置 曼哈顿距离 最近的 有效 点的下标(下标从 0 开始)。如果有多个最近的有效点,请返回下标 最小 的一个。如果没有有效点,请返回 -1 。

两个点 (x1, y1) 和 (x2, y2) 之间的 曼哈顿距离 为 abs(x1 - x2) + abs(y1 - y2) 。

示例 1:
输入:x = 3, y = 4, points = [[1,2],[3,1],[2,4],[2,3],[4,4]]
输出:2
解释:所有点中,[3,1],[2,4] 和 [4,4] 是有效点。有效点中,[2,4] 和 [4,4] 距离你当前位置的曼哈顿距离最小,都为 1 。[2,4] 的下标最小,所以返回 2 。

解析

本题是例行签到题。题目给定了一个目标点,坐标为 [x, y],同时给定另一组点的坐标,让我们找到这组点中,x或y坐标有一个与目标点相等并且曼哈顿距离最小的点。

本题只需要遍历一遍所有点,首先判断是否存在一个坐标与目标点相同,如果是,那么计算该点到目标点的曼哈顿距离,并更新最小值及下标。

注意初始时将最小值设为最大的整型,下标设为-1即可。这样如果不存在满足题意的点,下标将保持-1不会更新,直接返回也符合题目要求。
C++代码如下:

// 2021.03.06 leetcode bi-weekly contest 47 - code
  //No 1
  int nearestValidPoint(int x, int y, vector<vector<int>>& points) {
    int ans = INT_MAX,index=-1;
    for (int i = 0; i < points.size();++i) {
      auto p = points[i];
      if (x == p[0] || y == p[1]) {
        int tmp = abs(p[0] - x) + abs(p[1] - y);
        if (tmp < ans) {
          ans = tmp;
          index = i;
        }
      }
    }
    return index;
  }

No.2 判断一个数字是否可以表示成三的幂的和

给你一个整数 n ,如果你可以将 n 表示成若干个不同的三的幂之和,请你返回 true ,否则请返回 false 。

对于一个整数 y ,如果存在整数 x 满足 y == 3x ,我们称这个整数 y 是三的幂。

示例 1:
输入:n = 12
输出:true
解释:12 = 3^1 + 3^2

解析

本题让我们判断一个数能否表示为3的幂之和。如果不加限制,显然每个整数都能表示,因为就相当于转化到3进制。不过题目要求是,表示成3的幂之和,每一项的系数最多是1.

换言之,一个数n显然可以写成 n = ∑ i = 0 a i ∗ 3 i n=\sum_{i=0}{a_i*3^i} n=i=0ai3i,但是题目要求每个 a i a_i ai都不能超过1.三进制表示每一位可以是0,1,2,那么本题就转化为:一个整数的三进制表示中,是否每一位都不是2.

那这样就很容易了,将输入的这个数转化为三进制表示,再去看是不是满足每一位都不是2即可,满足返回true,反之返回false。

C++代码如下:

 //No 2
  bool checkPowersOfThree(int n) {
    vector<int>p;
    while (n) {
      p.push_back(n % 3);
      n /= 3;
    }
    for (auto k : p) {
      if (k == 2) return false;
    }
    return true;
  }

本题还可以进一步简化,将判断放在求三进制表示的循环当中。当然复杂度还是 O ( l o g N ) O(logN) O(logN),但是减小了空间使用,也减小了时间复杂度常数。

No.3 所有子字符串美丽值之和

一个字符串的 美丽值 定义为:出现频率最高字符与出现频率最低字符的出现次数之差。

比方说,“abaacc” 的美丽值为 3 - 1 = 2 。
给你一个字符串 s ,请你返回它所有子字符串的 美丽值 之和。

示例 1:
输入:s = “aabcb”
输出:5
解释:美丽值不为零的字符串包括 [“aab”,“aabc”,“aabcb”,“abcb”,“bcb”] ,每一个字符串的美丽值都为 1 。

解析

本题定义了一个字符串的美丽值,其计算方法是,一个字符串中刚出现次数最多的字符的出现次数,与出现次数最小的次数之差,当然这里出现次数最小,显然至少为1,不考虑未出现的字符。

本题要求给定一个字符串,求它每个子串美丽值的和。

这里先分享一种基本接近于暴力求解的思路,思路很简单,求每个子串出现各字符的次数,找出最多与最少,记录当前子串的美丽值,累加起来。当我们遍历完所有的子串,自然找出了美丽值总和。不过这里显然存在优化空间:

每个字串都可以视作以某个下标开始的字符构成的字符串,以每一个下标作为开始,都存在一组子串,这一组子串的字符出现情况,可以在一次循环中累加得到,而不需要对每个字串从头遍历一次。举例来说,如果我们知道从下标 i i i j j j的子串 s [ i ] [ j ] s[i][j] s[i][j]的字符出现情况,那么对于子串 s [ i ] [ j + 1 ] s[i][j+1] s[i][j+1]只要多统计一个字符即可。

字符出现情况可以保存在长度为26的数组中,因为输入仅有小写英文字母构成。

C++代码如下:

//No 3
  int beauty(vector<int>&count) {
    int minV = INT_MAX, maxV = INT_MIN;
    for (auto cc : count) {
      if (cc > 0) {
        minV = min(minV, cc);
        maxV = max(maxV, cc);
      }
    }
    return maxV - minV;
  }
  int beautySum(string s) {
    int ans = 0;
    for (int i = 0; i < s.size(); ++i) {
      vector<int>count(26, 0);
      for (int j = i; j < s.size(); ++j) {
        count[s[j] - 'a']++;
        ans += beauty(count);
      }
    }
    return ans;
  }

首先写一个beauty函数从字符出现数组中遍历一次找到最大值与最小值并计算差值,得到该字符出现状态下的美丽值,这一过程复杂度可以看做 O ( 26 ) O(26) O(26)。对于输入字符串s,外层循环负责遍历所有的子串起始位置 i i i,内层循环逐个遍历起始位置后的每个字符,每遍历一个字符,就相当于考虑了一个子串,将字符加入统计,并计算美丽值,叠加在结果上。总的复杂度是 O ( 26 N 2 ) O(26N^2) O(26N2),按照题意输入字符串长度不超过500,因此符合要求不会超时。

No.4 统计点对的数目

给你一个无向图,无向图由整数 n ,表示图中节点的数目,和 edges 组成,其中 edges[i] = [ui, vi] 表示 ui 和 vi 之间有一条无向边。同时给你一个代表查询的整数数组 queries 。

第 j 个查询的答案是满足如下条件的点对 (a, b) 的数目:

a < b
cnt 是与 a 或者 b 相连的边的数目,且 cnt 严格大于 queries[j] 。
请你返回一个数组 answers ,其中 answers.length == queries.length 且 answers[j] 是第 j 个查询的答案。

请注意,图中可能会有 重复边 。

解析

本题给定一个无向图,包含若干节点与连接它们的边,对于每一对点(a,b),都存在一个cnt值,表示与a或b相连的边的数目,显然这个值就是与a相连的边加上与b相连的边,再减去ab之间的边的数目(计算了两次)。题目又给了一组查询,让我们返回对每个查询值,cnt值大于查询值的点对数目,两个点只构成一个点对,也即只计算a<b的情况。最直接的思路就是根据输入的边的集合,先把每一对点的cnt都算出来,再根据查询数组中每一个的值,找符合要求的点对数目,但由于点的数目最大是 2 ∗ 1 0 4 2*10^4 2104,而查询数目最大是20,这样做会超时。我们也没办法对每一个点对的cnt值排序,这样复杂度达到了 O ( N 2 l o g N 2 ) O(N^2logN^2) O(N2logN2)

这里参考了大佬的思路,其核心在于加速对每个查询值查找符合要求点对的过程。

首先不考虑cnt值中包含(a,b)之间被重复计算的边的情况,我们就认为与a相连的数目加上与b相连的数目比查询值大,就符合题意,那么此时我们可以统计每个点相连的边的数目,并对其排序,对于每个查询值,我们首先枚举一个点a,找出另一个点c使得当相连边数少于c的边数就无法与a构成超过查询值的点对,这样所有比c边数要多的都符合要求,都可以统计在结果中。由于我们事先对每个点边的数目排序,只要知道c的下标,那么c之后的点都符合要求,其数量也直接可得。而且由于我们查找点c都从先定好的点a之后查找,所以尽管排序后我们已经不知道每个点原先的编号,但仍然不会重复计算点对。

这里完成了第一步,也就是不考虑重复计算相连边的点对数目统计,接下来,我们需要对当前统计之中不合题意的去除,所谓不合题意就是虽然a+b大于查询值,但是减掉重复计算的边(ab之间的边)就不大于查询值了。这里可以遍历所有的边,对每条边的顶点p和q,都计算一下是不是p+q>查询值,而p+q-pq之间的边<=查询值,如果是,证明该点对其实不符合要求,应当减掉;反之,如果是p+q小于等于查询值,这种情况在前一步就没统计,也无需去除,要么就是减掉二者之间边数仍然符合大于查询值,应当予以保留。这一步要遍历所有的边,数量级为 1 0 5 10^5 105

在进行上述步骤之前,首先遍历边的集合,统计每个点的边数,统计每两个点之间的连接数,同时对边去一下重,这样第二步筛选遍历边的时候可以少一点。这里大佬采取了一种编码方式,将两点之间的边的数目存储在哈希表而非二维数组,减小了内存使用与查找时间。

C++代码如下:

  //No 4
  int encode(int n, int a, int b) {
    return max(a, b) * (n + 1) + min(a, b);
  }
  vector<int> countPairs(int n, vector<vector<int>>& edges, vector<int>& queries) {
    vector<int> deg(n + 1, 0);
    int ne = edges.size();
    unordered_map<int, int> overlap;
    vector<vector<int>>disEdges;
    for (int i = 0; i < ne; ++i) {
      int p = edges[i][0], q = edges[i][1];
      deg[p]++;
      deg[q]++;
      int idx = encode(n, p, q);
      if (overlap.find(idx) == overlap.end()) {
        disEdges.push_back({ p,q });
      }
      overlap[idx]++;
    }
    vector<int> sortedDeg(deg.begin() + 1, deg.end());
    sort(sortedDeg.begin(), sortedDeg.end());
    int nq = queries.size();
    vector<int>ans(nq);
    for (int i = 0; i < nq; ++i) {
      int l = 0, r = n - 1;
      int cnt = 0;
      while (l < n) {
        while (r > l&& sortedDeg[l] + sortedDeg[r] > queries[i]) {
          --r;
        }
        cnt += (n - max(l, r) - 1);
        ++l;
      }
      for (int j = 0; j < disEdges.size(); ++j) {
        int p = disEdges[j][0], q = disEdges[j][1];
        int idx = encode(n, p, q);
        if (deg[p] + deg[q] > queries[i] && deg[p] + deg[q] - overlap[idx] <= queries[i]) --cnt;
      }
      ans[i] = cnt;
    }
    return ans;
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值