更新时间:2025-04-04
- 算法题解目录汇总:算法刷题记录——题解目录汇总
- 技术博客总目录:计算机技术系列博客——目录页
11. 盛最多水的容器
给定一个长度为 n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
。
找出其中的两条线,使得它们与 x 轴
共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入:height = [1,1]
输出:1
提示:
n == height.length
2 <= n <= 10^5
0 <= height[i] <= 10^4
方法:双指针法
- 初始化指针:左指针
left
指向数组起始位置,右指针right
指向数组末尾。 - 计算当前容量:每次计算左右指针形成的容器面积,即
min(height[left], height[right]) * (right - left)
。 - 移动指针策略:移动高度较小的指针,以尝试寻找更大的高度,从而可能获得更大的容量。
- 更新最大容量:在每次循环中更新记录的最大容量。
代码实现(Java):
public int maxArea(int[] height) {
int left = 0;
int right = height.length - 1;
int maxArea = 0;
while (left < right) {
int currentHeight = Math.min(height[left], height[right]);
int currentWidth = right - left;
maxArea = Math.max(maxArea, currentHeight * currentWidth);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxArea;
}
复杂度分析:
- 时间复杂度:
O(n)
,仅需一次遍历数组。 - 空间复杂度:
O(1)
,仅使用常数级别的额外空间。
17. 电话号码的字母组合
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下,与电话按键相同。注意 1
不对应任何字母。
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
示例 3:
输入:digits = "2"
输出:["a","b","c"]
提示:
0 <= digits.length <= 4
digits[i] 是范围 ['2', '9'] 的一个数字
方法一:回溯法
通过递归生成所有可能的字母组合,每次递归选择一个字母加入当前路径,处理下一个数字。
代码实现(Java):
public class Solution {
public List<String> letterCombinations(String digits) {
List<String> result = new ArrayList<>();
if (digits == null || digits.isEmpty()) return result;
String[] mapping = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
backtrack(result, new StringBuilder(), digits, 0, mapping);
return result;
}
private void backtrack(List<String> result, StringBuilder current, String digits, int index, String[] mapping) {
if (index == digits.length()) {
result.add(current.toString());
return;
}
char digit = digits.charAt(index);
String letters = mapping[digit - '2']; // 根据数字获取对应字母集合
for (char c : letters.toCharArray()) {
current.append(c); // 添加当前字母
backtrack(result, current, digits, index + 1, mapping); // 递归处理下一个数字
current.deleteCharAt(current.length() - 1); // 回溯,删除最后添加的字母
}
}
}
复杂度分析:
- 时间复杂度:
O(3^m*4^n)
,其中m
是输入中对应 3 个字母的数字个数,n 是 4 个字母的数字个数。 - 空间复杂度:
O(k)
,k
为结果数量,递归栈深度最大为输入长度。
方法二:迭代法(队列)
通过逐步扩展现有组合生成所有可能结果,利用队列管理中间过程。
代码实现(Java):
public class Solution {
public List<String> letterCombinations(String digits) {
List<String> result = new ArrayList<>();
if (digits == null || digits.isEmpty()) return result;
String[] mapping = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
Queue<String> queue = new LinkedList<>();
queue.offer(""); // 初始空字符串
for (int i = 0; i < digits.length(); i++) {
char digit = digits.charAt(i);
String letters = mapping[digit - '2'];
int size = queue.size();
for (int j = 0; j < size; j++) {
String s = queue.poll();
for (char c : letters.toCharArray()) {
queue.offer(s + c); // 生成新组合并入队
}
}
}
result.addAll(queue);
return result;
}
}
复杂度分析:
- 时间复杂度:
O(3^m*4^n)
,每个数字的处理需要遍历当前队列中所有元素。 - 空间复杂度:
O(3^m*4^n)
,队列存储所有中间组合。
方法三:迭代法(逐步构造)
直接通过遍历每个数字并扩展现有结果,生成所有组合。
代码实现(Java):
public class Solution {
public List<String> letterCombinations(String digits) {
List<String> result = new ArrayList<>();
if (digits == null || digits.isEmpty()) return result;
String[] mapping = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
result.add(""); // 初始空字符串
for (char digit : digits.toCharArray()) {
List<String> temp = new ArrayList<>();
String letters = mapping[digit - '2'];
for (String s : result) {
for (char c : letters.toCharArray()) {
temp.add(s + c); // 将当前字母与现有字符串拼接
}
}
result = temp; // 更新结果
}
return result;
}
}
复杂度分析:
- 时间复杂度:
O(3^m*4^n)
,每次迭代生成新的组合。 - 空间复杂度:
O(3^m*4^n)
,存储所有中间结果。
19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
提示:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
方法一:双指针法(一次遍历)
使用快慢指针技巧,让快指针先移动n步,然后同时移动快慢指针。当快指针到达链表末尾时,慢指针正好指向要删除节点的前驱节点。通过哑节点简化边界条件处理。
- 初始化哑节点:避免处理头节点删除的特殊情况。
- 快指针先移动n步:拉开快慢指针的间距。
- 同步移动快慢指针:直到快指针到达链表末尾。
- 删除目标节点:修改慢指针的next指针跳过目标节点。
代码实现(Java):
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy, slow = dummy;
// 快指针先移动n步
for (int i = 0; i < n; i++) {
fast = fast.next;
}
// 同步移动,直到快指针到达末尾
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
// 删除目标节点
slow.next = slow.next.next;
return dummy.next;
}
}
复杂度分析:
- 时间复杂度:
O(L)
,其中L
为链表长度,只需一次遍历。 - 空间复杂度:
O(1)
,仅使用固定额外空间。
方法二:计算链表长度(两次遍历)
先遍历链表获取长度 L
,再定位到倒数第n
个节点的前驱位置(正数第 L-n
个节点),直接删除目标节点。
- 获取链表长度:遍历链表统计节点总数。
- 定位前驱节点:通过长度计算前驱位置,移动到该位置。
- 删除目标节点:修改前驱节点的next指针。
代码实现(Java):
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
int length = getLength(head);
ListNode curr = dummy;
// 移动到前驱节点位置
for (int i = 0; i < length - n; i++) {
curr = curr.next;
}
// 删除目标节点
curr.next = curr.next.next;
return dummy.next;
}
// 辅助函数:计算链表长度
private int getLength(ListNode head) {
int len = 0;
while (head != null) {
len++;
head = head.next;
}
return len;
}
}
复杂度分析:
- 时间复杂度:
O(L)
,两次遍历,总体仍为线性时间。 - 空间复杂度:
O(1)
,仅使用固定额外空间。
20. 有效的括号
给定一个只包括 '(',')'
,'{','}'
,'[',']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
示例 4:
输入:s = "([])"
输出:true
提示:
1 <= s.length <= 10^4
s 仅由括号 '()[]{}' 组成
方法:栈辅助法
利用栈结构匹配括号,通过遍历字符串处理括号闭合关系。
- 提前剪枝:字符串长度为奇数时直接返回false,因为有效括号必须成对出现
- 栈结构操作:
- 遇到左括号时压入栈顶。
- 遇到右括号时弹出栈顶元素,检查是否匹配。
- 三种失效情况处理:
- 右括号出现时栈为空(无对应左括号)。
- 右括号与栈顶左括号类型不匹配。
- 遍历结束后栈中仍有未匹配左括号。
代码实现(Java):
class Solution {
public boolean isValid(String s) {
if (s.length() % 2 != 0) return false; // 奇数长度直接无效
Deque<Character> stack = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') { // 左括号入栈
stack.push(c);
} else { // 右括号匹配
if (stack.isEmpty()) return false; // 栈空说明无对应左括号
char top = stack.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
return false;
}
}
}
return stack.isEmpty(); // 最后检查栈是否为空
}
}
复杂度分析:
- 时间复杂度: 单次遍历字符串
O(n)
,栈操作O(1)
,总时间复杂度O(n)
。
声明
- 本文版权归
优快云
用户Allen Wurlitzer
所有,遵循CC-BY-SA
协议发布,转载请注明出处。- 本文题目来源
力扣-LeetCode
,著作权归领扣网络
所有。商业转载请联系官方授权,非商业转载请注明出处。