1761 字
9 分钟
3月23日刷题笔记

本篇题目#

#415字符串相加
Easy
#54螺旋矩阵
Medium
#5最长回文子串
Medium

一、415. 字符串相加#

#415字符串相加
Easy

思路: 模拟竖式加法,从个位开始往高位算,每一位相加后记录进位。

  1. 对齐个位,从右往左:用 ij 两个指针分别指向两个字符串末尾,每轮往左移一位
  2. 每一位做三件事:取出当前位的数字 → 加起来(含进位)→ 本位写入结果,进位留给下一轮
  3. 循环结束检查进位:如果最后还有进位(比如 999+1),在结果最前面补一个 "1"
class Solution:
def addStrings(self, num1: str, num2: str) -> str:
res = ""
i, j, carry = len(num1) - 1, len(num2) - 1, 0
# 只要其中一个指针还没有跳到边界外就继续执行这个循环
while i >= 0 or j >= 0:
n1 = int(num1[i]) if i >= 0 else 0
n2 = int(num2[j]) if j >= 0 else 0
tmp = n1 + n2 + carry # 实际加起来得到的值
carry = tmp // 10 # 用 // 得到要进位的数字,比如 9+5=14 写 4 进 1
res = str(tmp % 10) + res # tmp%10 是实际写在竖式下面的值
i -= 1
j -= 1
# 如果两个指针都走完了且 carry 不等于 0,在最前面补 "1"
return '1' + res if carry else res
CAUTION

注意点

  • ij 从末尾往左移,模拟从个位开始的竖式加法
  • tmp // 10 取进位,tmp % 10 取本位
  • 新结果拼在 res 前面,因为是从低位往高位算的
  • if carry 等价于 if carry != 0,数字 0 在 Python 中为 False
TIP

//% 常用技巧

a = (a // b) * b + (a % b) # 验算公式
n // 10 # 去掉个位
n % 10 # 取个位
n % 2 # 判断奇偶(0偶1奇)
i % len(arr) # 循环索引,超出边界自动绕回

二、54. 螺旋矩阵#

#54螺旋矩阵
Medium

思路:topbottomleftright 四条边界标记还没读过的区域,顺时针依次读完每条边后把对应边界往内收缩一格,不断循环直到区域为空。

class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix or not matrix[0]:
return []
res = []
# 初始化四个边界指针
top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1
while top <= bottom and left <= right:
# 1. 上边:从左到右
for j in range(left, right + 1):
res.append(matrix[top][j])
top += 1
# 2. 右边:从上到下
for i in range(top, bottom + 1):
res.append(matrix[i][right])
right -= 1
# 3. 下边:从右到左(需要判断 top <= bottom,防止重复读)
if top <= bottom:
for j in range(right, left - 1, -1):
res.append(matrix[bottom][j])
bottom -= 1
# 4. 左边:从下到上(需要判断 left <= right,防止重复读)
if left <= right:
for i in range(bottom, top - 1, -1):
res.append(matrix[i][left])
left += 1
return res
CAUTION

注意点

  • 读完上边和右边后,topright 已经改变,while 不会在中途重新检查,所以读下边和左边前需要手动 if 判断,防止单行或单列时重复读
  • 反向循环 range(right, left-1, -1) 中 stop 写 left-1,是因为 range 取不到 stop,多减一才能让 left 本身被包含
  • 四个方向:上边 j 增,右边 i 增,下边 j 减,左边 i

三、5. 最长回文子串#

#5最长回文子串
Medium

思路一:中心扩展 O(n²)#

以每个字符为中心向两边扩展,奇偶回文分开处理。

class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
ans_left = ans_right = 0
for i in range(n):
# 设置两个指针
# 左指针向左走 右指针向右走
l = r = i
# 只要左右指针没越界,同时左指针指向的元素等于右指针指向元素的时候就循环
while l >= 0 and r < n and s[l] == s[r]:
l -= 1
r += 1
# 注意这时候跳出循环了,证明左右指针现在指向的两个元素不相同了
# 那么现在这两个指针的上一个位置形成的区间就是一个回文串
# 所以左指针 l 要 +1,右指针 r 要 -1,然后判断回文串是否比之前记录的长
# 当前回文的长度是 (r-1) - (l+1) + 1 = r - l - 1
if r - l - 1 > ans_right - ans_left:
ans_left, ans_right = l + 1, r
# 这里要n-1 因为r是从i+1开始的
for i in range(n - 1): # 偶回文
l, r = i, i + 1
while l >= 0 and r < n and s[l] == s[r]:
l -= 1
r += 1
# 当前回文的长度是 (r-1) - (l+1) + 1 = r - l - 1
if r - l - 1 > ans_right - ans_left:
ans_left, ans_right = l + 1, r
return s[ans_left: ans_right]
TIP

循环结束时 lr 各多走了一步,真正的回文是 s[l+1..r-1],长度为 r - l - 1。用左闭右开区间 [l+1, r) 存储,最后 s[ans_left:ans_right] 直接切片。

奇回文示例:"babad" 为例,i=2,中心是 'b'

扩展过程:
l=2, r=2 → s[2]='b' == s[2]='b' ✓ 继续
l=1, r=3 → s[1]='a' == s[3]='a' ✓ 继续
l=0, r=4 → s[0]='b' != s[4]='d' ✗ 停止
停止后:l=0, r=4
b a b a d
↑ ↑
l r ← 这两个已经不匹配了,各多走了一步
真正的回文是 l+1 到 r-1:
b a b a d
↑ ↑
l+1 r-1 → "aba"

偶回文示例: s = "abba"n=4

i=0: l=0,r=1 s[0]='a' != s[1]='b' 扩展失败,长度 2
i=1: l=1,r=2 s[1]='b' == s[2]='b' 继续扩展
l=0,r=3 s[0]='a' == s[3]='a' 继续扩展
l=-1,r=4 越界,停止
长度 = r - l - 1 = 4 - (-1) - 1 = 4 → "abba"
i=2: l=2,r=3 s[2]='b' != s[3]='a' 扩展失败,长度 2

思路二:Manacher 算法 O(n)#

利用回文的对称性,避免重复计算,把时间复杂度从 O(n²) 降到 O(n)。

核心步骤:

  1. 插入 # 消除奇偶差异:s = "bab"t = "^#b#a#b#$",所有回文都变成奇回文
  2. half_len[i] 记录以 t[i] 为中心的最长回文半径
  3. box_m(当前右边界最远的回文中心)和 box_r(其右边界)复用已知结果
  4. 每次扩展都会让 box_r 右移,总扩展次数为 O(n)
IMPORTANT

Manacher 时间复杂度为 O(n) 的原因

box_r 只会单调右移,每次 while 扩展都让 box_r 增加,所以整个算法的总扩展次数不超过 len(t),即 O(n)。利用镜像对称性跳过已知结果是关键。

class Solution:
def longestPalindrome(self, s: str) -> str:
# 第一步:改造字符串,消除奇偶差异
# 在每个字符之间插入 #,首尾加哨兵 ^ 和 $
# 例:s = "bab" → t = "^#b#a#b#$"
t = "#".join("^" + s + "$")
# 第二步:初始化 half_len 数组
# half_len[i] = 以 t[i] 为中心的最长回文的半径
half_len = [0] * (len(t) - 2)
half_len[1] = 1
# 第三步:初始化 box
# box_m = 右边界最靠右的回文中心
# box_r = 该回文的右边界(不含)
box_m = box_r = max_i = 0
# 第四步:遍历每个位置,计算 half_len[i]
for i in range(2, len(half_len)):
hl = 1
if i < box_r:
# i 在 box 内,利用镜像对称性初始化
# i' = box_m * 2 - i(i 关于 box_m 的镜像)
hl = min(half_len[box_m * 2 - i], box_r - i)
# 暴力扩展,哨兵保证不越界
while t[i - hl] == t[i + hl]:
hl += 1
box_m, box_r = i, i + hl
half_len[i] = hl
if hl > half_len[max_i]:
max_i = i
# 第五步:还原回 s 的下标
hl = half_len[max_i]
return s[(max_i - hl) // 2 : (max_i + hl) // 2 - 1]
3月23日刷题笔记
https://www.yyylegend.com/posts/3月23日刷题笔记/
作者
YYYLEGEND
发布于
2026-03-23
许可协议
CC BY-NC-SA 4.0