力扣初級演算法(二)【字串】
力扣初級演算法(二)【字串】
字串問題在面試中出現頻率很高,你極有可能在面試中遇到。
我們推薦以下題目:反轉字串,字串中第一個唯一字元,字串轉整數(atoi)和 實現 strStr() 。
344. 反轉字串
難度:簡單
編寫一個函式,其作用是將輸入的字串反轉過來。輸入字串以字元陣列
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"]
解題思路
-
樸素的想法是,反向遍歷陣列,然後進行拷貝即可。
但是對於本題來說,要求原地操作 ,我們可以使用雙指標來交換陣列中首尾的值,實現反轉。
-
讓左右指標分別向中間移動,同時交換對應的值即可。
-
在C#語言當中,可以利用元組直接交換兩個元素值,
方法一:雙指標
public void ReverseString(char[] s) { for (int left = 0, right = s.Length - 1; left < right; left++, right--) (s[left], s[right]) = (s[right], s[left]); }
387. 字串中的第一個唯一字元
難度:簡單
給定一個字串,找到它的第一個不重複的字元,並返回它的索引。如果不存在,則返回 -1。
示例:
s = "leetcode" 返回 0 s = "loveleetcode" 返回 2
提示:你可以假定該字串只包含小寫字母。
解題思路
-
樸素的想法是,遍歷字串並統計各個字元的出現次數。
-
然後再次遍歷該字串,如果某個字元的出現次數為一,就返回這個字元的下標。
-
這裡可以考慮用雜湊表來統計出現次數,因為只包含小寫字母,所以也可以用陣列來統計。
這裡給出LINQ的解法。
方法一:統計數量
public int FirstUniqChar(string s) { var res = s.GroupBy(x => x).Where(y => y.Count() == 1).FirstOrDefault(); return res is null ? -1 : s.IndexOf(res.First()); }
8. 字串轉換整數 (atoi)
難度:中等
請你來實現一個
atoi
函式,使其能將字串轉換成整數。首先,該函式會根據需要丟棄無用的開頭空格字元,直到尋找到第一個非空格的字元為止。接下來的轉化規則如下:
- 如果第一個非空字元為正或者負號時,則將該符號與之後面儘可能多的連續數字字元組合起來,形成一個有符號整數。
- 假如第一個非空字元是數字,則直接將其與之後連續的數字字元組合起來,形成一個整數。
- 該字串在有效的整數部分之後也可能會存在多餘的字元,那麼這些字元可以被忽略,它們對函式不應該造成影響。
注意:假如該字串中的第一個非空格字元不是一個有效整數字符、字串為空或字串僅包含空白字元時,則你的函式不需要進行轉換,即無法進行有效轉換。
在任何情況下,若函式不能進行有效的轉換時,請返回 0 。
提示:
- 本題中的空白字元只包括空格字元
' '
。- 假設我們的環境只能儲存 32 位大小的有符號整數,如果數值超過這個範圍,請返回 INT_MAX 或INT_MIN 。
示例 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。
解題思路
-
樸素的想法是,掃描的給定的字串,對讀到到的不同字元做不同處理。
-
想法很簡單,但是實現起來就稍有一些麻煩了,顯然我們要做出的處理不止一種,
這裡肯定需要根據讀到的不同字元做不同的判斷。
-
這裡我們對不同的情況稍加分析,大抵上可以歸類為以下幾種情況:
- 整數前面的空白符
- '+' '-'號
- 數字
- 其他字元
-
我們應該做出如下的處理
- 對於整數前面的空白符,直接跳過即可
- 對於'+' '-'號,如果出現在數字前,視為正負數識別符號,如果出現在數字後,視為其他字元
- 對於數字,視為數字。
- 對於其他字元,做讀取結束處理
-
其中,較為複雜的部分在於空白符和正負號出現的位置,這很難直接用if語句或switch語句判斷。此時,我們可以考慮再引入一個狀態標記,用於標記當前讀取的字串處於什麼狀態。
- 如果還未讀取到任何數字,那麼讀取到空白符和正負號都是合法的
- 如果已經讀取到數字,再讀取到任意其他字元都是非法的。
-
這裡我們定義了三種狀態,分別是起始狀態、數字狀態、以及結束狀態用來終止讀取。
-
在不同的狀態下,我們再對讀取到的字元做不同處理,這樣就簡單多了。
方法一:狀態機
enum MyState
{
NONE,
DIGIT,
FINISH,
}
public int MyAtoi(string s)
{
var state = new MyState();
var sb = new StringBuilder();
foreach (var item in s.TrimStart())
{
switch (state)
{
case MyState.NONE:
if (item.Equals('-') || item.Equals('+') || Char.IsDigit(item))
{
state = MyState.DIGIT;
sb.Append(item);
}
else
{
state = MyState.FINISH;
}
break;
case MyState.DIGIT:
if (Char.IsDigit(item))
{
sb.Append(item);
}
else
{
state = MyState.FINISH;
}
break;
default:
break;
}
if (state.Equals(MyState.FINISH)) break;
}
try
{
return int.Parse(sb.ToString());
}
catch (OverflowException)
{
return sb[0].Equals('-') ? int.MinValue : int.MaxValue;
}
catch (FormatException)
{
return 0;
}
}
思考
- 對於狀態位的定義,可以為符號位單獨定義一個狀態,也可以為不合法的資料定義一個失敗的狀態,這些留給讀者自行嘗試。
- 除此之外,你還可以考慮不使用庫函式來自行判斷是否為數字,是否溢位等。
28. 實現 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()定義相符。
解題思路
- 樸素的想法是,遍歷給定的字串,看看能否從某一個字元開始,和給定的子串完全匹配。
方法一:暴力列舉
public int StrStr(string haystack, string needle)
{
for (int i = 0; i <= haystack.Length - needle.Length; i++)
if (FindP(i)) return i;
return -1;
bool FindP(int start)
{
for (int i = 0; i < needle.Length; i++, start++)
if (!haystack[start].Equals(needle[i])) return false;
return true;
}
}
-
上面這種解法是最容易想到的,
不過除此之外,還可以考慮使用字串匹配的一個經典演算法,KMP演算法。
-
這裡給出一個講解的很好的KMP演算法視訊,感興趣的讀者可以深入瞭解一下。
方法二:KMP演算法
public int StrStr(string haystack, string needle)
{
// dp[i] 表示 i 前面的都匹配成功時的相同前後綴長度
var dp = new int[needle.Length];
// 填表
for (int l = 0, r = 1; r < needle.Length - 1;)
if (needle[l].Equals(needle[r]))
{
dp[++r] = ++l;
}
else if (l == 0)
{
dp[++r] = 0;
}
else
{
l = dp[l];
}
// 字串匹配
for (int i = 0, j = 0; i - j <= haystack.Length - needle.Length;
i += j == 0 ? 1 : 0, j = dp[j])
{
for (; j < needle.Length; j++, i++)
if (!haystack[i].Equals(needle[j])) break;
if (j == needle.Length) return i - needle.Length;
}
return -1;
}