LeetCode 第273场周赛

LeetCode 第273场周赛 解析

这次周赛应该是2021年度的最后一场周赛了,下一场就到2022年了。
这篇博客对本次周赛的题目做一个解析。题目链接位于这里

No 1.反转两次的数字

反转 一个整数意味着倒置它的所有位。

例如,反转 2021 得到 1202 。反转 12300 得到 321 ,不保留前导零 。
给你一个整数 num ,反转 num 得到 reversed1 ,接着反转 reversed1 得到 reversed2 。如果 reversed2 等于 num ,返回 true ;否则,返回 false 。

示例 1:
输入:num = 526
输出:true
解释:反转 num 得到 625 ,接着反转 625 得到 526 ,等于 num 。

示例 2:
输入:num = 1800
输出:false
解释:反转 num 得到 81 ,接着反转 81 得到 18 ,不等于 num 。

示例 3:
输入:num = 0
输出:true
解释:反转 num 得到 0 ,接着反转 0 得到 0 ,等于 num 。

提示:

0 < = n u m < = 1 0 6 0 <= num <= 10^6 0<=num<=106

解析

本题是对一个输入的整数,判断对它按数位反转两次后是否跟原数一致。实际上如果只是单纯的翻转,也即按数位翻过来,反转两次肯定和原数一样,但这里的问题是,每次翻转要去除前导零。比如50反转就是5而不是05.
本题最简单的思路就是模拟这一过程,如:

//No 1
  bool isSameAfterReversals(int num) {
    string n1 = to_string(num);
    int i = 0;
    while (i < n1.size() && n1[i] == '0')++i;
    if (i == n1.size())--i;
    n1 = n1.substr(i);
    string n2 = n1;
    reverse(n2.begin(), n2.end());
    i = 0;
    while (i < n2.size() && n2[i] == '0')++i;
    if (i == n2.size())--i;
    n2 = n2.substr(i);
    string n3 = n2;
    reverse(n3.begin(), n3.end());
    return n1 == n3;
  }

按照题意先转为字符串,记为原数。然后反转字符串并去除前导零,进行2次操作,记为变换后的数,比较和原数是否相等即可。

但思考一下我们就会发现,其实不用这样做。我们知道造成反转前后的差异的就是两次反转带来的先导0. 这里明确两点:

  1. 2次反转后的“首”=1次反转后的“尾”=反转前的“首”,反转前的原数是个正常数字不可能由先导0,所以第二次反转根本不存在先导0去除的问题;
  2. 2次反转后的“尾”=1次反转后的“首”=反转前的“尾”,反转前的数如果末尾有0,第一次反转会跑到前面而被去掉,2次反转后末尾的0就不见了,跟原数不相等。
    因此,造成反转前后不相等的情况只能是上述的情况2,就是原数末尾有0,即它是10的倍数。有一个特例的0,因为只有一位所以不能被去除。

所以,如果一个数不等于0而又是10的整数倍,2次反转必然不相等;反之,如果是0或者不是10的倍数,那反转前后不变。
因此本题只需要:

bool isSameAfterReversals(int num) {
      return (num==0)||(num%10!=0);
    }

即可。

No 2.执行所有后缀指令

现有一个 n x n 大小的网格,左上角单元格坐标 (0, 0) ,右下角单元格坐标 (n - 1, n - 1) 。给你整数 n 和一个整数数组 startPos ,其中 startPos = [startrow, startcol] 表示机器人最开始在坐标为 (startrow, startcol) 的单元格上。

另给你一个长度为 m 、下标从 0 开始的字符串 s ,其中 s[i] 是对机器人的第 i 条指令:‘L’(向左移动),‘R’(向右移动),‘U’(向上移动)和 ‘D’(向下移动)。

机器人可以从 s 中的任一第 i 条指令开始执行。它将会逐条执行指令直到 s 的末尾,但在满足下述条件之一时,机器人将会停止:

下一条指令将会导致机器人移动到网格外。
没有指令可以执行。
返回一个长度为 m 的数组 answer ,其中 answer[i] 是机器人从第 i 条指令 开始 ,可以执行的 指令数目 。

示例 1:
在这里插入图片描述

输入:n = 3, startPos = [0,1], s = “RRDDLU”
输出:[1,5,4,3,1,0]
解释:机器人从 startPos 出发,并从第 i 条指令开始执行:

  • 0: “RRDDLU” 在移动到网格外之前,只能执行一条 “R” 指令。
  • 1: “RDDLU” 可以执行全部五条指令,机器人仍在网格内,最终到达 (0, 0) 。
  • 2: “DDLU” 可以执行全部四条指令,机器人仍在网格内,最终到达 (0, 0) 。
  • 3: “DLU” 可以执行全部三条指令,机器人仍在网格内,最终到达 (0, 0) 。
  • 4: “LU” 在移动到网格外之前,只能执行一条 “L” 指令。
  • 5: “U” 如果向上移动,将会移动到网格外。

解析

本题给定了一个二维网格和初始位置,以及一个长度m的字符串构成的指令。我们需要对指令每个位置开始,向后遍历,每个字符代表一个机器人的移动,从起始位置和起始指令开始,逐个字符执行,直到所有指令执行完或机器人会被移出网格,记录能移动的指令数量。返回从 0 0 0 m − 1 m-1 m1每个下标作为起始指令能执行的数量。

本题数据范围不大,直接模拟过程即可。对于m条质量,每个作为开始向后遍历,时间复杂度为 O ( N 2 ) O(N^2) O(N2)

每条指令执行前,先判断执行当前指令会不会导致机器人出界,如果会,就直接退出,表明无法执行当前以及后续指令了。否则,就根据指令内容,改变机器人位置,增加计数器。
C++代码如下:

//No 2
  int numE(int n, int sr, int sc, string e, int si) {
    int count = 0;
    int r = sr, c = sc,m=e.size();
    while (si < m) {
      if ((e[si] == 'L' && sc == 0) || (e[si] == 'R' && sc == n - 1) || (e[si] == 'U' && sr == 0) || (e[si] == 'D' && sr == n - 1)) break;
      if (e[si] == 'L')--sc;
      else if (e[si] == 'R')++sc;
      else if (e[si] == 'U')--sr;
      else ++sr;
      ++count;
      ++si;
    }
    return count;
  }
  vector<int> executeInstructions(int n, vector<int>& startPos, string s) {
    int m = s.size();
    vector<int>ans(m, 0);
    for (int i = 0; i < m; ++i) {
      ans[i] = numE(n, startPos[0], startPos[1], s, i);
    }
    return ans;
  }

No 3. 相同元素的间隔之和

给你一个下标从 0 开始、由 n 个整数组成的数组 arr 。

arr 中两个元素的 间隔 定义为它们下标之间的 绝对差 。更正式地,arr[i] 和 arr[j] 之间的间隔是 |i - j| 。

返回一个长度为 n 的数组 intervals ,其中 intervals[i] 是 arr[i] 和 arr 中每个相同元素(与 arr[i] 的值相同)的 间隔之和 。

注意:|x| 是 x 的绝对值。

示例 1:

输入:arr = [2,1,3,1,2,3,3]
输出:[4,2,7,2,4,4,5]
解释:

  • 下标 0 :另一个 2 在下标 4 ,|0 - 4| = 4
  • 下标 1 :另一个 1 在下标 3 ,|1 - 3| = 2
  • 下标 2 :另两个 3 在下标 5 和 6 ,|2 - 5| + |2 - 6| = 7
  • 下标 3 :另一个 1 在下标 1 ,|3 - 1| = 2
  • 下标 4 :另一个 2 在下标 0 ,|4 - 0| = 4
  • 下标 5 :另两个 3 在下标 2 和 6 ,|5 - 2| + |5 - 6| = 4
  • 下标 6 :另两个 3 在下标 2 和 5 ,|6 - 2| + |6 - 5| = 5

解析

本题要求我们对给定的数组,求每个位置上的元素与整个数组其他与它值一样的元素的间隔(即下标差的绝对值)之和。
显然,每个下标的间隔之和都只跟相同元素有关,所以先通过哈希表将相同元素的下标分离出来,得到多个下表数组 I d Id Id。每个下表数组 I d Id Id都记录了一个值在原数组出现的所有位置,然后对于每个下表数组,分别求每个位置的间隔之和即可。
例如对上述实例,共有1,2,3三个不同元素,下表数组分别是 [ 1 , 3 ] , [ 0 , 4 ] , [ 2 , 5 , 6 ] [1,3],[0,4],[2,5,6] [1,3],[0,4],[2,5,6]对下表数组[1,3]两个位置间隔都是 ∣ 1 − 3 ∣ = 2 |1-3|=2 13=2所以将计算后的结果2填回到最终返回数组对应的下标1,3位置即可.

但这里要注意,数组长度也就是下标的数量最大是 1 0 5 10^5 105。所以对于每个下表数组 I d Id Id,不能用 O ( N 2 ) O(N^2) O(N2)的暴力方法,也即对每个 I d Id Id的位置,都遍历一遍 I d Id Id计算差的绝对值之和。否则会超时。

这里可以对 I d Id Id排个序,当然,如果是按照数组顺序生成的元素下标数组,本身就是有序的,从小到大。
那么对于 I d Id Id中的第i个位置,我们考虑和前一个位置i-1的关系。求第i个元素 I d [ i ] Id[i] Id[i]的间隔和,记作 s u m I n t e r [ i ] sumInter[i] sumInter[i],可以将数组分为三段:

  1. 对 于 j < i − 1 : s u m I n t e r [ i ] 1 = ∑ j = 0 i − 2 I d [ i ] − I d [ j ] = ∑ j = 0 i − 2 ( I d [ i − 1 ] − I d [ j ] ) + ( i − 1 ) ∗ ( I d [ i ] − I d [ i − 1 ] ) 对于 {j < i-1}: sumInter[i]_1=\sum_{j=0}^{i-2}{Id[i]-Id[j]}=\sum_{j=0}^{i-2}{(Id[i-1]-Id[j])}+(i-1)*(Id[i]-Id[i-1]) j<i1:sumInter[i]1=j=0i2Id[i]Id[j]=j=0i2(Id[i1]Id[j])+(i1)(Id[i]Id[i1]) 这部分的数均小于 I d [ i ] 和 I d [ i − 1 ] Id[i]和Id[i-1] Id[i]Id[i1],因此绝对差是 I d [ i ] Id[i] Id[i]减去这些数,相比于 s u m I n t e r [ i − 1 ] 1 sumInter[i-1]_1 sumInter[i1]1,减数从 I d [ i − 1 ] Id[i-1] Id[i1]增加到了 I d [ i ] Id[i] Id[i],每一个差都增加了一个二者的差值。因此 s u m I n t e r [ i ] 1 = s u m I n t e r [ i − 1 ] 1 + ( i − 1 ) ∗ ( I d [ i ] − I d [ i − 1 ] ) sumInter[i]_1=sumInter[i-1]_1+(i-1)*(Id[i]-Id[i-1]) sumInter[i]1=sumInter[i1]1+(i1)(Id[i]Id[i1])
  2. 对 于 j = i − 1 或 i : s u m I n t e r [ i ] 2 = a b s ( I d [ i ] − I d [ i − 1 ] ) + a b s ( I d [ i ] − I d [ i ] ) = I d [ i ] − I d [ i − 1 ] ; s u m I n t e r [ i − 1 ] 2 = a b s ( I d [ i − 1 ] − I d [ i − 1 ] ) + a b s ( I d [ i − 1 ] − I d [ i ] ) = I d [ i ] − I d [ i − 1 ] 对于j=i-1或i: sumInter[i]_2=abs(Id[i]-Id[i-1])+abs(Id[i]-Id[i]) = Id[i]-Id[i-1]; sumInter[i-1]_2=abs(Id[i-1]-Id[i-1])+abs(Id[i-1]-Id[i]) = Id[i]-Id[i-1] j=i1i:sumInter[i]2=abs(Id[i]Id[i1])+abs(Id[i]Id[i])=Id[i]Id[i1];sumInter[i1]2=abs(Id[i1]Id[i1])+abs(Id[i1]Id[i])=Id[i]Id[i1],也就是说对于j等于i-1或i, s u m I n t e r [ i ] 2 = s u m I n t e r [ i − 1 ] 2 sumInter[i]_2= sumInter[i-1]_2 sumInter[i]2=sumInter[i1]2
  3. 对 于 j > i : s u m I n t e r [ i ] 3 = ∑ j = i + 1 m − 1 I d [ j ] − I d [ i ] = ∑ j = i + 1 m − 1 ( I d [ j ] − I d [ i − 1 ] ) − ( m − i − 1 ) ∗ ( I d [ i ] − I d [ i − 1 ] ) 对于j>i: sumInter[i]_3=\sum_{j=i+1}^{m-1}{Id[j]-Id[i]}=\sum_{j=i+1}^{m-1}{(Id[j]-Id[i-1])}-(m-i-1)*(Id[i]-Id[i-1]) j>i:sumInter[i]3=j=i+1m1Id[j]Id[i]=j=i+1m1(Id[j]Id[i1])(mi1)(Id[i]Id[i1]) 这些数大于 I d [ i ] 和 I d [ i − 1 ] Id[i]和Id[i-1] Id[i]Id[i1],绝对差是这些数减去 I d [ i ] Id[i] Id[i],相比于 s u m I n t e r [ i − 1 ] 3 sumInter[i-1]_3 sumInter[i1]3,被减数从 I d [ i − 1 ] Id[i-1] Id[i1]增加到了 I d [ i ] Id[i] Id[i],每一个差都减小了了一个二者的差值。因此 s u m I n t e r [ i ] 3 = s u m I n t e r [ i − 1 ] 3 + ( m − i − 1 ) ∗ ( I d [ i ] − I d [ i − 1 ] ) sumInter[i]_3=sumInter[i-1]_3+(m-i-1)*(Id[i]-Id[i-1]) sumInter[i]3=sumInter[i1]3+(mi1)(Id[i]Id[i1])
    综上:
    s u m I n t e r [ i ] = s u m I n t e r [ i ] 1 + s u m I n t e r [ i ] 2 + s u m I n t e r [ i ] 3 = s u m I n t e r [ i − 1 ] 1 + ( i − 1 ) ∗ ( I d [ i ] − I d [ i − 1 ] ) + s u m I n t e r [ i − 1 ] 2 + s u m I n t e r [ i − 1 ] 3 + ( m − i − 1 ) ∗ ( I d [ i ] − I d [ i − 1 ] ) = s u m I n t e r [ i − 1 ] + [ ( i − 1 ) − ( m − i − 1 ) ] ∗ ( I d [ i ] − I d [ i − 1 ] ) sumInter[i]= sumInter[i]_1+ sumInter[i]_2+ sumInter[i]_3= sumInter[i-1]_1+(i-1)*(Id[i]-Id[i-1])+sumInter[i-1]_2+sumInter[i-1]_3+(m-i-1)*(Id[i]-Id[i-1])= sumInter[i-1]+[(i-1)-(m-i-1)]*(Id[i]-Id[i-1]) sumInter[i]=sumInter[i]1+sumInter[i]2+sumInter[i]3=sumInter[i1]1+(i1)(Id[i]Id[i1])+sumInter[i1]2+sumInter[i1]3+(mi1)(Id[i]Id[i1])=sumInter[i1]+[(i1)(mi1)](Id[i]Id[i1])
    得到了同一个值的坐标数组,在增序下的递推公式。 s u m I n t e r [ i ] = s u m I n t e r [ i − 1 ] + [ ( i − 1 ) − ( m − i − 1 ) ] ∗ ( I d [ i ] − I d [ i − 1 ] ) sumInter[i]= sumInter[i-1]+[(i-1)-(m-i-1)]*(Id[i]-Id[i-1]) sumInter[i]=sumInter[i1]+[(i1)(mi1)](Id[i]Id[i1])
    每个间隔和是前一个坐标间隔和的基础上,变动二者差值的若干倍,变动的倍数取决于前一个坐标之前和当前坐标之后各自有多少坐标。
    这样就可以在 O ( N ) O(N) O(N)的复杂度计算出一个值对应的一组下标的间隔和,遍历所有值的下表数组,就得到了所求的全部数据。
    C++代码如下:
//No 3
  vector<long long> getDistances(vector<int>& arr) {
    unordered_map<int, vector<int>>ids;
    int n = arr.size();
    for (int i = 0; i < n; ++i) {
      ids[arr[i]].push_back(i);
    }
    vector<long long>ans(n, 0);
    for (auto id : ids) {
      vector<int>v = id.second;
      long long sumall = 0;
      int m = v.size();
      for (int i = 0; i < m; ++i) {
        sumall += (long long)v[i];
      }
      long long t = sumall - m * (long long)v[0];
      ans[v[0]] = t;
      for (int i = 1; i < m; ++i) {
        int pos = v[i];
        long long tmp = t + (long long)(v[i] - v[i-1]) * ((i-1)-(m - i - 1));
        ans[pos] = tmp;
        t = tmp;
      }
    }
    return ans;
  }

No 4. 还原原数组

Alice 有一个下标从 0 开始的数组 arr ,由 n 个正整数组成。她会选择一个任意的 正整数 k 并按下述方式创建两个下标从 0 开始的新整数数组 lower 和 higher :

对每个满足 0 <= i < n 的下标 i ,lower[i] = arr[i] - k
对每个满足 0 <= i < n 的下标 i ,higher[i] = arr[i] + k
不幸地是,Alice 丢失了全部三个数组。但是,她记住了在数组 lower 和 higher 中出现的整数,但不知道每个整数属于哪个数组。请你帮助 Alice 还原原数组。

给你一个由 2n 个整数组成的整数数组 nums ,其中 恰好 n 个整数出现在 lower ,剩下的出现在 higher ,还原并返回 原数组 arr 。如果出现答案不唯一的情况,返回 任一 有效数组。

注意:生成的测试用例保证存在 至少一个 有效数组 arr 。

示例 1:
输入:nums = [2,10,6,4,8,12]
输出:[3,7,11]
解释:
如果 arr = [3,7,11] 且 k = 1 ,那么 lower = [2,6,10] 且 higher = [4,8,12] 。
组合 lower 和 higher 得到 [2,6,10,4,8,12] ,这是 nums 的一个排列。
另一个有效的数组是 arr = [5,7,9] 且 k = 3 。在这种情况下,lower = [2,4,6] 且 higher = [8,10,12] 。

提示:

2 ∗ n = = n u m s . l e n g t h 2 * n == nums.length 2n==nums.length
1 < = n < = 1000 1 <= n <= 1000 1<=n<=1000
1 < = n u m s [ i ] < = 1 0 9 1 <= nums[i] <= 10^9 1<=nums[i]<=109
生成的测试用例保证存在 至少一个 有效数组 arr

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/recover-the-original-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解析

本题题意简述为,有一个长度为n的正整数数组arr,以及一个正整数k,对其中每个元素都加k,减k,得到了2n个数。现在已知这2n个数,即输入变量nums,求一个可能的原数组。
可能的原数组,即找到一个k,使得数组2n个数能够分为两组,每组n个数,每组对应的数之间差2k,k是正整数。本题可以通过适当的枚举方式找到一个解。
实际上恢复原数组的核心是这个正整数k值,对于每个k值我们可以从小到大尝试恢复原数组,也就是看是不是存在两两对应差2k的情况,如果能找出来就得到了问题的解,否则就查看下一个k。

由于nums中元素值域很大,不可能从这个角度枚举k值。我们可以换一个思路。

首先不妨设原数组n个元素为 { a 0 , a 1 , . . . , a n − 1 } \{a_0,a_1,...,a_{n-1}\} {a0,a1,...,an1},且满足递增顺序,即 { a 0 ≤ a 1 ≤ , . . . , ≤ a n − 1 } \{a_0\leq a_1 \leq,...,\leq a_{n-1}\} {a0a1,...,an1}那么题目输入的数组实际上就是由 { a i − k , a i + k ∣ 0 ≤ i ≤ n − 1 } \{a_i-k,a_i+k | 0\leq i \leq n-1\} {aik,ai+k0in1}组成的。虽然我们无法直接得到数组nums的顺序,但不难看出, a 0 − k a_0-k a0k a n − 1 + k a_{n-1}+k an1+k就是nums中最小和最大的数。
由于数组元素的个数并不多,我们可以根据已知的数组最小元素 a 0 − k a_0-k a0k,枚举对应的 a 0 + k a_0+k a0+k,二者之差即为2k,从而得到k值,这样的枚举方式显然不会超时。

另外,由于我们有 { a 0 ≤ a 1 ≤ , . . . , ≤ a n − 1 } \{a_0\leq a_1 \leq,...,\leq a_{n-1}\} {a0a1,...,an1},则一定有 { a 0 + k ≤ a 1 + k ≤ , . . . , ≤ a n − 1 + k } \{a_0+k\leq a_1+k \leq,...,\leq a_{n-1}+k\} {a0+ka1+k,...,an1+k},所以不难得到,数组nums至少存在 n − 1 n-1 n1个元素不小于 a 0 + k a_0+k a0+k。因此我们首先将nums排序,其中最小的就是 a 0 − k a_0-k a0k,而接下来从第二小(排序后下标1)到排序后下标n就是可能的 a 0 + k a_0+k a0+k,而从 n + 1 n+1 n+1 2 n − 1 2n-1 2n1 n − 1 n-1 n1个数不可能是 a 0 + k a_0+k a0+k

对于枚举的 a 0 + k a_0+k a0+k,与 a 0 − k a_0-k a0k相减,结果应当是正整数k的二倍,所以不可能是0或任意奇数,如果遇到说明这不可能是 a 0 + k a_0+k a0+k,直接跳过即可。

差是大于0的偶数时,说明这是可能的解。若此时枚举的 a 0 + k a_0+k a0+k在排序后的nums中下标为i,由于 { a 0 + k ≤ a 1 + k ≤ , . . . , ≤ a n − 1 + k } \{a_0+k\leq a_1+k \leq,...,\leq a_{n-1}+k\} {a0+ka1+k,...,an1+k},则对于 a j , 1 ≤ j ≤ n − 1 a_j,1 \leq j \leq n-1 aj,1jn1,一定有 a j − k a_j-k ajk在i之前,而 a j + k a_j+k aj+k在i之后。可以采用双指针,从1和i+1的下标分别找对应的-k与+k数,定义一个bool数组记录已经确定的数对避免重复,利用数组有序的性质,可以找到全部的相差与枚举的值一致的数对,根据数对求出原数组的对应值并保存。

当遍历结束,可以通过找出来的数对数量是不是n判断当前解是否可行。可行解能够完美将nums分成两部分,从而得到的原数组的值也恰好为n。可行解直接返回即为所求。否则查找下一个可能的 a 0 + k a_0+k a0+k

C++代码如下:

//No 4
  vector<int> recoverArray(vector<int>& nums) {
    int n = nums.size() / 2;
    sort(nums.begin(), nums.end());
    for (int i = 0; i <= n; ++i) {
      int d = nums[i] - nums[0];
      if (d == 0 || d % 2 == 1) continue;
      vector<bool>vis(2 * n);
      vector<int>ans;
      ans.push_back(nums[0] + d / 2);
      vis[0] = true;
      vis[i] = true;
      int l = 1, r = i + 1;
      while (r < 2 * n) {
        while (l < 2 * n && vis[l])++l;
        while (r < 2 * n && nums[r] - nums[l] < d)++r;
        if (r<2 * n && nums[r] - nums[l]>d) break;
        vis[l] = true;
        vis[r] = true;
        ans.push_back(nums[l] + d / 2);
        ++l;
        ++r;
      }
      if (ans.size() == n) return ans;
    }
    return {};
  }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值