更新时间:2025-03-29
- LeetCode题解专栏:实战算法解题 (专栏)
- 技术博客总目录:计算机技术系列目录页
优先整理热门100及面试150,不定期持续更新,欢迎关注!
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)
。
声明
- 本文版权归
CSDN
用户Allen Wurlitzer
所有,遵循CC-BY-SA
协议发布,转载请注明出处。- 本文题目来源
力扣-LeetCode
,著作权归领扣网络
所有。商业转载请联系官方授权,非商业转载请注明出处。