您的位置:首页 > 其它

LeetCode刷题之路(一)(1~10)

2019-08-10 21:21 155 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/weixin_42412973/article/details/99103448

突然想起有一个做题的网站(LeetCode)地址:https://leetcode-cn.com/,心血来潮,于是注册做了第一题“两数之和”

感觉非常有意思,因为它是给定你初始格式,让你来完成里面的函数,和之前完全由自己写又不一样,上来第一道题差点儿把自己

看懵了,以后会不时做几道题,并且补充相关知识点!

目录:

第一题    两数之和

第二题是链表就不说了(不过也得不时回顾,要不然就忘了!!!)

第三题    无重复字符的最长子串

第四题    先空着(不是太懂)

第五题    最长回文子串

第六题    Z字形变换

第七题    整数反转

第八题    字符串转换整数(atoi)

第九题    回文数

第十题    标为困难,先空着嘻嘻

第一题    两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

[code]class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {

}
};

上面是一开始系统给的

居然有vector,感觉自己并不太熟,于是查了下vector的用法:

从菜鸟教程上摘了部分:

一、什么是vector?

向量是一个封装了动态大小数组的顺序容器,他能够存放各种类型的对象。可以简单的认为,向量是一个能够存放任意类型的动态数组。

二、容器特性

1、顺序序列。顺序容器中的元素按照严格的线性顺序排序。可以通过元素在序列中的位置(!!!)访问对应的元素

2、动态数组。支持对序列中的任意元素进行快速直接访问,甚至可以通过指针算述进行该操作。操供了在序列末尾相对快速地添加/删除元素的操作。

3、能够感知内存分配器的(Allocator-aware)容器使用一个内存分配器对象来动态地处理它的存储需求。

三、基本用法及函数

[code]引用:
#include < vector>
using namespace std;

Vector< vector< int> >v; 二维向量//这里最外的<>要有空格。否则在比较旧的编译器下无法通过

补充:网上一般都会有一维向量的去重,二维向量的去重是这样的类似,

vector< vector <int> >ans;

vector<vector<int>>::iterator IE = unique(ans.begin(),ans.end());
ans.erase(IE,ans.end());

函数:

补充vector的排序:

sort(vector.begin(),vector.end());

其他具体内容看https://www.runoob.com/w3cnote/cpp-vector-container-analysis.html

现在开始做题,于是我尝试写了一下,用了两层循环,哈哈,然后过了,发现居然还有这个功能:

这一看就有神奇方法,于是我想了想:

感觉是可以在O(n)的时间内做完,比如对于nums中的每一个元素进行标记,然后从0到target/2,判断该数和(target-该数)是否被标记过,这里要注意类似8(4+4)这种两数相同的情况,还要注意要有负数这种情况,然后我就写了一发,哈哈,栈溢出,然后我就不会写了

看了一下题解,amazing!哈希表!怎么把他忘了!

先学学哈希表:

见W3Cschoolhttps://www.w3cschool.cn/cpp/cpp-fu8l2ppt.html

第一种方法:两遍哈希

[code]class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int>ans;
map<int,int> mapTest;
for(int i=0;i<nums.size();i++){
mapTest[nums[i]]=i;
}
for(int i=0;i<nums.size();i++){
if(mapTest.find(target-nums[i])!=mapTest.end()&&mapTest[target-nums[i]]!=i){
ans.push_back(i);
ans.push_back(mapTest[target-nums[i]]);
break;
}
}
return ans;
}
};

Amazing!

题解中还提供了一种一遍循环的方法,就是在边构造hash,边查找

[code]class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int>ans;
map<int,int> mapTest;
for(int i=0;i<nums.size();i++){
if(mapTest.find(target-nums[i])!=mapTest.end()){
ans.push_back(i);
ans.push_back(mapTest[target-nums[i]]);
}
mapTest[nums[i]]=i;
}
return ans;
7ff7

}
};

这种方法有个比上一个方法较好的地方就是:

我给你一组【3,3】6这个例子,你按第一个方法去做时,不好理解,因为此时map【3】=1不等于0

但是第二种办法就很好,在加入第二个3之前,就可以先找到第一个3的value,然后才将其覆盖

Amazing!原来LeetCode这么有趣!

第三题    无重复字长的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

遇到字符串的题我就比较懵逼了,不过这题标明中等,咱就来看一下吧,这题要求的是最长的字串的长度,一想应该是这种解题思路:

给两个指针从字符串的头开始,一个先不动,一个每次动一个,当遇到一个字符在前面出现过(也就是重复)时,将那个之前不动的向后提(只能是向后提!),提到那个产生重复的前面。在整个过程中,不断更新(j-i+1)和答案之间的最大值即可。

在这里的难点应该就是如何知道是否重复,以及产生重复的元素的位置在哪儿,想了一下,Amazing!上面刚用了map,这里也可以用一下:

另外注意下C++中字符串的遍历!!!

[code]class Solution {
public:
int lengthOfLongestSubstring(string s) {
int ans=0;
map<char,int>mapTest;
int i=0,j=0;
for(;i<s.length();i++){
if(mapTest.find(s[i])!=mapTest.end()){
//只能往前移
if(mapTest[s[i]]>=j){
j=mapTest[s[i]]+1;
}
mapTest[s[i]]=i;

ans=max(ans,i-j+1);
}else{
mapTest[s[i]]=i;
ans=max(ans,i-j+1);
}
}
return ans;
}
};

map挺好用的,一交发现有点儿慢:

不行,再想想:

找了一下博客里有这样说的,然后我改用了unordered_map,结果是一样的,再想想:

不行,想不到了,看了下题解,哈哈,也使用了map,看看有啥不一样?

好像是一样的,再看看评论区吧

天呢!这里有个用了两层循环来找重复的居然用时16ms。。。

不过好像基本思路基本都是一样的,继续!

第五题    最长回文子串

好熟悉的一题:

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:

输入: "cbbd"
输出: "bb"

解法一:

一开始忘了马拉车!!!所以一番思索,结果想了个O(n^2)的。。。

想的是按照回文串的性质,我以字符串中间元素作为回文串的中心,让他同时往两个方向(左右)移动,在移动过程中,同时判断以它为中心的回文串的长度,并记录最大值的左右届,然后最后根据左右届返回对应的字符串即可。这里需要注意的是由于原字符串的长度可奇可偶,所以若为偶数,则中心不太好找,所以我们将其做类似以下的扩展:

abcd             ===>      @a@b@c@d@    就是插入一个不常用的字符串即可

结果如下:

代码如下:

[code]class Solution {
public:
string longestPalindrome(string s) {
char *change=new char[2*s.length()+1];
for(int i=0;i<2*s.length()+1;i++){
if(i%2==1){
change[i]=s[(i-1)/2];
}else{
change[i]='@';
}
}
int ans=0;
int left=0;
int right=0;
int middle1=s.length();
int middle2=s.length();
int length=s.length();
while(middle1>0){
int l=middle1;
int r=middle1;
while(l>=0&&r<2*length+1){
if(change[l]!=change[r]){
break;
}
if((r-l)/2>ans){
ans=(r-l)/2;
left=l/2;
right=(r-2)/2;
}
l--;
r++;
}
middle1-=1;
}
while(middle2<2*length){
int l=middle2;
int r=middle2;
while(l>=0&&r<2*length+1){
if(change[l]!=change[r]){
break;
}
if((r-l)/2>ans){
ans=(r-l)/2;
left=l/2;
right=(r-2)/2;
}
l--;
r++;
}
middle2+=1;
}
string Answer="";
for(int i=left;i<=right;i++){
Answer+=s[i];
}
return Answer;
}
};

然后看了一下题解!!!马拉车!!!

解法二:马拉车

我们先学习一下马拉车,这个不太好理解:

第六题    Z字形变换

又是字符串的题

这种题我想了想,觉得一般都是有规律的,然后我找了找,果然有:

我将转化为Z字形的字符串分成了几组(按    ||        这种形式分的)

                                                                       ||    ||

                                                                       ||

比如上面第一个例子   L               是一组

                                   E       T    

                                   E

这种每组拥有的字符数量为2*numRows-2,能分s.length()/(2*numRows-2)这些组,剩余s.length()%(2*numRows-2)这些字符

然后按Z字形行遍历,找每一行添加进答案中的字符的下标的规律即可,比较难处理的就是剩余那部分字符的处理,不过你可以看成第(s.length()/(2*numRows-2)+1)的处理,然后注意边界就可以了

[code]class Solution {
public:
string convert(string s, int numRows) {
string ans="";
if(numRows==1){
ans+=s;
}else{
int sum=2*numRows-2;
int groupNum=s.length()/sum;
int shengyuNum=s.length()%sum;
for(int i=0;i<numRows;i++){
if(i==0){
for(int j=0;j<groupNum;j++){
ans+=s[j*sum];
}
if(shengyuNum!=0){
ans+=s[groupNum*sum];
}
}else if(i==numRows-1){
for(int j=0;j<groupNum;j++){
ans+=s[numRows-1+j*sum];
}
if(shengyuNum>=numRows){
ans+=s[numRows-1+groupNum*sum];
}
}else{
for(int j=0;j<groupNum;j++){
ans+=s[i+j*sum];
ans+=s[(j+1)*sum-i];
}
if(shengyuNum<=numRows){
if((groupNum*sum+i)<s.length()){
ans+=s[groupNum*sum+i];
}
}else {
int temp=shengyuNum-numRows;
if(i>=numRows-temp-1){
ans+=s[groupNum*sum+i];
ans+=s[(groupNum+1)*sum-i];
}else{
ans+=s[groupNum*sum+i];
}
}
}

}
}
return ans;
}
};

写的很麻烦!

第七题    整数反转

总算遇到个被标为简单的题了

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

示例 1:

输入: 123
输出: 321
 示例 2:

输入: -123
输出: -321
示例 3:

输入: 120
输出: 21
注意:

假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−2^31,  2^31 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。

这题一看,不就是整数反转吗,但是这题有个非常重要的就是他给定了最后答案的数据范围,若超过了,只能标为0,然后我就在每一遍答案*10的过程中进行检测(用了long感觉有点儿投机取巧)

[code]class Solution {
public:
int reverse(int x) {
long ans=0;
long temp=x;
while(temp!=0){
long y=temp%10;
ans+=y;
if((ans*10<-2147483648&&temp<=-10)||(ans*10>=2147483648)&&temp>=10){
ans=0;
break;
}else{
if(temp>=10||temp<=-10){
ans*=10;
}
temp=temp/10;
}

}
return ans;
}
};

速度不快,看看人家题解怎么写的:

[code]class Solution {
public:
int reverse(int x) {
int rev = 0;
while (x != 0) {
int pop = x % 10;
x /= 10;
if (rev > INT_MAX/10 || (rev == INT_MAX / 10 && pop > 7)) return 0;
if (rev < INT_MIN/10 || (rev == INT_MIN / 10 && pop < -8)) return 0;
rev = rev * 10 + pop;
}
return rev;
}
};

提前检测,大体思路差不多,不过这种将我那种*10检测,转为了/10了,避免了*10导致int溢出,而换用long的操作了。。!!

第八题    字符串转换整数(atoi)

这题是一道字符串处理题吧,有点儿复杂。。

请你来实现一个 atoi 函数,使其能将字符串转换成整数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。

当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。

注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:

假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231,  231 − 1]。如果数值超过这个范围,qing返回  INT_MAX (231 − 1) 或 INT_MIN (−231) 。

示例 1:

输入: "42"
输出: 42
示例 2:

输入: "   -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
     我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
示例 3:

输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
示例 4:

输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
     因此无法执行有效的转换。
示例 5:

输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。 
     因此返回 INT_MIN (−231) 。

这题我的思路就是先去掉原串中字符前面的空格,然后进行处理即可,里面题目中的溢出,我们就可以用第七题中的检测方法来做了!!!

[code]class Solution {
public:
int myAtoi(string str) {
int ans=0;
string s="";
//先删掉所有字符前的空格吧
int i=0;
while(i<str.length()){
if(str[i]!=' '){
break;
}
i++;
}
while(i<str.length()){
s+=str[i++];
}
//进行处理
if(s.length()==0){
ans=0;
}else if(s[0]<48||s[0]>57){
if(s[0]!='+'&&s[0]!='-'){
ans=0;
}else{
//这种情况一开始是+、-号
if(s[0]=='-'){
for(int g=1;g<s.length();g++){
if(s[g]<48||s[g]>57){
break;
}
if(ans<INT_MIN/10||(ans==INT_MIN/10&&-1*(s[g]-'0')<-8)){
ans=INT_MIN;
break;
}
if(ans>0){
ans=ans*-1;
}
ans=ans*10+-1*(s[g]-'0');
}
}else{
for(int g=1;g<s.length();g++){
if(s[g]<48||s[g]>57){
break;
}
if(ans>INT_MAX/10||(ans==INT_MAX/10&&(s[g]-'0')>7)){
ans=INT_MAX;
break;
}
ans=ans*10+(s[g]-'0');
}
}
}
}else{
//这种情况从一开始就是数字,而且是正的
for(int h=0;h<s.length();h++){
if(s[h]<48||s[h]>57){
break;
}
//检测溢出,用刚做过一题的方法
if(ans>INT_MAX/10||(ans==INT_MAX/10&&(s[h]-'0')>7)){
ans=INT_MAX;
break;
}
ans=ans*10+(s[h]-'0');

}
}

return ans;
}
};

有点儿慢,难道这题有什么好方法???我去看看题解:

正则表达式!!!我在这就不说了,python很万能哈哈,想了解的同学可以看一下我之前写的一篇博客

https://blog.csdn.net/weixin_42412973/article/details/98480252

第九题    回文数

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

示例 1:

输入: 121
输出: true
示例 2:

输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:

输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
进阶:

你能不将整数转为字符串来解决这个问题吗?

题目中“你能不将整数转为字符串来解决这个问题吗?”,一想,那就将整数倒置比对吧,可能会int溢出,所以改为long(不过考虑空间的话就不行了),然后就过了:

[code]class Solution {
public:
bool isPalindrome(int x) {
bool ans=false;
if(x<0){
ans=false;
}else{
long temp=0;
long temp2=x;
while(x!=0){
int y=x%10;
x/=10;
temp=temp*10+y;
}
if(temp==temp2){
ans=true;
}
}
return ans;
}
};

看了下题解,果然自己太年轻了:

这里需要考虑如何到一半了?题解中(C#)给出的很精妙,同时考虑了是否为回文串:

[code]public class Solution {
public bool IsPalindrome(int x) {
// 特殊情况:
// 如上所述,当 x < 0 时,x 不是回文数。
// 同样地,如果数字的最后一位是 0,为了使该数字为回文,
// 则其第一位数字也应该是 0
// 只有 0 满足这一属性
if(x < 0 || (x % 10 == 0 && x != 0)) {
return false;
}

int revertedNumber = 0;
while(x > revertedNumber) {
revertedNumber = revertedNumber * 10 + x % 10;
x /= 10;
}

// 当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。
// 例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,revertedNumber = 123,
// 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
return x == revertedNumber || x == revertedNumber/10;
}
}

这里的while(x>revertedNumber)咱想想为啥这个可以当作已经到一半的标志:

对于偶数位的数:在此循环中,要想x<=revertedNumber只能是当revertedNumber的位数超过或等于原数的一半,同时也可以判断是否为回文串了,若是的话,应该是x==revertedNumber

对于奇数位的数同理,不过此时revertedNumber一定比x多一位循环才结束

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: