一、盛最多水的容器
这是一个寻找容器最大存水量的问题,涉及对数组中垂直线条的计算,找到形成最大面积的两条线。
1、数学分析
问题定义
- 给定数组
height
,每个元素表示垂直线的高度。 - 选取两条线
i
和j
,以min(height[i], height[j])
为高度,j - i
为宽度,计算形成的容器面积:
[ {Area} = {min}(height[i], height[j]) times (j - i) ]
- 找出最大面积。
解法
- 暴力解法:枚举所有可能的线对,计算面积,找到最大值。时间复杂度为 (O(n^2)),不符合效率要求。
- 双指针法:通过左右指针逐步收敛,动态优化面积。时间复杂度为 (O(n))。
2、解题思路
- 初始化双指针
left
和right
指向数组两端。 - 计算当前面积,并与最大面积
max_area
比较。 - 移动指针:始终移动较短线对应的指针,以尝试增大面积。
- 重复步骤 2 和 3,直至
left >= right
。
3、C++ 实现
#include <iostream>
#include <vector>
using namespace std;
int maxArea(vector<int>& height) {
int left = 0, right = height.size() - 1;
int max_area = 0;
while (left < right) {
// 计算当前面积
int current_area = min(height[left], height[right]) * (right - left);
max_area = max(max_area, current_area);
// 移动较短线的指针
if (height[left] < height[right]) {
++left;
} else {
--right;
}
}
return max_area;
}
4、Python 实现
from typing import List
def maxArea(height: List[int]) -> int:
left, right = 0, len(height) - 1
max_area = 0
while left < right:
# 计算当前面积
current_area = min(height[left], height[right]) * (right - left)
max_area = max(max_area, current_area)
# 移动较短线的指针
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_area
5、复杂度分析
5.1、时间复杂度
- O(n):双指针每次移动一步,共遍历整个数组一次。
5.2、空间复杂度
- O(1):仅使用了常数额外空间。
二、整数转罗马数字
这是一个整数转罗马数字的问题,需要根据整数的值逐步匹配符号并生成对应的罗马数字字符串。
1、数学分析
罗马数字构造规则
- 使用预定义的罗马数字符号和值:
- I (1), V (5), X (10), L (50), C (100), D (500), M (1000)
- 使用减法形式处理特定值:
- 4 → IV, 9 → IX
- 40 → XL, 90 → XC
- 400 → CD, 900 → CM
- 按照从大到小的顺序匹配罗马数字。
解法
- 创建符号与值的映射表。
- 从最大值开始,依次匹配整数。
- 每次匹配后,减少当前数字,更新结果字符串。
- 重复直到数字为 0。
2、解题思路
- 准备映射表:
{values} = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
{symbols} = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
- 从高位开始遍历:
- 如果当前值可以被减去,将对应符号加入结果,并减去值。
- 重复直到数字小于当前值。
- 返回结果字符串。
3、C++ 实现
#include <iostream>
#include <vector>
#include <string>
using namespace std;
string intToRoman(int num) {
// 罗马数字的值和符号
vector<int> values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
vector<string> symbols = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
string result = "";
// 遍历每个值
for (int i = 0; i < values.size() && num > 0; ++i) {
while (num >= values[i]) {
result += symbols[i]; // 加入符号
num -= values[i]; // 减去值
}
}
return result;
}
4、Python 实现
def intToRoman(num: int) -> str:
# 罗马数字的值和符号
values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
symbols = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
result = ""
# 遍历每个值
for i in range(len(values)):
while num >= values[i]:
result += symbols[i] # 加入符号
num -= values[i] # 减去值
return result
5、复杂度分析
5.1、时间复杂度
- O(1):罗马数字最多包含 13 个符号。算法需要遍历固定的符号和值列表,对每个符号进行常数次操作。
5.2、空间复杂度
- O(1):仅使用了常数空间存储结果和符号表。
三、罗马数字转整数
这是一个罗马数字转整数的问题,需要根据罗马数字字符的规则依次解析并累加数值,同时注意处理特殊的减法规则。
1、数学分析
罗马数字解析规则
- 每个字符对应一个整数值:
- I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000
- 小数值在大数值左边时,表示减法:
- IV = 4, IX = 9
- XL = 40, XC = 90
- CD = 400, CM = 900
- 小数值在大数值右边时,表示加法。
解法
- 从左到右遍历字符串,根据规则逐步解析。
- 若当前字符代表的数值小于下一字符,使用减法;否则使用加法。
- 最终累加结果即为整数值。
2、解题思路
- 准备映射表:
{mapping} = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
- 遍历罗马数字字符串:
- 若当前字符对应值小于下一字符,累加负值。
- 否则累加正值。
- 返回累加结果。
3、C++ 实现
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int romanToInt(string s) {
unordered_map<char, int> mapping = {
{'I', 1}, {'V', 5}, {'X', 10},
{'L', 50}, {'C', 100}, {'D', 500}, {'M', 1000}
};
int result = 0;
for (int i = 0; i < s.size(); ++i) {
int current = mapping[s[i]];
int next = (i < s.size() - 1) ? mapping[s[i + 1]] : 0;
// 判断加法或减法
if (current < next) {
result -= current;
} else {
result += current;
}
}
return result;
}
4、Python 实现
def romanToInt(s: str) -> int:
mapping = {
'I': 1, 'V': 5, 'X': 10,
'L': 50, 'C': 100, 'D': 500, 'M': 1000
}
result = 0
for i in range(len(s)):
current = mapping[s[i]]
next_value = mapping[s[i + 1]] if i < len(s) - 1 else 0
# 判断加法或减法
if current < next_value:
result -= current
else:
result += current
return result
5、复杂度分析
5.1、时间复杂度
- O(n):其中 (n) 是罗马数字字符串的长度,每个字符处理一次。
5.2、空间复杂度
- O(1):仅使用了常量级的存储空间。
四、最长公共前缀
这是一个最长公共前缀的问题,需要在一个字符串数组中找到所有字符串开头的最长公共部分。
1、数学分析
问题特点
- 公共前缀是每个字符串开头的相同部分。
- 如果任意两个字符串的某个位置字符不同,则最长公共前缀到此为止。
解法
- 逐字符比较:
- 假设第一个字符串为基准,从左到右逐字符与其他字符串对应位置比较。
- 若发现不同或基准字符串已遍历完毕,则停止,当前已比较的部分即为最长公共前缀。
- 分治法:
- 将数组分成两部分,递归查找两部分的最长公共前缀。
- 合并两部分的公共前缀即为整体公共前缀。
- 排序法:
- 先对数组排序,最长公共前缀必定是第一个字符串与最后一个字符串的公共前缀。
- 比较两个字符串,逐字符找到公共部分。
2、解题思路
选择逐字符比较法实现:
- 如果数组为空,直接返回空字符串。
- 取第一个字符串为基准,与其他字符串逐一比较。
- 比较时,逐字符对比每个字符串的对应位置,若不同或到达末尾,则停止。
- 返回公共前缀部分。
3、C++ 实现
#include <iostream>
#include <vector>
#include <string>
using namespace std;
string longestCommonPrefix(vector<string>& strs) {
if (strs.empty()) return "";
// 以第一个字符串为基准
string prefix = strs[0];
for (int i = 1; i < strs.size(); ++i) {
int j = 0;
// 比较当前字符串与基准的公共部分
while (j < prefix.size() && j < strs[i].size() && prefix[j] == strs[i][j]) {
++j;
}
// 更新公共前缀
prefix = prefix.substr(0, j);
// 如果公共前缀为空,直接返回
if (prefix.empty()) return "";
}
return prefix;
}
4、Python 实现
def longestCommonPrefix(strs: list[str]) -> str:
if not strs:
return ""
# 以第一个字符串为基准
prefix = strs[0]
for i in range(1, len(strs)):
j = 0
# 比较当前字符串与基准的公共部分
while j < len(prefix) and j < len(strs[i]) and prefix[j] == strs[i][j]:
j += 1
# 更新公共前缀
prefix = prefix[:j]
# 如果公共前缀为空,直接返回
if not prefix:
return ""
return prefix
5、复杂度分析
5.1、时间复杂度
- O(S):其中 (S) 是所有字符串中字符总数。最坏情况下,需要对每个字符进行一次比较。
5.2、空间复杂度
- O(1):仅使用了常量空间存储临时变量。
五、三数之和
这是一个数组与双指针问题,需要在数组中寻找所有满足条件的三元组,同时避免重复的结果。
1、数学分析
题目解析
我们需要找到三元组 ([a, b, c]) 满足:
-
( i ≠ j ≠ k ) , 且 ( n u m s [ i ] + n u m s [ j ] + n u m s [ k ] = 0 ) (i \neq j \neq k),且 (nums[i] + nums[j] + nums[k] = 0) (i=j=k),且(nums[i]+nums[j]+nums[k]=0)
-
结果中不能有重复的三元组。
解法思路
3. 对数组进行排序,使得可以使用双指针法进行查找。
4. 对每个元素作为三元组的第一个元素,使用双指针找到剩余两个元素满足条件:
- 左指针指向当前元素右侧起点,右指针指向数组末尾。
- 如果当前三数之和为 0,则记录结果,同时跳过重复元素。
- 如果和小于 0,则移动左指针以增加和。
- 如果和大于 0,则移动右指针以减小和。
- 遍历完成后返回结果。
2、解题思路
- 对数组排序,时间复杂度为 ( O ( n log n ) ) (O(n \log n)) (O(nlogn))
- 遍历数组,以当前元素作为第一个数。
- 对于每个第一个数,使用双指针从剩余部分寻找两个数的和为目标值。
- 跳过重复元素,避免结果中有重复的三元组。
- 返回所有找到的三元组。
3、C++ 实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end()); // 排序
for (int i = 0; i < nums.size(); ++i) {
if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过重复元素
int target = -nums[i];
int left = i + 1, right = nums.size() - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) {
result.push_back({nums[i], nums[left], nums[right]});
// 跳过重复元素
while (left < right && nums[left] == nums[left + 1]) ++left;
while (left < right && nums[right] == nums[right - 1]) --right;
++left;
--right;
} else if (sum < target) {
++left;
} else {
--right;
}
}
}
return result;
}
4、Python 实现
def threeSum(nums: list[int]) -> list[list[int]]:
nums.sort() # 排序
result = []
for i in range(len(nums)):
if i > 0 and nums[i] == nums[i - 1]: # 跳过重复元素
continue
target = -nums[i]
left, right = i + 1, len(nums) - 1
while left < right:
total = nums[left] + nums[right]
if total == target:
result.append([nums[i], nums[left], nums[right]])
# 跳过重复元素
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
elif total < target:
left += 1
else:
right -= 1
return result
5、复杂度分析
5.1、时间复杂度
- 排序复杂度为 ( O ( n log n ) ) (O(n \log n)) (O(nlogn))
- 遍历数组时每个元素都使用双指针,复杂度为 (O(n^2))。
- 总时间复杂度为 (O(n^2))。
5.2、空间复杂度
- 排序使用 ( O ( log n ) ) (O(\log n)) (O(logn)) 的额外空间。
- 结果存储使用 (O(k)),其中 (k) 为结果中三元组的数量。
- 总空间复杂度为 (O(k))。
【注】:刚学会使用数学公式输入,前面的有空再调整,后续文章会注意数学公式输入。