LeetCode第189场周赛(Weekly Contest 189)解题报告

最后一题,难度是真滴大,计算几何的问题,具体公式是百度的,不过赛后自己也推导,同时也看了大佬们的公式,然后用一个比较简单的方式进行讲解

第一题:模拟。

第二题:字符串排序。

第三题:暴力。

第四题:计算几何。

详细题解如下。


1.在既定时间做作业的学生人数

         AC代码(C++)

2. 重新排列句子中的单词

         AC代码(C++)

3.收藏清单

         AC代码(C++)

4.圆形靶内的最大飞镖数量

         AC代码(C++)


LeetCode第189场周赛地址:

https://leetcode-cn.com/contest/weekly-contest-189


1.在既定时间做作业的学生人数

题目链接

https://leetcode-cn.com/problems/number-of-students-doing-homework-at-a-given-time/

题意

给你两个整数数组 startTime(开始时间)和 endTime(结束时间),并指定一个整数 queryTime 作为查询时间。

已知,第 i 名学生在 startTime[i] 时开始写作业并于 endTime[i] 时完成作业。

请返回在查询时间 queryTime 时正在做作业的学生人数。形式上,返回能够使 queryTime 处于区间 [startTime[i], endTime[i]](含)的学生人数。

示例 1:

输入:startTime = [1,2,3], endTime = [3,2,7], queryTime = 4
输出:1
解释:一共有 3 名学生。
第一名学生在时间 1 开始写作业,并于时间 3 完成作业,在时间 4 没有处于做作业的状态。
第二名学生在时间 2 开始写作业,并于时间 2 完成作业,在时间 4 没有处于做作业的状态。
第二名学生在时间 3 开始写作业,预计于时间 7 完成作业,这是是唯一一名在时间 4 时正在做作业的学生。

示例 4:

输入:startTime = [1,1,1,1], endTime = [1,3,2,4], queryTime = 7
输出:0

示例 5:

输入:startTime = [9,8,7,6,5,4,3,2,1], endTime = [10,10,10,10,10,10,10,10,10], queryTime = 5
输出:5

提示:

  • startTime.length == endTime.length
  • 1 <= startTime.length <= 100
  • 1 <= startTime[i] <= endTime[i] <= 1000
  • 1 <= queryTime <= 1000

 

解题思路

根据题目,其实就是找到在数组中(两个数组分别表示 开始 和 结束),然后判断 在这个范围内的个数,那么直接模拟即可。

AC代码(C++)

class Solution {
public:
    int busyStudent(vector<int>& startTime, vector<int>& endTime, int queryTime) {
        int ans = 0;
        int n = startTime.size();
        for(int i = 0;i < n; ++i)
        {
            if(startTime[i] <= queryTime && queryTime <= endTime[i]) ++ans;
        }
        return ans;
    }
};

2. 重新排列句子中的单词

题目链接

https://leetcode-cn.com/problems/rearrange-words-in-a-sentence/

题意

「句子」是一个用空格分隔单词的字符串。给你一个满足下述格式的句子 text :

  • 句子的首字母大写
  • text 中的每个单词都用单个空格分隔。

请你重新排列 text 中的单词,使所有单词按其长度的升序排列。如果两个单词的长度相同,则保留其在原句子中的相对顺序。

请同样按上述格式返回新的句子。

示例 1:

输入:text = "Leetcode is cool"
输出:"Is cool leetcode"
解释:句子中共有 3 个单词,长度为 8 的 "Leetcode" ,长度为 2 的 "is" 以及长度为 4 的 "cool" 。
输出需要按单词的长度升序排列,新句子中的第一个单词首字母需要大写。

示例 2:

输入:text = "Keep calm and code on"
输出:"On and keep calm code"
解释:输出的排序情况如下:
"On" 2 个字母。
"and" 3 个字母。
"keep" 4 个字母,因为存在长度相同的其他单词,所以它们之间需要保留在原句子中的相对顺序。
"calm" 4 个字母。
"code" 4 个字母。

示例 3:

输入:text = "To be or not to be"
输出:"To be or to be not"

提示:

  • text 以大写字母开头,然后包含若干小写字母以及单词间的单个空格。
  • 1 <= text.length <= 10^5

解题思路

根据题目,其实我们需要就是,将字符串分割得到各个单词,然后再把单词进行排序

  • 先按单词长度排序
  • 若相同,则按出现位置排序

根据字符串的长度,整个单词最多也就有 1e5 个,那么进行排序,O(n * logn),不会超时。

那么就先将 字符串分割,C++ 没有 split 函数,只能自己实现,其实也很好做,就是不断的往后枚举,当需要 空格,表示此时已经是一个单词了

注意的是,一开始字符串首字母是大写,我们要重新排序的时候,所有单词都是小写,因此要事先把 首字母的大写变回小写。

同时,由于我们排序,不仅要求是 长度,还需要位置,那么也就是说,我们需要同时记录两个信息,分割得到的单词,以及对应这个单词出现的位置(下标)。

那么可以,用多个数组,也可以用 结构体等等来记录。

剩下的就是排序,按照排序规则来重写 sort 函数的 cmp 即可。

最后排好序后,把新的 单词 顺序,拼接成 新的字符串。

AC代码(C++)

bool cmp(pair<string, int> a, pair<string, int> b)
{
    if(a.first.size() != b.first.size()) return a.first.size() < b.first.size();
    return a.second < b.second;
}

class Solution {
public:
    string arrangeWords(string text) {
        vector<pair<string, int> > words;
        string tep = "";
        tep += text[0] - 'A' + 'a';
        int cnt = 1;
        for(int i = 1;i < text.size(); ++i)
        {
            if(text[i] == ' ')
            {
                words.push_back(make_pair(tep, cnt));
                tep = "";
                ++cnt;
            }
            else
            {
                tep += text[i];
            }
        }
        words.push_back(make_pair(tep, cnt));
        
        sort(words.begin(), words.end(), cmp);
        
        string res = words[0].first;
        res[0] = res[0] - 'a' + 'A';
        
        for(int i = 1;i < words.size(); ++i)
        {
            res += ' ';
            res += words[i].first;
        }
        return res;
    }
};

3.收藏清单

题目链接

https://leetcode-cn.com/problems/people-whose-list-of-favorite-companies-is-not-a-subset-of-another-list/

题意

给你一个数组 favoriteCompanies ,其中 favoriteCompanies[i] 是第 i 名用户收藏的公司清单(下标从 0 开始)。

请找出不是其他任何人收藏的公司清单的子集的收藏清单,并返回该清单下标。下标需要按升序排列。

示例 1:

输入:favoriteCompanies = [["leetcode","google","facebook"],["google","microsoft"],["google","facebook"],["google"],["amazon"]]
输出:[0,1,4] 
解释:
favoriteCompanies[2]=["google","facebook"] 是 favoriteCompanies[0]=["leetcode","google","facebook"] 的子集。
favoriteCompanies[3]=["google"] 是 favoriteCompanies[0]=["leetcode","google","facebook"] 和 favoriteCompanies[1]=["google","microsoft"] 的子集。
其余的收藏清单均不是其他任何人收藏的公司清单的子集,因此,答案为 [0,1,4] 。

示例 2:

输入:favoriteCompanies = [["leetcode","google","facebook"],["leetcode","amazon"],["facebook","google"]]
输出:[0,1] 
解释:favoriteCompanies[2]=["facebook","google"] 是 favoriteCompanies[0]=["leetcode","google","facebook"] 的子集,因此,答案为 [0,1] 。

提示:

  • 1 <= favoriteCompanies.length <= 100
  • 1 <= favoriteCompanies[i].length <= 500
  • 1 <= 。favoriteCompanies[i][j].length <= 20
  • favoriteCompanies[i] 中的所有字符串 各不相同 。
  • 用户收藏的公司清单也 各不相同 ,也就是说,即便我们按字母顺序排序每个清单, favoriteCompanies[i] != favoriteCompanies[j] 仍然成立。
  • 所有字符串仅包含小写英文字母

解题分析

拿到题目,一开始就想着能不能暴力枚举,也就是,当我们选择某一个 i 用户,去判断它是不是其他 用户( j )的 子集。

那么在判断的时候,由于 i 是要去找对应 j 中存不存在,如果我们一开始进行排序,那么只需要遍历一次 j 里面的数据,就可以了,这是因为,如果 进行了排序,那么 当我们 i 找到对应 j 中的位置 jj 时,那么此时要判断 i 的下一个 字符串 是不是 在 j 中,那么由于排序的原因,一定是从 jj 后去找的。

所以总的时间复杂度就是 O(n * n * m),其中 n 是 favor.size, m 是 facor[ i ].size,那么根据数据范围是 100 *100 *500,不会超时。

AC代码(C++)

class Solution {
public:
    vector<int> peopleIndexes(vector<vector<string>>& fC) {
        for(auto& f : fC) sort(f.begin(), f.end());  // 先进行排序
        int n = fC.size();

        vector<int> ans;
        for(int i = 0;i < n; ++i)  // 对每一个用户进行排序
        {
            bool flag = true;
            for(int j = 0;j < n; ++j)   // 枚举其他所有用户,是不是它的父集
            {
                if(i == j) continue;
                
                int curSize = fC[i].size();
                int k = 0;   // 对于 i 中的 下标
                
                for(int jj = 0;jj < fC[j].size() && k < curSize; ++jj)  // 只需要遍历 j 中的数据即可
                {
                    if(fC[j][jj] == fC[i][k]) ++k;
                }
                if(k >= curSize)  // 如果发现, k 走完了,那就说明,在 j 中找到了所有 i 中的字符串,那么也就是 i 是其他子集,这样子就不加进答案中,所以是 false
                {
                    flag = false;
                    break;
                }
            }
            if(flag) ans.push_back(i);   // 当为真的时候,说明不是其他子集,那么加入答案。
        }
        
        return ans;
    }
};

4.圆形靶内的最大飞镖数量

题目链接

https://leetcode-cn.com/problems/maximum-number-of-darts-inside-of-a-circular-dartboard/

题意

墙壁上挂着一个圆形的飞镖靶。现在请你蒙着眼睛向靶上投掷飞镖。

投掷到墙上的飞镖用二维平面上的点坐标数组表示。飞镖靶的半径为 r 。

请返回能够落在 任意 半径为 r 的圆形靶内或靶上的最大飞镖数。

示例 1:

【示例有图,具体看链接】
输入:points = [[-2,0],[2,0],[0,2],[0,-2]], r = 2
输出:4
解释:如果圆形的飞镖靶的圆心为 (0,0) ,半径为 2 ,所有的飞镖都落在靶上,此时落在靶上的飞镖数最大,值为 4 。

示例 2:

【示例有图,具体看链接】
输入:points = [[-3,0],[3,0],[2,6],[5,4],[0,9],[7,8]], r = 5
输出:5
解释:如果圆形的飞镖靶的圆心为 (0,4) ,半径为 5 ,则除了 (7,8) 之外的飞镖都落在靶上,此时落在靶上的飞镖数最大,值为 5 

提示:

  • 1 <= points.length <= 100
  • points[i].length == 2
  • -10^4 <= points[i][0], points[i][1] <= 10^4
  • 1 <= r <= 5000

解题分析

拿到题目,看到应该是 图形 + 数学 的问题,那么最重要的就是进行分析。

有一个性质:最佳圆,一定满足,至少两个点在圆的边缘上。

简单证明,但我们找到一个最佳圆,只有一个点在边上,其他都在圆内,那么此时我们可以调整圆,使得一个 点到 边上,而其他点还在圆内,这样子,使得这个最佳圆可以尽可能把剩下还没有包括进来的点,有机会包括进来,从而使得更多点包括。

那么根据这个发现,我们就想到,如果我们枚举任意两个点,假设这两个点,在圆的边上,那么就可以 计算出 这个圆的圆心(半径知道,又知道两个点在圆的边上),这样子我们得到任意两个点组合出来的圆,那么计算这个圆包含了多少个点。

由于我们是遍历所有可能的圆,也就是能求出其中包含点最多的个数。

那么这样子,时间复杂度应该是,O(n * n * n),前两个 n,是枚举任意两个点(不重复枚举),然后求出此时 这个圆 的圆心,那么第三个 n 就是,枚举所有的点,判断在这个圆内的个数。

那么根据数据范围,不会超时。

那么剩下的问题就转化为:已知圆的半径,还有 圆上两个点,怎么求出 圆心坐标

(比赛当初,是百度得到的公式),然后比赛结束后,自己推导了一下(自己推导的比较复杂,然后看到有大佬的比较简单明了)

注意,求出来的圆心,可能有两种情况,根据图中的一些值计算

那么中点 (midx, midy) 到 圆心的距离,也就可以求出来了

那么此时,我们如果知道从 (midx, midy) 到 (xc, yc) 的单位向量,假设是 (e1, e2) 的话,那么根据向量运算,

然后我们发现,这个从  (midx, midy) 到 (xc, yc) 的向量,相当于是 (x1, y1) 到 (x2, y2) 的向量,经过 顺时针 或者 逆时针 旋转可以得到的,那么我们可以很容易的计算出 (x1, y1) 到 (x2, y2) 的单位向量为:

那么其经过旋转,其实这用到了一些知识,具体可以去查资料,其实很简单的,那么,当其 逆时针旋转 90 °,得到的是 (-v2, v1)。 顺时针旋转 90°,得到的是 (v2, -v1)。

那么代入之前的式子,我们可以得到 

因此,我们代码计算的时候,也是这样子一步步计算即可。

AC代码(C++)

const double EPS = 1e-7;
class Solution {
public:
    
    int cal(vector<vector<int>>& points, double xc, double yc, double r)
    {
        // 计算这个圆中,包含了几个点
        int cnt = 0;
        for(auto p : points)
        {
            double x = p[0], y = p[1];
            double d = (xc - x) * (xc - x) + (yc - y) * (yc - y);
            // 本来应该是 d < r * r 或者 d == r * r,但是 浮点数计算 == 要有误差的
            // 本来应该是 fabs() < EPS,但是我们直接给 r * r + EPS,那么 == 就变成了 < 
            if(d < r * r + EPS) ++cnt;
        }
        return cnt;
    }
    
    int numPoints(vector<vector<int>>& points, int r) {
        int ans = 1;   // 答案至少是 1,因为自己一个点就是一个圆
        int n = points.size();
        
        for(int i = 0;i < n; ++i)
        {
            int x1 = points[i][0], y1 = points[i][1];
            for(int j = i + 1;j < n; ++j)   // 不重复枚举
            {
                int x2 = points[j][0], y2 = points[j][1];
                
                int d2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);  // 计算其距离平方
                
                if(d2 == 0 || d2 > r * r * 4) continue;  //我们要保证两个点在圆上,那么最大距离就是直径,超过就不会同时在边上
                
                double midx = (x1 + x2) / 2.0, midy = (y1 + y2) / 2.0;  // 中点
                double d = sqrt(d2);  // 距离 d
                double centerDist = sqrt(1.0 * r * r - 1.0 * d * d / 4.0);  // 中点到 圆心距离
                
                double v1 = x1 - x2, v2 = y1 - y2;  // 向量(后面要单位化,所以 / d)

                // 圆心的两种情况
                double xc = midx + v2 / d * centerDist;
                double yc = midy - v1 / d * centerDist;
                ans = max(ans, cal(points, xc, yc, r));   // 每一种情况都去计算,包含了几个点,求其中最大值
                
                xc = midx - v2 / d * centerDist;
                yc = midy + v1 / d * centerDist;
                ans = max(ans, cal(points, xc, yc, r));
            }
        }
        
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值