LeetCode第155场周赛(Weekly Contest 155)解题报告 (拓扑排序还没看)

惨不忍睹,隔了好久,终于来做题解了,排名:409 / 1602。其实都不是很难,但在比赛的时候想不出来也是我的菜。后面补题能写完前三题,但是第四题的拓扑排序还真的没见过,又学到新知识了。

第一题,简单排序后就可以了。

第二题,根据题目所给的范围,时间复杂度只能是 O(logn) 才能通过,不然就会超时,所以想到的就是可以利用二分查找在答案范围内找出答案。还使用了容斥原理。

第三题,连通问题,使用并查集解决

第四题,拓扑排序(即有一个完成的顺序)

详细题解如下。


1. 最小绝对差(Minimum Absolute Difference)

               AC代码(Java)

               AC代码(C++)

2. 丑数 III(Ugly Number III)

               AC代码(C++)

3.交换字符串中的元素(Smallest String with Swaps)

               AC代码(C++)

4.项目管理(Sort Items by Groups Respecting Dependencies)

               AC代码(C++)

 


LeetCode第157场周赛地址:

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


1. 最小绝对差(Minimum Absolute Difference)

题目链接

https://leetcode-cn.com/contest/weekly-contest-155/problems/minimum-absolute-difference/

题意

给你个整数数组 arr,其中每个元素都 不相同

请你找到所有具有最小绝对差的元素对,并且按升序的顺序返回。

示例 1:

输入:arr = [4,2,1,3]
输出:[[1,2],[2,3],[3,4]]

示例 2:

输入:arr = [1,3,6,10,15]
输出:[[1,3]]

示例 3:

输入:arr = [3,8,-10,23,19,-4,-14,27]
输出:[[-14,-10],[19,23],[23,27]]

提示:

  1. 2 <= arr.length <= 10^5
  2. -10^6 <= arr[i] <= 10^6

解题思路

最小绝对差肯定是相邻的两个数,所以先进行排序,复杂度是O(nlogn)。

然后从头到尾扫一遍数组,记下最小的绝对差。

再从头到尾扫一遍数组,只要相邻两个的差 等于 最小的绝对差,那么就把这两个数就是要的元素对。

所以总的时间复杂度是O(nlogn)。

AC代码(Java)

class Solution {
    public List<List<Integer>> minimumAbsDifference(int[] arr) {
        
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        
        Arrays.sort(arr);
        int res = (int)2e6+10;
        
        for(int i = 0;i < arr.length-1;i++)
            res = Math.min(res, arr[i+1]-arr[i]);
        
        for(int i = 0;i < arr.length-1;i++)
        {
            if(res == arr[i+1]-arr[i])
                ans.add(Arrays.asList(arr[i], arr[i+1]));
        }
        
        return ans;
        
    }
}

AC代码(C++)

class Solution {
public:
    vector<vector<int>> minimumAbsDifference(vector<int>& arr) {
        
        vector<vector<int>> ans;
        sort(arr.begin(), arr.end());
        int minVal = INT_MAX;
        
        for(int i = 0;i < arr.size()-1;i++)
        {
            minVal = min(minVal, arr[i+1]-arr[i]);
        }
        
        for(int i = 0;i < arr.size()-1;i++)
        {
            if(arr[i+1]-arr[i] == minVal)
            {
                ans.push_back({arr[i], arr[i+1]});
            }
        }
        return ans;
    }
};

 


2. 丑数 III(Ugly Number III)

题目链接

https://leetcode-cn.com/contest/weekly-contest-155/problems/ugly-number-iii/

题意

请你帮忙设计一个程序,用来找出第 n 个丑数。

丑数是可以被 a  b  c 整除的 正整数

示例 1:

输入:n = 3, a = 2, b = 3, c = 5
输出:4
解释:丑数序列为 2, 3, 4, 5, 6, 8, 9, 10... 其中第 3 个是 4。

示例 2:

输入:n = 5, a = 2, b = 11, c = 13
输出:10
解释:丑数序列为 2, 4, 6, 8, 10, 11, 12, 13... 其中第 5 个是 10。

示例 3:

输入:n = 1000000000, a = 2, b = 217983653, c = 336916467
输出:1999999984

提示:

  1. 1 <= n, a, b, c <= 10^9
  2. 1 <= a * b * c <= 10^18
  3. 本题结果在 [1, 2 * 10^9] 的范围内

解题思路

丑数是可以被 a 或 b 或 c 整除的 正整数。

本来的想法是,在答案中 [1, 2e9] 中遍历,同时用一个变量记录是第几个丑数,但这样子的时间复杂度是O(n),由于 n 的取值问题,会导致超时。

所以要有一个方法比 O(n) 复杂度低,那就考虑 O(logn),所以考虑使用二分查找答案。

当二分查找答案是,对于第 num 个数,想要知道它是第 几个 丑数,那就要知道对于 num 而言,被 a 或 b 或 c 整除的个数有几个。

比如对于 num 而言,能被 a 整除的数有 num/a 个。而现在需要考虑能被 a 或 b 或 c 整除的个数,利用容斥定理:(A∪B∪C = A+B+C - A∩B - B∩C - C∩A + A∩B∩C),可以用韦恩图会更直观,具体可以查阅相关资料。

所以利用二分查找,就可以判断出某个 num 数,有几个丑数在前面,但这个数不一定是要求的第 n 个丑数,因为从 第 n 个丑数对应数,到第 n+1 个丑数间的数都满足被 a 或 b 或 c 整除的个数为 n 个。

所以我们要取算出来 n 个丑数的 num 中的最小值,所以判断的时候,不能一出现是有 n 个被 a 或 b 或 c 整除的数 num 就认为是答案,应该要算出所有满足条件的 num 中的最小值。

AC代码(C++)

// 复杂度如果 O(n),根据n的取值,会超时,所以要使用比O(n)复杂度更低的
// 那就是 O(logn),也就是二分查找。

#define LL long long 

class Solution {
public:
    
    
    LL gcd(LL a, LL b)
    {
        if(b == 0
            return a;
        return gcd(b, a%b);
    }
    
    LL lcm(LL a, LL b)
    {
        return a/gcd(a,b)*b;
    }
    
    int nthUglyNumber(int n, int a, int b, int c) {
        
        LL ans = 2e9;
        LL left = 1, right = 2e9;
        LL mid;
        LL ab = lcm(a,b);
        LL ac = lcm(a,c);
        LL bc = lcm(b,c);
        LL abc = lcm(a, lcm(b,c));
        
        while(left <= right)
        {
            mid = (right + left)/2;
            // 容斥原理
            LL cnt = mid/a + mid/b + mid/c - mid/ab - mid/ac - mid/bc + mid/abc;
            
            if(cnt >= n)  // 只要符合这个条件,求出其中的最小值,才是答案。
            {
                ans = min(ans, mid);
                right = mid-1;
            }
            else
                left = mid+1;
                
        }
        return ans;
        
    }
};


3.交换字符串中的元素(Smallest String with Swaps)

题目链接

https://leetcode-cn.com/contest/weekly-contest-155/problems/smallest-string-with-swaps/

题意

给你一个字符串 s,以及该字符串中的一些「索引对」数组 pairs,其中 pairs[i] = [a, b] 表示字符串中的两个索引(编号从 0 开始)。

你可以 任意多次交换 在 pairs 中任意一对索引处的字符。

返回在经过若干次交换后,s 可以变成的按字典序最小的字符串。

示例 1:

输入:s = "dcab", pairs = [[0,3],[1,2]]
输出:"bacd"
解释: 
交换 s[0] 和 s[3], s = "bcad"
交换 s[1] 和 s[2], s = "bacd"

示例 2:

输入:s = "dcab", pairs = [[0,3],[1,2],[0,2]]
输出:"abcd"
解释:
交换 s[0] 和 s[3], s = "bcad"
交换 s[0] 和 s[2], s = "acbd"
交换 s[1] 和 s[2], s = "abcd"

示例 3:

输入:s = "cba", pairs = [[0,1],[1,2]]
输出:"abc"
解释:
交换 s[0] 和 s[1], s = "bca"
交换 s[1] 和 s[2], s = "bac"
交换 s[0] 和 s[1], s = "abc"

提示:

  1. 1 <= s.length <= 10^5
  2. 0 <= pairs.length <= 10^5
  3. 0 <= pairs[i][0], pairs[i][1] < s.length
  4. s 中只含有小写英文字母

解题分析

根据题目意思,利用给出的 pairs 数组,可以知道有对应的若干位置是连通的,那么我们只要把连通中的字母统计,然后把字典序小的字母放连通中的前面,就可以把 s 变成字典序最小。

那么根据点与点之间的连通,同时连通可传递,可以考虑使用 并查集(考虑了连通情况,集合),关于并查集的知识可以参考我的另一篇博客:并查集 详细介绍

我们利用一个数组 cnts[ ][26],记录每个集合中的字母情况,每个位置 i 都有一个Boss,即对应在这个集合中,因此我们遍历所有位置,得到对应的Boss,然后利用cnts 存放这个集合中的字母情况。

接着遍历所有的位置,找到对应的Boss后,遍历该Boss(集合)中的字母情况,小的先放(放了之后,对应这个字母数量 - 1)

这样子就可以得到字典序最小的字符串 s

AC代码(C++)

const int MAXN = 1e5+10;
int fa[MAXN];
int cnts[MAXN][26]; // 用于记录第 i 个字符连通的集合(Boss)对应有的字母情况(0-25 对应 a-z)

class Solution {
public:
    
    // 并查集初始化
    void init(int n)
    {
        for(int i = 0;i < n;i++)
            fa[i] = i;
    }
    
    // 并查集查找根节点(Boss)+路径压缩
    int findFa(int x)
    {
        if(fa[x] != x)
            fa[x] = findFa(fa[x]);
            
        return fa[x];
    }
    
    // 将两个节点对应的集合,合并
    void join(int x, int y)
    {
        x = findFa(x);
        y = findFa(y);
        if(y != x)
            fa[y] = x;
    }
    
    string smallestStringWithSwaps(string s, vector<vector<int>>& pairs) {
        
        memset(cnts, 0, sizeof(cnts));
        init(s.size());
        
        // 先把所有集合进行合并
        for(int i = 0;i < pairs.size();i++)
        {
            int x = pairs[i][0], y = pairs[i][1];
            join(x, y);
        }
        
        // 记录每个节点对应的Boss,然后记录下这个Boss集合中有的字母情况
        for(int i = 0;i < s.size();i++)
        {
            fa[i] = findFa(i);
            cnts[fa[i]][s[i]-'a']++;
        }
        
        // 枚举每个节点
        for(int i = 0;i < s.size();i++)
        {
            // 每个节点对应的集合中字母情况,优先放小的在前,保证字典序最小
            for(int j = 0;j < 26;j++)
            {
                if(cnts[fa[i]][j] > 0)
                {
                    s[i] = 'a' + j;
                    cnts[fa[i]][j]--;   // 找到并且使用后,对应的字母个数 -- 
                    break;              // 跳出,因为这个位置的s 已经处理了,到下一个位置的 s 处理
                }
            }
        }
        
        return s;
        
    }
};

 


4.项目管理(Sort Items by Groups Respecting Dependencies)

题目链接

https://leetcode-cn.com/contest/weekly-contest-155/problems/sort-items-by-groups-respecting-dependencies/

题意

公司共有 n 个项目和  m 个小组,每个项目要不没有归属,要不就由其中的一个小组负责。

我们用 group[i] 代表第 i 个项目所属的小组,如果这个项目目前无人接手,那么 group[i] 就等于 -1。(项目和小组都是从零开始编号的)

请你帮忙按要求安排这些项目的进度,并返回排序后的项目列表:

  • 同一小组的项目,排序后在列表中彼此相邻。
  • 项目之间存在一定的依赖关系,我们用一个列表 beforeItems 来表示,其中 beforeItems[i] 表示在进行第 i 个项目前(位于第 i 个项目左侧)应该完成的所有项目。

结果要求:

如果存在多个解决方案,只需要返回其中任意一个即可。

如果没有合适的解决方案,就请返回一个 空列表

示例 :有图示,具体点进链接查看

提示:

  1. 1 <= m <= n <= 3*10^4
  2. group.length == beforeItems.length == n
  3. -1 <= group[i] <= m-1
  4. 0 <= beforeItems[i].length <= n-1
  5. 0 <= beforeItems[i][j] <= n-1
  6. i != beforeItems[i][j]

解题分析

根据题目,进行排序的要求有两个

  • 1、同一个小组的项目,需要相邻
  • 2、项目之间有一定的依赖性,即某些项目需要其他项目先完成

看第二点要求,这个是拓扑排序的一个算法(拓扑排序是一个比较常用的图论算法,经常用于完成有依赖关系的任务的排序。)

有依赖关系,所以就是考虑拓扑排序,但这里还多一个要求一,因为简单的拓扑排序无法满足要求一。

我们可以先把要求一中,要同一个小组的项目变成一个大项目(如果同一小组只有一个项目,那就认为是单独的一个项目)。然后将大项目和其他没有组的项目进行拓扑排序,这样子就保证满足了要求二。

然后同一小组中,可能也会存在依赖关系,所以对于每个大项目(即同一小组的项目)进行拓扑排序,即可满足要求一。

(具体代码实现,自己还不会拓扑排序,所以先使用了第155场周赛,中国排名第3的“wnjxyk”的代码)

AC代码(C++)

const int MAXN=3e4*2+50;
vector<int> items[MAXN];
int idx[MAXN], timeline;
set<int> inEdgeSet[MAXN], toEdge[MAXN], pool;
int inEdge[MAXN];
queue<int> que;
vector<int> poi;
int rnk[MAXN];
int cmp_arr[MAXN][2];

bool topsort(vector<vector<int>>& edges){
    while(!que.empty()) que.pop();
    for (auto i: poi){
        for (auto x: edges[i]) {
            if (idx[x]==-1) continue;
            if (idx[i]==idx[x]) continue;
            inEdgeSet[idx[i]].insert(idx[x]);
            toEdge[idx[x]].insert(idx[i]);
        }
    }
    
    pool.clear(); int cnt=0;
    for (auto i: poi){
        if (pool.insert(idx[i]).second==false) continue;
        ++cnt;
        inEdge[idx[i]]=inEdgeSet[idx[i]].size();
        
        if (inEdge[idx[i]]==0) que.push(idx[i]);// , printf("Add %d\n", idx[i]);
    }
    
    timeline=0;
    while(!que.empty()){
        int x=que.front();  que.pop();
        // printf("> %d\n", x);
        rnk[x]=++timeline; --cnt;
        for (auto v: toEdge[x]){
            --inEdge[v];
            if (inEdge[v]==0) que.push(v);
        }
    }
    
    return cnt==0;
}

inline bool cmp(int a, int b){
    if (cmp_arr[a][0]!=cmp_arr[b][0]) return cmp_arr[a][0]<cmp_arr[b][0];
    return cmp_arr[a][1]<cmp_arr[b][1];
}

class Solution {
public:
    vector<int> sortItems(int n, int m, vector<int>& group, vector<vector<int>>& beforeItems) {
        for (int i=0; i<m; i++) items[i].clear();
        for (int i=0; i<n; i++) {
            if (group[i]==-1) {
                idx[i]=i;
            }else{
                items[group[i]].push_back(i);
                idx[i]=n+group[i];
            }
        }
        
        bool flag;
        vector<int> ans;
        
        for (int i=0; i<n+m; i++) cmp_arr[i][0]=cmp_arr[i][1]=0;
        for (int i=0; i<n+m; i++) inEdge[i]=0, inEdgeSet[i].clear(), toEdge[i].clear();
        poi.clear(); for (int i=0; i<n; i++) poi.push_back(i);
        flag=topsort(beforeItems);
        
        if (!flag) return ans;
        for (int i=0; i<n; i++) cmp_arr[i][0]=rnk[idx[i]];
        
        for (int i=0; i<n+m; i++) inEdge[i]=0, inEdgeSet[i].clear(), toEdge[i].clear();
        for (int i=0; i<n+m; i++) idx[i]=-1;
        
        for (int i=0; i<m; i++){
            if (items[i].size()==0) continue;
            // printf("Topsort %d\n", i);
            poi=items[i];
            for (auto x: poi) idx[x]=x;
            flag=topsort(beforeItems);
            if (!flag) return ans;
            for (auto x: poi) cmp_arr[x][1]=rnk[x];
            for (auto x: poi) idx[x]=-1;
        }
        
        for (int i=0; i<n; i++) ans.push_back(i);
        sort(ans.begin(), ans.end(), cmp);
        
        return ans;
    }
};

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值