长大后想做什么?做回小孩!

0%

LeetCode——有效数字

什么是面向测试编程?试试本题就知道了!

NO.65 有效数字 困难

8Q6Wd0.png

没有get到这道题目的点,这样的题目很搞心态。尤其是看了作者的解答之后更懵逼了,作者使用了责任链的设计模式,从来没想到解算法题还能用到设计模式。大牛的思维方式就适合我不一样,解个算法题都能考虑到扩展性和复用性。

思路一:暴力法

按序遍历字符串,逐位判断是否合法。注意要去除首尾空格。

这种方法就是比较恶心,很容易有考虑不到的情况。

而且测试用例中有:”.1”、”.2”、”+.8”、”46.”、”2e0”等等,预期输出都是true。真的恶心到了。。。

切身感受什么是面向测试编程!!!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public boolean isNumber(String s) {
if (s==null)return false;
s = s.trim();
if (s.length()==0)return false;
boolean pointSeen = false;//是否出现过 .
boolean eSeen = false;//是否出现过 e
boolean numberSeen = false;//是否出现过 0-9
boolean numberAfterE = true;//e之后是否出现 0-9
for(int i=0; i<s.length(); i++) {
if('0' <= s.charAt(i) && s.charAt(i) <= '9') {//当前元素是数字
numberSeen = true;
numberAfterE = true;
} else if(s.charAt(i) == '.') {//当前元素是 .
if(eSeen || pointSeen) {//已经出现过 e或者.,则非法
return false;
}
pointSeen = true;
} else if(s.charAt(i) == 'e') {//当前元素 e
if(eSeen || !numberSeen) {//已经出现过 e或者e之前没出现过数字,则非法
return false;
}
numberAfterE = false;//注意这点很重要,现在开始记录e之后是否有数字
eSeen = true;
} else if(s.charAt(i) == '-' || s.charAt(i) == '+') {//当前元素是-或+
if(i != 0 && s.charAt(i-1) != 'e') {//如果-或+不是第一个元素 或者 之前不是 e
return false;
}
} else {//当前元素不是0-9、. 、e 、- 、+,非法
return false;
}
}
//是否有数字并且e之后也有数字
return numberSeen && numberAfterE;
}

时间复杂度:O(n)

写完暴力法,忍不住给下面这个骚操作点了个赞 : )

1
2
3
4
5
6
7
class Solution:
def isNumber(self, s: str) -> bool:
try:
key=float(s)
return True
except:
return False

思路二:确定有限自动机(DFA)

下面搬运自leetcode社区windliang,我不是大佬,我只是大佬的搬运工。

先画出状态转换图:

8QIq3t.png

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

这种方式思路清晰多了,但是之前没有接触过这种方法实现起来还是很生疏的。

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

}
}
//橙色部分的状态代表合法数字
return state == 2 || state == 3 || state == 5 || state == 8;
}

时间复杂度:O(n)

思路三:责任链模式

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

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

虽然代码变多了,但是维护性,扩展性变的很强了。比如,题目新增了一种情况,”0x123” 16 进制也算是合法数字。这样的话,解法一和解法二就没什么用了,完全得重新设计。但对于解法三,我们只需要新增一个类,专门判断这种情况,然后加到执行者的数组里就够了,很牛逼!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//每个类都实现这个接口
interface NumberValidate {
boolean validate(String s);
}
//定义一个抽象类,用来检查一些基础的操作,是否为空,去掉首尾空格,去掉 +/-
//doValidate 交给子类自己去实现
abstract class NumberValidateTemplate implements NumberValidate{

public boolean validate(String s)
{
if (checkStringEmpty(s))
{
return false;
}

s = checkAndProcessHeader(s);

if (s.length() == 0)
{
return false;
}

return doValidate(s);
}

private boolean checkStringEmpty(String s)
{
if (s.equals(""))
{
return true;
}

return false;
}

private String checkAndProcessHeader(String value)
{
value = value.trim();

if (value.startsWith("+") || value.startsWith("-"))
{
value = value.substring(1);
}


return value;
}



protected abstract boolean doValidate(String s);
}

//实现 doValidate 判断是否是整数
class IntegerValidate extends NumberValidateTemplate{

protected boolean doValidate(String integer)
{
for (int i = 0; i < integer.length(); i++)
{
if(Character.isDigit(integer.charAt(i)) == false)
{
return false;
}
}

return true;
}
}

//实现 doValidate 判断是否是科学计数法
class SienceFormatValidate extends NumberValidateTemplate{

protected boolean doValidate(String s)
{
s = s.toLowerCase();
int pos = s.indexOf("e");
if (pos == -1)
{
return false;
}

if (s.length() == 1)
{
return false;
}

String first = s.substring(0, pos);
String second = s.substring(pos+1, s.length());

if (validatePartBeforeE(first) == false || validatePartAfterE(second) == false)
{
return false;
}


return true;
}

private boolean validatePartBeforeE(String first)
{
if (first.equals("") == true)
{
return false;
}

if (checkHeadAndEndForSpace(first) == false)
{
return false;
}

NumberValidate integerValidate = new IntegerValidate();
NumberValidate floatValidate = new FloatValidate();
if (integerValidate.validate(first) == false && floatValidate.validate(first) == false)
{
return false;
}

return true;
}

private boolean checkHeadAndEndForSpace(String part)
{

if (part.startsWith(" ") ||
part.endsWith(" "))
{
return false;
}

return true;
}

private boolean validatePartAfterE(String second)
{
if (second.equals("") == true)
{
return false;
}

if (checkHeadAndEndForSpace(second) == false)
{
return false;
}

NumberValidate integerValidate = new IntegerValidate();
if (integerValidate.validate(second) == false)
{
return false;
}

return true;
}
}
//实现 doValidate 判断是否是小数
class FloatValidate extends NumberValidateTemplate{

protected boolean doValidate(String floatVal)
{
int pos = floatVal.indexOf(".");
if (pos == -1)
{
return false;
}

if (floatVal.length() == 1)
{
return false;
}

NumberValidate nv = new IntegerValidate();
String first = floatVal.substring(0, pos);
String second = floatVal.substring(pos + 1, floatVal.length());

if (checkFirstPart(first) == true && checkFirstPart(second) == true)
{
return true;
}

return false;
}

private boolean checkFirstPart(String first)
{
if (first.equals("") == false && checkPart(first) == false)
{
return false;
}

return true;
}

private boolean checkPart(String part)
{
if (Character.isDigit(part.charAt(0)) == false ||
Character.isDigit(part.charAt(part.length() - 1)) == false)
{
return false;
}

NumberValidate nv = new IntegerValidate();
if (nv.validate(part) == false)
{
return false;
}

return true;
}
}
//定义一个执行者,我们把之前实现的各个类加到一个数组里,然后依次调用
class NumberValidator implements NumberValidate {

private ArrayList<NumberValidate> validators = new ArrayList<NumberValidate>();

public NumberValidator()
{
addValidators();
}

private void addValidators()
{
NumberValidate nv = new IntegerValidate();
validators.add(nv);

nv = new FloatValidate();
validators.add(nv);

//nv = new HexValidate();
//validators.add(nv);

nv = new SienceFormatValidate();
validators.add(nv);
}

@Override
public boolean validate(String s)
{
for (NumberValidate nv : validators)
{
if (nv.validate(s) == true)
{
return true;
}
}

return false;
}


}
public boolean isNumber(String s) {
NumberValidate nv = new NumberValidator();
return nv.validate(s);
}

本人菜鸟,有错误请告知,感激不尽!

更多题解和学习记录博客:博客github