计蒜客T3473丑数、Leetcode2401最长优雅子数组、Leetcode167两数之和、Leetcode581最短无序连续子数组
目录
计蒜客T3473 丑数
题意:
解题思路:
Code:
Leetcode2401 最长优雅子数组
题意:
解题思路:
Code:
Leetcode167两数之和
题意:
解题思路:
Code:
Leetcode581 最短无序连续子数组
题意:
解题思路:
Code:
今天是暑训的第三天了,渐渐适应了一些。力扣上的题还是非常考验思维的,从前两天的看完题目就歇菜,今天能通过一定思考将不太难的题目写出来,这点还是有进步的。总结之前每次学新知识的时候都非常的痛苦,觉得非常难理解,要隔天再看才能看的差不多。特别是寒训的时候,刚刚接触算法,脑袋转的很慢,一道题看好久好久都不能理解,还好那时候坚持下来了~ 虽说现在还是学新东西花时间很长,但是确实比之前要好得多。再说回到今天的题目:本次的题目没有非常明确的主题,但是题目的质量都非常高,有很多需要学习的思想和技巧。
计蒜客T3473 丑数
链接:USACO Humble Numbers - 计蒜客
(我记得humble不是谦虚的意思嘛,查了一下还有简陋、低下的意思,又学到了哈哈)
题意:
说实话刚开始我连题都没看懂,还让豆(包)老师给我解释了一下。题目的意思是让找到第n个质因数全部在集合中的数字(题中叫丑数)。比如26,它的质因数2和13,你要看看它们是否都在所给的集合之中。还要注意的就是它们都是升序的。
解题思路:
这题主要是感觉有点别扭。首先你要保证总是从小到大的,其次你还要保证中间不要漏掉数字。这题开始我是没有什么思路的,想上CSDN上找一下找到了林学长的博客,用二层循环进行查找。看了一下本题的数据相对比较小,二重循环是O(nk)的复杂度。其实你要想中间不漏掉数只能进行枚举了。第i个数一定是它前面的某一个数乘上集合中的某一个元素得到的,就不断进行枚举就行了,每次找到此次枚举到的最小值。
Code:
int f[N],s[N];//f用来存丑数,s存丑数下标
int k,n;
void solve()
{cin >> k >> n;f[0]=1;for(int i=1; i<=k; i++) cin >> a[i];for(int i=1; i<=n; i++){int minn=INT_MAX;//第i个丑数一定是i之前的某个丑数乘集合中素数得到的for(int j=1; j<=k; j++)//j每次更新s[j]都从0开始枚举{while(a[j]*f[s[j]]<=f[i-1]) s[j]++;//集合中第j个数与前面所有丑数枚举minn=min(minn,a[j]*f[s[j]]);}f[i]=minn;}cout << f[n];
}
关于数据范围这个事情我还有话说,有些题目他的范围比较反常,比如都在1e3左右,或者在1e9以上。如果很小的话肯定是用暴力手段进行解决,比如直接打二重循环。如果比较大的话很可能就是有技巧的,认真看题大胆尝试。比如在CCPC的时候有一道题的数据范围在1e9,当时我们就很奇怪这就算是O(n)也很难过啊,即使这样我们还是被题目背景给欺骗了,没有大胆进行尝试,一直以为是用了什么巧妙的方法。
Leetcode2401 最长优雅子数组
链接:2401. 最长优雅子数组 - 力扣(LeetCode)
本题极大的拓宽了我的见识,之前对位运算的涉猎非常少,本题让我见识到了它的强大之处。
在说这道题之前先简单看一下位运算常见运算符的运算规则:
/*位运算基本规则:
&运算:只要有0则为0(可以收集所有的0)
| 运算:只要有1则为1(可以收集所有的1)
^ 运算:相同为0,不同为1
*/
//例:
1010011
0110000
1110010
0011011
1011001
//&运算: 0010000
// | 运算: 1111011
// ^ 运算:1010011
题意:
找到一个连续的序列,此序列中的所有元素两两进行与运算都是0。
解题思路:
开始我是用了比较暴力的方法,每次加入一个新元素都与前面的所有元素进行比较。写的时候深深的感到代码水平太差,这种模拟都要改上半天。看到题解之后开始是比较懵的,因为这方法跟之前就不是一个维度。后来了解了相关的位运算规则,慢慢看懂了它的原理。所以说以后看到位运算的题可以仔细想想,真的非常巧妙。这题总体是 滑动窗口+位运算,由于或运算是只要有一个0那么就为0,所以用或运算将前面所有的1都统计起来,如果当前元素与这个数做与运算为0的话那么它与前面任意一个数做与运算就都是0了。如果不是0的话就依次将前面的数用异或^给剔除,直到符合条件为止。把当前元素加进来的时候要和当前&运算和做一次 | 运算。对于这个异或^剔除再详细说一下:如果是1 1那么&运算就是1,那么如果再做一次^就是0,那么就把收集的这个1给解掉了,而0 0不管是&还是^都是0,会发现这个时候^和&的作用刚好是相反的。说了这么多看一下代码。
Code:
class Solution {
public:int longestNiceSubarray(vector<int>& v) {int ans = 0, left = 0, or_ = 0;for (int right = 0; right < v.size(); right++) {while (or_ & v[right]) // 有交集,并不是和任意数&运算都是0or_ ^= v[left++]; // 从or中去掉集合 v[left]or_ |= v[right]; // 把nums[right]中的1并入 or 中ans = max(ans, right - left + 1);}return ans;}
};
Leetcode167两数之和
链接:167. 两数之和 II - 输入有序数组 - 力扣(LeetCode)
此题相对简单,没有特别难的思想,难得靠自己写一道···
题意:
给一个数组和数字t,如果能找到两个不同的数字之和刚好等于 t 的话输出它们的序号。
解题思路:
首先遍历一遍将它们的序号和值用map给存起来,然后再从头到尾遍历,如果此时的值在与t的差值在表中可以查到的话就把它们的下表给输出就行了。
Code:
class Solution {
public:vector<int> twoSum(vector<int>& v, int t) {unordered_map<int, int> m;vector<int> a;for(int i=0; i<v.size(); i++) m[v[i]]=i+1;//将数组中数的下标存起来for(int i=0; i<v.size(); i++){if(m[t-v[i]]&&m[t-v[i]]-1!=i)//如果数组中有另一个数字且不相同{a.push_back(i+1);a.push_back(m[t-v[i]]);break;}}return a;}
};
Leetcode581 最短无序连续子数组
链接:581. 最短无序连续子数组 - 力扣(LeetCode)
此题也是独立写出来的哈哈,虽然有待优化的地方但是时间还是O(n) !
题意:
题目大意比较简单就是找到一个尽量短的区间做升序排序之后使得整个序列都是升序的。
解题思路:
本题我用的是双指针法,一直维护区间以及它之后的最大值和最小值。如果出现递减的话就将相邻的两个点都给压进区间,也就是将右端点更新到那里。如果此时的值比最大值小就要将右端点更新到这里。如果此时的值比最小值还要小的话那就要从区间的左边向前查找,直到找到小于此数的位置并将左端点更新到这个地方。
Code:
/*在检索过程中元素小于区间中最大值或者出现递减的情况都要加入*/
/*还要注意判断是否小于区间中的最小值,如果是的话还要更新左边界*/
class Solution {
public:int findUnsortedSubarray(vector<int>& v){int l=0,r=-1;//初始化双指针int maxx=INT_MIN,minn=INT_MAX;for(int i=1; i<v.size(); i++){if(r!=-1)//已经找到了{maxx=max(maxx,v[i]);minn=min(minn,v[i]);}if(v[i-1]>v[i])//当出现递减更新右边界{if(r==-1)//第一次找到{l=i-1,r=i;maxx=max(v[i],v[i-1]);minn=min(v[i],v[i-1]);}else r=i;}if(v[i]<maxx)//如果此时的值比区间中的最大值小的话{r=i;//更新右边界if(v[i]<=minn)//如果此时的值比区间中的最小值都要小还要往左更新左边界{auto it=upper_bound(v.begin(),v.begin()+l,v[i]);if(it!=v.begin()+l) l=it-v.begin();}}}return r-l+1;}
};
写的时候这个二分函数有点忘了,之前有一次就记不太清楚,还是得记一下。OK,这次补题就结束了~~~