突然想起有一個做題的網站(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]
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
}
};
上面是一開始系統給的
居然有vector,感覺自己並不太熟,於是查了下vector的用法:
從菜鳥教程上摘了部分:
一、什麼是vector?
向量是一個封裝了動態大小數組的順序容器,他能夠存放各種類型的對象。可以簡單的認爲,向量是一個能夠存放任意類型的動態數組。
二、容器特性
1、順序序列。順序容器中的元素按照嚴格的線性順序排序。可以通過元素在序列中的位置(!!!)訪問對應的元素
2、動態數組。支持對序列中的任意元素進行快速直接訪問,甚至可以通過指針算述進行該操作。操供了在序列末尾相對快速地添加/刪除元素的操作。
3、能夠感知內存分配器的(Allocator-aware)容器使用一個內存分配器對象來動態地處理它的存儲需求。
三、基本用法及函數
引用:
#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
第一種方法:兩遍哈希
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,邊查找
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;
}
};
這種方法有個比上一個方法較好的地方就是:
我給你一組【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++中字符串的遍歷!!!
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@ 就是插入一個不常用的字符串即可
結果如下:
代碼如下:
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)的處理,然後注意邊界就可以了
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感覺有點兒投機取巧)
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;
}
};
速度不快,看看人家題解怎麼寫的:
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) 。
這題我的思路就是先去掉原串中字符前面的空格,然後進行處理即可,裏面題目中的溢出,我們就可以用第七題中的檢測方法來做了!!!
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(不過考慮空間的話就不行了),然後就過了:
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#)給出的很精妙,同時考慮了是否爲迴文串:
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多一位循環才結束