惨不忍睹,隔了好久,终于来做题解了,排名: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
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]]
提示:
2 <= arr.length <= 10^5
-10^6 <= arr[i] <= 10^6
最小绝对差肯定是相邻的两个数,所以先进行排序,复杂度是O(nlogn)。
然后从头到尾扫一遍数组,记下最小的绝对差。
再从头到尾扫一遍数组,只要相邻两个的差 等于 最小的绝对差,那么就把这两个数就是要的元素对。
所以总的时间复杂度是O(nlogn)。
class Solution {
public List> minimumAbsDifference(int[] arr) {
List> ans = new ArrayList>();
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;
}
}
class Solution {
public:
vector> minimumAbsDifference(vector& arr) {
vector> 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;
}
};
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 <= n, a, b, c <= 10^9
1 <= a * b * c <= 10^18
- 本题结果在
[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 中的最小值。
// 复杂度如果 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;
}
};
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 <= s.length <= 10^5
0 <= pairs.length <= 10^5
0 <= pairs[i][0], pairs[i][1] < s.length
s
中只含有小写英文字母
根据题目意思,利用给出的 pairs 数组,可以知道有对应的若干位置是连通的,那么我们只要把连通中的字母统计,然后把字典序小的字母放连通中的前面,就可以把 s 变成字典序最小。
那么根据点与点之间的连通,同时连通可传递,可以考虑使用 并查集(考虑了连通情况,集合),关于并查集的知识可以参考我的另一篇博客:并查集 详细介绍
我们利用一个数组 cnts[ ][26],记录每个集合中的字母情况,每个位置 i 都有一个Boss,即对应在这个集合中,因此我们遍历所有位置,得到对应的Boss,然后利用cnts 存放这个集合中的字母情况。
接着遍历所有的位置,找到对应的Boss后,遍历该Boss(集合)中的字母情况,小的先放(放了之后,对应这个字母数量 - 1)
这样子就可以得到字典序最小的字符串 s
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>& 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;
}
};
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 <= m <= n <= 3*10^4
group.length == beforeItems.length == n
-1 <= group[i] <= m-1
0 <= beforeItems[i].length <= n-1
0 <= beforeItems[i][j] <= n-1
i != beforeItems[i][j]
根据题目,进行排序的要求有两个
看第二点要求,这个是拓扑排序的一个算法(拓扑排序是一个比较常用的图论算法,经常用于完成有依赖关系的任务的排序。)
有依赖关系,所以就是考虑拓扑排序,但这里还多一个要求一,因为简单的拓扑排序无法满足要求一。
我们可以先把要求一中,要同一个小组的项目变成一个大项目(如果同一小组只有一个项目,那就认为是单独的一个项目)。然后将大项目和其他没有组的项目进行拓扑排序,这样子就保证满足了要求二。
然后同一小组中,可能也会存在依赖关系,所以对于每个大项目(即同一小组的项目)进行拓扑排序,即可满足要求一。
(具体代码实现,自己还不会拓扑排序,所以先使用了第155场周赛,中国排名第3的“wnjxyk”的代码)
const int MAXN=3e4*2+50;
vector items[MAXN];
int idx[MAXN], timeline;
set inEdgeSet[MAXN], toEdge[MAXN], pool;
int inEdge[MAXN];
queue que;
vector poi;
int rnk[MAXN];
int cmp_arr[MAXN][2];
bool topsort(vector>& 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] sortItems(int n, int m, vector& group, vector>& beforeItems) {
for (int i=0; i ans;
for (int i=0; i