题目链接
https://leetcode.cn/problems/valid-number/description/
https://leetcode.cn/problems/text-justification/description/
https://leetcode.cn/problems/minimum-window-substring/description/
题解链接
65.有效数字
68.文本左右对齐
https://leetcode.cn/problems/text-justification/solutions/6314/shun-zhao-si-lu-xiang-by-powcai
76.最小覆盖子串
https://leetcode.cn/problems/minimum-window-substring/solutions/1503454/by-flix-1kac
题解
65.有效数字
初见感觉这题是个模拟题。
解法一:字符串

有限状态自动机,第一次看到这种解法。
解法二:字符串
逻辑很清晰,就是有些条件我没看懂。
解法三:字符串、模拟
要看一遍题解说明可能才看得懂代码
68.文本左右对齐
解法一:贪心、字符串
看懂了,但是自己实现起来肯定不会。
解法二:脑筋急转弯
和上一个解法有点像。
解法三:数学

和解法二也有点像。
76.最小覆盖子串
解法一:哈希表、双指针、字符串、滑动窗口
应该多了解一些python的库的API的作用,善于利用。
解法二:哈希表、字符串、滑动窗口
解法三:滑动窗口
方法一

优化法

代码
python
#65.有效数字
#解法一:字符串
class Solution:
def isNumber(self, s: str) -> bool:
states = [
{ ' ': 0, 's': 1, 'd': 2, '.': 4 }, # 0. start with 'blank'
{ 'd': 2, '.': 4 } , # 1. 'sign' before 'e'
{ 'd': 2, '.': 3, 'e': 5, ' ': 8 }, # 2. 'digit' before 'dot'
{ 'd': 3, 'e': 5, ' ': 8 }, # 3. 'digit' after 'dot'
{ 'd': 3 }, # 4. 'digit' after 'dot' ('blank' before 'dot')
{ 's': 6, 'd': 7 }, # 5. 'e'
{ 'd': 7 }, # 6. 'sign' after 'e'
{ 'd': 7, ' ': 8 }, # 7. 'digit' after 'e'
{ ' ': 8 } # 8. end with 'blank'
]
p = 0 # start with state 0
for c in s:
if '0' <= c <= '9': t = 'd' # digit
elif c in "+-": t = 's' # sign
elif c in "eE": t = 'e' # e or E
elif c in ". ": t = c # dot, blank
else: t = '?' # unknown
if t not in states[p]: return False
p = states[p][t]
return p in (2, 3, 7, 8)
#解法二:字符串
class Solution:
def isNumber(self, s: str) -> bool:
s = s.strip()
numbers = [str(i) for i in range(10)]
n = len(s)
e_show_up, dot_show_up, num_show_up, num_after_e = False, False, False, False
for i in range(n):
c = s[i]
if c in numbers:
num_show_up = True
num_after_e = True
elif c in ('+', '-'):
if i > 0 and s[i-1] != 'e':
return False
elif c == '.':
if dot_show_up or e_show_up:
return False
dot_show_up = True
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
#解法三:字符串、模拟
def isNumber(self, s: str) -> bool:
num_dot = s.count('.')
num_e = s.count('e')
num_E = s.count('E')
if num_dot > 1 or num_e > 1 or num_E > 1: return False
if 'e' not in s and 'E' not in s:
return self.is_decimal(s) if num_dot == 1 else self.is_integer(s)
if 'e' in s:
before_e, after_e = s.split('e')
elif 'E' in s:
before_e, after_e = s.split('E')
return (self.is_decimal(before_e) or self.is_integer(before_e)) and self.is_integer(after_e)
def is_decimal(self, s):
if len(s) == 0: return False
if s[0] in ('+', '-'): s = s[1:]
count_digit = 0
count_dot = 0
for char in s:
if char.isdigit(): count_digit += 1
elif char == '.': count_dot += 1
else: return False
return count_digit >= 1 and count_dot == 1
def is_integer(self, s):
if len(s) == 0: return False
if s[0] in ('+', '-'): s = s[1:]
if len(s) == 0: return False
for char in s:
if not char.isdigit(): return False
return True
python
#68.文本左右对齐
#解法一:贪心、字符串
class Solution:
def process(self, curLen, curWords, maxWidth):
# 空格数量
num_space = maxWidth - curLen
# 如果只有一个单词就没必要考虑分配,直接填充空格即可
if len(curWords) == 1:
return curWords[0] + ' ' * (maxWidth - curLen)
# 每个空隙分到的空格数量
num_sep = num_space // (len(curWords) - 1)
# 分到空格数量多一个的空隙
head_sep = num_space % (len(curWords) - 1)
cur = ''
# 分配
for i in range(len(curWords) - 1):
cur = cur + curWords[i] + (' ' * (num_sep + 1) if i < head_sep else ' ' * num_sep)
# 分配结束之后把最后一个单词连上
cur = cur + curWords[-1]
return cur
def fullJustify(self, words: List[str], maxWidth: int) -> List[str]:
ret = []
curLen, curWords = 0, []
for w in words:
# 切分判断的条件,单词长度加上基本的空格长度
if curLen + len(w) + len(curWords) <= maxWidth:
curLen += len(w)
curWords.append(w)
else:
ret.append(self.process(curLen, curWords, maxWidth))
curLen, curWords = len(w), [w]
# 剩下没有安排的就是最后一行,按照最后一行靠左处理
if len(curWords) > 0:
cur = ''
for i in range(len(curWords) - 1):
cur = cur + curWords[i] + ' '
cur = cur + curWords[-1]
cur += ' ' * (maxWidth - len(cur))
ret.append(cur)
return ret
#解法二:脑筋急转弯
class Solution:
def fullJustify(self, words: List[str], maxWidth: int) -> List[str]:
res = []
n = len(words)
i = 0
def one_row_words(i):
# 至少 一行能放下一个单词, cur_row_len
left = i
cur_row_len = len(words[i])
i += 1
while i < n:
# 当目前行 加上一个单词长度 再加一个空格
if cur_row_len + len(words[i]) + 1 > maxWidth:
break
cur_row_len += len(words[i]) + 1
i += 1
return left, i
while i < n:
left, i = one_row_words(i)
# 该行几个单词获取
one_row = words[left:i]
# 如果是最后一行了
if i == n :
res.append(" ".join(one_row).ljust(maxWidth," "))
break
# 所有单词的长度
all_word_len = sum(len(i) for i in one_row)
# 至少空格个数
space_num = i - left - 1
# 可以为空格的位置
remain_space = maxWidth - all_word_len
# 单词之间至少几个空格,还剩几个空格没用
if space_num:
a, b = divmod(remain_space, space_num)
# print(a,b)
# 该行字符串拼接
tmp = ""
for word in one_row:
if tmp:
tmp += " " * a
if b:
tmp += " "
b -= 1
tmp += word
#print(tmp, len(tmp))
res.append(tmp.ljust(maxWidth, " "))
return res
#解法三:数学
class Solution:
def fullJustify(self, words: List[str], maxWidth: int) -> List[str]:
ans = []
n = len(words)
i = 0
while i < n:
start = i # 这一行第一个单词的下标
sum_len = -1 # 第一个单词之前没有空格
while i < n and sum_len + len(words[i]) + 1 <= maxWidth:
sum_len += len(words[i]) + 1 # 单词之间至少要有一个空格
i += 1
extra_spaces = maxWidth - sum_len # 这一行剩余未分配的空格个数
gaps = i - start - 1 # 这一行单词之间的空隙个数(单词个数减一)
# 特殊情况:如果只有一个单词,或者是最后一行,那么左对齐,末尾补空格
if gaps == 0 or i == n:
row = ' '.join(words[start: i]) + ' ' * extra_spaces # 末尾补空格
ans.append(row)
continue
# 一般情况:把 extra_spaces 个空格均匀分配到 gaps 个空隙中(靠左的空格更多)
avg, rem = divmod(extra_spaces, gaps)
spaces = ' ' * (avg + 1) # +1 表示加上单词之间已有的一个空格
# 前 rem 个空隙多一个空格
row = (spaces + ' ').join(words[start: start + rem + 1]) + \
spaces + spaces.join(words[start + rem + 1: i])
ans.append(row)
return ans
python
#76.最小覆盖子串
#解法一:哈希表、双指针、字符串、滑动窗口
class Solution:
def minWindow(self, s: str, t: str) -> str:
if len(t) > len(s):
return ''
cnt = collections.Counter(t) # 哈希表:记录需要匹配到的各个元素的数目
need = len(t) # 记录需要匹配到的字符总数【need=0表示匹配到了】
n = len(s)
start, end = 0, -1 # 记录目标子串s[start, end]的起始和结尾
min_len = n + 1 # 符合题意的最短子串长度【初始化为一个不可能的较大值】
left = right = 0 # 滑动窗口的左右边界
for right in range(n):
# 窗口右边界右移一位
ch = s[right] # 窗口中新加入的字符
if ch in cnt: # 新加入的字符位于t中
if cnt[ch] > 0: # 对当前字符ch还有需求
need -= 1 # 此时新加入窗口中的ch对need有影响
cnt[ch] -= 1
# 窗口左边界持续右移
while need == 0: # need=0,当前窗口完全覆盖了t
if right - left + 1 < min_len: # 出现了更短的子串
min_len = right - left + 1
start, end = left, right
ch = s[left] # 窗口中要滑出的字符
if ch in cnt: # 刚滑出的字符位于t中
if cnt[ch] >= 0: # 对当前字符ch还有需求,或刚好无需求(其实此时只有=0的情况)
need += 1 # 此时滑出窗口的ch会对need有影响
cnt[ch] += 1
left += 1 # 窗口左边界+1
return s[start: end+1]
#解法二:哈希表、字符串、滑动窗口
class Solution:
def minWindow(self, s: str, t: str) -> str:
cnt = Counter(t)
ans_l = -1
ans_r = len(s)
l = 0
count = len(cnt)
for r, c in enumerate(s):
if c in cnt:
cnt[c] -= 1
if cnt[c] == 0:
count -= 1
while count == 0:
if ans_r - ans_l > r - l:
ans_l, ans_r = l, r
if s[l] in cnt:
if cnt[s[l]] == 0:
count += 1
cnt[s[l]] += 1
l += 1
return "" if ans_l == -1 else s[ans_l: ans_r+1]
#解法三:滑动窗口
#方法一
# 请选择 Python3 提交代码,而不是 Python
class Solution:
def minWindow(self, s: str, t: str) -> str:
cnt_s = Counter() # s 子串字母的出现次数
cnt_t = Counter(t) # t 中字母的出现次数
ans_left, ans_right = -1, len(s)
left = 0
for right, c in enumerate(s): # 移动子串右端点
cnt_s[c] += 1 # 右端点字母移入子串
while cnt_s >= cnt_t: # 涵盖
if right - left < ans_right - ans_left: # 找到更短的子串
ans_left, ans_right = left, right # 记录此时的左右端点
cnt_s[s[left]] -= 1 # 左端点字母移出子串
left += 1
return "" if ans_left < 0 else s[ans_left: ans_right + 1]
#优化法
class Solution:
def minWindow(self, s: str, t: str) -> str:
cnt = defaultdict(int) # 比 Counter 更快
for c in t:
cnt[c] += 1
less = len(cnt) # 有 less 种字母的出现次数 < t 中的字母出现次数
ans_left, ans_right = -1, len(s)
left = 0
for right, c in enumerate(s): # 移动子串右端点
cnt[c] -= 1 # 右端点字母移入子串
if cnt[c] == 0:
# 原来窗口内 c 的出现次数比 t 的少,现在一样多
less -= 1
while less == 0: # 涵盖:所有字母的出现次数都是 >=
if right - left < ans_right - ans_left: # 找到更短的子串
ans_left, ans_right = left, right # 记录此时的左右端点
x = s[left] # 左端点字母
if cnt[x] == 0:
# x 移出窗口之前,检查出现次数,
# 如果窗口内 x 的出现次数和 t 一样,
# 那么 x 移出窗口后,窗口内 x 的出现次数比 t 的少
less += 1
cnt[x] += 1 # 左端点字母移出子串
left += 1
return "" if ans_left < 0 else s[ans_left: ans_right + 1]