Date: 2019--8-12
1. 把字符串转换成整数 (考察知识点:字符串和进制转换)
题目描述
将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。
输入描述:
输入一个字符串,包括数字字母符号,可以为空
输出描述:
如果是合法的数值表达则返回该数字,否则返回0
示例1
输入
复制
+2147483647
1a33
输出
复制
2147483647
0
分析:刚开始对于该题想到的是直接int,利用一个try 语法;但是想想应该考察的不是这一点,需要考虑字符串的写法,所以就改为一个字符一个字符的进行判断:
首先仍然是需要对输入字符进行非空判断;
然后把第一个字符单独进行考察:
i)第一个字符为符号或者为非数字时,先不直接返回0,可以直接将其结果a令为0.
ii)对于第一位是数字时,可以直接第一位的数字*(10**(len(s)-1)),表达最高位。
然后遍历考察后面位上的字符,如果在0~9之间,则相应*之后进行累加,否则不属于0~9之间的字符时,直接返回0
最后补充考虑第一位是符号位时的情况,+ 时输出a,-时输出-a
最后在最外层还需输出一个a,考虑到如果第一位不是符号位,而且满足每一位都在0~9之间时的输出。
# -*- coding:utf-8 -*-
class Solution:
def StrToInt(self, s):
# write code here
if not s:
return 0
else:
if s[0] >'9'or s[0] <'0': # 这里是连接是or,因为else中是与条件
a = 0 # 不能直接返回0,因为可能第一位是符号位
else:
a = int(s[0])*(10**(len(s)-1)) # 如果第一位的字符在0~9之间,则转换为最高位的数
if len(s)>1:
for i in range(1, len(s)):
if s[i] <= '9' and s[i] >='0': # 首先判断第i位的字符是否在0~9之间。如果是,累加
a += int(s[i])*(10**(len(s)-1-i))
else: # 否则,第i位上的为字符,则不符合规则,返回0
return 0
if s[0] == '+': # 有 + 符号的时候的输出
return a
elif s[0] == '-': # 有 - 符号时的输出
return -a
return a # 没有前缀符号时的一般输出
# -*- coding:utf-8 -*- 另一种取巧的思路,利用try语句,也通过,而且时间复杂度更小!
class Solution:
def StrToInt(self, s):
# write code here
try:
return int(s)
except Exception as e:
return 0
另外该题也可以先初始化一个数字列表0~9 和 一个符号字典{‘+’:1, '-':-1},然后根据第一位字符的情况进行两路讨论,后面的字符进行遍历,讨论字符情况进行处理(不断进行累加)。
# -*- coding:utf-8 -*- # 引入数字列表和符号字典,时间复杂度与第一个方法相似,空间复杂度大一点点
class Solution:
def StrToInt(self, s):
if not s:
return 0
str2num={'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,'0':0}
flag2num={'-':-1,'+':1}
first=s[0]
if first in ['+','-']:
flag=flag2num[first]
x=0
for i in s[1:]:
if i not in str2num:
return 0
x=x*10+str2num[i]
return flag*x
else:
x=0
for i in s:
if i not in str2num:
return 0
x=x*10+str2num[i]
return x
2. 数组中重复的数字 (考查知识点: 数组)
题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
分析:刚开始没有理解这题是什么意思,后来想一想题目:需要找到任意一个重复的数字,首先将其赋值给duplication[0],然后return true,系统后台会自动给出相应的正确输出。
思路:我想的第一种方法就是:借用一个长度为len(numbers)的全零数组B,然后遍历numbers中的每一个数字,在B数组中的相应位置对其出现次数进行累加,如果第一次出现次数等于2,则直接赋值并返回。否则在最外层返回false.(这里题目给出了:numbers是一个长度为n的数组里的所有数字都在0到n-1的范围内,所以可以假定B数组的数组也是默认从0~n-1的)
# -*- coding:utf-8 -*-
class Solution:
# 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
# 函数返回True/False
def duplicate(self, numbers, duplication):
# write code here
if not numbers:
return False
B = [0]* len(numbers) # 初始化全零数组 B,用于计算numbers中的数字的频次
for i in range(len(numbers)): # 遍历
B[numbers[i]] += 1 # 根据numbers[i]的数字,在B中对应位置进行累加计算频次,
if B[numbers[i]] == 2: # 如果频次第一次出现2,即可进行赋值并return
duplication[0] = numbers[i]
return True
return False
第二种取巧的方法是利用collections模块中的Counter()库,对numbers中的每个数字进行计算频次,但我觉得考该题的本意不在这里(这是一种高级算法),但解决问题还是可以的。
# -*- coding:utf-8 -*-
import collections
class Solution:
# 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
# 函数返回True/False
def duplicate(self, numbers, duplication):
# write code here
if not numbers:
return False
c_dicts = collections.Counter(numbers) # 根据该函数得到每个数字的频次统计,是一个字典
for k,v in c_dicts.items():
if v >1: # 如果相应的K的频次大于1,进行赋值并返回
duplication[0] = k
return True
return False
3. 构建乘积数组 (考查知识点:数组) (下标的选择有点难以理解,多看!!)
题目描述
给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。
分析:思路一:超级高的时间复杂度,不考虑,但写一下思路:对于每一个元素B[i ]的计算直接采用累乘A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。但是显然从因子来分析,发现其可以当成前后两部分,进而看成两部分的乘积!
思路二:从上面直观的想法来看可以发现,我们可以利用“牺牲空间换取时间”的想法,构造两个列表,用来存储前向的递归乘积和后向的递归乘积;head[i] = A[0]*A[1]*……*A[i-1] tail[i] = A[-1]*A[-2]*……*A[-i],注意的是:这两个列表的首元素都是1,而且tail在取的时候采用后向遍历法(负索引)
# -*- coding:utf-8 -*-
class Solution:
def multiply(self, A):
# write code here
if not A:
return False
head = [1] # 初始值,是当求第一个元素时,第一个元素的值应当被赋予1,而是采用A[1]……*A[n-1]
tail = [1] # 初始值,是当求最后一个元素时,最后一个元素的值应当被赋予1,而是采用A[-2]……*A[-n]
for i in range(len(A)-1):
head.append(head[i]*A[i]) # 前向进行累乘的结果,最后长度等于len(A)
tail.append(tail[i]*A[-i-1]) # 反向进行累乘的结果,最后长度等于len(A)
return [head[j]*tail[-j-1] for j in range(len(head))] # 注意head是前向取,而tail是后向索引在取!
附上牛客上别人直观的解释:
思路:按上图把每行被1分割的两部分乘积都计算出来,这样可以从首尾分别用累乘算出两个列表,然后两个列表首尾相乘就是B的元素
4. 正则表达式匹配 (考察知识:字符串,正则表达式) 好多细节需要去讨论,较为复杂!!! 多看看
题目描述
请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配
分析思路:(先写一下整体的思路)总共分为四个大部分,然后有的部分下面需要进行讨论
1) 两个字符串都为空,直接匹配成功
2)字符串为空,但模式非空,有可能匹配不成功,有可能成功(当模式长度大于1且第二位是*时候,进行模式后移两位的递归调用;否则直接匹配不成功)
3)字符串非空,但是模式为空,显然匹配不成功
4)字符串和模式都不为空的时候,需要借助模式中的第二位是否为*进行分类讨论:
4.1)如果模式长度大于1且模式第二位是*时, 需要进行首位是否相等的判断
4.1.1) 如果首位不相等且模式首位不为.时,字符串不变,模式后移两位(相当于将模式前两位视为空)
4.1.2) 否则分为下面三种情况,任意一种成立都可以递归调用:
i) 将p的前两位当成空:p后移动两位,s不变,进行递归调用
ii) 将p的前两位去匹配s的第一位:p后移动两位,s移动一位,(相当于*代重复0次)
iii) 尝试用p的前两位去匹配s的前多位字符:p不变,s后移动一位(尝试匹配多位字符)
4.2) 如果第二个字符不等于*, 当字符串和模式的第一位相等或者模式第一位为.时,匹配第一位成功,则往后递归调用进行匹配;否则匹配不成功!
# 这是目前所有offer题中,我注释写的最长的了。因为难易理解,有很多细节需要去考虑,一定要多看多看多看!!!
# -*- coding:utf-8 -*-
class Solution:
# s, pattern都是字符串
def match(self, s, pattern):
# write code here
# 如果两者都为空,则匹配成功
if len(s) == 0 and len(pattern) == 0:
return True
# 如果字符串为空,模式不为空。需要判断模式的长度,如果大于1且第二位为*,则递归匹配,且模式后移两位,否则如果长度为1或者第二位不是*,匹配不成功
elif len(s) == 0 and len(pattern) != 0:
if len(pattern)>1 and pattern[1] == '*':
return self.match(s,pattern[2:]) # 此时相当于将pattern中的*运用为0次。
# 否则如果长度为1或者第二位不是*,匹配不成功
else:
return False
# 如果字符串非空,但是模式为空,显然匹配不会成功。
elif len(s) != 0 and len(pattern) == 0:
return False
# 两者都不为空,需要借助模式的第二位进行讨论:
else:
# 模式第二位是*
if len(pattern)>1 and pattern[1] == '*':
# 如果第一位不等且不为. 此时模式后移两位,相当于将模式的前两位视为空。
if s[0] != pattern[0] and pattern[0] != '.':
return self.match(s, pattern[2:])
else:
# 模式第二位为*,且模式第一位和字符串第一位相等,有以下三种情况,任意一种情况成立都可以:
#1 将p前两位当成空:p后移两位,s不变
#2 p的前两位去匹配第一位:p后移两位,s后移一位(此时相当于*代表重复0次,p与s[0]匹配)
#3 p的前两位去匹配s的多位:p不变,s后移一位(此时相当于p的前两位尝试着去匹配s中的前多位字符)
return self.match(s,pattern[2:]) or self.match(s[1:],pattern[2:]) or self.match(s[1:],pattern)
else:
# 模式第二位不是*时,先判断如果第一位相等或者模式的第一位是. 则他们第一位匹配成功,都向后移动一位
if s[0] == pattern[0] or pattern[0] == '.':
return self.match(s[1:],pattern[1:])
# 否则,模式第二位不是*,且第一位不相等 且模式第一位非. 则匹配不成功
else:
return False
5. 表示数值的字符串 (考查知识点:字符串+正则表达式+数学规则)
题目描述
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
分析:该题和第一题有着类似的取巧通过的方法,但不是题目的本意(强烈不建议大家这样操作,我是个什么样怪人啊!!)
先粘出取巧的通过代码:
# -*- coding:utf-8 -*-
class Solution:
# s字符串
def isNumeric(self, s):
# write code here
try:
return float(s)
except Exception as e:
return False
第二种方法:基于正则表达式的思想 利用re库进行匹配
# -*- coding:utf-8 -*-
import re
class Solution:
# s字符串
def isNumeric(self, s):
# write code here
return re.match(r"^[\+\-]?[0-9]*(\.[0-9]*)?([eE][\+\-]?[0-9]+)?$",s)
第三种:数学规则方法,建议多看!
思路解析:分别初始化三个标记来分别标记符号位、小数点、e是否出现过
1) 对于e的情况,不能同时出现两个e;而且e不能在最后一位(因为e后要接数字)
2)对于符号位的情况,如果前面出现过了符号位,则该符号应该出现在e之后,否则错误
如果是第一次出现符号位,如果i>1,则前一位必须是e,否则false
3) 对于小数点的情况,不能出现两次,且出现过了e就不能再有小数点了出现了
如果是第一次出现小数点,但前面已经出现过了e就不行
4)否则不是e 小数点 符号 ,就应该是0-9之间的数,否则false
因为整个里面是来判断不符合的情况,所以里面的情况都不满足的话,跳到最外层给出True的结果!
# -*- coding:utf-8 -*-
class Solution:
# s字符串
def isNumeric(self, s):
# write code here
if len(s) <= 0:
return False
# 分别标记是否出现过正负号、小数点、e,因为这几个需要特殊考虑
has_sign = False
has_point = False
has_e = False
for i in range(len(s)):
# 1 对于e的情况
if s[i] == 'E' or s[i] == 'e':
# 不同出现两个e
if has_e:
return False
# e不能出现在最后面,因为e后面要接数字
else:
has_e = True
if i == len(s)-1:
return False
# 2 对于符号位的情况
elif s[i] == '+' or s[i] == '-':
# 如果前面已经出现过了符号位,那么这个符号位,必须是跟在e后面的
if has_sign:
if s[i-1] != 'e' or s[i-1] != 'E':
return False
# 如果这是第一次出现符号位,而且出现的位置不是字符串第一个位置,那么就只能出现在e后面
else:
has_sign = True
if i > 0 and s[i-1] != 'e' and s[i-1] != 'E':
return False
# 3 对于小数点的情况
elif s[i] == '.':
# 小数点不能出现两次;而且如果已经出现过e了,那么就不能再出现小数点,因为e后面只能是整数
if has_point or has_e:
return False
# 如果是第一次出现小数点,如果前面出现过e,那么还是不能出现小数点
else:
has_point = True
if i > 0 and has_e:
return False
else:
# 4 其他字符必须是‘0’到‘9’之间的
if s[i] < '0' or s[i] > '9':
return False
return True
6. 字符流中第一个不重复的字符 (考查知识点:字符串)
题目描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。
分析:本题刚开始没有理解为什么有两个函数,还有一个insert函数,难道时需要不断进行字符串的补充,后来看他们的讨论确实是这样的。所以便采用__init__函数,建立一个全局成员函数:self.s。然后不断对这个s进行char的补充。
正式的输出不重复的字符是来自于FirstAppearingOnce函数,然后利用了count函数对s中的字符进行频次计算,频次等于1的字符将被依次放入res中,然后返回第一个即可。
# -*- coding:utf-8 -*-
class Solution:
# 返回对应char
def __init__(self,):
self.s = ''
def FirstAppearingOnce(self):
# write code here
res = list(filter(lambda c: self.s.count(c)==1,self.s))
return res[0] if res else '#'
def Insert(self, char):
# write code here
self.s += char
但我感觉上面的思路也不是本题的考察本意,毕竟我现在没有剑指offer书,不知道寓意如何?
第二种方法:
"""
解法:利用一个int型数组表示256个字符,这个数组初值置为-1.
每读出一个字符,将该字符的位置存入字符对应数组下标中。
若值为-1标识第一次读入,不为-1且>0表示不是第一次读入,将值改为-2.
之后在数组中找到>0的最小值,该数组下标对应的字符为所求。
在python中,ord(char)是得到char对应的ASCII码;chr(idx)是得到ASCII位idx的字符
"""
class Solution:
def __init__(self):
self.char_list = [-1 for i in range(256)]
self.index = 0 # 记录当前字符的个数,可以理解为输入的字符串中的下标
def FirstAppearingOnce(self):
# write code here
min_value = 500
min_idx = -1
for i in range(256):
if self.char_list[i] > -1:
if self.char_list[i] < min_value:
min_value = self.char_list[i]
min_idx = i
if min_idx > -1:
return chr(min_idx)
else:
return '#'
def Insert(self, char):
# 如果是第一出现,则将对应元素的值改为下边
if self.char_list[ord(char)] == -1:
self.char_list[ord(char)] = self.index
# 如果已经出现过两次了,则不修改
elif self.char_list[ord(char)] == -2:
pass
# 如果出现过一次,则进行修改,修改为-2
else:
self.char_list[ord(char)] = -2
self.index += 1
第三种方法: 该解法虽然通过了,但我觉得有问题。
class Solution:
def __init__(self):
self.s = ''
self.queue = [] #按顺序保存所有只出现一次的字符
self.second = [] #按顺序保存所有出现过的字符
def FirstAppearingOnce(self):
if self.queue:
return self.queue[0]
return '#'
def Insert(self, char):
self.s += char
if char in self.queue:
self.queue.pop(self.queue.index(char)) # 如果一个字符出现了3次呢,前两次就抵消了?
elif char not in self.second:
self.queue.append(char) # 是否会把出现了奇数词的字符放进去?
self.second.append(char)