c++ LeetCode (初級字符串篇) 九道算法例題代碼詳解(二)


原文作者:aircraft

原文鏈接:https://www.cnblogs.com/DOMLX/p/11089327.html

 

      已經刷了很多篇leetcode題了,不過最近在找c++的實習工作(大佬們有推薦的嗎QAQ),現在慢慢補上吧

      雖然有點煩,但是還是要提一句,刷leetcode題目的時候,最好還是自己先思考然后寫出一個通過的代碼,再去看其他人的代碼參考比較好,當然了,寫不出來就當我沒說。盡力而為即可。

 

 

一套面試題的目錄在此,還在繼續完善中。。。。。。

c/c++ 2019面試題目錄

 

一、反轉字符串

 

編寫一個函數,其作用是將輸入的字符串反轉過來。輸入字符串以字符數組 char[] 的形式給出。

不要給另外的數組分配額外的空間,你必須原地修改輸入數組、使用 O(1) 的額外空間解決這一問題。

你可以假設數組中的所有字符都是 ASCII 碼表中的可打印字符。

 

示例 1:

輸入:["h","e","l","l","o"]
輸出:["o","l","l","e","h"]

示例 2:

輸入:["H","a","n","n","a","h"]
輸出:["h","a","n","n","a","H"]


我的代碼:64ms左右

class Solution { public: void reverseString(vector<char>& s) { int len = s.size(); int i = 0,j = len - 1; while(i < j){ s[i] = s[i]^s[j]; s[j] = s[i]^s[j]; s[i] = s[i]^s[j]; i++; j--; } } };

 

 

  我的思路就是很簡單的用兩個i,j 一個由前向后移動,另外一個反之。這樣就可以不斷的把頭尾字符串交換了,當i<j時就說明已經交換完畢了結束循環。

 

看看大佬們的代碼:44ms左右

class Solution { public: void reverseString(vector<char>& s) { for (int i = 0; i < s.size() / 2; i++) swap(s[i], s[s.size() - i - 1]); } };

  好吧,看來跟我的差別不是很大,就是調用了庫函數交換,以及省略些判斷過程。

 

 

、整數反轉

 

給出一個 32 位的有符號整數,你需要將這個整數中每位上的數字進行反轉。

示例 1:

輸入: 123
輸出: 321

 示例 2:

輸入: -123
輸出: -321

示例 3:

輸入: 120
輸出: 21

注意:

假設我們的環境只能存儲得下 32 位的有符號整數,則其數值范圍為 [−231,  231 − 1]。請根據這個假設,如果反轉后整數溢出那么就返回 0。

 

 

我的代碼:8ms左右

class Solution { public: int reverse(int x) { int tem = 0; if(x > 0){ while(x != 0){ if(tem > (INT_MAX - (x % 10)) / 10 )return 0; tem = tem * 10 + x % 10; x /= 10; } } else{ while(x != 0){ if(tem < (INT_MIN - (x % 10)) / 10)return 0; tem = tem * 10 + x % 10; x /= 10; } } return tem; } };

  這道題的思路很簡單,在大一學c語言的時候就遇到過將數字反轉的測試題目,只要對X%10就能將其個位數字保留下來,然后tem = tem * 10 + x % 10; 就相當於將原來的末尾數保留下來,x /= 10; 已經拿到個位數了,那么就將原來的數除以10,將原來的十位數字移動到個位,這樣組合起來就是每加入一個數就將上一個數值乘10加上后來的個位數字即可,

比如21----%10,就保留1,然后將21/10,並且因為是整型數據所以剩下2,然后2%10,還是2,tem = tem * 10 + x % 10;這樣就是把1乘10+2,就將數字反轉了。

  不過這個跟大一的題目比較多了一個環境只能存儲32 位的有符號整數,則其數值范圍為 [−231,  231 − 1]。這其實就是有符號的Int型的存儲范圍,最大可以用內置的INT_MAX 表示,最小則是INT_MIN。

  也就是說我們的環境是不予許出現超出這兩個范圍的數字的,那么我們一開始想的肯定是加一個判斷--比如:tem * 10 + x % 10>INT_MAX就結束循環,或者tem * 10 + x % 10<INT_MIN,這個思路是沒有問題的,不過在最后如果因為判斷條件成立要結束這個循環的話tem * 10 + x % 10 有些值必然會超過環境允許的范圍,而報錯,那么我們就換一個寫法,數學的在大於小於符號左右移動數值的方法大家都學過吧。

我們只要將tem * 10 + x % 10>INT_MAX  變成  tem < (INT_MIN - (x % 10)) / 10 就不會在某一次判斷的時候出現超過環境允許范圍的數值了。

 

看看大佬們的代碼:8ms左右

class Solution { public: int reverse(int x) { int sum = 0; for (; x != 0; x /= 10) { int yu = x % 10; if (sum > (INT_MAX - yu) / 10) return 0; if (sum < (INT_MAX - yu) / 10) return 0; sum = sum * 10 + yu; } return sum; } };

  這個思路跟我一樣,不過我一開始寫的時候覺得需要對於正數負數需要分開判斷,其實不需要,這樣寫就簡化美觀了很多。

 

大佬的代碼:1ms左右

class Solution { public: int reverse(int x) { if(x == 0) return 0; else { int flag = 1; if(x > 0) flag = 1; else flag = -1; long long l = abs((long long)(x)); long long res = 0; int ans; while(l > 0) { int temp = l % 10; res = res*10 + temp; l = l / 10; } if(flag == 1) ans =  res > INT_MAX ? 0:res; else  ans =  res*flag < INT_MIN ? 0:res*flag; return ans; } } };

  這個代碼就是直接用64位的環境變量來存儲,最后加一個判斷,不過題目好像說只能在32的環境下吧,這個應該是為了刷速度寫的。

 

、字符串中的第一個唯一字符

 

給定一個字符串,找到它的第一個不重復的字符,並返回它的索引。如果不存在,則返回 -1。

案例:

s = "leetcode"
返回 0.

s = "loveleetcode",
返回 2.

 

注意事項:您可以假定該字符串只包含小寫字母。

 

我的代碼:80ms

class Solution { public: int firstUniqChar(string s) { unordered_map<char,int> umap; int len = s.size(); if(len == 1) return 0; for(auto s1:s){ umap[s1]++; } for(int i = 0; i < len; i++){ if(umap[s[i]] == 1) return i; } return -1; } };

  我的思路就是將每個數字都存儲起來然后計數,計數為1就是那一個字符了,不過呢,這樣簡單的東西我用了需要做很多特殊處理的unorder_map容器就比較花時間了,這個思路的還是用數組來存儲會快上很多,比如下面的代碼

20ms左右

class Solution { public: int firstUniqChar(string s) { static const auto io_sync = []{ std::ios::sync_with_stdio(false); std::cin.tie(nullptr); return nullptr; }(); int times[26]={0}; for(int i=0;i<s.size();i++){ times[s[i]-'a']++; } for(int i=0;i<s.size();i++){ if(times[s[i]-'a']==1) return i; } return -1; } };

 

還有12ms大佬的代碼:

class Solution { public: int firstUniqChar(string s) { static const auto io_sync = []{ std::ios::sync_with_stdio(false); std::cin.tie(nullptr); return nullptr; }(); int num = 0, len = s.length(); int a[26] = {0}, pos[26] = {0}; char c[26]; for(int i = 0; i < len; i++){ if(a[s[i] - 'a'] == 0){ c[num++] = s[i]; pos[s[i] - 'a'] = i; } a[s[i] - 'a']++; } for(int i = 0; i < num; i++){ if(a[c[i] - 'a'] == 1){ return pos[c[i] - 'a']; } } return -1; } };

  這個就是將前面那個20ms的代碼優化了一下,我們從string類型中拿數據的話要比從一個char數組里拿數據要做的工作會多很多。

 

 

、有效的字母異位詞

 

給定兩個字符串 st ,編寫一個函數來判斷 t 是否是 s 的字母異位詞。

示例 1:

輸入: s = "anagram", t = "nagaram"
輸出: true

示例 2:

輸入: s = "rat", t = "car"
輸出: false

說明:
你可以假設字符串只包含小寫字母。

進階:
如果輸入字符串包含 unicode 字符怎么辦?你能否調整你的解法來應對這種情況?

 

我的代碼:16ms左右

class Solution { public: bool isAnagram(string s, string t) { unordered_map<char,int> umap_s,umap_t; int len_s = s.size(); int len_t = t.size(); if(len_s != len_t) return false; for(int i = 0; i < len_s; i++){ umap_s[s[i]]++; umap_t[t[i]]++; } for(auto ch:umap_s){ if(umap_s[ch.first] != umap_t[ch.first]) return false; } return true; } };

  就是像unordered_map以及map類的容器真的是非常的好用,在一些局部代碼或者是小型項目,對速度的需要沒有確切需求的地方都可以很好的使用。

  我的思路就是用兩個容器分別存儲,然后計數,如果雙方某一個字符的計數值不同,那么肯定不是異位字符串了。

  但是我們其實用char或者int類型的數組來操作就可以做到了,並且速度優化很多。

 

大佬們的代碼:1ms左右

 

class Solution { public: bool isAnagram(string s, string t) { //字母異位詞,指的是字母相同,排列不同的單詞
        if (s.size() != t.size()) return false; int m[26] = {0}; for (int i = 0; i < s.size(); ++i) ++m[s[i] - 'a']; for (int i = 0; i < t.size(); ++i){ if (--m[t[i]-'a'] < 0) return false; } return true; } };

 

  這個代碼就簡化了很多,並且提高的速度,直接用一個int型的數組存儲第一個字符串的-‘a'之后的ASCII的值,其他沒出現的都置為0,然后第二個for循環就是把第二個字符串的數值都拿去跟第一個通過相減的方式比對,因為你有一個,我也有 一個那么減后就是為0,而如果我有你沒有,那么減后就會小於0,這樣判斷是有大前提的s.size() != t.size(),兩個的size是相同的。

 

、驗證回文字符串

 

給定一個字符串,驗證它是否是回文串,只考慮字母和數字字符,可以忽略字母的大小寫。

說明:本題中,我們將空字符串定義為有效的回文串。

示例 1:

輸入: "A man, a plan, a canal: Panama"
輸出: true

示例 2:

輸入: "race a car"
輸出: false

 

 

我的代碼:20ms

class Solution { public: bool isPalindrome(string s) { int len = s.size(); if(len == 1) return true; int i = 0,j = len - 1; while(i < j){ if(s[i] >= '0' && s[i] <= '9' || s[i] >= 'A' && s[i] <= 'Z' || s[i] >= 'a' && s[i] <= 'z'); else{ i++; continue; } if(s[j] >= '0' && s[j] <= '9' || s[j] >= 'A' && s[j] <= 'Z' || s[j] >= 'a' && s[j] <= 'z'); else{ j--; continue; } if(s[i] >= 'a' || s[j] >= 'a'){ if(abs(s[i] - s[j]) == 0 || abs(s[i] - s[j]) == 32){ i++; j--; } else return false; } else if(abs(s[i] - s[j]) == 0){ i++; j--; } else return false; } return true; } };

  我的思路就是忽略掉大小寫的影響,以及不屬於數字或者字符的影響,剩下的用兩個指針來移動對比,一個頭指針,一個尾,如果有一個比對不成立,那么就不是回文字符串,這個代碼有很多可以優化的地方,比如判斷是字符或者數字,都可以直接使用庫函數,然后題目說忽略大小寫的影響,那么干脆把所有字符串都換成大寫或者小寫在來比對好了。。。。反正當時我是沒有想到用庫函數,都是自己寫了。

 

大佬們的代碼:1ms左右

class Solution { public: bool isPalindrome(string s) { int lefts=0; int rights=s.size()-1; while(lefts<rights) { while(!isalnum(s[lefts])) { lefts++; if(lefts>=rights) return true; } while(!isalnum(s[rights])) { rights--; if(lefts>rights) return false; } if(tolower(s[lefts])!=tolower(s[rights])) return false; lefts++; rights--; } return true; } };

  這個代碼就是一個外循環來遍歷,而里面的兩個while則是用來分別跳過左邊不是數字或者字符的數,然后在將左邊的值與右邊的值都轉為小寫比對,若是不相同那么就不是回文字符串了。

isalnum()用來判斷字符或者數字,tolower()轉為小寫。

、字符串轉整數

請你來實現一個 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 (−2
31
) 。


我的代碼:8ms左右
class Solution { public: int myAtoi(string str) { int sign = 0,res = 0,i = -1,len = str.size(); if(len < 1) return 0; while(i < len && str[++i] == ' '); if(i >= len) return 0; if(str[i] == '-'){ sign = -1; i++; } else if(str[i] == '+') i++; if(sign != -1){ while(str[i] >= '0' && str[i] <= '9'){ if(res > ((INT_MAX - (str[i] - '0')) / 10)) return INT_MAX; res = res * 10 + (str[i]-'0'); i++; } return res; } while(str[i] >= '0' && str[i] <= '9'){ if(-res < ((INT_MIN + (str[i] - '0') ) / 10) ) return INT_MIN; if(res * 10 > (INT_MAX -(str[i] - '0')) ){ return -res * 10 - (str[i]-'0'); } res = res * 10 + (str[i]-'0'); i++; } return -res; } };

  字符串轉數字這個代碼應該初學c語言的時候大家都學過,而這個題目就是加一個限制,環境只能容納32位有符號整型的值。所以要多加點判斷。

  字符串轉數字無非就是res * 10 + (str[i]-'0'); 將字符的值-‘0’的值,然后如果剛轉的這個數前面已經有數字存在了,那么就把前面的那個高位數乘10提高一個位數加上去就行了。

   而要加判斷的話,大於0的值比較,一般是這樣的想法:res * 10 + (str[i]-'0');>INT_MAX就不成立,然而環境不允許存在比INT_MAX大的值,那么我們就移動一下比較符號左右的運算,變成這樣:

  res > ((INT_MAX - (str[i] - '0')) / 10);這樣的預判式子就不會突破環境變量的限制了。

小於0的話就這樣改:-res < ((INT_MIN + (str[i] - '0') ) / 10);


大佬們的代碼:1ms左右
class Solution { public: int myAtoi(string str) { int cur = 0; int len = str.length(); int flag = 1; int ret = 0; while(cur < len && str[cur]==' ') { cur ++; } if(cur ==len) return 0; else if(str[cur]=='-') flag = -1; else if(str[cur]=='+') flag = 1; else if(isdigit(str[cur])) ret = str[cur] -'0'; else 
            return 0; cur +=1; while(cur < len && isdigit(str[cur])) { int temp = (str[cur] -'0') * flag; if (ret > INT_MAX / 10 || ret == INT_MAX / 10 && temp > 7) return INT_MAX; if (ret < INT_MIN / 10 || ret == INT_MIN / 10 && temp < -8) return INT_MIN; ret =ret *10 + temp; cur ++; } return ret; } };

  這個代碼就比我的優化很多了,我的分別把正負值分開算,多了很多代碼和判斷,而這里直接放在一個循環里,通過flag來確定符號就挺好的。

 

、實現 strStr() 函數。

 

 

給定一個 haystack 字符串和一個 needle 字符串,在 haystack 字符串中找出 needle 字符串出現的第一個位置 (從0開始)。如果不存在,則返回  -1

 

示例 1:

 

輸入: haystack = "hello", needle = "ll"
輸出: 2

 

示例 2:

 

輸入: haystack = "aaaaa", needle = "bba"
輸出: -1

 

說明:

 

當 needle 是空字符串時,我們應當返回什么值呢?這是一個在面試中很好的問題。

 

對於本題而言,當 needle 是空字符串時我們應當返回 0 。這與C語言的 strstr() 以及 Java的 indexOf() 定義相符。

 

我的代碼:4ms

 

class Solution { public: int strStr(string haystack, string needle) { int ht_len = haystack.size(); int nl_len = needle.size(); int len = ht_len - nl_len; int i, j = 0; if(nl_len == 0) return 0; for(i = 0; i <=len; i++){ if(haystack[i] == needle[j]){ while(j < nl_len && haystack[i + j] == needle[j]) j++; if(j == nl_len) return i; j = 0; } } return -1; } };

  我的思路就是:從第一個字符串中找第二個子串出現的位置,那么只要從第一個串里面找到第二個子串相匹配的字符,然后記住位置,遍歷一個子串長度的數據對比,都相同的話就代表找到了。

  同樣的,假如第二個字符串長度為3,第一個為6,那么第一個字符串到第四個位置之后就沒有必要再去比較判斷了,所以真正可以比較長度就是len1 - len2的長度。

 

大佬們的代碼:1ms左右

class Solution { int issame(string haystack, string needle,int k) { if(haystack.length()-k<needle.length()) return -1; for(int i=0;i<needle.length();i++) { if(haystack[i+k]!=needle[i]) return issame(haystack,needle,k+1); } return k; } public: int strStr(string haystack, string needle) { if(needle=="") return 0; return issame(haystack,needle,0); } };

  這個大佬用遞歸的方法不斷的遞歸尋找到第二串的第一個字符在第一個串出現的位置,找到后就遍歷比對,若不成立則繼續遞歸並且增加k值,直到k值不滿足haystack.length()-k<needle.length()這個條件就說明不存在,返回-1.利用了空間換時間的方法,增加了速度,而且編程之美里面說過,“迭代是人,遞歸為神”,能用遞歸的方法會使得代碼更加簡潔和快速,不過要注意堆棧的溢出問題就是了。

 

 

 

、報數

 

報數序列是一個整數序列,按照其中的整數的順序進行報數,得到下一個數。其前五項如下:

1.     1
2.     11
3.     21
4.     1211
5.     111221

1 被讀作  "one 1"  ("一個一") , 即 11
11 被讀作 "two 1s" ("兩個一"), 即 21
21 被讀作 "one 2",  "one 1" ("一個二" ,  "一個一") , 即 1211

給定一個正整數 n(1 ≤ n ≤ 30),輸出報數序列的第 n 項。

注意:整數順序將表示為一個字符串。

 

示例 1:

輸入: 1
輸出: "1"

示例 2:

輸入: 4
輸出: "1211"

 

 

我的代碼:8ms左右

class Solution { public: string countAndSay(int n) { vector<string> list; int len; int j = 0; int count = 1; string temp = ""; string ch = ""; list.emplace_back("1"); for (int i = 1; i < n; i++) { len = list[i - 1].size(); while (j < len) { ch = list[i - 1][j]; if (j == (len - 1)) { temp += "1" + ch; break; } while (j < len - 1 && list[i - 1][j] == list[i - 1][j + 1]) { count += 1; j++; } temp += to_string(count) + ch; count = 1; j++; } list.emplace_back(temp); temp = ""; j = 0; } return list[n - 1]; } };

  這題理解題目的意思就比較重要了,一開始我沒理解寫了很多都通過不了,自己也找不到原因。后來是刪除了所有代碼重新思考了一遍才寫出來。

  反正就是下一個數就是把上一個數字按照一定語法來解讀,比如1211后面的111221,因為1211是先一個1 所以為11,然后是一個2 所以為 12,然后兩個1 為21,全部組合起來就是111221,是這么個意思,所以我們就用上一個不斷的去推出下一個,這就是一個迭代過程。

  那我們只需要紀錄下第一個字符,然后計算有幾個一樣的寫上就好,遇到不一樣的在替換紀錄的字符繼續計數,比如1112,三個1=31,替換為2,有一個2=12  組合起來就是3112

 

看看大佬們的代碼:1ms左右

class Solution { public: string countAndSay(int n) { string str = "1"; if(n == 1) return str; for(int i = 2; i <= n; ++i) { string tmp = ""; char cur = str[0]; int cnt = 1; for(int j = 1; j < str.length(); ++j) { if(str[j] != cur) { tmp.push_back(char(cnt+'0')); tmp.push_back(cur); cur = str[j]; cnt = 1; } else ++cnt; } tmp.push_back(char(cnt+'0')); tmp.push_back(cur); str = tmp; } return str; } };

  大佬的思路跟我差不多,不過寫法比我優化很多。

 

 

 、最長公共前綴

 

編寫一個函數來查找字符串數組中的最長公共前綴。

如果不存在公共前綴,返回空字符串 ""

示例 1:

輸入: ["flower","flow","flight"]
輸出: "fl"

示例 2:

輸入: ["dog","racecar","car"]
輸出: ""
解釋: 輸入不存在公共前綴。

說明:

所有輸入只包含小寫字母 a-z 。

 

 

我的代碼:20ms

class Solution { public: string longestCommonPrefix(vector<string>& strs) { if(strs.empty()) return ""; int len = INT_MAX; string temp; for(auto str:strs){ if(str.size() < len) len = str.size(); } for(int i = 0; i < len; i++){ temp = strs[0].substr(0,i + 1); for(auto str:strs){ if(str.substr(0,i + 1).find(temp) == str.npos) return strs[0].substr(0,i); } } return strs[0].substr(0,len); } };

  題目的意思很清楚,也不需要什么思路就是比較就是了,我的是一次把多個字符切割好去比較,直到最后一次比較不成功,之前的就是最長公共前綴,這樣看起來代碼很清楚,不過效率特別慢,因為中間做了很多很多事。

 

大佬們的代碼:1ms左右

class Solution { public: string longestCommonPrefix(vector<string>& strs) { string res; int n=strs.size(); if(n==0) return res; else if(n==1) return strs[0]; char c; for(int i=0;;i++){ c=strs[0][i]; for(int j=1;j<n;j++){ if(c=='\0'||strs[j][i]!=c) return res; } res+=c; } return res; } };

  一個個字符去遍歷比較,都有的話就加到結果里,最后返回的就是最長公共前綴了。

 

后面還是會陸續更新leetcode算法篇,也有其他面試教程篇或者網絡編程篇之類的。想要的話就關注我把!!!!感謝各位。

 

 

若有興趣交流分享技術,可關注本人公眾號,里面會不定期的分享各種編程教程,和共享源碼,諸如研究分享關於c/c++,python,前端,后端,opencv,halcon,opengl,機器學習深度學習之類有關於基礎編程,圖像處理和機器視覺開發的知識

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM