唉,昨天沒有寫這個系列的,被KMP給卡住了,不過還好,突破了。
照例三題,你們等下就會看到KMP算法了,實在是鬼斧生工。
文章目錄
第一題:實現strStr
實現 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() 定義相符。
來源:力扣(LeetCode) 鏈接:https://leetcode-cn.com/problems/implement-strstr
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
第二題:外觀數列
「外觀數列」是一個整數序列,從數字 1 開始,序列中的每一項都是對前一項的描述。前五項如下:
-
1
-
11
-
21
-
1211
-
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”
解釋:當 n = 3 時,序列是 “21”,其中我們有 “2” 和 “1” 兩組,“2” 可以讀作 “12”,也就是出現頻次 = 1 而 值 = 2;類似 “1” 可以讀作 “11”。所以答案是 “12” 和 “11” 組合在一起,也就是 “1211”。
來源:力扣(LeetCode) 鏈接:https://leetcode-cn.com/problems/count-and-say
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
第三題:atoi實現
請你來實現一個 atoi 函數,使其能將字符串轉換成整數。
首先,該函數會根據需要丟棄無用的開頭空格字符,直到尋找到第一個非空格的字符爲止。接下來的轉化規則如下:
如果第一個非空字符爲正或者負號時,則將該符號與之後面儘可能多的連續數字字符組合起來,形成一個有符號整數。
假如第一個非空字符是數字,則直接將其與之後連續的數字字符組合起來,形成一個整數。
該字符串在有效的整數部分之後也可能會存在多餘的字符,那麼這些字符可以被忽略,它們對函數不應該造成影響。
注意:假如該字符串中的第一個非空格字符不是一個有效整數字符、字符串爲空或字符串僅包含空白字符時,則你的函數不需要進行轉換,即無法進行有效轉換。
在任何情況下,若函數不能進行有效的轉換時,請返回 0 。
提示:
本題中的空白字符只包括空格字符 ’ ’ 。
假設我們的環境只能存儲 32 位大小的有符號整數,那麼其數值範圍爲 [−231, 231 − 1]。如果數值超過這個範圍,請返回 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) 。
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/string-to-integer-atoi
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
我的題解(1) --KMP算法
代碼就不放這篇了,放了也基本看不懂,解釋的話沒幾千字解釋不清楚,所以我就把我的KMP博客鏈接放這兒吧。
我的題解(2)–遞歸
我的好兄弟他跟我說看不懂這道題,讓我趕緊解決KMP之後過來,我一看,這不是很簡單嗎!!!
這麼說把,這道題,不管你給的數字是多少,最後肯定要回到1,從1出發。也不要想着什麼暴力算法,我試過了,給你個9,你就得輸出14個字符,那給你個30你是不是得輸出上百個字符啊。
既然都要回到1,又是重1出發一層一層嵌套,那很直觀的想法就是遞歸嘛。說實話這是我爲數不多的用遞歸的情況,之前在快排、上面那個KMP用到,好像就沒了。
代碼中已經帶上了我的思考,當然,老規矩,流程圖不能少。
#include<iostream>
#include<string>
#include<vector>
using namespace std;
//先描述一個簡單的字符串吧
string show(string str,int n)
{
//遞歸結束條件
if (n == 0)
{
return str;
}
int fast = 0, slow = 0;//快慢指針
int sz = str.size();
int count = 0;
string new_str;
while(slow < sz)
{
if (fast<sz && str[fast] == str[slow])
{
count++;
fast++;
}
else
{
new_str.push_back(count+48);
new_str.push_back(str[slow]);
count = 0;
slow = fast;
}
}
n--;
new_str = show(new_str,n); //前面不是return了?爲什麼會繞回來這裏?哦,遞歸,原來是這樣,只是第二層return了,當然回到前一層
return new_str;
}
string countAndSay(int n) {
if (n == 1)
return "1";
else
{
string s = "1";
string t = show(s, n-1); //這裏的n對應的是n+1,因爲n=1是個特殊情況
return t;
}
}
int main()
{
string a = countAndSay(2);
cout << a << endl;
}
我的題解(3)
這題其實沒什麼難的,就是細節很多,所以排在了中等難度,我就拿過來了。
唉,懺愧,提交了12次。
#include<iostream>
#include<string>
using namespace std;
int myAtoi(string str)
{
int sz = str.size();
string str_temp;
int zf_flag = 1; //正數or複數//emmm,或者就一個正號
for (int a = 0; a < sz; a++)
{
//進入取值地界了
if (str[a] != 32)
{
//不是從這裏進,從第一個非空格1進
if (str[a] == 45 || str[a] == 43 || (str[a] >= 48 && str[a] <= 57))
{
if (str[a] == 45)
{
zf_flag = -1;
a++;
}
else if (str[a] == 43)
{
zf_flag = 0;
a++;
}
while(str[a] == 48) //把0濾掉
a++;
while (str[a] >= 48 && str[a] <= 57)
{
str_temp.push_back(str[a]);
a++;
}
break; //退出循環
}
else
break;
}
}
//接下來對取出的值進行操作
int temp_size = str_temp.size();
int ret = 0;
int i = 0;
int pow_num = 0;
//對負數進行操作
if (zf_flag == -1)
{
if (temp_size == 0)
return 0;
if (temp_size < 10)
{
while (temp_size > 0)
{
temp_size--;
pow_num = pow(10, temp_size);
ret += (int)(str_temp[i] - 48) * pow_num;
i++;
}
return (-1)*ret;
}
//判斷臨界條件
else if (temp_size == 10)
{
if (str_temp[0] > 50)
return INT_MIN;
ret = INT_MIN;
while (temp_size > 0)
{
temp_size--;
pow_num = pow(10, temp_size);
ret += (int)(str_temp[i] - 48) * pow_num;
i++;
}
if (ret <= 0)
return (-1)*(ret - INT_MIN);
else
return INT_MIN;
}
else
return INT_MIN;
}
//對正數進行操作
if (zf_flag == 1 || zf_flag == 0)
{
if (temp_size == 0)
return 0;
if (temp_size < 10)
{
while (temp_size > 0)
{
temp_size--;
pow_num = pow(10, temp_size);
ret += (int)(str_temp[i] - 48) * pow_num;
i++;
}
return ret;
}
else if (temp_size == 10)
{
if(str_temp[0]>50)
return INT_MAX;
ret = INT_MAX;
while (temp_size > 0)
{
temp_size--;
pow_num = pow(10, temp_size);
ret -= (int)(str_temp[i] - 48) * pow_num;
i++;
}
if (ret <= 0)
return INT_MAX;
else
return (INT_MAX-ret);
}
else
return INT_MAX;
}
return 0;
}
int main()
{
//int a = '9'; //57
//int b = '0'; //48
//int c = '-'; //45
//int d = ' '; //32
//int e = '+'; //43
int d = myAtoi("2147483633");
cout << d << endl;
return 0;
}
官方題解(1) – Rabin Karp - 常數複雜度
方法恆強,反正我是沒看懂。上次(前天)說要搞定哈希表之後,就被快排拖住了,解決了快排之後,又讓KMP卡住了,所以哈希表我到現在還不會用。所以就看不懂了。
有一種最壞時間複雜度也爲 O(N)O(N) 的算法。思路是這樣的,先生成窗口內子串的哈希碼,然後再跟 needle 字符串的哈希碼做比較。
這個思路有一個問題需要解決,如何在常數時間生成子串的哈希碼?
算法
計算子字符串 haystack.substring(0, L) 和 needle.substring(0, L) 的哈希值。
從起始位置開始遍歷:從第一個字符遍歷到第 N - L 個字符。
根據前一個哈希值計算滾動哈希。
如果子字符串哈希值與 needle 字符串哈希值相等,返回滑動窗口起始位置。
返回 -1,這時候 haystack 字符串中不存在 needle 字符串。
實現
PythonJava
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
L, n = len(needle), len(haystack)
if L > n:
return -1
# base value for the rolling hash function
a = 26
# modulus value for the rolling hash function to avoid overflow
modulus = 2**31
# lambda-function to convert character to integer
h_to_int = lambda i : ord(haystack[i]) - ord('a')
needle_to_int = lambda i : ord(needle[i]) - ord('a')
# compute the hash of strings haystack[:L], needle[:L]
h = ref_h = 0
for i in range(L):
h = (h * a + h_to_int(i)) % modulus
ref_h = (ref_h * a + needle_to_int(i)) % modulus
if h == ref_h:
return 0
# const value to be used often : a**L % modulus
aL = pow(a, L, modulus)
for start in range(1, n - L + 1):
# compute rolling hash in O(1) time
h = (h * a - h_to_int(start - 1) * aL + h_to_int(start + L - 1)) % modulus
if h == ref_h:
return start
return -1
複雜度分析
時間複雜度:O(N)O(N),計算 needle 字符串的哈希值需要 O(L)O(L) 時間,之後需要執行 (N - L)(N−L) 次循環,每次循環的計算複雜度爲常數。
空間複雜度:O(1)O(1)。
作者:LeetCode
鏈接:https://leetcode-cn.com/problems/implement-strstr/solution/shi-xian-strstr-by-leetcode/
來源:力扣(LeetCode) 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
難搞。
官方題解(2)
無,我的代碼時空複雜度都超過百分百的提交了。看我的吧。
官方題解(3) 有限狀態機(DFA)
難怪難度排到中等。
方法一:自動機
思路
字符串處理的題目往往涉及複雜的流程以及條件情況,如果直接上手寫程序,一不小心就會寫出極其臃腫的代碼。
因此,爲了有條理地分析每個輸入字符的處理方法,我們可以使用自動機這個概念:
我們的程序在每個時刻有一個狀態 s,每次從序列中輸入一個字符 c,並根據字符 c 轉移到下一個狀態 s’。這樣,我們只需要建立一個覆蓋所有情況的從 s 與 c 映射到 s’ 的表格即可解決題目中的問題。
算法
本題可以建立如下圖所示的自動機:
我們也可以用下面的表格來表示這個自動機:
接下來編程部分就非常簡單了:我們只需要把上面這個狀態轉換表抄進代碼即可。
另外自動機也需要記錄當前已經輸入的數字,只要在 s’ 爲 in_number 時,更新我們輸入的數字,即可最終得到輸入的數字。
INT_MAX = 2 ** 31 - 1
INT_MIN = -2 ** 31
class Automaton:
def __init__(self):
self.state = 'start'
self.sign = 1
self.ans = 0
self.table = {
'start': ['start', 'signed', 'in_number', 'end'],
'signed': ['end', 'end', 'in_number', 'end'],
'in_number': ['end', 'end', 'in_number', 'end'],
'end': ['end', 'end', 'end', 'end'],
}
def get_col(self, c):
if c.isspace():
return 0
if c == '+' or c == '-':
return 1
if c.isdigit():
return 2
return 3
def get(self, c):
self.state = self.table[self.state][self.get_col(c)]
if self.state == 'in_number':
self.ans = self.ans * 10 + int(c)
self.ans = min(self.ans, INT_MAX) if self.sign == 1 else min(self.ans, -INT_MIN)
elif self.state == 'signed':
self.sign = 1 if c == '+' else -1
class Solution:
def myAtoi(self, str: str) -> int:
automaton = Automaton()
for c in str:
automaton.get(c)
return automaton.sign * automaton.ans
作者:LeetCode-Solution
鏈接:https://leetcode-cn.com/problems/string-to-integer-atoi/solution/zi-fu-chuan-zhuan-huan-zheng-shu-atoi-by-leetcode-/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
總結
這些題做完,我們應該掌握遞歸算法,熟悉KMP算法,然後準備去學DFA了!!!