python code practice(二):KMP演算法、二分搜尋的實現、雜湊表
1、替換空格
題目描述:請實現一個函式,將一個字串中的每個空格替換成“%20”。例如,當字串為We Are Happy.則經過替換之後的字串為We%20Are%20Happy。
分析:
將長度為1的空格替換為長度為3的“%20”,字串的長度變長。 如果允許我們開闢一個新的陣列來存放替換空格後的字串, 那麼這道題目就非常簡單。設定兩個指標分別指向新舊字串首元素, 遍歷原字串,如果碰到空格就在新字串上填入“%20”, 否則就複製元字串上的內容。但是如果面試官要求 在原先的字串上操作,並且保證原字串有足夠長的空間來存放替換後的字串,那麼我們就得另想方法。
class Solution: def replaceSpace(self, s): s = s.replace(' ', '20%') return s s = Solution() print(s.replaceSpace('We Are Happy'))
方法2:
首先遍歷原字串,找出字串的長度以及其中的空格數量, 根據原字串的長度和空格的數量我們可以求出最後新字串的長度。 設定兩個指標point1和point2分別指向原字串和新字串的末尾位置。 (這裡為什麼取末尾開始遍歷,而不是取起始開始遍歷,是為了利用point1==point2這個判斷條件) 如果point1指向內容不為空格,那麼將內容賦值給point2指向的位置, 如果point1指向為空格,那麼從point2開始賦值“02%” 直到point1==point2時表明字串中的所有空格都已經替換完畢。
class Solution: def replaceSpace(self, oldStringList): blankNumber = 0 #空格數量 oldStringLen = len(oldStringList) #遍歷原字串,統計字串中的空格數量 for i in range(oldStringLen): if oldStringList[i] == ' ': blankNumber += 1 #計算新字串所需要的長度 newStringLen = oldStringLen + blankNumber*2 #宣告新字串列表 newStringList = [' '] * newStringLen #設定兩個指標,分別指向原字串和新字串的末尾位置 point_old = oldStringLen - 1 point_new = newStringLen - 1 #遍歷替換 while point_old != point_new: #如果兩個指標位置不同,表示沒有替換完成 if oldStringList[point_old] != ' ': newStringList[point_new] = oldStringList[point_old] point_old -= 1 point_new -= 1 else: newStringList[point_new] = '0' newStringList[point_new-1] = '2' newStringList[point_new-2] = '%' point_old -= 1 point_new -= 3 #指標恰好相同時,將之前的字元也補上 if point_old > 0: for i in range(point_old, -1, -1): newStringList[i] = oldStringList[i] #將字串陣列/列表組合為字串 newString = '' for i in range(newStringLen): newString += str(newStringList[i]) return newString s = Solution() print(s.replaceSpace('We Are Happy'))
2、正則表示式匹配
請實現一個函式用來匹配包括'.'和'*'的正則表示式。模式中的字元'.'表示任意一個字元,而'*'表示它前面的字元可以出現任意次(包含0次)。 在本題中,匹配是指字串的所有字元匹配整個模式。例如,字串"aaa"與模式"a.a"和"ab*ac*a"匹配,但是與"aa.a"和"ab*a"均不匹配。
這道題需要把題意首先仔細研究清楚。
先分享一個較為清晰的思路:
前提條件:'.'表示任意一個字元;'*'表示它前面的字元可以出現任意次(包含0次)。
首先,考慮特殊情況:
1)兩個字串都為空,返回true
2)當第一個字串不空,而第二個字串空了,返回false(因為這樣,就無法匹配成功了,而如果第一個字串空了,第二個字串非空,還是可能匹配成功的,比如第二個字串是“a*a*a*a*”,由於‘*’之前的元素可以出現0次,所以有可能匹配成功)
之後就開始匹配第一個字元,這裡有兩種可能:匹配成功或匹配失敗。但考慮到pattern下一個字元可能是‘*’, 這裡我們分兩種情況討論:pattern下一個字元為‘*’或不為‘*’:
1)pattern下一個字元不為‘*’:
這種情況比較簡單,直接匹配當前字元。如果匹配成功,繼續匹配下一個;如果匹配失敗,直接返回false。注意這裡的“匹配成功”,除了兩個字元相同的情況外,還有一種情況,就是pattern的當前字元為‘.’, 同時str的當前字元不為‘\0’。
2)pattern下一個字元為‘*’:
pattern下一個字元為‘*’時,稍微複雜一些,因為‘*’可以代表0個或多個。這裡把這些情況都考慮到:
a)當‘*’匹配0個字元時,str當前字元不變,pattern當前字元後移兩位,跳過這個‘*’符號;
b)當‘*’匹配1個或多個時,str當前字元移向下一個,pattern當前字元不變。(這裡匹配1個或多個可以看成一種情況,因為:當匹配一個時,由於str移到了下一個字元,而pattern字元不變,就回到了上邊的情況a;當匹配多於一個字元時,相當於從str的下一個字元繼續開始匹配)
C++ code:
class Solution { public: bool match(char* str, char* pattern) { if(*str=='\0' && *pattern=='\0') return true; if(*str!='\0' && *pattern=='\0') return false; if(*(pattern+1)!='*'){ if(*str==*pattern || (*str!='\0' && *pattern=='.')) return match(str+1,pattern+1); else return false; } else{ if(*str==*pattern || (*str!='\0' && *pattern=='.')) return match(str,pattern+2) || match(str+1,pattern); else return match(str,pattern+2); } } };
python版本:
思路:當模式中的第二個字元是“*”時:
如果字串第一個字元跟模式第一個字元不匹配,則模式後移2個字元,繼續匹配。如果字串第一個字元跟模式第一個字元匹配,可以有3種匹配方式:
1.模式後移2字元,相當於x*被忽略;
2.字串後移1字元,模式後移2字元,相當於x*匹配一位;
3.字串後移1字元,模式不變,即繼續匹配字元下一位,相當於x*匹配多位;
當模式中的第二個字元不是“*”時:
如果字串第一個字元和模式中的第一個字元相匹配,那麼字串和模式都後移一個字元,然後匹配剩餘的部分。
如果字串第一個字元和模式中的第一個字元相不匹配,直接返回False。
def match(self, s, pattern): # write code here #如果兩者都為空,則匹配成功 if (len(s) == 0 and len(pattern) == 0): return True #如果模式為空,字串不為空,則匹配不成功 if (len(s) > 0 and len(pattern) == 0): return False if len(pattern) > 1 and pattern[1] == '*': if s and (pattern[0] == '.' or s[0] == pattern[0]): f1 = self.match(s[1:], pattern)#多個 f2 = self.match(s[1:], pattern[2:])#一個 f3 = self.match(s, pattern[2:])#零個 if f1 or f2 or f3: return True else: return False else: return self.match(s, pattern[2:]) elif s and (pattern[0] == '.' or s[0] == pattern[0]): return self.match(s[1:], pattern[1:]) #如果字串為空,模式不為空,但模式長度等於1,或者模式長度大於1但第二個字元不為’*‘,則匹配不成功 else: return False
另一個:
這道題邊界情況也有點多,首先判斷s和pattern的長度,分出了四種情況,其中
1.如果s與pattern都為空,則True;
2.如果s不為空,而pattern為空,則False;
3.如果s為空,而pattern不為空,判斷pattern是否是a...這種情況,*可以代表0次,這樣一來可以將pattern往後移兩位再進行match遞迴;
4.如果s、pattern不為空,又可以分為兩種情況:
4.1.如果pattern的第二個字元不為*時,如果s[0]與pattern[0]能匹配上就將s和pattern都往後移1位再進行match,否則不匹配為False;
4.2.如果pattern的第二個字元為*時,如果s[0]與pattern[0]匹配不上,則將pattern後移2位再進行match;如果s[0]與pattern[0]能匹配上,會出現三種情況,分別是pattern[1] = '\'的*代表的三種情況0、1或多個,分別對應pattern後移2位s不變、pattern後移2位,s後移1位、pattern不變s後移1位,這三種情況都有可能出現所以用or或運算連線三種情況的遞迴。
講的比較麻煩,不直觀,直接看程式碼吧。
# -*- coding:utf-8 -*- class Solution: # s, pattern都是字串 def match(self, s, pattern): # write code here len_s = len(s) len_pattern = len(pattern) # 如果s與pattern都為空,則True if len_s == 0 and len_pattern == 0: return True # 如果s不為空,而pattern為空,則False elif len_s != 0 and len_pattern == 0: return False # 如果s為空,而pattern不為空,則需要判斷 elif len_s == 0 and len_pattern != 0: # pattern中的第二個字元為*,則pattern後移兩位繼續比較 if len_pattern > 1 and pattern[1] == '*': return self.match(s, pattern[2:]) else: return False # 如果s不為空,pattern也不為空,則需要判斷 else: # pattern的第二個字元為*的情況 if len_pattern > 1 and pattern[1] == '*': # s與pattern的第一個元素不同,則s不變,pattern後移兩位,相當於pattern前兩位當成空 if s[0] != pattern[0] and pattern[0] != '.': return self.match(s, pattern[2:]) # 如果s[0]與pattern[0]相同,且pattern[1]為* else: # 會有三種情況 # pattern後移2個,s不變;相當於把pattern前兩位當成空,匹配後面的,把*當做0次 F1 = self.match(s, pattern[2:]) # pattern後移2個,s後移1個;相當於pattern前兩位與s[0]匹配,把*當做1次 F2 = self.match(s[1:], pattern[2:]) # pattern不變,s後移1個;相當於pattern前兩位,與s中的多位進行匹配,把*當做多次 F3 = self.match(s[1:], pattern) # 有一個為真就能返回真值 return F1 or F2 or F3 # pattern的第二個字元不為*的情況 else: # s和pattern的第一個字元匹配上了,都往後移1位 if s[0] == pattern[0] or pattern[0] == '.': return self.match(s[1:],pattern[1:]) else: return False
3、表示數值的字串
請實現一個函式用來判斷字串是否表示數值(包括整數和小數)。例如,字串"+100","5e2","-123","3.1416"和"-1E-16"都表示數值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
思路:首先要想到所有的情況,然後進行分類討論。-123.45e-67
1、+-號後面必定為數字或後面為.(-.123 = -0.123)
2、+-號只出現在第一位或在eE的後一位
3、.後面必定為數字或為最後一位(233. = 233.0)
4、eE後面必定為數字或+-號
# -*- coding:utf-8 -*- class Solution: # s字串 def isNumeric(self, s): # write code here # 標記符號、小數點、e是否出現過 sign = False decimal = False hasE = False for i in range(len(s)): if (s[i] == 'e' or s[i] == 'E'): # e後面一定要接數字 if (i == len(s)-1): return False # 不能同時存在兩個e if (hasE == True): return False hasE = True elif (s[i] == '+' or s[i] == '-'): # 第二次出現+-符號,則必須緊接在e之後 if (sign and s[i-1] != 'e' and s[i-1] != 'E'): return False # 第一次出現+-符號,且不是在字串開頭,則也必須緊接在e之後 elif (sign == False and i > 0 and s[i-1] != 'e' and s[i-1] != 'E'): return False sign = True elif (s[i] == '.'): # e後面不能接小數點,小數點不能出現兩次 if (hasE or decimal): return False decimal = True # 非法字元 elif(s[i] < '0' or s[i] > '9'): return False return True
4、字元流中第一個不重複的字元
題目描述
請實現一個函式用來找出字元流中第一個只出現一次的字元。例如,當從字元流中只讀出前兩個字元"go"時,第一個只出現一次的字元是"g"。當從該字元流中讀出前六個字元“google"時,第一個只出現一次的字元是"l"。
輸出描述:
如果當前字元流沒有存在出現一次的字元,返回#字元。
分析:
這題其實並不難,裡面需要唯一注意的點就是我們需要返回的字元是 第一次只出現一次的字元, 所以當我們用map儲存的時候,因為map是亂序的,所以我們需要額外判斷我們返回的字元在字元流中的序號是不是最小。
這題分三步:
新建一個string input用來儲存我們接收到的所有字元,同時也能給我們後續判斷字元出現的順序做參照,再新建一個hashmap,用來儲存每個字元我們接收過的次數。
insert function 填寫: 我們首先判斷hashmap的keyset裡有沒有當前收到字元,沒有我們需要把keyset更新,有的話我們需要把對應的value更新,同時我們將收到的字串放進前面我們新建的string input裡。
FirstAppearingOnce function 填寫: 我們需要先新建一個int index,來儲存我們現在找到的最小的只出現一次的字元的index,然後我們新建一個char result,因為題目裡提到,如果沒有找到符合的字元,我們需要返回“#”,所以我們將char result設為預設值“#”。接下來我們遍歷整個hashmap,如果有隻出現一次的字元,我們記錄下它的index,如果小於我們建立的int index,我們更新int index,同時更新我們對應的result。最後,我們return result即可。
根據該思路寫的java code:
import java.util.*; public class Solution { //Insert one char from stringstream String input = ""; Map<Character,Integer> map = new HashMap<>(); public void Insert(char ch) { if(!map.keySet().contains(ch)){ map.put(ch,1); }else{ map.put(ch,map.get(ch)+1); } input += ch; } //return the first appearence once char in current stringstream public char FirstAppearingOnce() { int index = Integer.MAX_VALUE; char result = '#'; for(Character c: map.keySet()){ if(map.get(c) == 1){ if(input.indexOf(c) < index){ index = input.indexOf(c); result = input.charAt(index); } } } return result; } }
貼一個python的:
# -*- coding:utf-8 -*- class Solution: def __init__(self): self.s='' self.dict={} #建立字典,key為讀取的字串中的每一個字元,val為每個字元出現的個數的計數值 # 返回對應char def FirstAppearingOnce(self): # write code here for i in self.s: #遍歷字串s中的字元 if self.dict[i]==1: #如果某個字元對應的計數為1,則返回該字元 return i return '#' #在所有字元遍歷完後,進行判斷 def Insert(self, char): # write code here self.s=self.s+char #從字元流中讀入字元到字串s中 if char in self.dict: self.dict[char]=self.dict[char]+1 #如果讀入的字元在字串中已存在,在字典中對應的字元計數加一 else: self.dict[char]=1 #如果讀入的字元在字串中不存在,則字典中對應的字元計數為一(即新增了一個新的字元)
5、二分搜尋
leetcode 69. x的平方根
題目描述:
實現 int sqrt(int x) 函式。
計算並返回 x 的平方根,其中 x 是非負整數。
由於返回型別是整數,結果只保留整數的部分,小數部分將被捨去。
示例 1:
輸入: 4
輸出: 2
示例 2:
輸入: 8
輸出: 2
說明: 8 的平方根是 2.82842...,
由於返回型別是整數,小數部分將被捨去。
分析:基本不等式+二分法
- 基本不等式(a+b)/2 >=√ab 推導自 (a-b)^2 >= 0,注意 a>0 且 b>0
class Solution: def mySqrt(self, x: int) -> int: r = x while r*r > x:
r = (r + x/r) // 2 return int(r)
class Solution: def mySqrt(self, x: int) -> int: l, h = 0, x while l < h: m = (l + h) // 2 if m**2 <= x < (m+1)**2: return m elif m**2 < x: l = m + 1 else: h = m - 1 return l
方法1:庫函式
def mySqrt(self, x): """ :type x: int :rtype: int """ return int(math.sqrt(x))
方法2:二分法
class Solution: def mySqrt(self, x: int) -> int: left = 0 right = math.ceil(x / 2) #math.ceil(x)返回大於等於引數x的最小整數,即對浮點數向上取整 res = 0 while left <= right: mid = left + (right - left) // 2 tmp = mid * mid if tmp == x: return mid elif tmp < x: left = mid + 1 else: right = mid - 1 return right
方法3:
class Solution: def mySqrt(self, x: int) -> int: r = x while r * r > x: r = (r + x // r) // 2 return r
&n