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

更新时间: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

方法:双指针法

  1. 初始化指针:左指针 left 指向数组起始位置,右指针 right 指向数组末尾。
  2. 计算当前容量:每次计算左右指针形成的容器面积,即 min(height[left], height[right]) * (right - left)
  3. 移动指针策略:移动高度较小的指针,以尝试寻找更大的高度,从而可能获得更大的容量。
  4. 更新最大容量:在每次循环中更新记录的最大容量。

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

  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. 本文版权归 优快云 用户 Allen Wurlitzer 所有,遵循CC-BY-SA协议发布,转载请注明出处。
  2. 本文题目来源 力扣-LeetCode ,著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值