這道LeetCode題究竟有什么坑點,讓它的反對是點贊的9倍?


本文始發於個人公眾號:TechFlow,原創不易,求個關注


今天是LeetCode專題的第38篇文章,我們一起來看看第65題,Valid Number。

曾經我們聊到過算法當中的一個類別——模擬題。所謂的模擬題就是題面非常簡單,也不涉及任何復雜的算法,但是要實現的功能比較麻煩,非常考驗人思維的縝密程度,很難寫出bug-free的代碼來。今天要說的65題可以說是其中的典范,它的題面非常簡單,簡單到只有一句話,但是要實現非常麻煩,比較鍛煉人的耐心,以至於反對的人是點贊的9倍,我們一起來看看。

題面

給定一個字符串,判斷它是否是一個合法的浮點數。

從題面來看只有一句話,似乎非常簡單。但是實際上如果你仔細研究一下樣例和提示,會發現事情可能和你想的不太一樣。

樣例

"0" => true
" 0.1 " => true
"abc" => false
"1 a" => false
"2e10" => true
" -90e3   " => true
" 1e" => false
"e3" => false
" 6e-1" => true
" 99e2.5 " => false
"53.5e93" => true
" --6 " => false
"-+3" => false
"95a54e53" => false

提示

我們有意將問題陳述地比較模糊。在實現代碼之前,你應當事先思考所有可能的情況。這里給出一份可能存在於有效十進制數字中的字符列表:

數字 0-9
指數 - "e"
正/負號 - "+"/"-"
小數點 - "."
當然,在輸入中,這些字符的上下文也很重要。

不知道大家感受到了沒有,這其中的情況不少,涉及的符號就很多。除了基本的數字之外,還有小數點、空格、正負號、e。這些符號帶來的合法和非法的情況都很多,更何況這些符號之間還可以互相組合,又會引申出新的情況。

更坑爹的一點是,這題簡單粗暴,只有這一個解法,我們別無選擇,只有覆蓋所有的情況才能通過。所以如果你試着去想清楚所有的這些情況,你會發現這是非常困難的一件事,甚至可能會越想情況越多,覺得怎么也理不清楚,即使你理出了很多情況,也不知道是否有遺漏,很容易讓人抓狂並且心煩氣躁,明明很簡單的問題做不出來,感受肯定不好。先別着急,這正是模擬題考驗人的地方,它考驗的不僅是思維,也是心態。

所以,先深吸一口氣,冷靜下來,我們仔細地分析一下這些情況。

解法

我們從列舉所有的情況入手是非常困難的,因為符號之間互相組合的情況實在是很多,一一列舉全並且用代碼實現的代價很大。我們先把這些煩人的情況放在一邊,我們先來思考一下問題所在。

這個問題的核心就是判斷浮點數是否合法,合法的浮點數的情況也是不少的,但這些情況雖然多,彼此之間是有聯系的。最起碼我們可以發現符號之間的順序,如果我們把一個合法的浮點數進行拆分,它大概可以分成以下幾個部分。

首先是符號位,表示這個數是正數還是負數。題目當中沒有明說,但是我們可以猜測出來,正數用正號表示也是合法的。

第二個部分是科學記數法的前半部分,它可以是一個小數。

第三個部分是e,即科學記數法當中的e。

最后一個部分是整數部分,表示e的指數,根據科學記數法的定義,必然是一個整數。但是可以是負數。

當我們把這四個部分列舉出來之后,再來進行判斷就容易多了。因為這四個部分是有順序的,我們只需要判斷它們順序的合理性就可以了。根據順序的合理性,我們可以進一步推測出每一個符號允許出現的位置,所有和預期位置不符的符號都是非法的。

根據這一點我們可以推導出一些結論:

  1. 空格只能出現在首尾,出現在中間一定是非法的。
  2. 正負號只能出現在兩個地方,第一個地方是數字的最前面,表示符號。第二個位置是e后面,表示指數的正負。如果出現在其他的位置一定也是非法的。
  3. 數字,數字沒有特別的判斷,本題當中沒有前導0的問題。
  4. e只能出現一次,並且e之后一定要有數字才是合法的,123e這種也是非法的。
  5. 小數點,由於e之后的指數一定是整數,所以小數點最多只能出現一次,並且一定要在e之前。所以如果之前出現過小數點或者是e,再次出現小數點就是非法的。

當我們把每一個符號合法的情況梳理清楚之后,會發現其實也沒有那么復雜,情況也沒有那么多。這其實也是常用套路,我們把互相耦合的一些變量拆分開了,彼此互不影響。這樣我們就可以單獨考慮這其中的每個零件,而不用面對它們互相耦合的復雜情況了。

我們把剛才梳理出來的全部用代碼實現就可以通過這題了:

class Solution:
 def isNumber(self, s: str) -> bool:  s = s.strip()  numbers = [str(i) for i in range(10)]  n = len(s)   # 用4個標記記錄e和小數點以及數字和e之后的數字有無出現過  e_show_up, dot_show_up, num_show_up, num_after_e = False, False, False, False   for i in range(n):  c = s[i]  # 如果是數字,則將數字和e后出現的數字都標記為true  # 沒有e的浮點數也認為e之后出現過數字  if c in numbers:  num_show_up = True  num_after_e = True  # 如果是正負號,只有出現在首位或者是e后面才是合法  elif c in ('+', '-'):  if i > 0 and s[i-1] != 'e':  return False  # 如果是小數點,那么必須保證e和小數點都沒有出現過  elif c == '.':  if dot_show_up or e_show_up:  return False  dot_show_up = True  # 如果是e,要保證已經有數字出現,並且e沒有出現過  elif c == 'e':  if e_show_up or not num_show_up:  return False  e_show_up = True  num_show_up = False  # 其他情況都視為非法  else:  return False   return num_show_up and num_after_e 

總結

這題我們看代碼好像也不復雜,但是想要把這么多條件都梳理清楚,寫出這樣簡單的代碼也不是一件容易的事情。必須建立在我們對問題有了充分的思考的基礎上,其實我們的代碼還疏漏了一個條件就是前導零的情況。如果0出現在數字最前面其實也是非法的,不過這題當中沒有針對這樣的case,但實際上我們是應該考慮的,這里也是我偷懶了。

很多人很討厭模擬題,包括我在內,原因就是情況太多很惡心人,經常會中招遺漏一些情況。看看這題的評分也能看得出來,點贊的只有678,反對的卻又4572,可見一斑。但其實模擬題也是一種對思維的鍛煉,需要我們有冷靜的思維和理智的分析,這也是一個優秀的選手必不可少的。希望大家都能攻克這道難關。

今天的文章就到這里,原創不易,掃碼關注我,獲取更多精彩文章。


免責聲明!

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



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