题目描述(困难难度)

65. Valid Number - 图1

给定一个字符串,判断它是否代表合法数字,当然题目描述的样例不够多,会使得设计算法中出现很多遗漏的地方,这里直接参考评论区@yeelan0319给出的更多测试样例。

  1. test(1, "123", true);
  2. test(2, " 123 ", true);
  3. test(3, "0", true);
  4. test(4, "0123", true); //Cannot agree
  5. test(5, "00", true); //Cannot agree
  6. test(6, "-10", true);
  7. test(7, "-0", true);
  8. test(8, "123.5", true);
  9. test(9, "123.000000", true);
  10. test(10, "-500.777", true);
  11. test(11, "0.0000001", true);
  12. test(12, "0.00000", true);
  13. test(13, "0.", true); //Cannot be more disagree!!!
  14. test(14, "00.5", true); //Strongly cannot agree
  15. test(15, "123e1", true);
  16. test(16, "1.23e10", true);
  17. test(17, "0.5e-10", true);
  18. test(18, "1.0e4.5", false);
  19. test(19, "0.5e04", true);
  20. test(20, "12 3", false);
  21. test(21, "1a3", false);
  22. test(22, "", false);
  23. test(23, " ", false);
  24. test(24, null, false);
  25. test(25, ".1", true); //Ok, if you say so
  26. test(26, ".", false);
  27. test(27, "2e0", true); //Really?!
  28. test(28, "+.8", true);
  29. test(29, " 005047e+6", true); //Damn = =|||

解法一 直接法

什么叫直接法呢,就是没有什么通用的方法,直接分析题目,然后写代码,直接贴两个 leetcode Disscuss 的代码吧,供参考。

想法一

把当前的输入分成几类,再用几个标志位来判断当前是否合法。

  1. public boolean isNumber(String s) {
  2. s = s.trim();
  3. boolean pointSeen = false;
  4. boolean eSeen = false;
  5. boolean numberSeen = false;
  6. boolean numberAfterE = true;
  7. for(int i=0; i<s.length(); i++) {
  8. if('0' <= s.charAt(i) && s.charAt(i) <= '9') {
  9. numberSeen = true;
  10. numberAfterE = true;
  11. } else if(s.charAt(i) == '.') {
  12. if(eSeen || pointSeen) {
  13. return false;
  14. }
  15. pointSeen = true;
  16. } else if(s.charAt(i) == 'e') {
  17. if(eSeen || !numberSeen) {
  18. return false;
  19. }
  20. numberAfterE = false;
  21. eSeen = true;
  22. } else if(s.charAt(i) == '-' || s.charAt(i) == '+') {
  23. if(i != 0 && s.charAt(i-1) != 'e') {
  24. return false;
  25. }
  26. } else {
  27. return false;
  28. }
  29. }
  30. return numberSeen && numberAfterE;
  31. }

时间复杂度:O(n)。

空间复杂度:O(1)。

想法二,遍历过程中,把遇到不符合的都返回 false,到最后成功到达末尾就返回 true。C++ 的代码,可以参考一下思想。

  1. bool isNumber(const char *s)
  2. {
  3. int i = 0;
  4. // skip the whilespaces
  5. for(; s[i] == ' '; i++) {}
  6. // check the significand
  7. if(s[i] == '+' || s[i] == '-') i++; // skip the sign if exist
  8. int n_nm, n_pt;
  9. for(n_nm=0, n_pt=0; (s[i]<='9' && s[i]>='0') || s[i]=='.'; i++)
  10. s[i] == '.' ? n_pt++:n_nm++;
  11. if(n_pt>1 || n_nm<1) // no more than one point, at least one digit
  12. return false;
  13. // check the exponent if exist
  14. if(s[i] == 'e') {
  15. i++;
  16. if(s[i] == '+' || s[i] == '-') i++; // skip the sign
  17. int n_nm = 0;
  18. for(; s[i]>='0' && s[i]<='9'; i++, n_nm++) {}
  19. if(n_nm<1)
  20. return false;
  21. }
  22. // skip the trailing whitespaces
  23. for(; s[i] == ' '; i++) {}
  24. return s[i]==0; // must reach the ending 0 of the string
  25. }

时间复杂度:O(n)。

空间复杂度:O(1)。

解法二 自动机

自己最开始想到的就是这个,编译原理时候在学到的自动机,就是一些状态转移。这一块内容很多,自己也很多东西都忘了,但不影响我们写算法,主要参考这里

65. Valid Number - 图2

如上图,从 0 开始总共有 9 个状态,橙色代表可接受状态,也就是表示此时是合法数字。总共有四大类输入,数字,小数点,e 和 正负号。我们只需要将这个图实现就够了。

  1. public boolean isNumber(String s) {
  2. int state = 0;
  3. s = s.trim();//去除头尾的空格
  4. //遍历所有字符,当做输入
  5. for (int i = 0; i < s.length(); i++) {
  6. switch (s.charAt(i)) {
  7. //输入正负号
  8. case '+':
  9. case '-':
  10. if (state == 0) {
  11. state = 1;
  12. } else if (state == 4) {
  13. state = 6;
  14. } else {
  15. return false;
  16. }
  17. break;
  18. //输入数字
  19. case '0':
  20. case '1':
  21. case '2':
  22. case '3':
  23. case '4':
  24. case '5':
  25. case '6':
  26. case '7':
  27. case '8':
  28. case '9':
  29. //根据当前状态去跳转
  30. switch (state) {
  31. case 0:
  32. case 1:
  33. case 2:
  34. state = 2;
  35. break;
  36. case 3:
  37. state = 3;
  38. break;
  39. case 4:
  40. case 5:
  41. case 6:
  42. state = 5;
  43. break;
  44. case 7:
  45. state = 8;
  46. break;
  47. case 8:
  48. state = 8;
  49. break;
  50. default:
  51. return false;
  52. }
  53. break;
  54. //小数点
  55. case '.':
  56. switch (state) {
  57. case 0:
  58. case 1:
  59. state = 7;
  60. break;
  61. case 2:
  62. state = 3;
  63. break;
  64. default:
  65. return false;
  66. }
  67. break;
  68. //e
  69. case 'e':
  70. switch (state) {
  71. case 2:
  72. case 3:
  73. case 8:
  74. state = 4;
  75. break;
  76. default:
  77. return false;
  78. }
  79. break;
  80. default:
  81. return false;
  82. }
  83. }
  84. //橙色部分的状态代表合法数字
  85. return state == 2 || state == 3 || state == 5 || state == 8;
  86. }

时间复杂度:O(n)。

空间复杂度:O(1)。

解法三 责任链模式

解法二看起来已经很清晰明了了,只需要把状态图画出来,然后实现代码就很简单了。但是缺点是,如果状态图少考虑了东西,再改起来就会很麻烦。

这里作者提出来,利用责任链的设计模式,会使得写出的算法扩展性以及维护性更高。这里用到的思想就是,每个类只判断一种类型。比如判断是否是正数的类,判断是否是小数的类,判断是否是科学计数法的类,这样每个类只关心自己的部分,出了问题很好排查,而且互不影响。

  1. //每个类都实现这个接口
  2. interface NumberValidate {
  3. boolean validate(String s);
  4. }
  5. //定义一个抽象类,用来检查一些基础的操作,是否为空,去掉首尾空格,去掉 +/-
  6. //doValidate 交给子类自己去实现
  7. abstract class NumberValidateTemplate implements NumberValidate{
  8. public boolean validate(String s)
  9. {
  10. if (checkStringEmpty(s))
  11. {
  12. return false;
  13. }
  14. s = checkAndProcessHeader(s);
  15. if (s.length() == 0)
  16. {
  17. return false;
  18. }
  19. return doValidate(s);
  20. }
  21. private boolean checkStringEmpty(String s)
  22. {
  23. if (s.equals(""))
  24. {
  25. return true;
  26. }
  27. return false;
  28. }
  29. private String checkAndProcessHeader(String value)
  30. {
  31. value = value.trim();
  32. if (value.startsWith("+") || value.startsWith("-"))
  33. {
  34. value = value.substring(1);
  35. }
  36. return value;
  37. }
  38. protected abstract boolean doValidate(String s);
  39. }
  40. //实现 doValidate 判断是否是整数
  41. class IntegerValidate extends NumberValidateTemplate{
  42. protected boolean doValidate(String integer)
  43. {
  44. for (int i = 0; i < integer.length(); i++)
  45. {
  46. if(Character.isDigit(integer.charAt(i)) == false)
  47. {
  48. return false;
  49. }
  50. }
  51. return true;
  52. }
  53. }
  54. //实现 doValidate 判断是否是科学计数法
  55. class SienceFormatValidate extends NumberValidateTemplate{
  56. protected boolean doValidate(String s)
  57. {
  58. s = s.toLowerCase();
  59. int pos = s.indexOf("e");
  60. if (pos == -1)
  61. {
  62. return false;
  63. }
  64. if (s.length() == 1)
  65. {
  66. return false;
  67. }
  68. String first = s.substring(0, pos);
  69. String second = s.substring(pos+1, s.length());
  70. if (validatePartBeforeE(first) == false || validatePartAfterE(second) == false)
  71. {
  72. return false;
  73. }
  74. return true;
  75. }
  76. private boolean validatePartBeforeE(String first)
  77. {
  78. if (first.equals("") == true)
  79. {
  80. return false;
  81. }
  82. if (checkHeadAndEndForSpace(first) == false)
  83. {
  84. return false;
  85. }
  86. NumberValidate integerValidate = new IntegerValidate();
  87. NumberValidate floatValidate = new FloatValidate();
  88. if (integerValidate.validate(first) == false && floatValidate.validate(first) == false)
  89. {
  90. return false;
  91. }
  92. return true;
  93. }
  94. private boolean checkHeadAndEndForSpace(String part)
  95. {
  96. if (part.startsWith(" ") ||
  97. part.endsWith(" "))
  98. {
  99. return false;
  100. }
  101. return true;
  102. }
  103. private boolean validatePartAfterE(String second)
  104. {
  105. if (second.equals("") == true)
  106. {
  107. return false;
  108. }
  109. if (checkHeadAndEndForSpace(second) == false)
  110. {
  111. return false;
  112. }
  113. NumberValidate integerValidate = new IntegerValidate();
  114. if (integerValidate.validate(second) == false)
  115. {
  116. return false;
  117. }
  118. return true;
  119. }
  120. }
  121. //实现 doValidate 判断是否是小数
  122. class FloatValidate extends NumberValidateTemplate{
  123. protected boolean doValidate(String floatVal)
  124. {
  125. int pos = floatVal.indexOf(".");
  126. if (pos == -1)
  127. {
  128. return false;
  129. }
  130. if (floatVal.length() == 1)
  131. {
  132. return false;
  133. }
  134. NumberValidate nv = new IntegerValidate();
  135. String first = floatVal.substring(0, pos);
  136. String second = floatVal.substring(pos + 1, floatVal.length());
  137. if (checkFirstPart(first) == true && checkFirstPart(second) == true)
  138. {
  139. return true;
  140. }
  141. return false;
  142. }
  143. private boolean checkFirstPart(String first)
  144. {
  145. if (first.equals("") == false && checkPart(first) == false)
  146. {
  147. return false;
  148. }
  149. return true;
  150. }
  151. private boolean checkPart(String part)
  152. {
  153. if (Character.isDigit(part.charAt(0)) == false ||
  154. Character.isDigit(part.charAt(part.length() - 1)) == false)
  155. {
  156. return false;
  157. }
  158. NumberValidate nv = new IntegerValidate();
  159. if (nv.validate(part) == false)
  160. {
  161. return false;
  162. }
  163. return true;
  164. }
  165. }
  166. //定义一个执行者,我们把之前实现的各个类加到一个数组里,然后依次调用
  167. class NumberValidator implements NumberValidate {
  168. private ArrayList<NumberValidate> validators = new ArrayList<NumberValidate>();
  169. public NumberValidator()
  170. {
  171. addValidators();
  172. }
  173. private void addValidators()
  174. {
  175. NumberValidate nv = new IntegerValidate();
  176. validators.add(nv);
  177. nv = new FloatValidate();
  178. validators.add(nv);
  179. nv = new HexValidate();
  180. validators.add(nv);
  181. nv = new SienceFormatValidate();
  182. validators.add(nv);
  183. }
  184. @Override
  185. public boolean validate(String s)
  186. {
  187. for (NumberValidate nv : validators)
  188. {
  189. if (nv.validate(s) == true)
  190. {
  191. return true;
  192. }
  193. }
  194. return false;
  195. }
  196. }
  197. public boolean isNumber(String s) {
  198. NumberValidate nv = new NumberValidator();
  199. return nv.validate(s);
  200. }

时间复杂度:

空间复杂度:

解法二中自动机的应用,会使得自己的思路更清晰。而解法三中,作者提出的对设计模式的应用,使自己眼前一亮,虽然代码变多了,但是维护性,扩展性变的很强了。比如,题目新增了一种情况,”0x123” 16 进制也算是合法数字。这样的话,解法一和解法二就没什么用了,完全得重新设计。但对于解法三,我们只需要新增一个类,专门判断这种情况,然后加到执行者的数组里就够了,很棒!

windliang wechat

添加好友一起进步~

如果觉得有帮助的话,可以点击 这里 给一个 star 哦 ^^

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情