42、接雨水
链接:42、接雨水
题目描述:
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
方法 1
# 方法1:前后缀分解
class Solution:
def trap(self, height: List[int]) -> int:
n = len(height)
pre_max = [0] * n # pre_max[i] 表示从 height[0] 到 height[i] 的最大值
pre_max[0] = height[0]
for i in range(1, n):
pre_max[i] = max(pre_max[i - 1], height[i])
suf_max = [0] * n # suf_max[i] 表示从 height[i] 到 height[n-1] 的最大值
suf_max[-1] = height[-1]
for i in range(n - 2, -1, -1):
suf_max[i] = max(suf_max[i + 1], height[i])
ans = 0
for h, pre, suf in zip(height, pre_max, suf_max):
ans += min(pre, suf) - h # 累加每个水桶能接多少水
return ans
代码逻辑
- 初始化变量:
- n:柱子的数量。
- pre_max:一个数组,pre_max[i] 表示从 height[0] 到 height[i] 的最大值。
- suf_max:一个数组,suf_max[i] 表示从 height[i] 到 height[n-1] 的最大值。
- ans:用于存储最终结果,即总共能接住的雨水量。
- 计算 pre_max 数组:
- pre_max[0] 初始化为 height[0]。
- 从左到右遍历数组,对于每个位置 i,pre_max[i] 等于 pre_max[i-1] 和 height[i] 中的较大值。
- 计算 suf_max 数组:
- suf_max[-1] 初始化为 height[-1]。
- 从右到左遍历数组,对于每个位置 i,suf_max[i] 等于 suf_max[i+1] 和 height[i] 中的较大值。
- 计算总共能接住的雨水量:
- 遍历 height 数组,对于每个位置 i,能接住的雨水量等于 min(pre_max[i], suf_max[i]) - height[i]。
- 将每个位置能接住的雨水量累加到 ans 中。
- 返回结果:
- 返回 ans,即总共能接住的雨水量。
复杂度分析
- 时间复杂度:O(n),因为我们遍历了数组三次。
- 空间复杂度:O(n),因为我们使用了两个额外的数组 pre_max 和 suf_max。
方法 2
# 方法 2:相向双指针
class Solution:
def trap(self, height: List[int]) -> int:
ans = left = pre_max = suf_max = 0
right = len(height) - 1
while left < right:
pre_max = max(pre_max, height[left])
suf_max = max(suf_max, height[right])
if pre_max < suf_max:
ans += pre_max - height[left]
left += 1
else:
ans += suf_max - height[right]
right -= 1
return ans
代码逻辑
- 初始化变量:
- ans:用于存储最终结果,即总共能接住的雨水量。
- left:左指针,初始值为 0。
- right:右指针,初始值为 len(height) - 1。
- pre_max:左侧的最大高度,初始值为 0。
- suf_max:右侧的最大高度,初始值为 0。
- 双指针遍历:
- 使用 while left < right 循环,直到左指针和右指针相遇。
- 在每次循环中,更新 pre_max 和 suf_max:
- pre_max:左侧的最大高度,等于 pre_max 和 height[left] 中的较大值。
- suf_max:右侧的最大高度,等于 suf_max 和 height[right] 中的较大值。
- 比较 pre_max 和 suf_max:
- 如果 pre_max 小于 suf_max,说明左侧的最大高度决定了当前能接住的雨水量:
- ans 增加 pre_max - height[left]。
- 左指针右移 (left += 1)。
- 否则,说明右侧的最大高度决定了当前能接住的雨水量:
- ans 增加 suf_max - height[right]。
- 右指针左移 (right -= 1)。
- 如果 pre_max 小于 suf_max,说明左侧的最大高度决定了当前能接住的雨水量:
- 返回结果:
- 返回 ans,即总共能接住的雨水量。
示例
- 假设输入 height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
- 双指针遍历过程:
- 初始状态:left = 0, right = 11, pre_max = 0, suf_max = 0, ans = 0
- 第一次循环:pre_max = 0, suf_max = 1, ans = 0, right = 10
- 第二次循环:pre_max = 0, suf_max = 2, ans = 0, left = 1
- 第三次循环:pre_max = 1, suf_max = 2, ans = 0, left = 2
- 第四次循环:pre_max = 1, suf_max = 2, ans = 1, left = 3
- 第五次循环:pre_max = 2, suf_max = 2, ans = 1, right = 9
- 第六次循环:pre_max = 2, suf_max = 2, ans = 2, right = 8
- 第七次循环:pre_max = 2, suf_max = 2, ans = 2, right = 7
- 第八次循环:pre_max = 2, suf_max = 3, ans = 2, left = 4
- 第九次循环:pre_max = 2, suf_max = 3, ans = 3, left = 5
- 第十次循环:pre_max = 2, suf_max = 3, ans = 5, left = 6
- 第十一循环:pre_max = 2, suf_max = 3, ans = 6, left = 7
最终结果为 6,即总共能接住 6 个单位的雨水。
复杂度分析
- 时间复杂度:O(n),因为我们只遍历了数组一次。
- 空间复杂度:O(1),因为我们只使用了常数空间的变量。
方法 3
class Solution:
def trap(self, height: List[int]) -> int:
ans = 0
st = []
for i, h in enumerate(height):
while st and h >= height[st[-1]]:
bottom_h = height[st.pop()]
if not st: # len(st) == 0
break
left = st[-1]
dh = min(height[left], h) - bottom_h # 面积的高
ans += dh * (i - left - 1)
st.append(i)
return ans
代码逻辑
- 初始化变量:
- ans:用于存储最终结果,即总共能接住的雨水量。
- st:一个栈,用于存储柱子的索引。
- 遍历数组:
- 使用 for i, h in enumerate(height) 遍历数组,i 是当前柱子的索引,h 是当前柱子的高度。
- 在每次循环中,检查栈顶元素对应的柱子高度是否小于等于当前柱子的高度:
- 如果是,则弹出栈顶元素,计算能接住的雨水量。
- bottom_h:弹出栈顶元素对应的柱子高度。
- 如果栈为空,说明没有左边界,跳出循环。
- left:栈顶元素的索引,表示左边界。
- dh:当前能接住的雨水的高度,等于 min(height[left], h) - bottom_h。
- ans 增加 dh * (i - left - 1),即当前能接住的雨水量。
- 将当前柱子的索引压入栈中。
- 返回结果:
- 返回 ans,即总共能接住的雨水量。
示例
- 假设输入 height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
- 单调栈遍历过程:
- 初始状态:ans = 0, st = []
- 第一次循环:i = 0, h = 0, st = [0]
- 第二次循环:i = 1, h = 1, st = [0, 1]
- 第三次循环:i = 2, h = 0, st = [0, 1, 2]
- 第四次循环:i = 3, h = 2
- 弹出 2,bottom_h = 0
- 弹出 1,bottom_h = 1
- left = 0, dh = min(0, 2) - 1 = 1
- ans += 1 * (3 - 0 - 1) = 2
- st = [0, 3]
- 第五次循环:i = 4, h = 1, st = [0, 3, 4]
- 第六次循环:i = 5, h = 0, st = [0, 3, 4, 5]
- 第七次循环:i = 6, h = 1
- 弹出 5,bottom_h = 0
- 弹出 4,bottom_h = 1
- left = 3, dh = min(2, 1) - 1 = 1
- ans += 1 * (6 - 3 - 1) = 1
- st = [0, 3, 6]
- 第八次循环:i = 7, h = 3
- 弹出 6,bottom_h = 1
- 弹出 3,bottom_h = 2
- left = 0, dh = min(0, 3) - 2 = 2
- ans += 2 * (7 - 0 - 1) = 12
- st = [0, 7]
- 第九次循环:i = 8, h = 2, st = [0, 7, 8]
- 第十次循环:i = 9, h = 1, st = [0, 7, 8, 9]
- 第十一循环:i = 10, h = 2
- 弹出 9,bottom_h = 1
- 弹出 8,bottom_h = 2
- left = 7, dh = min(3, 2) - 2 = 1
- ans += 1 * (10 - 7 - 1) = 2
- st = [0, 7, 10]
- 第十二循环:i = 11, h = 1, st = [0, 7, 10, 11]
最终结果为 6,即总共能接住 6 个单位的雨水。
复杂度分析
- 时间复杂度:O(n),因为我们只遍历了数组一次,并且每个元素最多只会被压入和弹出栈一次。
- 空间复杂度:O(n),因为在最坏情况下,栈中可能会存储所有的柱子索引。
13、罗马数字转整数
链接:13、罗马数字转整数
题目描述:
罗马数字包含以下七种字符: I
, V
, X
, L
,C
,D
和 M
。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2
写做 II
,即为两个并列的 1
。12
写做 XⅡ
,即为 X
+ II
。 27
写做 XⅤⅠ
, 即为 XX
+ Ⅴ
+ II
。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4
不写做IIII
,而是 IV
。数字 1
在数字 5
的左边,所表示的数等于大数 5
减小数 1
得到的数值 4
。同样地,数字 9
表示为IX
。这个特殊的规则只适用于以下六种情况:
I
可以放在V
(5) 和X
(10) 的左边,来表示 4 和 9。X
可以放在L
(50) 和C
(100) 的左边,来表示 40 和 90。C
可以放在D
(500) 和M
(1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。
示例 1:
输入: s = “III
”
输出: 3
示例 2:
输入: s = “IV
”
输出: 4
示例 3:
输入: s = “IX
”
输出: 9
示例 4:
输入: s = “LVIII
”
输出: 58
解释: L
= 50, V
= 5, III
= 3.
示例 5:
输入: s = “MCMXCIV
”
输出: 1994
解释: M
= 1000, CM
= 900, XC
= 90, IV
= 4.
方法 1
class Solution:
def romanToInt(self, s: str) -> int:
# 创建一个字典,将罗马数字字符映射到对应的整数值
Roman2Int = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
# 初始化结果变量Int为0
Int = 0
# 获取字符串s的长度
n = len(s)
# 遍历字符串的每个字符,除了最后一个字符
for i in range(n - 1):
# 如果当前字符的值小于下一个字符的值
if Roman2Int[s[i]] < Roman2Int[s[i + 1]]:
# 减去当前字符的值
Int -= Roman2Int[s[i]]
else:
# 否则,加上当前字符的值
Int += Roman2Int[s[i]]
# 最后加上字符串的最后一个字符的值
return Int + Roman2Int[s[-1]]
12、整数转罗马数字
链接:
题目描述:
七个不同的符号代表罗马数字,其值如下:
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则:
- 如果该值不是以 4 或 9 开头,请选择可以从输入中减去的最大值的符号,将该符号附加到结果,减去其值,然后将其余部分转换为罗马数字。
- 如果该值以 4 或 9 开头,使用 减法形式,表示从以下符号中减去一个符号,例如 4 是 5 (
V
) 减 1 (I
):IV
,9 是 10 (X
) 减 1 (I
):IX
。仅使用以下减法形式:4 (IV
),9 (IX
),40 (XL
),90 (XC
),400 (CD
) 和 900 (CM
)。 - 只有 10 的次方(
I
,X
,C
,M
)最多可以连续附加 3 次以代表 10 的倍数。你不能多次附加 5 (V
),50 (L
) 或 500 (D
)。如果需要将符号附加4次,请使用 减法形式。
给定一个整数,将其转换为罗马数字。
示例 1:
输入:num = 3749
输出: “MMMDCCXLIX”
解释:
3000 = MMM 由于 1000 (M) + 1000 (M) + 1000 (M)
700 = DCC 由于 500 (D) + 100 © + 100 ©
40 = XL 由于 50 (L) 减 10 (X)
9 = IX 由于 10 (X) 减 1 (I)
注意:49 不是 50 (L) 减 1 (I) 因为转换是基于小数位
示例 2:
输入:num = 58
输出:“LVIII”
解释:
50 = L
8 = VIII
示例 3:
输入:num = 1994
输出:“MCMXCIV”
解释:
1000 = M
900 = CM
90 = XC
4 = IV
方法
R = (
("", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), # 个位
("", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), # 十位
("", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), # 百位
("", "M", "MM", "MMM"), # 千位
)
class Solution:
def intToRoman(self, num: int) -> str:
return R[3][num // 1000] + R[2][num // 100 % 10] + R[1][num // 10 % 10] + R[0][num % 10]
详细步骤
- 定义元组 R:R 是一个包含四个子元组的元组,每个子元组分别对应个位、十位、百位和千位的罗马数字表示。
- 第一个子元组表示个位的罗马数字。
- 第二个子元组表示十位的罗马数字。
- 第三个子元组表示百位的罗马数字。
- 第四个子元组表示千位的罗马数字。
- 转换逻辑:
- num // 1000 获取千位的数字,并从 R[3] 中获取对应的罗马数字。
- num // 100 % 10 获取百位的数字,并从 R[2] 中获取对应的罗马数字。
- num // 10 % 10 获取十位的数字,并从 R[1] 中获取对应的罗马数字。
- num % 10 获取个位的数字,并从 R[0] 中获取对应的罗马数字。
- 返回结果:将各个位的罗马数字拼接起来,形成最终的罗马数字表示。
复杂度分析
- 时间复杂度:O(1)。
- 空间复杂度:O(1)。
58、最后一个单词的长度
给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。
单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。
示例 1:
输入:s = “Hello World”
输出:5
解释:最后一个单词是“World”,长度为 5。
方法 1
class Solution:
def lengthOfLastWord(self, s: str) -> int:
# 初始化长度变量为0
length = 0
# 清除字符串首尾的空格
s = s.strip()
# 从字符串的末尾向前遍历
for i in range(len(s) - 1, -1, -1):
# 如果遇到空格,返回当前长度
if s[i] == ' ':
return length
else:
# 否则,长度加1
length += 1
# 如果遍历完字符串都没有遇到空格,返回当前长度
return length
详细步骤
- 初始化长度变量:
- length = 0,初始化一个变量length为0,用于存储最后一个单词的长度。
- 清除字符串首尾的空格:
- s = s.strip(),使用strip()方法去除字符串首尾的空格,以确保我们只处理实际的单词部分。
- 从字符串的末尾向前遍历:
- for i in range(len(s) - 1, -1, -1): 使用一个循环从字符串的末尾向前遍历。range(len(s) - 1, -1, -1)表示从len(s) - 1(字符串的最后一个字符)到0(字符串的第一个字符)进行遍历。
- 判断当前字符是否为空格:
- 如果当前字符是空格,说明我们已经找到了最后一个单词的结束位置,返回当前的length。
- 否则,将length加1,继续向前遍历。
- 返回最后一个单词的长度
复杂度分析:
- 时间复杂度:O(m + n)
- 空间复杂度:O(m), word_dict 的复杂度是 O(m)。
方法 2
class Solution:
def lengthOfLastWord(self, s: str) -> int:
return len(s.rstrip().split(" ")[-1])
思路
- 先去掉字符串的最后空格
- 将字符串按照空格分组
- 取分组后的最后一项
- 计算它的长度