LeetCode之Weekly Contest 93
Posted youdias
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode之Weekly Contest 93相关的知识,希望对你有一定的参考价值。
第一题:二进制间距
问题:
给定一个正整数 N
,找到并返回 N
的二进制表示中两个连续的 1 之间的最长距离。
如果没有两个连续的 1,返回 0
。
示例 1:
输入:22 输出:2 解释: 22 的二进制是 0b10110 。 在 22 的二进制表示中,有三个 1,组成两对连续的 1 。 第一对连续的 1 中,两个 1 之间的距离为 2 。 第二对连续的 1 中,两个 1 之间的距离为 1 。 答案取两个距离之中最大的,也就是 2 。
示例 2:
输入:5 输出:2 解释: 5 的二进制是 0b101 。
示例 3:
输入:6 输出:1 解释: 6 的二进制是 0b110 。
示例 4:
输入:8 输出:0 解释: 8 的二进制是 0b1000 。 在 8 的二进制表示中没有连续的 1,所以返回 0 。
提示:
1 <= N <= 10^9
链接:https://leetcode-cn.com/contest/weekly-contest-93/problems/binary-gap/
分析:
首先将数据转换为二进制用vector存储起来,然后在存储数字vector中的1的坐标,新的坐标vector中相邻两个差值最大的就是最大距离。
如果0个数不到两个,则返回0
AC Code:
1 class Solution { 2 public: 3 int binaryGap(int N) { 4 int ret = 0; 5 vector<int> nums; //转换为二进制数字,存储每一位 6 while (N) 7 { 8 nums.emplace_back(N & 0x01); 9 N /= 2; 10 } 11 vector<int> diss;//记录1的坐标 12 for (int i = 0; i < nums.size(); i++) 13 { 14 if (nums[i] == 1) 15 { 16 diss.emplace_back(i); 17 } 18 } 19 if (diss.size() <= 1) 20 { 21 return 0; 22 } 23 int f = diss[0]; 24 for (int i = 1; i < diss.size(); i++) 25 { 26 ret = max(ret, diss[i] - diss[i - 1]); 27 } 28 29 return ret; 30 } 31 };
其他:
目前看不到用时最短code,第一code:
1 class Solution { 2 public int binaryGap(int N) { 3 int prev = -1; 4 int ret = 0; 5 for(int i = 0;i < 31;i++){ 6 if(N<<~i<0){ 7 if(prev >= 0){ 8 ret = Math.max(ret, i - prev); 9 } 10 prev = i; 11 } 12 } 13 return ret; 14 } 15 }
将数据左移0-30位,0左移30位还是0,1左移30位是0x80000000,最高位是符号位,如果N<<~i <0,说明当前最高位是1,思路其实和上面差不多,都是找到两个1之间距离,更新取大值,不过我的是从低位算起,这个是从高位算起。
第一题:重新排序得到 2 的幂
问题:
从正整数 N
开始,我们按任何顺序(包括原始顺序)将数字重新排序,注意其前导数字不能为零。
如果我们可以通过上述方式得到 2 的幂,返回 true
;否则,返回 false
。
示例 1:
输入:1 输出:true
示例 2:
输入:10 输出:false
示例 3:
输入:16 输出:true
示例 4:
输入:24 输出:false
示例 5:
输入:46 输出:true
提示:
1 <= N <= 10^9
链接:https://leetcode-cn.com/contest/weekly-contest-93/problems/reordered-power-of-2/
分析:
如果将数字按照每位数字进行重新排列组合后检验是否是2的幂,一来比较复杂,二来位数多了后排列组合可能太多。反向思考下,知道数据“长度”,找到符合该长度的可能的2的幂的数字,然后数字按照大小重新排序,如果一致则说明能组成2的幂。
AC Code:
1 class Solution { 2 public: 3 bool reorderedPowerOf2(int N) { 4 //先找到数字位数,得到该范围内的合法数据,每个数字单独排序,如果vector一致,则可以 5 bool ret = false; 6 vector<int> ornums; 7 int length = 0; 8 while (N) 9 { 10 ornums.emplace_back(N % 10); 11 N /= 10; 12 } 13 sort(ornums.begin(), ornums.end()); 14 15 length = ornums.size(); 16 vector<vector<int>> checks; 17 checks = getvalidnums(length); 18 int sameflag = true; 19 for (int i = 0; i < checks.size(); i++) 20 { 21 if (checks[i].size() == length) 22 { 23 sameflag = true; 24 for (int j = 0; j < checks[i].size(); j++) 25 { 26 if (checks[i][j] != ornums[j]) 27 { 28 sameflag = false; 29 break; 30 } 31 } 32 // 33 if (sameflag == false) 34 { 35 continue; 36 } 37 else 38 { 39 return true; 40 } 41 } 42 } 43 return ret; 44 45 } 46 vector<vector<int>> getvalidnums(int len) 47 { 48 vector<vector<int>> ret; 49 vector<int> tmp; 50 vector<int> tmpnum; 51 tmp = getvalidnum(len); 52 for (int i = 0; i < tmp.size(); i++) 53 { 54 int tmpdata = tmp[i]; 55 tmpnum.clear(); 56 while (tmpdata) 57 { 58 tmpnum.emplace_back(tmpdata % 10); 59 tmpdata /= 10; 60 } 61 sort(tmpnum.begin(), tmpnum.end()); 62 ret.emplace_back(tmpnum); 63 } 64 return ret; 65 } 66 vector<int> getvalidnum(int len) 67 { 68 vector<int> ret; 69 int low = (int)pow(10, len - 1); 70 int high = (int)pow(10, len); 71 for (int i = 0; (int)pow(2, i) < high; i++) 72 { 73 if ((int)pow(2, i) >= low) 74 { 75 ret.emplace_back((int)pow(2, i)); 76 } 77 } 78 79 return ret; 80 } 81 82 };
其他:
第一code:
1 classcl Solution { 2 3 int[] freq(char[] s) 4 { 5 int[] f = new int[10]; 6 for(char c : s){ 7 f[c-‘0‘]++; 8 } 9 return f; 10 } 11 12 public boolean reorderedPowerOf2(int N) { 13 char[] s = Integer.toString(N).toCharArray(); 14 int[] f = freq(s); 15 16 for(long i = 1;i <= 1<<30;i*=2){ 17 int[] g = freq(Long.toString(i).toCharArray()); 18 if(Arrays.equals(f, g))return true; 19 } 20 return false; 21 } 22 }
看起来像是JAVA,首先将数字转换为字符串s,然后转为数字数组f,f实际上记录的是0-9每个数字出现的个数。在1-1<<30 范围内(每次加倍),检测得到的数字i的数字数组(即组成数字的0-9每个的个数)是否和f一致。
第三题:优势洗牌
问题:
给定两个大小相等的数组 A
和 B
,A 相对于 B 的优势可以用满足 A[i] > B[i]
的索引 i
的数目来描述。
返回 A
的任意排列,使其相对于 B
的优势最大化。
示例 1:
输入:A = [2,7,11,15], B = [1,10,4,11] 输出:[2,11,7,15]
示例 2:
输入:A = [12,24,8,32], B = [13,25,32,11] 输出:[24,32,8,12]
提示:
1 <= A.length = B.length <= 10000
0 <= A[i] <= 10^9
0 <= B[i] <= 10^9
链接:https://leetcode-cn.com/contest/weekly-contest-93/problems/advantage-shuffle/
分析:
目的是想要赢得场次多,类似田忌赛马,如果赢不了,直接拿最差的来应对。
首先将两个队伍按照实力排序,从强到弱AB对战,如果A能赢则迎战,否则A中最弱的来应对,A中的当前最强应对B的下一个。
AC Code:
1 class Solution { 2 public: 3 vector<int> advantageCount(vector<int>& A, vector<int>& B) { 4 vector<int> ret; 5 vector<int> ShadowA(A); //引用传递,避免修改到AB,其实用不到的 6 vector<int> ShadowB(B); 7 sort(ShadowA.begin(), ShadowA.end()); //先进行排序,方便对战 8 sort(ShadowB.begin(), ShadowB.end()); 9 vector<pair<int, int>> battles; // 10 //vector<int> Aleft; //本来想要的是如果B中的某一个A中剩余部分没有人赢得了,跳过,结束后A中剩余部分直接和B中剩余部分随便匹配,反正打不过。不过提交出错后调试发现直接拿当前最弱的来应对就好了。 11 //vector<int> Bleft; 12 int ilow = 0; //A中当前最弱的坐标 13 for (int i = ShadowA.size() - 1, j = ShadowB.size() - 1; i >= ilow, j >= 0;) 14 { 15 16 if (ShadowA[i] > ShadowB[j]) //能赢,进行匹配 17 { 18 pair<int, int> tmp{ ShadowB[j], ShadowA[i] }; 19 battles.emplace_back(tmp); 20 i--; 21 j--; 22 //continue; 23 } 24 else 25 { 26 //Bleft.emplace_back(ShadowB[j]); 27 pair<int, int> tmp{ ShadowB[j], ShadowA[ilow] }; //打不过,直接拿A中最弱的一个来应对 28 battles.emplace_back(tmp); 29 ilow++; 30 j--; 31 //continue; 32 } 33 34 } 35 36 for (int i = 0; i < B.size(); i++) //将交战匹配对按照B中原有顺序排列输出A的出战顺序 37 { 38 for (int j = battles.size() - 1; j >= 0; j--) 39 { 40 if (battles[j].first == B[i]) 41 { 42 ret.emplace_back(battles[j].second); 43 battles.erase(battles.begin() + j); 44 break; 45 } 46 } 47 } 48 49 return ret; 50 } 51 };
其他:
第一code:
1 class Solution { 2 public int[] advantageCount(int[] a, int[] b) { 3 int n = a.length; 4 int[][] bi = new int[n][]; 5 for(int i = 0;i < n;i++){ 6 bi[i] = new int[]{b[i], i}; 7 } 8 Arrays.sort(bi, new Comparator<int[]>() { 9 public int compare(int[] a, int[] b) { 10 return a[0] - b[0]; 11 } 12 }); 13 Arrays.sort(a); 14 int p = 0; 15 int[] ra = new int[n]; 16 Arrays.fill(ra, -1); 17 boolean[] used = new boolean[n]; 18 for(int i = 0;i < n;i++){ 19 while(p < n && a[p] <= bi[i][0]){ 20 p++; 21 } 22 if(p < n){ 23 ra[bi[i][1]] = a[p]; 24 used[p] = true; 25 p++; 26 } 27 } 28 int q = 0; 29 for(int i = 0;i < n;i++){ 30 if(!used[i]){ 31 while(q < n && ra[q] != -1)q++; 32 assert q < n; 33 ra[q] = a[i]; 34 } 35 } 36 return ra; 37 } 38 }
还是java,不太懂而且没环境不太好调试查看,大概意思是对A进行排序,然后B中的每一个充A中找到最接近但比B大的数字来迎战?
第二用的是C++:
1 class Solution { 2 public: 3 vector<int> advantageCount(vector<int>& a, vector<int>& b) { 4 int i,j,k,n,l; 5 vector<pair<int,int> > bb; 6 vector<pair<int,int> > aa; 7 vector<int> ans; 8 bb.clear(); 9 n=a.size(); 10 for (i=0;i<n;i++) 11 bb.push_back(make_pair(b[i],i)); 12 ans.clear(); 13 for (i=0;i<n;i++) 14 ans.push_back(-1); 15 sort(a.begin(),a.end()); 16 reverse(a.begin(),a.end()); 17 sort(bb.begin(),bb.end()); 18 reverse(bb.begin(),bb.end()); 19 j=0; 20 for (i=0;i<a.size();i++) 21 { 22 while ((j<bb.size())&&(bb[j].first>=a[i])) j++; 23 if (j==bb.size()) break; 24 ans[bb[j].second]=a[i]; 25 j++; 26 } 27 l=0; 28 for (k=i;k<a.size();k++) 29 { 30 while ((l<ans.size())&&(ans[l]!=-1)) l++; 31 ans[l]=a[k]; 32 l++; 33 } 34 return ans; 35 } 36 };
第四题:最低加油次数
问题:
汽车从起点出发驶向目的地,该目的地位于出发位置东面 target
英里处。
沿途有加油站,每个 station[i]
代表一个加油站,它位于出发位置东面 station[i][0]
英里处,并且有 station[i][1]
升汽油。
假设汽车油箱的容量是无限的,其中最初有 startFuel
升燃料。它每行驶 1 英里就会用掉 1 升汽油。
当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中。
为了到达目的地,汽车所必要的最低加油次数是多少?如果无法到达目的地,则返回 -1
。
注意:如果汽车到达加油站时剩余燃料为 0,它仍然可以在那里加油。如果汽车到达目的地时剩余燃料为 0,仍然认为它已经到达目的地。
示例 1:
输入:target = 1, startFuel = 1, stations = [] 输出:0 解释:我们可以在不加油的情况下到达目的地。
示例 2:
输入:target = 100, startFuel = 1, stations = [[10,100]] 输出:-1 解释:我们无法抵达目的地,甚至无法到达第一个加油站。
示例 3:
输入:target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]] 输出:2 解释: 我们出发时有 10 升燃料。 我们开车来到距起点 10 英里处的加油站,消耗 10 升燃料。将汽油从 0 升加到 60 升。 然后,我们从 10 英里处的加油站开到 60 英里处的加油站(消耗 50 升燃料), 并将汽油从 10 升加到 50 升。然后我们开车抵达目的地。 我们沿途在1两个加油站停靠,所以返回 2 。
提示:
1 <= target, startFuel, stations[i][1] <= 10^9
0 <= stations.length <= 500
0 < stations[0][0] < stations[1][0] < ... < stations[stations.length-1][0] < target
链接:https://leetcode-cn.com/contest/weekly-contest-93/problems/minimum-number-of-refueling-stops/
分析:
最开始采用贪心策略,在当前能跑到的范围内找到油量最大的加油站,在那里加油,最后几分钟纯粹尝试下,当然失败了,局部的第二大未必就比另一个范围的最大值小。
当初做LeetCode的初衷本来就是长长见识,避免彻底废掉,上周的第四题折腾了好几个晚上,到现在92周总结都还没写,所以这次没准备纠结太多,稍微尝试了下,比如从起点开始,得到所有可能的路径,然后一直扩展
下去,最后得到所有能到达终点的策略,从中选择加油次数最小的。
比如给出的例子中,100/10, [[10,60],[20,30],[30,30],[60,40]]
初始油量10,[0,10],10范围内只有一个加油站没得选,{[0,10],[10,60]},当前位置10,油量60,可以到到极限70,剩下加油站都可以到达,下一次扩展结果
0/10+10/60+20/30:当前油量:80,位置20,剩余里程80
0/10+10/60+30/30:当前油量:70,位置30,剩余里程70
0/10+10/60+60/40:当前油量:50,位置60,剩余里程40
上面任意一个都可以到达目的地。
假设某一路线A一能够到达目的地,剩余部分只要已经加油次数超过A的次数,即可舍去。
但是这样涨下去可能爆内存,而且贪心时候给出的一个测例{ { 13, 21 }, { 26, 115 }, { 100, 47 }, { 225, 99 }, { 299, 141 }, { 444, 198 }, { 608, 190 }, { 636, 157 }, { 647, 255 }, { 841, 123 } };
尝试手动模拟,结果数据太多难以模拟,而且懒得折腾了,直接参考第一code。
做法是首先得到当前能到达的所有加油站,按照油量排序存储起来,在油量最多的那个里面加油,然后再根据当前位置以及油量,找到所有能够到达的位置,将加油站油量存储起来。
通过优先队列存储油量,这样的好处是自动在top位置存储当前油量最多的加油站,由于队列里面存储的加油站都是当前可达的,所以即使油量多的两个加油站是相邻的,也会在后面更新过程中用到,避免错过局部最大值。
在{ { 13, 21 }, { 26, 115 }, { 100, 47 }, { 225, 99 }, { 299, 141 }, { 444, 198 }, { 608, 190 }, { 636, 157 }, { 647, 255 }, { 841, 123 } }例子中,target 1000,初始油量299
首先第一次可达前5个加油站,按照油量排序:299/141,26/115,225/99,100/47,12/21,
选择299/141加油,此时位置299,油量299+141-299=141,剩余加油站[26/115,225/99,100/47,12/21],能够到达最远距离299+141=440(其实最远距离就是油量累加和),没有新的可以到达的加油站,从剩余加油站中选择油量最大的,即26/115,
当前能够到达440+115=555位置,新增加油站444/198,插入进去,当前剩余加油站[444/198,225/99,100/47,12/21],选择最大油量198,
则当前位置444,能够到达最远距离299+141+115+198=753,新增加油站后剩余加油站 [647/255,608/190,636/157,225/99,100/47,12/21 ],选择最大油量647/255
则此时能到达最远距离753+255=1008,达到target1000,则最终选择的加油站是26/115,299/141,444/198,647/255,即需要加油4次。
AC Code:
1 class Solution { 2 public: 3 int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) { 4 priority_queue<int> queue; 5 int ret = 0; 6 int p = 0; //加油站下标 7 int d = startFuel; //当前能到达的位置 8 while (true) 9 { 10 while (p < stations.size() && stations[p][0] <= d) //将当前能到达的加油站都存储起来 11 { 12 queue.push(stations[p][1]); 13 p++; 14 } 15 if (d >= target) //当前油量能够到达终点 16 { 17 return ret; 18 } 19 if (queue.empty() == 1) 20 { 21 return -1; 22 } 23 d += queue.top(); //在能到达的加油站中取油量最大的一个 24 queue.pop(); 25 ret++; 26 } 27 } 28 };
其他:
1.需要熟悉STL 中的一些基本性质,比如C++中priorty_queue是大堆,Java中貌似是最小堆,所以第一的code中需要存储-XXX来变相将小堆当做大堆用。
2.了解贪心算法的局限性
3.需要提高建模能力,好多问题本质上都是数学问题。
总结:
答出了三题,不过这一次题目相对简单,之前一次答出三题,提交错误好几次都能200+名,这次就提交错误两次都600+名了,虽然LeetCode周赛只是用来练习,能够排名靠前也是好的,难题要能做出来,简单题要能做的快。就目前个人能力来说,15+20+20+35的时间分配比较合理,而且一般来说最后30分钟还基本上都用到了前面三个题中,希望年前能够AK一次。
以上是关于LeetCode之Weekly Contest 93的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode(Weekly Contest 188)题解
LeetCode(Weekly Contest 188)题解