当前位置: 首页 > news >正文

算法刷题记录——LeetCode篇(1.2) [第11~20题](持续更新)

更新时间: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步,然后同时移动快慢指针。当快指针到达链表末尾时,慢指针正好指向要删除节点的前驱节点。通过哑节点简化边界条件处理。

  1. 初始化哑节点:避免处理头节点删除的特殊情况。
  2. 快指针先移动n步:拉开快慢指针的间距。
  3. 同步移动快慢指针:直到快指针到达链表末尾。
  4. 删除目标节点:修改慢指针的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 个节点),直接删除目标节点。

  1. 获取链表长度:遍历链表统计节点总数。
  2. 定位前驱节点:通过长度计算前驱位置,移动到该位置。
  3. 删除目标节点:修改前驱节点的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 仅由括号 '()[]{}' 组成

方法:栈辅助法

利用栈结构匹配括号,通过遍历字符串处理括号闭合关系。

  1. 提前剪枝:字符串长度为奇数时直接返回false,因为有效括号必须成对出现
  2. 栈结构操作
    • 遇到左括号时压入栈顶。
    • 遇到右括号时弹出栈顶元素,检查是否匹配。
  3. 三种失效情况处理
    • 右括号出现时栈为空(无对应左括号)。
    • 右括号与栈顶左括号类型不匹配。
    • 遍历结束后栈中仍有未匹配左括号。

代码实现(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)

声明

  1. 本文版权归 CSDN 用户 Allen Wurlitzer 所有,遵循CC-BY-SA协议发布,转载请注明出处。
  2. 本文题目来源 力扣-LeetCode ,著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
http://www.dtcms.com/a/99146.html

相关文章:

  • Labview学习记录
  • 杂草YOLO系列数据集4000张
  • 【MySQL基础-16】MySQL DELETE语句:深入理解与应用实践
  • Ray AI - 概述、安装、入门
  • 【HTML 基础教程】HTML <head>
  • Java多线程与高并发专题——Condition 和 wait/notify的关系
  • python:模块
  • app整改报告怎么写?app整改方案分享
  • 液压式精密矫平机——精准掌控,重塑金属平整新高度
  • 【黑马点评】Redis解决集群的session共享问题
  • wait函数等待多个子进程
  • vue3对比vue2新增特性
  • CSS 边框(Border)样式详解
  • 泛目录优化:无极泛目录优化网站,技术解析与风险控制指南
  • Flutter开发There are multiple heroes that share the same tag within a subtree报错
  • C++ explicit
  • 使用Java操作Redis
  • 在 Windows 中查看 Nginx 当前占用的端口
  • 基于高德地图实现地图交互功能的探索与总结
  • 函数式组件中的渲染函数 JSX
  • Python基础教程:从格式化到项目管理
  • QT操作PDF文件
  • 计算机视觉准备八股中
  • 多任务眼底血管与眼底血管中心线提取
  • Oracle数据库数据编程SQL<3.1 PL/SQL 匿名块 及 流程控制中的条件判断、循环、异常处理和随机函数应用>
  • CSS 美化页面(一)
  • 【Ai插件开发】Notepad++ AI插件开发进阶:集成Ai模型问答功能与流式交互实现
  • 【区块链安全 | 第九篇】基于Heimdall设计的智能合约反编译项目
  • SpringCould微服务架构之Docker(5)
  • [笔记.AI]初始向量