算法周赛笔记(7月第4周)— LeetCode 第252场周赛

·  阅读 402

本周只参加了一场LeetCode的周赛

还是先说战绩:1道题。/(ㄒoㄒ)/~~

第一题2分钟写完了,然后一直卡在第二题,卡到比赛结束😓

第三和第四题连面都没见着

后面进行笔记整理的时候,对那些没见着的题,都是先自己试着做一下,做不出来再看题解,结果第三题自己做出来了,本来有机会过2道的QAQ

看来需要调整一下做题时的死脑筋了,一道题卡太久就先看看后面的题有没有机会。

题目

1952

三除数

给你一个整数 n。如果n恰好有三个正除数,返回true;否则,返回false。如果存在整数k,满足n = k * m,那么整数m就是n的一个除数

C++代码

class Solution {
public:
    bool isThree(int n) {
        for (int i = 2; i <= n / i; i++) {
            if (n % i == 0) {
                return i == n / i;
            }
        }
        return false;
    }
};
复制代码

签到题,略过。

1953

你可以工作的最大周数

给你 n 个项目,编号从 0 到 n - 1 。同时给你一个整数数组 milestones ,其中每个 milestones[i] 表示第 i 个项目中的阶段任务数量。

你可以按下面两个规则参与项目中的工作:

  • 每周,你将会完成 某一个 项目中的 恰好一个 阶段任务。你每周都 必须 工作。
  • 连续的 两周中,你 不能 参与并完成同一个项目中的两个阶段任务。 一旦所有项目中的全部阶段任务都完成,或者仅剩余一个阶段任务都会导致你违反上面的规则,那么你将 停止工作 。注意,由于这些条件的限制,你可能无法完成所有阶段任务。

返回在不违反上面规则的情况下你 最多 能工作多少周。

示例1

输入:milestones = [1,2,3] 输出:6 解释:一种可能的情形是:

  • 第 1 周,你参与并完成项目 0 中的一个阶段任务。
  • 第 2 周,你参与并完成项目 2 中的一个阶段任务。
  • 第 3 周,你参与并完成项目 1 中的一个阶段任务。
  • 第 4 周,你参与并完成项目 2 中的一个阶段任务。
  • 第 5 周,你参与并完成项目 1 中的一个阶段任务。
  • 第 6 周,你参与并完成项目 2 中的一个阶段任务。 总周数是 6 。

用项目编号来展示即是

[0,2,1,2,1,2]

示例2

输入:milestones = [5, 3 , 6]

输出:14

解释:

一种可能的安排,用项目编号来展示即是

[2,0,1,2,0,1,2,0,1,2,0,2,0,2]

题解

其实这是一道偏向数学推导和找规律的智力题。一周只能完成一个任务,且不能在相邻的两周做同一个项目中的两个任务。我们换一种思路。

将每个项目看成某一种颜色的球,该项目中的每个阶段任务看成是1个球。假设某个项目有3个阶段性任务,那么该颜色的球就有3个。

则问题就变成了,给定不同颜色的一定数量的球,如何排列球,使得任意相邻的两个球的颜色都不一样,问最多能排列多少个球,满足这样的条件。

为了简化描述,我们直接用项目编号来表示球的颜色。

通过尝试多个测试用例,我们容易发现,关键在于找出个数最多的那种颜色的球。(即,找到阶段性任务最多的那个项目)

随后,我们将该种颜色的球的个数,与剩余其他所有球的个数做比较。通过插空的方式进行排列。

我们设球的个数最多的那种颜色的球,个数为 longest,剩余其他所有球的个数总和为rest

先将个数最多的这种颜色的球,排成一列,随后尝试将剩下的球,插入到其中的空位。

比如个数最多的是编号为0这种颜色的球,比如它一共有9个(longest=9),那么我们先将它排成一列

0 0 0 0 0 0 0 0 0

随后考虑rest的大小,考虑将其他颜色的球插入进去,这里我们分情况讨论。

假设9个0号球能够全部被隔开,则我们至少需要9-1=8个其他颜色的球(其他颜色的球用x表示),即

0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0

如果rest < 8(即 rest < longest - 1时),则剩余的球不足以全部隔开0号球,此时在满足条件的情况下,最多能放置的球的个数为rest * 2 + 1,比如rest = 6,则最多能放置的球的个数为2 * 6 + 1 = 13,如下

0 x 0 x 0 x 0 x 0 x 0 x 0

所以,第一种情况是,在rest < longest - 1时,我们能把rest全部的球都放上去(其余的球不管什么颜色,都能被个数最多的那种颜色的球,给隔开,在上面的示例中便是0号球),但对longest个球(个数最多的那种颜色的球),只能尽可能多放(最多能放rest + 1),所以总共最多能放rest * 2 + 1个球。所以,

  • rest < longest - 1时,答案是 2 * rest + 1

随后,考虑当 rest >= longest - 1这种情况,即longest个球能全部放上去的情况。

先考虑恰好rest = longest - 1,这种情况刚好把rest个球全部插空到longest个球的空位上,此时答案是rest + longest,即全部的球都能被放上去。

再考虑rest > longest - 1的情况,此时剩余的球能够把全部的longest个球隔开,还有多余的。我们这样考虑,先用个数数量第二大的球(假设其颜色编号为1),进行插入。

0 1 0 1 0 1 0 1 0 1 0 x 0 x 0 x 0

剩余还未插入的位置用x进行了表示,由于1号球的个数比0号球的小,所以可能无法将0号球全部隔开(如果1号球的个数恰好比0号球的个数小1,则可以隔开)。由于插入了1号球,空位又变多了。我们可以接着插入个数第三大的球(假设编号为2),2号球可以插入到任意空位(因为排列中的球没有2号球,不存在2号球相邻的情况),且2号球一定能全部插入(空位变得很多,而2号球的个数很少),将2号球全部插入后,同理,可以在任意位置插入全部的3号球,接着在任意空位插入全部的4号球,如此依赖,全部的球都能够被插入。

所以

  • rest > longest - 1时,答案也是rest + longest,即全部的球都能被放进答案中。

经过上面的分析和讨论,一共就只有2种情况

  • rest < longest - 1,此时对rest部分的球,能全部插入,对longest部分的球,能够尽可能地插入(rest + 1),答案是2 * rest + 1
  • rest >= longest - 1,此时能够插入全部的球,答案是rest + longest

C++代码如下

class Solution {
public:
    long long numberOfWeeks(vector<int>& milestones) {
        int n = milestones.size();
        int longest = 0; // 最长的
        long long rest = 0;
        for (int i = 0; i < n; i++) {
            rest += milestones[i];
            longest = max(longest, milestones[i]);
        }
        rest -= longest;
        if (rest >= longest - 1) return longest + rest;
        else return rest * 2 + 1;
    }
};
复制代码

1954

收集足够苹果的最小花园周长

给你一个用无限二维网格表示的花园,每一个 整数坐标处都有一棵苹果树。整数坐标 (i, j) 处的苹果树有 |i| + |j| 个苹果。

你将会买下正中心坐标(0, 0) 的一块 正方形土地 ,且每条边都与两条坐标轴之一平行。

给你一个整数 neededApples ,请你返回土地的 最小周长 ,使得 至少neededApples 个苹果在土地 里面或者边缘上

|x|的值定义为:

如果 x >= 0 ,那么值为 x 如果 x < 0 ,那么值为 -x

题解

这是一道较为简单的找规律的题,只要自己动手画图,找规律,归纳出公式,即可。(可惜自己做题很死脑筋,当晚周赛卡在第二题,一直卡住,就没有看后面的题了。本来这道题是能做出来的,唉

下面给出公式推导过程

由于只在整数坐标处有苹果树,并且我们需要一块以原点(0,0)为中心的正方形土地,我们不妨画一个二维坐标轴,容易知道,正方形的边长只能为偶数。图示如下

我们称上图黄色部分的线段长度为正方形的半径r)。由于只在整数坐标的位置有苹果树,并且我们在圈地摘苹果时,只能圈出一块以原点(0,0)为中心的正方形。所以r的取值只能是1n的正整数。

我们首先容易得到,正方形的边长等于8 * r

其次,我们看所有恰好在正方形的边上的苹果树。

先看上面的边,边的最左侧的坐标为(-r, r),然后往边的右侧移动,坐标为整数的点依次有 (-(r-1), r)(-(r-2), r),...,(-1, r)(0, r);然后看边的右半部分,分别有 (1, r)(2, r),...,(r, r),发现左右是对称的。则这条边上的全部的苹果个数为:左侧部分的苹果数乘2,加上最中间的(0, r)。我们先算左侧部分的,如下

[(r - 1) + r] + [(r - 2) + r] + .. + [1 + r] = r × r + (1 + 2 + ... + r)

结果是 r2+(1+r)×r2r^2+\frac{(1+r) \times r}{2},这条边上全部的苹果数就是 (r2+(1+r)×r2)×2+r=3r2+2r(r^2+\frac{(1+r) \times r}{2}) \times 2 + r = 3r^2+2r

容易推得,四条边上的苹果数量都是一样的,于是我们乘以个4,但是需要注意4个顶角位置的苹果数量被多加了一次,所以再减去一次。结果就是(3r2+2r)×42r×4=12r2(3r^2+2r) \times 4 - 2r \times 4 = 12r^2

所以,在正方形半径为r的正方形边上,苹果的数量为 12r212r^2,而正方形的周长为 8r8r

我们只需要从 r=1r=1 开始,对苹果数量进行累加,直到超过neededApples即可。

C++代码如下:

class Solution {
public:
    long long minimumPerimeter(long long neededApples) {
        long long sum = 0;
        long long i = 0;
        while (sum < neededApples) {
            ++i;
            sum += 12 * i * i;
        }
        return 8 * i;
    }
};
复制代码

1955

统计特殊子序列的数目

特殊序列 是由 正整数 个 0 ,紧接着 正整数 个 1 ,最后 正整数 个 2 组成的序列。

  • 比方说,[0,1,2] 和 [0,0,1,1,1,2] 是特殊序列。

  • 相反,[2,1,0] ,[1] 和 [0,1,2,0] 就不是特殊序列。

给你一个数组 nums 包含整数 0,1 和 2),请你返回 不同特殊子序列的数目 。由于答案可能很大,请你将它对 10^9^ + 7 取余 后返回。

一个数组的 子序列 是从原数组中删除零个或者若干个元素后,剩下元素不改变顺序得到的序列。如果两个子序列的 下标集合 不同,那么这两个子序列是 不同的

示例1

输入:nums = [0,1,2,2]
输出:3
解释:特殊子序列为 [0,1,2,2],[0,1,2,2] 和 [0,1,2,2]
复制代码

示例2

输入:nums = [2,2,0,0]
输出:0
解释:数组 [2,2,0,0] 中没有特殊子序列。
复制代码

示例3

输入:nums = [0,1,2,0,1,2]
输出:7
解释:特殊子序列包括:

- [0,1,2,0,1,2]
- [0,1,2,0,1,2]
- [0,1,2,0,1,2]
- [0,1,2,0,1,2]
- [0,1,2,0,1,2]
- [0,1,2,0,1,2]
- [0,1,2,0,1,2]
复制代码

题解

经过列举简单的用例,容易发现,在构造特殊序列时,我们需要000这样的子序列,也需要00111这样的子序列,也需要00111222这样的子序列。这些子序列都可能与其前面或后面的数字,组成满足0011222这样的特殊序列。

所以这道题考虑用动态规划的思路来做,如下:

我们将形如000这样的,只由正整数个0组成的子序列,称为0型序列;将形如00111这样的,只由正整数个0紧接着正整数个1组成的子序列,称为1型序列;将形如0011222这样的,由正整数个0紧接着正整数个1紧接着正整数个2组成的子序列,称为2型序列(即题目中描述的特殊序列)。

那么,我们可以用

  • f(i,0)来表示从nums[0]nums[i]中,0型序列的个数;
  • f(i,1)来表示从nums[0]nums[i]中,1型序列的个数;
  • f(i,2)来表示从nums[0]nums[i]中,2型序列的个数

随后我们枚举i

  • nums[i]=0时,可以不使用这个0,则不产生新的0型序列,则f(i,0)至少等于f(i-1,0);也可以使用这个0,将这个0追加到所有的f(i-1,0)0型序列后面,形成新的0型序列;这个0也可以自己单独成为一个0型序列(不使用i-1之前的任何0型序列)。而对于f(i-1,1)表示的1型序列,这个0没有任何作用,对于f(i-1,2)表示的2型序列,同样没有作用。容易得到此时的状态转移方程为:

    f(i,0)=2 * f(i-1,0) + 1

    f(i,1)=f(i-1,1)

    f(i,2)=f(i-1,2)

  • nums[i]=1时,类似的,可以将这个1追加到所有的f(i-1,1)的1型序列后面,形成新的1型序列。而对f(i-1,0)的0型序列,可以追加这个1,形成新的1型序列;对f(i-1,2)的全部2型序列,不产生作用。故此时的状态转移方程为:

    f(i,0)=f(i-1,0)

    f(i,1)=2 * f(i-1,1) + f(i-1,0)

    f(i,2)=f(i-1,2)

  • nums[i]=2时,类似的,可以将这个2追加到所有的f(i-1,2)的2型序列后面,形成新的2型序列。而对f(i-1,1)的1型序列,可以追加这个2,形成新的2型序列;对f(i-1,0),不产生作用。故此时的状态转移方程为:

    f(i,0)=f(i-1,0)

    f(i,1)=f(i-1,1)

    f(i,2)=2 * f(i-1,2) + f(i-1,1)

而我们最终要求解的答案即是 f(n-1,2)

简化一下代码思路,我们只需要遍历nums数组,设当前遍历的位置是i,则

  • nums[i]=0,更新f(i,0)=2 * f(i-1,0) + 1
  • nums[i]=1,更新f(i,1)=2 * f(i-1,1) + f(i-1,0)
  • nums[i]=2,更新f(i,2)=2 * f(i-1,2) + f(i-1,1)

因为第i个位置的各个f(i,?) 只依赖于前一个位置i-1f(i-1,?),所以可以压缩到一维,进行滚动更新

C++代码如下

class Solution {
public:
    int countSpecialSubsequences(vector<int>& nums) {
        int MOD = 1e9 + 7;
        long long f0 = 0, f1 = 0, f2 = 0;
        for(int i = 0; i < nums.size(); i++) {
            if(nums[i] == 0) f0 = (2 * f0 + 1) % MOD;
            if(nums[i] == 1) f1 = (2 * f1 + f0) % MOD;
            if(nums[i] == 2) f2 = (2 * f2 + f1) % MOD;
        }
        return f2;
    }
};
复制代码

(完)

下周再接再厉!我就不信下周还是1道题!

下周见!

分类:
后端
标签:
分类:
后端
标签: