LeetCode之Weekly Contest 90
LeetCode第90场周赛记录
第一题:亲密字符串
问题:
给定两个由小写字母构成的字符串 A
和 B
,只要我们可以通过交换 A
中的两个字母得到与 B
相等的结果,就返回 true
;否则返回 false
。
示例 1:
输入: A = "ab", B = "ba" 输出: true
示例 2:
输入: A = "ab", B = "ab" 输出: false
示例 3:
输入: A = "aa", B = "aa" 输出: true
示例 4:
输入: A = "aaaaaaabc", B = "aaaaaaacb" 输出: true
示例 5:
输入: A = "", B = "aa" 输出: false
链接:https://leetcode-cn.com/contest/weekly-contest-90/problems/buddy-strings/
分析:
1.如果两个字符串长度不等,一定不满足
2.如果两个字符串完全相同,需要至少两个完全相同的字符,才能满足交换两个字母后相同,所以如果长度小于2,也一定不满足。
3.其他情况两个字符串需要有且仅有两个不同的字符,差异的两个字符仅仅是顺序不同。
AC Code:
1 class Solution { 2 public: 3 bool buddyStrings(string A, string B) { 4 if (A.size() != B.size() || A.size()<2) 5 { 6 return false; 7 } 8 //记录不同的,如果两者可以交换,返回true,完全相同,返回true 9 int counter = 0; //记录差异点数 10 string A1=""; //分别保存差异内容 11 string A2=""; 12 for (int i = 0; i < A.size(); i++) 13 { 14 if (A[i] != B[i]) 15 { 16 counter++; 17 A1 += A[i]; 18 A2 += B[i]; 19 } 20 } 21 if (counter == 0) 22 { 23 //完全一样的两个字符串 24 //需要有两个完全一样的字符 25 map<char, int> tmp; 26 for (int i = 0; i < A.size(); i++) 27 { 28 if (tmp.count(A[i])==1) 29 { 30 tmp[A[i]]++; 31 } 32 else 33 { 34 tmp[A[i]] = 1; 35 } 36 } 37 map<char, int>::iterator it; 38 for (it = tmp.begin(); it != tmp.end(); it++) 39 { 40 if (it->second >= 2) 41 { 42 return true; 43 } 44 } 45 return false; 46 } 47 if (A1.size()!=2) 48 { 49 return false; 50 } 51 else 52 { 53 if (A1[0] == A2[1] && A1[1] == A2[0]) 54 { 55 return true; 56 } 57 else 58 { 59 return false; 60 } 61 } 62 } 63 };
其他:
第一的做法:
1 class Solution { 2 public boolean buddyStrings(String A, String B) { 3 int[] l = new int[256]; 4 boolean two = false; 5 for(int i = 0; i < A.length(); i++) { 6 if(++l[A.charAt(i)] > 1) two = true; 7 } 8 for(int i = 0; i < B.length(); i++) { 9 --l[B.charAt(i)]; 10 } 11 for(int out: l) { 12 if(out != 0) return false; 13 } 14 int error = 0; 15 for(int i = 0; i < A.length(); i++) { 16 if(A.charAt(i) != B.charAt(i)) { 17 error++; 18 } 19 } 20 if(error == 2) return true; 21 if(error > 2) return false; 22 return two; 23 } 24 }
用的好像是Java,思路是先创建256大小字符数组【如果要对空间进行优化的话,可以只创建26个大小数组,因为题目中明确只有a-z,对应做一个-‘a’处理即可,不过区别不大】,用来统计每个字母出现个数,先统计A的数量,一旦有某个字符出现两次及以上,将标志two设为true。然后利用B中字符减去对应个数,结束后一旦某个字符对应位置不是0,则说明A中和B中对应字符个数不一致,一定不满足要求,返回false,然后统计AB中差异个数,如果差异个数是2,返回true,如果差异超过2,返回false,接下来一定是没有差异(如果只有一个字符差异,一定不满足AB中对应字符个数一致要求,前面就会返回false),返回two的值,即是否有某个字符数量达到2.
第二题:括号的分数
题目:
给定一个平衡括号字符串 S
,按下述规则计算该字符串的分数:
()
得 1 分。AB
得A + B
分,其中 A 和 B 是平衡括号字符串。(A)
得2 * A
分,其中 A 是平衡括号字符串。
示例 1:
输入: "()" 输出: 1
示例 2:
输入: "(())" 输出: 2
示例 3:
输入: "()()" 输出: 2
示例 4:
输入: "(()(()))" 输出: 6
提示:
S
是平衡括号字符串,且只含有(
和)
。2 <= S.length <= 50
链接:https://leetcode-cn.com/contest/weekly-contest-90/problems/score-of-parentheses/
分析:
1.如果是(),值为1;
2.如果是“”,值为0;
3.对于某个string S,如果形如()S`,结果为1+value(S`)
如果形如(S`)S``,结果为2*value(S`)+value(S``)
已知给出字符串是平衡的,只需要按照规则递归分解求值即可
需要做的就是找出S`以及剩余字符串S``
AC code:
1 class Solution { 2 public: 3 int scoreOfParentheses(string S) { 4 if (S.size() == 0) //空串值为0 5 { 6 return 0; 7 } 8 if (S.size() == 2) //已知括号匹配,长度2的一定是(),值是1 9 { 10 return 1; 11 } 12 string tmp = ""; //嵌套的字符串 13 string left = ""; //当前匹配后剩余的字符串 14 stack<char> tmpstack; //S[0]一定是'(' 15 if (S[1] == ')') // ()S`类型 16 { 17 for (int j = 2; j < S.size(); j++) 18 { 19 left += S[j]; 20 } 21 return 1 + scoreOfParentheses(left);; 22 } 23 tmp += S[1]; 24 tmpstack.push(S[1]); 25 for (int i = 2; i < S.size(); i++) 26 { 27 if (tmpstack.empty() == true && S[i] == ')') //完成匹配,形如(S`)S`` 28 { 29 for (int j = i + 1; j < S.size(); j++) 30 { 31 left += S[j]; 32 } 33 return 2 * scoreOfParentheses(tmp) + scoreOfParentheses(left); 34 } 35 else 36 { 37 tmp += S[i]; 38 if (tmpstack.empty() == true) 39 { 40 tmpstack.push(S[i]); 41 } 42 else if (tmpstack.top() == '(' && S[i] == ')') 43 { 44 tmpstack.pop(); 45 } 46 else 47 { 48 tmpstack.push(S[i]); 49 } 50 } 51 } 52 53 } 54 };
其他:
第一的做法:
1 class Solution { 2 public int scoreOfParentheses(String S) { 3 if(S.length() == 0) return 0; 4 if(S.charAt(0) == '(' && S.charAt(1) == ')') { 5 return 1 + scoreOfParentheses(S.substring(2)); 6 } 7 int d = 0; 8 int end = -1; 9 for(int i = 0; i < S.length(); i++) { 10 if(S.charAt(i) == '(') d++; 11 else d--; 12 if(d == 0) { 13 end = i+1; 14 break; 15 } 16 } 17 return 2 * scoreOfParentheses(S.substring(1, end-1)) + scoreOfParentheses(S.substring(end)); 18 } 19 }
基本思路基本一致,处理(S`)S``类型时候没有使用栈而是直接通过识别‘('和')’加减计数来找到(S`)
第三题:镜面反射
题目:
有一个特殊的正方形房间,每面墙上都有一面镜子。除西南角以外,每个角落都放有一个接受器,编号为 0
, 1
,以及 2
。
正方形房间的墙壁长度为 p
,一束激光从西南角射出,首先会与东墙相遇,入射点到接收器 0
的距离为 q
。
返回光线最先遇到的接收器的编号(保证光线最终会遇到一个接收器)。
示例:
输入: p = 2, q = 1 输出: 2 解释: 这条光线在第一次被反射回左边的墙时就遇到了接收器 2 。
提示:
1 <= p <= 1000
0 <= q <= p
链接:https://leetcode-cn.com/contest/weekly-contest-90/problems/mirror-reflection/
分析:
1,利用镜面反射特点,可以假设上面有无限长,光线一直向上走;
2,在1的前提下,每反射一次,向上高度增加q;
3,如果被某个接收器接收到了,说明当前高度是p的整数倍;
4,如果反射偶数次被接收到,则必定是2号接收到;
5,如果最终达到的“高度”是墙面高度p的奇数倍,则被1号接收到;
6,如果最终达到的“高度”是墙面高度p的偶数倍,则被0号接收到;
分析完成后问题就变得很简单了。
AC code:
1 class Solution { 2 public: 3 int mirrorReflection(int p, int q) { 4 //无限向上延伸,虚拟节点,整数倍获得结果 5 int counter = 1; 6 while (true) 7 { 8 if (q*counter % p == 0) 9 { 10 if (counter % 2 == 0) 11 { 12 return 2; 13 } 14 if (q*counter / p % 2 == 0) 15 { 16 return 0; 17 } 18 return 1; 19 } 20 counter++; 21 } 22 } 23 };
其他:
第一code
1 class Solution { 2 public int mirrorReflection(int p, int q) { 3 int curr = 0; 4 for(int i = 1; true; i++) { 5 curr += q; 6 curr %= (2*p); 7 if(curr == p) { 8 if(i%2 == 1) { 9 return 1; 10 } 11 else { 12 return 2; 13 } 14 } 15 if(curr == 0) { 16 return 0; 17 } 18 } 19 } 20 }
假设房间向上翻转一次,得到一个p宽,2*p高的长方形,curr记录当前高度(高度范围[0,2*p)),每次反射高度增加q,如果高度等于0,则会被0号接受,如果高度等于p,则被1或者2接收,其中反射奇数次被1号接收,反射偶数次被2号接收。
感觉自己的code有一点不好的就是如果pq很大且数据巧合有可能溢出,映射到2p范围内会更好。
第四题:雇佣 K 名工人的最低成本
题目:
- 用户通过次数0
- 用户尝试次数0
- 通过次数0
- 提交次数0
- 题目难度Hard
有 N
名工人。 第 i
名工人的工作质量为 quality[i]
,其最低期望工资为 wage[i]
。
现在我们想雇佣 K
名工人组成一个工资组。在雇佣 一组 K 名工人时,我们必须按照下述规则向他们支付工资:
- 对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资。
- 工资组中的每名工人至少应当得到他们的最低期望工资。
返回组成一个满足上述条件的工资组至少需要多少钱。
示例 1:
输入: quality = [10,20,5], wage = [70,50,30], K = 2 输出: 105.00000
示例 2:
输入: quality = [3,1,10,10,1], wage = [4,8,2,2,7], K = 3 输出: 30.66667
提示:
1 <= K <= N <= 10000
,其中N = quality.length = wage.length
1 <= quality[i] <= 10000
1 <= wage[i] <= 10000
- 与正确答案误差在
10^-5
之内的答案将被视为正确的。
链接:https://leetcode-cn.com/contest/weekly-contest-90/problems/minimum-cost-to-hire-k-workers/
分析:
速度有点慢,最后20分钟开始处理,没想太多,测试数据到第41个超时了,O(n*n)毫无前途。
TimeOut COde:
1 class Solution { 2 public: 3 double mincostToHireWorkers(vector<int>& quality, vector<int>& wage, int K) { 4 double min = 9999999.0; 5 for (int i = 0; i < quality.size(); i++) 6 { 7 vector<double> tmpmin; 8 double tmp = wage[i]*1.0 / quality[i]; 9 for (int j = 0; j < quality.size(); j++) 10 { 11 if (quality[j] * tmp < wage[j]) 12 { 13 continue; 14 } 15 else 16 { 17 tmpmin.push_back(quality[j] * tmp); 18 } 19 } 20 if (tmpmin.size() < K) 21 { 22 continue; 23 } 24 else 25 { 26 sort(tmpmin.begin(), tmpmin.end()); 27 double tmpsum = 0; 28 for (int index = 0; index < K; index++) 29 { 30 tmpsum += tmpmin[index]; 31 } 32 if (tmpsum < min) 33 { 34 min = tmpsum; 35 } 36 } 37 } 38 return min; 39 } 40 };
之前想法是算出每个人的“性价比”,即最低要求和工作质量的比值,然后按照该比值衡量所有人,如果该比值下的工资低于最低期望,则不会加入,然后在加入里面选出最低的K个,得到一个总和,按照每个人的标准衡量一遍,所有总和中最低的一个即为符合要求的结果。但是显然O(n*n)效率太低。
比赛到时见后参考别人code,发现按照性价比排序后只需要过一遍就能得到结果。
AC 思路:
单位工作质量所要工资其实和性价比相反,越低越好。但是并不是说比值越低总价就越低,比如一个工作质量/最低工资分别是[1,10]和[20,40],前者比值10,后者比值2,但是只要1个人的话显然需要选择前者。
按照wget/quality进行升序排序,则只需要获得quality总和即可,因为所采用的单位质量价值都是相同的,都采用的最低的一个。由于采用的单位质量价值相同,影响最终结果的只有quality值了,则每次需要将前k个中quality最大的一个移除进行后面的尝试。
在给出的第一组测试数据中,
[10,20,5]
[70,50,30]
计算wget/quality以及排序后结果如下:
[20,5.10]
[50,30,40]
[2.5,,6,7]
从中选择2个,则第一次尝试结果选择前两个,按照6计算,值为(20+5)*6=150
前面有分析过并非wget/quality低的总和就会少,所以需要进行遍历尝试后面的所有值,
当选择下一个时候,由于确定比例会选择7,影响到结果的只有quality值了,20>5,移除20,得到新的组合(5+10)*7=105
对于第二组测试数据,按照同样方式处理排序后结果如下
[10,10,3,1,1]
[2,2,4,7,8]
[0.2,0.2,1.3333,7,8]
选择3个,第一次前三个组合得到(10+10+3)*1.333=30.6667
接下来尝试比例7的,移除前面quality最大的10,(10+3+1)*7=98
尝试比例8 ,移除quality最大的10,(3+1+1)*8=40,则最小值30.6667
AC code:
1 class Solution { 2 public: 3 typedef struct Worker 4 { 5 int quality; 6 int wage; 7 double ratio; 8 bool operator< (const Worker &w1) 9 { 10 return ratio < w1.ratio; 11 } 12 }Worker; 13 14 double mincostToHireWorkers(vector<int>& quality, vector<int>& wage, int K) 15 { 16 vector < Worker > workers; 17 for (int i = 0; i < quality.size(); i++) 18 { 19 Worker w = { quality[i], wage[i], wage[i] * 1.0 / quality[i] }; 20 workers.push_back(w); 21 } 22 //按照性价比排序 23 sort(workers.begin(), workers.end()); 24 double ret = INT_MAX; 25 vector<int> tmpqs;//由于性价比是下降的(wget/qualite 上升),即单位质量花费是增加的,所以每次都把前面qualities最大的除去 26 for (int i = 0; i < workers.size(); i++) 27 { 28 if (tmpqs.size() < K) 29 { 30 tmpqs.push_back(workers[i].quality); 31 } 32 if (tmpqs.size() == K) 33 { 34 //得到一个组合,判断是否更小 35 int tmpsumquality=0; 36 int tmpmaxindex = 0; 37 int tmpmaxquality = INT_MIN; 38 for (int j = 0; j < K; j++) 39 { 40 tmpsumquality += tmpqs[j]; 41 if (tmpqs[j]>tmpmaxquality) 42 { 43 tmpmaxindex = j; 44 tmpmaxquality = tmpqs[j]; 45 } 46 } 47 ret = min(ret, tmpsumquality*workers[i].ratio); 48 //然后需要移除当前最大qualities 49 tmpqs.erase(tmpqs.begin() + tmpmaxindex); 50 } 51 } 52 return ret; 53 } 54 55 56 };
其他:
1.DBL_MAX默认是不认的,所以应改为INT_MAX
2.第一code
1 class Solution { 2 public double mincostToHireWorkers(int[] quality, int[] wage, int K) { 3 State[] l = new State[quality.length]; 4 for(int i = 0; i < quality.length; i++) { 5 l[i] = new State(quality[i], wage[i]); 6 } 7 Arrays.sort(l); 8 double ret = Double.MAX_VALUE; 9 PriorityQueue<Integer> q = new PriorityQueue<Integer>(); 10 long sum = 0; 11 for(int i = 0; i < l.length; i++) { 12 q.add(-l[i].quality); 13 sum += l[i].quality; 14 if(q.size() > K) { 15 sum += q.poll(); 16 } 17 if(q.size() == K) { 18 ret = Math.min(ret, sum * l[i].ratio()); 19 } 20 } 21 return ret; 22 } 23 class State implements Comparable<State> { 24 public int quality; 25 public int wage; 26 public State(int quality, int wage) { 27 super(); 28 this.quality = quality; 29 this.wage = wage; 30 } 31 public double ratio() { 32 return wage / (double) quality; 33 } 34 public int compareTo(State s) { 35 return Double.compare(ratio(), s.ratio()); 36 } 37 @Override 38 public String toString() { 39 return "State [quality=" + quality + ", wage=" + wage + "]"; 40 } 41 42 } 43 }
a.利用了PriorityQueue自动调整结构的特定,通过poll将最大quality移除掉
b.通过*-1将最小堆转换为最大堆
c.
1 for(int i = 0; i < l.length; i++) { 2 q.add(-l[i].quality); 3 sum += l[i].quality; 4 if(q.size() > K) { 5 sum += q.poll(); 6 } 7 if(q.size() == K) { 8 ret = Math.min(ret, sum * l[i].ratio()); 9 } 10 }
中,q.poll的有可能就是刚插入进去的,比如某个quality最大,同时ratio也比较大的,完全可以直接跳过,也许能够进一步优化,不过这样做的好处就是形式的统一。
总结:
第四次参加周赛了,虽说仍然没能完成四道题,不过至少四个都懂了,也算是有所收获。和高手比起来,感觉最欠缺的一是速度,二是对语言特性的掌握,三是对常用算法的掌握。
1.速度上来讲第一四道题做完不到20分钟,自己第一个AC都快18分钟了,即使会做没时间做也是白搭,即使是实际项目中,也是越早做出来越早发现问题有时间完善改进。
2.虽说C/C++/C#/Java/Go/Python/Swift/Kotlin/VB等都稍有涉猎,能拿来用,尤其C/C++从开始于到现在都六七年了,不过仅仅限于基础用法的使用,好多高阶用法并没掌握,所以一旦需要要么临时搜索查询,要么根本就不知道有只能笨拙的自己实现,掌握多门语言固然不错,但是要有自己的核心技能语言,暂且选择C++/C#好了,今后不仅要能用,更要用好,要深入理解掌握。
3.见多方能识广,承认有天才,但自认为不是,而大多数问题其实都已经存在且被解决,一方面固然需要提高自己的独立思考能力,另一方面也不能闭门造车,多学习其他人,才能更快的提高。
化用老师的一句话:大量练习,大量做题,大量阅读,自然能够提高。