LeetCode之Weekly Contest 102
第一题:905. 按奇偶校验排序数组
问题:
给定一个非负整数数组 A,返回一个由 A 的所有偶数元素组成的数组,后面跟 A 的所有奇数元素。
你可以返回满足此条件的任何数组作为答案。
示例:
输入:[3,1,2,4] 输出:[2,4,3,1] 输出 [4,2,3,1],[2,4,1,3] 和 [4,2,1,3] 也会被接受。
提示:
1 <= A.length <= 50000 <= A[i] <= 5000
链接:https://leetcode-cn.com/contest/weekly-contest-102/problems/sort-array-by-parity/
分析:
将数据按照奇偶分为两部分,然后在合成即可。
AC Code:
1 class Solution { 2 public: 3 vector<int> sortArrayByParity(vector<int>& A) 4 { 5 vector<int> ret; 6 vector<int> o; 7 vector<int> j; 8 for (int i = 0; i < A.size(); i++) 9 { 10 if (A[i] % 2 == 0) 11 { 12 o.emplace_back(A[i]); 13 } 14 else 15 { 16 j.emplace_back(A[i]); 17 } 18 } 19 ret.insert(ret.end(),o.begin(), o.end()); 20 ret.insert(ret.end(),j.begin(), j.end()); 21 22 return ret; 23 } 24 };
其他:
1.奇偶怎么拼的忘了,只记得odd,索性直接用j,o分别表示奇偶了,奇数 odd,偶数even
2.vector可以直接整体插入到另一个里面。a.insert(a.end(),b.begin().b.end())表示将b整个插入到a的尾部,如果将a.end改为a.begin,则整个插入到头部。
3.简单测试时候发现之前填写的printvec只能输出string,当时时间有限,只能遍历输出,其实编写模板函数更合适一些:
template <typename T > void SuperPrintVec(vector<T> data) { for (int i = 0; i < data.size(); i++) { cout << data[i] << " "; } cout << endl; }
可以用来在本地测试时候方便查看vector数据。
4.第一code:
class Solution: def sortArrayByParity(self, A): """ :type A: List[int] :rtype: List[int] """ A.sort(key=lambda x:x%2) return A
python?多熟悉几种语言,选择合适的语言能够更快的实现目的。
另外一个的code,两次遍历分别选择奇偶数据。
class Solution { public: vector<int> sortArrayByParity(vector<int>& A) { vector<int> res; for (int x : A) { if (x % 2 == 0) res.push_back(x); } for (int x : A) { if (x % 2 != 0) res.push_back(x); } return res; } };
第二题:904. 水果成篮
问题:
在一排树中,第 i 棵树产生 tree[i] 型的水果。
你可以从你选择的任何树开始,然后重复执行以下步骤:
- 把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。
- 移动到当前树右侧的下一棵树。如果右边没有树,就停下来。
请注意,在选择一颗树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类推,直至停止。
你有两个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。
用这个程序你能收集的水果总量是多少?
示例 1:
输入:[1,2,1] 输出:3 解释:我们可以收集 [1,2,1]。
示例 2:
输入:[0,1,2,2] 输出:3 解释:我们可以收集 [1,2,2]. 如果我们从第一棵树开始,我们将只能收集到 [0, 1]。
示例 3:
输入:[1,2,3,2,2] 输出:4 解释:我们可以收集 [2,3,2,2]. 如果我们从第一棵树开始,我们将只能收集到 [1, 2]。
示例 4:
输入:[3,3,3,1,2,1,1,2,3,3,4] 输出:5 解释:我们可以收集 [1,2,1,1,2]. 如果我们从第一棵树或第八棵树开始,我们将只能收集到 4 个水果。
提示:
1 <= tree.length <= 400000 <= tree[i] < tree.length
链接:https://leetcode-cn.com/contest/weekly-contest-102/problems/fruit-into-baskets/
分析:
有点像学操作系统时候讲的FIFO页面置换策略,buffer是2,如果来新的可以存放就就像,否则将最近一个保留,另一个踢出去。找到整个过程中最大的一个返回。
如0,1,2,2数据中:
首先0进来,然后1进来,然后2进来,需要淘汰一个,当前size2,前一个是1,将1之前的0淘汰,只保留最后几个连续的1,得到122,结束,得到size 3,3>2,返回3.
数据3 3 3 1 2 1 1 2 3 3 4中
首先进来3,3,3,接下来进来1,然后是2,需要更新,当前size是3331:4,前一个是1,保留连续的1,得到1,2,接下来若干个1,1,2进来,下一个3,需要更新,得到12112,长度5,
3前一个是2,保留最近的连续2,得到23,继续流程,加入4的时候需要更新,此时233:3,最近一个是3,保留前面的连续的3,得到334,结束返回最大长度5。
对于任意的数据,如果当前数字种类不到2,尽管加入,如果加入的是新的数字,且种类已经达到2,需要保留新加入数字之前的若干个连续数字,重复流程即可。
AC Code:
class Solution { public: int totalFruit(vector<int>& tree) { if (tree.size() <= 2) { return tree.size(); } vector<int> possibleans; //存储所有可能的结果 int firstkind=tree[0]; int secondkind=-1; int firstlocation=0; int currentkind =1; int currentsize = 1; int removekind = -1; int leftkind = -1; for (int i = 1; i < tree.size(); i++) { if (currentkind == 2 && tree[i] != firstkind && tree[i] != secondkind) { removekind = firstkind + secondkind - tree[i - 1]; leftkind = tree[i - 1]; //结束了, possibleans.emplace_back(currentsize); for (int j = i-2; j>=0; j--) { //cout << tree[j] << " " << secondkind << endl; if (tree[j] != leftkind) { currentsize=i-j; //firstlocation = j+1; firstkind = leftkind; secondkind = tree[i]; currentsize; break; } if (j == 0) { currentsize = i - j; //firstlocation = j+1; firstkind = leftkind; secondkind = tree[i]; currentsize; } } } else { if (tree[i] != firstkind) { secondkind = tree[i]; currentkind = 2; } currentsize++; } } possibleans.emplace_back(currentsize); sort(possibleans.begin(), possibleans.end()); return possibleans[possibleans.size() - 1]; } };
其他:
1.最开始想的是一次得到两种数据,然后淘汰先到的那个,后面才发现要淘汰的是最长未使用的那个,所有其实没必要记录坐标,直接拿最近连续数字长度+1就是得到第一个新数字时候的size。
2.第一个简单输出没进行调试没发现问题,这个需要调试才发现无法调试,可能之前安装了Force UTF8插件的缘故?反正就是不能调试,提示各种不同的问题,比如pdb打不开、文件版本不一致等等,浪费了大概半小时,最后按照搜到的博客进行设置,最终还好可以调试:
https://blog.csdn.net/deirjie/article/details/37962199
主要是清理解决方案,重新生成,菜单工具-》选项-》调试-》常规,将要求源文件和版本匹配勾选取消掉。
此外前面还有进行勾选源服务器支持等设置(参考https://blog.csdn.net/tahelin/article/details/30318341)
一直以来都是在一个工程文件里面进行LeetCode练习,虽说本来目的只是防止手生偶尔用用,但是文件还是太乱了,既然参加了周赛,最好还是不要太随意,以后每次周赛单独建立一个工程,文件相对干净些,而且最好赛前半小时热热身,检测一下环境,避免赛中被一些无关紧要的事情耽误。
3.第一code:
class Solution { public: int totalFruit(vector<int>& tree) { int i=0,j=0,ans=0,n=tree.size(); unordered_map<int,int> cnt; for(int i=0;i<n;i++){ while(j<n&&cnt.size()<=2){ ans=max(ans,j-i); cnt[tree[j]]++; j++; } if(cnt.size()<=2)ans=max(ans,j-i); if(cnt[tree[i]]==1)cnt.erase(cnt.find(tree[i])); else cnt[tree[i]]--; } return ans; } };
第三题:907. 子数组的最小值之和
问题:
给定一个整数数组 A,找到 min(B) 的总和,其中 B 的范围为 A 的每个(连续)子数组。
由于答案可能很大,因此返回答案模 10^9 + 7。
示例:
输入:[3,1,2,4] 输出:17 解释: 子数组为 [3],[1],[2],[4],[3,1],[1,2],[2,4],[3,1,2],[1,2,4],[3,1,2,4]。 最小值为 3,1,2,4,1,1,2,1,1,1,和为 17。
提示:
1 <= A <= 300001 <= A[i] <= 30000
链接:https://leetcode-cn.com/contest/weekly-contest-102/problems/sum-of-subarray-minimums/
分析:
最直接的念头就是根据数组长度从1到A.size(),得到所有子数组,得到每个子数组中的最小值,得到总和。不过O(n*n)基本要超时,得想办法优化。
然后想到另外一种排列组合的方法:对于任意一个数字,左侧有若干个,右侧有若干个,可能的排列方式有:
只有一个数字:即自身
只带有左侧数字:左侧数字中的队列+自身数字
只带有右侧数字:自身数字+右侧数字中的队列
带有两侧数字。
在这种方式下只需要获得当前数字是最小值的可能组合数目即可,需要找出左右两侧连续大于该数字的数量
比如给出的例子中:3 1 2 4
| 3 | 1 | 2 | 4 | |
| 左侧 | 0 | 1 | 0 | 0 |
| 右侧 | 0 | 2 | 1 | 0 |
| 都不带 | 1【3】 | 1【1】 | 1【2】 | 1【4】 |
| 带左侧 | 0 | 1【3 1】 | 0 | 0 |
| 带右侧 | 0 | 2【1 2】【1 2 4】 | 1 【2 4】 | 0 |
| 带两侧 | 0*0=0 | 1*2=2【3 1 2】【3 1 2 4】 | 0*1=0 | 0*0=0 |
| 组合数 | 1 | 6 | 2 | 1 |
| 累加和 | 3 | 6 | 4 | 4 |
则最终总和为3+6+4+4=17。
即在给出的数据中,首先得到每一个数字的左右两次连续比他大的数据数量left,right
然后该数据是最小值的数组数目:1+left+right+left*right
最终得到的累加和即为结果
AC Code:
static int x = []() { std::ios::sync_with_stdio(false); cin.tie(NULL); return 0; }(); class Solution { public: int sumSubarrayMins(vector<int>& A) { int ret = 0; vector<pair<int, int>> data; for (int i = 0; i < A.size(); i++) { int left = 0; int right = 0; for (int j = i-1; j >= 0; j--) { if (j == 0) { if (A[j] < A[i]) { left = i - j - 1; break; } else { left = i; break; } } if (A[j] < A[i]) { left = i - j-1; break; } } for (int j = i+1; j<A.size(); j++) { if (j == A.size() - 1) { if (A[j] <= A[i]) { right = j - i - 1; break; } else { right = j - i; } } if (A[j] <= A[i]) { right =j-i-1; break; } } data.emplace_back(make_pair(left, right)); } vector<int> nums; for (int i = 0; i < data.size(); i++) { int tmp = 0; tmp = 1 + data[i].first + data[i].second + data[i].first*data[i].second; nums.emplace_back(tmp); } ret = 0; for (int i = 0; i < data.size(); i++) { ret += nums[i] * A[i]; ret = ret % 1000000007; } return ret; } };
其他:
1.边界需要单独处理,或者通过flag记录结束循环是因为brake还是到达了边界,避免错过了边界值
2.提交超时,查看明细发现是最后一组数据(100/100),试着加上了
static int x = []() { std::ios::sync_with_stdio(false); cin.tie(NULL); return 0; }();
果然pass,完全可以默认加上这个,首先这个只是针对OJ环境有用,实际生产环境中未必会为了速度舍弃安全性兼容性等要求,其次这个东西其实和算法没什么关系,完全知识性的东西,知道有这么东西能关闭同步加快读写就够了,如果不是之前看到过这个,也许会花费大量时间检查code继续优化,有可能确实能够优化AC,也有可能折腾好长时间依然超时,和思路算法没什么关系,感觉没什么意思。
3.第一code
typedef long long ll; class Solution { const int P=1000000007; public: int sumSubarrayMins(vector<int>& a) { int n=a.size(); vector<int> l(n,-1),r(n,n),st; for(int i=0;i<n;i++){ while(!st.empty()&&a[st.back()]>a[i])st.pop_back(); if(!st.empty())l[i]=st.back(); st.push_back(i); } st.clear(); for(int i=n-1;i>=0;i--){ while(!st.empty()&&a[st.back()]>=a[i])st.pop_back(); if(!st.empty())r[i]=st.back(); st.push_back(i); } int ans=0; for(int i=0;i<n;i++){ int d=r[i]-l[i]-1; ans=(ll(ans)+ll(i-l[i])*ll(r[i]-i)*ll(a[i]))%P; } return ans; } };
第四题:906. 超级回文数
问题:
- 用户通过次数8
- 用户尝试次数44
- 通过次数8
- 提交次数163
- 题目难度Hard
如果一个正整数自身是回文数,而且它也是一个回文数的平方,那么我们称这个数为超级回文数。
现在,给定两个正整数 L 和 R (以字符串形式表示),返回包含在范围 [L, R] 中的超级回文数的数目。
示例:
输入:L = "4", R = "1000" 输出:4 解释: 4,9,121,以及 484 是超级回文数。 注意 676 不是一个超级回文数: 26 * 26 = 676,但是 26 不是回文数。
提示:
1 <= len(L) <= 181 <= len(R) <= 18L和R是表示[1, 10^18)范围的整数的字符串。int(L) <= int(R)
链接:https://leetcode-cn.com/contest/weekly-contest-102/problems/super-palindromes/
分析:
不想折腾了,困难难度下至少需要一两个小时,甚至都不一定能解出来,有其他的事情需要处理,不想在这个上来耽误太多时间,大概思考下做法好了。
之前有做个有关的题,感觉在这个上能用得到。
题目给出数字范围,得到范围内的超级回文数个数,超级回文数指的是本身是回文数,同时也是一个回文数的平方。
其实这种数字非常稀少,完全可以得到目标范围内的所有可能的数字,然后根据范围查找即可,只不过数字范围比较大,需要用一些大数计算相关的东西。
首先回文数字密度低于完全平方数,同时判断一个数字是否是回文数也要比判断一个数字是否是完全平方数要容易,以回文数为基础计算。
L/R都是[1,10^18]范围,根的范围则是[1,10^9]
在这个范围内遍历所有的回文数n,计算n的平方m=n*n,如果m也是回文数,存储m,继续寻找下一个回文数n’...
比如前面若干个是:

得到数组[1,4,9,121,484,...]等,然后挑选出在L/R范围内的数据。
之前有编写过获取下一个回文数的函数,也有判断一个数字是否是回文数的函数,针对这题也许得改变为检测字符串数字。
不过这题的难点在于大数计算,所有数字存储为字符串,然后计算的时候需要转回数字数组,进行逐位计算,对比的时候倒是可以直接和字符串进行对比。
需要实现字符串/数字数组直接的转换、大数乘法。
PS:如果不是先计算可能的数字的话,对于L/R,得到sqrt[L],sqrt[R]范围,在该范围内找回文数,计算回文数的平方(大数乘法),判断结果是否是回文数,计算量应该会少很多,不过大数如何求根还没有接触过相关算法。
PPS:如果使用一个本身就支持大数处理的语言,也许会简单很多.
====================================
update 2018年9月16日13:47:49
第一code直接使用stoll,然后查了下uint64_t足够用了,之前大数运算神马的想多了,如此一来简单多了。
还是之前思路,首先将L/R求根得到循环范围,然后在该范围内遍历回文数,得到的回文数计算平方后检测是否是回文数,如果是结果加1,否则继续遍历下一个。
其中获取下一个回文数的部分直接采用之前的一段code。
AC Code:
class Solution { public: int superpalindromesInRange(string L, string R) { int ret = 0; uint64_t left; uint64_t right; left = stoll(L); right = stoll(R); uint64_t ll = (uint64_t)sqrt(left); uint64_t rr = (uint64_t)sqrt(right); for (uint64_t i = ll; i <= rr; ) { uint64_t tmpdata = GetNextPalindrome(i); if (isNumPalindrome(i) == true && isNumPalindrome(i*i) == true) { ret++; } i = tmpdata; } return ret; } uint64_t GetNextPalindrome(uint64_t data) { //100以内的数字已经特殊考虑过,不存在差值1的两个回文数 //首先得到长度,如果是奇数,取前一半+中间值构造,如果是偶数,取前一半构造
//之前代码段中前面的直接处理了,这里加上,实现完整的获取下一个回文数的功能 if (data < 100) { vector<int> tmpdata= { 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99,101 }; for (int i = 0; i < tmpdata.size(); i++) { if (data < tmpdata[i]) { return tmpdata[i]; } } } uint64_t tmpdata = data; uint64_t length = 0; while (tmpdata) { tmpdata /= 10; length++; } //到这里得到数据长度,根据奇偶判断 if (length % 2 == 0) { //偶数长度 uint64_t highhalf; highhalf = data / (uint64_t)(pow(10, length / 2)); //得到前一半 uint64_t lowhalf; lowhalf = data % (uint64_t)(pow(10, length / 2)); //低一半 uint64_t tmphigh = GetPallindrom(highhalf); if (tmphigh > lowhalf) { //只需要将高一般构建结果即可 return data + (tmphigh - lowhalf); } else { highhalf += 1; uint64_t tmplength = 0; tmpdata = highhalf; while (tmpdata) { tmpdata /= 10; tmplength++; } if (tmplength == length / 2) { //没产生进位 return highhalf*pow(10, tmplength) + GetPallindrom(highhalf); } else { //返回奇数个的10X01 return highhalf*pow(10, tmplength - 1) + GetPallindrom(highhalf); } } } else { //奇数长度中间+1即可,如果原来是9,变为10XX01 uint64_t highhalf = data / (uint64_t)(pow(10, length / 2)); uint64_t mid = highhalf % 10; highhalf /= 10; uint64_t lowhalf = data % (uint64_t)(pow(10, length / 2)); uint64_t tmphighhalf = GetPallindrom(highhalf); //不需要动到中间位置数组 if (tmphighhalf > lowhalf) { return data + (tmphighhalf - lowhalf); } //需要更新中间数字 if (mid < 9) { return (highhalf * 10 + mid + 1)*pow(10, length / 2) + tmphighhalf; } else { //高一半+1不进位,只需要高一半+1,该位变0即可,比如191 -> 202 //如果高位+1后需要进位,如99X,则需要变为1001,即最小的高一位 mid = 0; highhalf += 1; tmphighhalf = GetPallindrom(highhalf); uint64_t tmplength = 0; tmpdata = highhalf; while (tmpdata) { tmpdata /= 10; tmplength++; } if (tmplength == length / 2) { //没产生进位 return (highhalf * 10 + mid)*pow(10, length / 2) + tmphighhalf; } else { //产生了进位 比如999应该变为1001 return highhalf*pow(10, tmplength) + tmphighhalf; } } } } uint64_t GetPallindrom(uint64_t data) { uint64_t ret = 0; while (data>0) { ret = ret * 10 + data % 10; data = data / 10; } //cout << "debug:" << data << " -> " << ret << endl;; return ret; } bool isNumPalindrome(uint64_t data) { bool ret = false; uint64_t ordata = data; uint64_t tmp=0; while (data) { tmp = data % 10 + tmp*10; data /= 10; } if (ordata == tmp) { ret = true; } return ret; } };
其他:
1.第四题本不想浪费时间的,结果复制粘贴时候看到第一code中直接使用了stoll,貌似可以直接转换为数字,查了下uint64_t范围覆盖到了10^18,这样完全转换为数字计算了,正好之前做一个回文素数的题的时候有编写获得下一个回文数的函数,直接拿来用,问题简单多了。对于LR,得到循环范围sqrt(L),sqrt(R),在循环范围内遍历回文数,将得到的回文数平方,判断是否是回文数。上面这段code最难的是直接拿来用的获取下一个回文数的函数,相对于i++然后判断是否是回文数,循环少了很多。
2.第一code:
typedef long long ll; class Solution { public: int superpalindromesInRange(string L, string R) { ll l=stoll(L),r=stoll(R); int ans=0; for(int n=1;n<100000;n++) for(int p=0;p<2;p++){ if(n>=10000&&p==0)continue; ll t=n,palin=n; if(p==1)t/=10; while(t>0){ palin=palin*10+t%10; t/=10; } palin*=palin; if(palin<l||palin>r)continue; string s=to_string(palin); bool ok=true; for(int i=0;i<s.size()/2;i++) if(s[i]!=s[s.size()-1-i]){ ok=false; break; } if(ok)ans++; } return ans; } };
总结:
1.第四题准备直接放弃的,看到可以转换数字,加上性格使然,还是多花了点时间AC了,选择性完美主义者/强迫症,有利有弊,不过之所以选择解决而不是直接放弃,直接原因是看到第一code中的stoll,如果真的自己琢磨,假设按照之前想法去实现大数运算,大概率是跑偏浪费时间的,所以学习很重要,至少能指明方向。
2.应该尝试构建自己的代码库,在有需要时候能够直接拿来用,比如获取下一个回文数函数,这次是翻到了,如果重新实现估计又要花费半天时间,每次竞赛的总结,更多的是针对code的学习与总结,其实一些code之外的东西也很值得反思学习。
3.感觉激情在逐渐消退,或者有事情比较忙?最开始的时候都是将第一code/用时最短code读懂了才放上去的,最近更多的像是为了满足格式,code只是大概看了下,其实有些并没怎么搞懂。其实很多ACM队员的code,可能很高效,但是很难理解,这就是差距吧。

浙公网安备 33010602011771号