Go-Python-Java-C-LeetCode高分解法-第三周合集

前言

本题解Go语言部分基于 LeetCode-Go
其他部分基于本人实践学习
个人题解GitHub连接:LeetCode-Go-Python-Java-C
Go-Python-Java-C-LeetCode高分解法-第一周合集
Go-Python-Java-C-LeetCode高分解法-第二周合集
本文部分内容来自网上搜集与个人实践。如果任何信息存在错误,欢迎读者批评指正。本文仅用于学习交流,不用作任何商业用途。

15. 3Sum

题目

Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Note:

The solution set must not contain duplicate triplets.

Example:

Given array nums = [-1, 0, 1, 2, -1, -4],

A solution set is:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

题目大意

给定一个数组,要求在这个数组中找出 3 个数之和为 0 的所有组合。

解题思路

解题思路(Go 版本):

  1. 对输入的数组进行排序,这样相同的数字会相邻排列。
  2. 遍历数组,将当前元素作为三元组的第一个元素(a),将问题转化为在剩余数组中查找两个数之和等于 -a 的问题。
  3. 使用双指针技巧,在剩余数组中查找满足条件的两个数,左指针从当前元素的下一个位置开始,右指针从数组末尾开始,逐步向中间移动。
  4. 如果当前三个数的和为 0,则将这个三元组添加到结果中。同时,为了避免重复解,需要跳过重复的元素。
  5. 移动左右指针,继续查找其他可能的解。

解题思路(Python 版本):

  1. 使用字典(counts)来记录每个数字的出现次数。
  2. 对输入数组进行排序,然后遍历排序后的数组。
  3. 对于每个数字,考虑三种情况:
    • 如果数字出现次数大于1,检查是否满足条件的三元组。
    • 如果数字是 0,检查是否满足条件的三元组。
    • 如果数字为负数,使用双指针技巧在剩余数组中查找满足条件的两个数。
  4. 使用双指针技巧,左指针从当前数字的下一个位置开始,右指针从数组末尾开始,逐步向中间移动。
  5. 如果找到满足条件的两个数,将这个三元组添加到结果中。为了避免重复解,需要跳过重复的元素。
  6. 移动左右指针,继续查找其他可能的解。

解题思路(Java 版本):

  1. 对输入的数组进行排序,这样相同的数字会相邻排列。
  2. 遍历数组,将当前元素作为三元组的第一个元素(a),将问题转化为在剩余数组中查找两个数之和等于 -a 的问题。
  3. 使用双指针技巧,在剩余数组中查找满足条件的两个数,左指针从当前元素的下一个位置开始,右指针从数组末尾开始,逐步向中间移动。
  4. 如果当前三个数的和为 0,则将这个三元组添加到结果中。同时,为了避免重复解,需要跳过重复的元素。
  5. 移动左右指针,继续查找其他可能的解。

解题思路(C++ 版本):

  1. 对输入的数组进行排序,这样相同的数字会相邻排列。
  2. 遍历数组,将当前元素作为三元组的第一个元素(a),将问题转化为在剩余数组中查找两个数之和等于 -a 的问题。
  3. 使用双指针技巧,在剩余数组中查找满足条件的两个数,左指针从当前元素的下一个位置开始,右指针从数组末尾开始,逐步向中间移动。
  4. 如果当前三个数的和为 0,则将这个三元组添加到结果中。同时,为了避免重复解,需要跳过重复的元素。
  5. 移动左右指针,继续查找其他可能的解。

代码

Go

func threeSum(nums []int) [][]int {
    var ans [][]int  // 存储结果的二维切片
    sort.Ints(nums)  // 对切片进行排序
    
    for i := 0; i < len(nums)-2; i++ {
        // 避免重复的情况
        if i > 0 && nums[i] == nums[i-1] {
            continue
        }
        
        target := -nums[i]  // 目标值,找到两个数使得它们的和等于目标值
        left := i + 1
        right := len(nums) - 1
        
        for left < right {
            sum := nums[left] + nums[right]
            
            if sum == target {
                ans = append(ans, []int{nums[i], nums[left], nums[right]})
                // 避免重复的情况
                for left < right && nums[left] == nums[left+1] {
                    left++
                }
                for left < right && nums[right] == nums[right-1] {
                    right--
                }
                left++
                right--
            } else if sum < target {
                left++
            } else {
                right--
            }
        }
    }
    
    return ans  // 返回结果二维切片
}

Python

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        ans = []  # 存储结果的列表
        counts = {}  # 用于记录每个数字出现次数的字典
        for i in nums:
            counts[i] = counts.get(i, 0) + 1  # 统计数字出现次数

        nums = sorted(counts)  # 对数字进行排序
        print(counts, nums)  # 打印数字出现次数和排序后的数字列表
        for i, num in enumerate(nums):
            if counts[num] > 1:  # 如果数字出现次数大于1
                if num == 0:
                    if counts[num] > 2:
                        ans.append([0, 0, 0])  # 若数字是0且出现次数大于2,添加[0, 0, 0]到结果列表
                        continue
                else:
                    if -num * 2 in counts:  # 如果两倍的负值在字典中存在
                        ans.append([num, num, -2 * num])  # 添加[num, num, -2*num]到结果列表
            if num < 0:
                two_sum = -num  # 计算需要找到的两个数的和
                # 在排序后的数字列表中寻找合适的数
                left = bisect.bisect_left(nums, (two_sum - nums[-1]), i + 1)
                # 寻找满足条件的 j 值,使得 num + j + k = 0
                for j in nums[left: bisect.bisect_right(nums, (two_sum // 2), left)]:
                    k = two_sum - j
                    if k in counts and k != j:
                        ans.append([num, j, k])  # 添加[num, j, k]到结果列表

        return ans  # 返回结果列表

Java

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    public List> threeSum(int[] nums) {
        List> ans = new ArrayList<>();  // 存储结果的列表
        Arrays.sort(nums);  // 对数组进行排序
        
        for (int i = 0; i < nums.length - 2; i++) {
            // 避免重复的情况
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            
            int target = -nums[i];  // 目标值,找到两个数使得它们的和等于目标值
            int left = i + 1;
            int right = nums.length - 1;
            
            while (left < right) {
                int sum = nums[left] + nums[right];
                
                if (sum == target) {
                    ans.add(Arrays.asList(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 ans;  // 返回结果列表
    }
}

Cpp

class Solution {
public:
    vector> threeSum(vector& nums) {
        vector> ans;  // 存储结果的二维向量
        sort(nums.begin(), nums.end());  // 对数组进行排序
        
        for (int i = 0; i < nums.size() - 2; i++) {
            // 避免重复的情况
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            
            int target = -nums[i];  // 目标值,找到两个数使得它们的和等于目标值
            int left = i + 1;
            int right = nums.size() - 1;
            
            while (left < right) {
                int sum = nums[left] + nums[right];
                
                if (sum == target) {
                    ans.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 ans;  // 返回结果二维向量
    }
};

所需要掌握的基础知识。

Go 版本:

  1. 切片(Slice): 在 Go 中,切片是动态数组,是一种常见的数据结构。你需要了解如何创建切片、访问切片元素、切片的长度和容量等基本操作。

  2. 排序(Sorting): Go 提供了排序函数 sort.Ints 来对整数切片进行排序。了解如何使用排序函数对切片进行排序。

  3. 循环和条件语句: 掌握 Go 中的 for 循环和 if 条件语句,以及如何在循环中使用 continuebreak

  4. 切片的添加元素: 学习如何使用 append 函数在切片末尾添加元素。

  5. 双指针技巧: 了解如何使用双指针技巧来解决查找问题,例如在本问题中的左右指针用法。

Python 版本:

  1. 列表(List): 在 Python 中,列表是一种动态数组。你需要了解如何创建列表、访问列表元素、列表的方法和属性等。

  2. 字典(Dictionary): 字典是键值对的集合,类似于其他编程语言中的哈希表或映射。了解如何使用字典存储数字出现次数。

  3. 排序: 学习如何使用内置函数 sorted 对列表进行排序。

  4. 循环和条件语句: 掌握在 Python 中的 for 循环和 if 条件语句,以及如何在循环中使用 continuebreak

  5. 双指针技巧: 学习如何使用双指针技巧来解决问题,例如在本问题中的左右指针用法。

Java 版本:

  1. 数组(Array): 了解如何创建数组、访问数组元素以及数组的属性。

  2. 排序: 掌握如何使用 Java 中的 Arrays.sort 方法对数组进行排序。

  3. 循环和条件语句: 学习在 Java 中的 for 循环和 if 条件语句,以及如何使用 breakcontinue 控制循环。

  4. 列表(List): Java 中的 List 是一种常见的数据结构,类似于动态数组。了解如何使用 List 存储数据。

  5. 双指针技巧: 掌握在 Java 中如何使用双指针技巧来解决问题,例如在本问题中的左右指针用法。

C++ 版本:

  1. 向量(Vector): C++ 中的 vector 类似于其他编程语言中的动态数组。了解如何创建向量、访问向量元素以及向量的方法。

  2. 排序: 学习如何使用 sort 函数对向量进行排序。

  3. 循环和条件语句: 掌握在 C++ 中的 for 循环和 if 条件语句,以及如何使用 breakcontinue 控制循环。

  4. 向量的添加元素: 了解如何使用 push_back 函数在向量末尾添加元素。

  5. 双指针技巧: 学习如何使用双指针技巧来解决问题,例如在本问题中的左右指针用法。

16. 3Sum Closest

题目

Given an array nums of n integers and an integer target, find three integers in nums such that the sum is closest to target. Return the sum of the three integers. You may assume that each input would have exactly one solution.

Example:

Given array nums = [-1, 2, 1, -4], and target = 1.

The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).

题目大意

给定一个数组,要求在这个数组中找出 3 个数之和离 target 最近。

解题思路

当然,我可以为您逐个介绍每个版本的解题思路。以下是对每个版本的解释:

Go 版本解题思路:

  1. 首先,获取数组的长度,然后对数组进行升序排序,以便于后续的双指针法查找。

  2. 初始化一个最小差值 minDiff 为整型最大值,并定义一个变量 ans 来存储最接近目标的和。

  3. 使用循环遍历数组,从第一个元素到倒数第三个元素。在循环中,检查是否需要跳过重复的元素。

  4. 尝试以当前元素为基准,计算三个元素的和 sum,分情况讨论:

    • 如果 sum 大于目标值 target,判断差值是否更小,若是则更新 ans
    • 如果 sum 小于目标值 target,判断差值是否更小,若是则更新 minDiff
  5. 如果 sum 小于目标值 target,使用双指针法在剩余区间内查找最接近目标值的和:

    • 初始化左指针 j 为当前元素的下一个元素,右指针 k 为数组末尾元素。
    • 在循环中,计算当前和 sum,若和等于目标值,直接返回目标值。
    • 若和大于目标值,根据差值是否更小,更新 ansminDiff,然后左指针右移。
    • 若和小于目标值,根据差值是否更小,更新 ansminDiff,然后右指针左移。
  6. 最后,返回存储最接近目标的和 ans

Python 版解题思路:

Python 版本的思路与 Go 版本类似,只是使用了 Python 特有的列表和内置函数,具体如下:

  1. 获取列表的长度,然后对列表进行升序排序。

  2. 初始化一个最小差值 minDiff 为正无穷大,并定义一个变量 closestSum 来存储最接近目标的和。

  3. 使用循环遍历列表,从第一个元素到倒数第三个元素。在循环中,检查是否需要跳过重复的元素。

  4. 尝试以当前元素为基准,计算三个元素的和 sum_,分情况讨论:

    • 如果 sum_ 大于目标值 target,判断差值是否更小,若是则更新 closestSum
    • 如果 sum_ 小于目标值 target,判断差值是否更小,若是则更新 minDiff
  5. 如果 sum_ 小于目标值 target,使用双指针法在剩余区间内查找最接近目标值的和:

    • 初始化左指针 j 为当前元素的下一个元素,右指针 k 为列表末尾元素。
    • 在循环中,计算当前和 sum_,若和等于目标值,直接返回目标值。
    • 若和大于目标值,根据差值是否更小,更新 closestSumminDiff,然后右指针左移。
    • 若和小于目标值,根据差值是否更小,更新 closestSumminDiff,然后左指针右移。
  6. 最后,返回存储最接近目标的和 closestSum

Java 版解题思路:

Java 版本的解题思路与其他版本类似,同样使用了双指针法和数组,具体如下:

  1. 获取数组的长度,然后对数组进行升序排序。

  2. 初始化一个最小差值 minDiff 为整型最大值,并定义一个变量 closestSum 来存储最接近目标的和。

  3. 使用循环遍历数组,从第一个元素到倒数第三个元素。在循环中,检查是否需要跳过重复的元素。

  4. 尝试以当前元素为基准,计算三个元素的和 sum,分情况讨论:

    • 如果 sum 大于目标值 target,判断差值是否更小,若是则更新 closestSum
    • 如果 sum 小于目标值 target,判断差值是否更小,若是则更新 minDiff
  5. 如果 sum 小于目标值 target,使用双指针法在剩余区间内查找最接近目标值的和:

    • 初始化左指针 j 为当前元素的下一个元素,右指针 k 为数组末尾元素。
    • 在循环中,计算当前和 sum,若和等于目标值,直接返回目标值。
    • 若和大于目标值,根据差值是否更小,更新 closestSumminDiff,然后右指针左移。
    • 若和小于目标值,根据差值是否更小,更新 closestSumminDiff,然后左指针右移。
  6. 最后,返回存储最接近目标的和 closestSum

C++ 版解题思路:

C++ 版本的解题思路与其他版本类似,同样使用了双指针法和向量(vector),具体如下:

  1. 获取向量的长度,然后对向量进行升序排序。

  2. 初始化一个最小差值 minDiff 为整型最大值,并定义一个变量 closestSum 来存储最接近目标的和。

  3. 使用循环遍历向量,从第一个元素到倒数第三个元素。在循环中,检查是否需要跳过重复的元素。

  4. 尝试以当前元素为基准,计算三个元素的和 sum,分情况讨论:

    • 如果 sum 大于目标值 target,判断差值是否更小,若是则更新 closestSum
    • 如果 sum 小于目标值 target,判断差值是否更小,若是则更新 minDiff
  5. 如果 sum 小于目标值 target,使用双指针法在剩余区间内查找最接近目标值的和:

    • 初始化左指针 j 为当前元素的下一个元素,右指针 k 为向量末尾元素。
    • 在循环中,计算当前和 sum,若和等于目标值,直接返回目标值。
    • 若和大于目标值,根据差值是否更小,更新 closestSumminDiff,然后右指针左移。
    • 若和小于目标值,根据差值是否更小,更新 closestSumminDiff,然后左指针右移。
  6. 最后,返回存储最接近目标的和 closestSum

代码

Go

func threeSumClosest(nums []int, target int) int {
    n := len(nums)             // 获取数组长度
    sort.Ints(nums)            // 对数组进行升序排序
    minDiff := math.MaxInt     // 初始化最小差值为整型最大值
    var ans int                // 用于存储最接近目标的和

    for i := 0; i < n-2; i++ { // 遍历数组,从第一个元素到倒数第三个元素
        if i > 0 && nums[i] == nums[i-1] {
            // 跳过重复的元素,避免重复计算
            continue
        }

        // 尝试以当前元素为基准,计算三个元素的和
        sum := nums[i] + nums[i+1] + nums[i+2]

        if sum > target {
            // 如果和大于目标值,判断差值是否更小,若是则更新结果
            if sum - target < minDiff {
                ans = sum
            }
            break  // 由于数组已经排序,之后的和会更大,不必再继续遍历
        }

        // 尝试以当前元素为基准,和最大的两个元素相加
        sum = nums[i] + nums[n-2] + nums[n-1]

        if sum < target {
            // 如果和小于目标值,判断差值是否更小,若是则更新结果
            if target - sum < minDiff {
                minDiff = target - sum
                ans = sum
            }
            continue  // 继续尝试更大的和
        }

        // 使用双指针法在剩余区间内查找最接近目标值的和
        j, k := i+1, n-1
        for j < k {
            sum = nums[i] + nums[j] + nums[k]
            if sum == target {
                // 如果和等于目标值,直接返回
                return target
            }
            if sum > target {
                // 如果和大于目标值,判断差值是否更小,若是则更新结果
                if sum - target < minDiff {
                    minDiff = sum - target
                    ans = sum
                }
                k--  // 缩小右侧指针的范围
            } else {
                // 如果和小于目标值,判断差值是否更小,若是则更新结果
                if target - sum < minDiff {
                    minDiff = target - sum
                    ans = sum
                }
                j++  // 增大左侧指针的范围
            }
        }
    }
    return ans  // 返回最接近目标值的和
}

Python

from typing import List

class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        n = len(nums)                   # 获取数组长度
        nums.sort()                     # 对数组进行升序排序
        minDiff = float('inf')          # 初始化最小差值为正无穷大
        closestSum = 0                  # 用于存储最接近目标的和

        for i in range(n - 2):          # 遍历数组,从第一个元素到倒数第三个元素
            if i > 0 and nums[i] == nums[i - 1]:
                # 跳过重复的元素,避免重复计算
                continue

            sum_ = nums[i] + nums[i + 1] + nums[i + 2]

            if sum_ > target:
                # 如果和大于目标值,判断差值是否更小,若是则更新结果
                if sum_ - target < minDiff:
                    closestSum = sum_
                    minDiff = sum_ - target
                break   # 由于数组已经排序,之后的和会更大,不必再继续遍历

            sum_ = nums[i] + nums[n - 2] + nums[n - 1]

            if sum_ < target:
                # 如果和小于目标值,判断差值是否更小,若是则更新结果
                if target - sum_ < minDiff:
                    closestSum = sum_
                    minDiff = target - sum_
                continue    # 继续尝试更大的和

            j, k = i + 1, n - 1
            while j < k:
                sum_ = nums[i] + nums[j] + nums[k]
                if sum_ == target:
                    # 如果和等于目标值,直接返回
                    return target
                if sum_ > target:
                    # 如果和大于目标值,判断差值是否更小,若是则更新结果
                    if sum_ - target < minDiff:
                        closestSum = sum_
                        minDiff = sum_ - target
                    k -= 1  # 缩小右侧指针的范围
                else:
                    # 如果和小于目标值,判断差值是否更小,若是则更新结果
                    if target - sum_ < minDiff:
                        closestSum = sum_
                        minDiff = target - sum_
                    j += 1  # 增大左侧指针的范围
        return closestSum  # 返回最接近目标值的和

Java

import java.util.Arrays;

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        int n = nums.length;                    // 获取数组长度
        Arrays.sort(nums);                      // 对数组进行升序排序
        int minDiff = Integer.MAX_VALUE;        // 初始化最小差值为整型最大值
        int closestSum = 0;                     // 用于存储最接近目标的和

        for (int i = 0; i < n - 2; i++) {       // 遍历数组,从第一个元素到倒数第三个元素
            if (i > 0 && nums[i] == nums[i - 1]) {
                // 跳过重复的元素,避免重复计算
                continue;
            }

            int sum = nums[i] + nums[i + 1] + nums[i + 2];

            if (sum > target) {
                // 如果和大于目标值,判断差值是否更小,若是则更新结果
                if (sum - target < minDiff) {
                    closestSum = sum;
                    minDiff = sum - target;
                }
                break;  // 由于数组已经排序,之后的和会更大,不必再继续遍历
            }

            sum = nums[i] + nums[n - 2] + nums[n - 1];

            if (sum < target) {
                // 如果和小于目标值,判断差值是否更小,若是则更新结果
                if (target - sum < minDiff) {
                    closestSum = sum;
                    minDiff = target - sum;
                }
                continue;  // 继续尝试更大的和
            }

            int j = i + 1, k = n - 1;
            while (j < k) {
                sum = nums[i] + nums[j] + nums[k];
                if (sum == target) {
                    // 如果和等于目标值,直接返回
                    return target;
                }
                if (sum > target) {
                    // 如果和大于目标值,判断差值是否更小,若是则更新结果
                    if (sum - target < minDiff) {
                        closestSum = sum;
                        minDiff = sum - target;
                    }
                    k--;  // 缩小右侧指针的范围
                } else {
                    // 如果和小于目标值,判断差值是否更小,若是则更新结果
                    if (target - sum < minDiff) {
                        closestSum = sum;
                        minDiff = target - sum;
                    }
                    j++;  // 增大左侧指针的范围
                }
            }
        }
        return closestSum;  // 返回最接近目标值的和
    }
}

Cpp

#include 
#include 
#include 
using namespace std;

class Solution {
public:
    int threeSumClosest(vector& nums, int target) {
        int n = nums.size();                    // 获取数组长度
        sort(nums.begin(), nums.end());         // 对数组进行升序排序
        int minDiff = INT_MAX;                  // 初始化最小差值为整型最大值
        int closestSum = 0;                     // 用于存储最接近目标的和

        for (int i = 0; i < n - 2; i++) {       // 遍历数组,从第一个元素到倒数第三个元素
            if (i > 0 && nums[i] == nums[i - 1]) {
                // 跳过重复的元素,避免重复计算
                continue;
            }

            int sum = nums[i] + nums[i + 1] + nums[i + 2];

            if (sum > target) {
                // 如果和大于目标值,判断差值是否更小,若是则更新结果
                if (sum - target < minDiff) {
                    closestSum = sum;
                    minDiff = sum - target;
                }
                break;  // 由于数组已经排序,之后的和会更大,不必再继续遍历
            }

            sum = nums[i] + nums[n - 2] + nums[n - 1];

            if (sum < target) {
                // 如果和小于目标值,判断差值是否更小,若是则更新结果
                if (target - sum < minDiff) {
                    closestSum = sum;
                    minDiff = target - sum;
                }
                continue;  // 继续尝试更大的和
            }

            int j = i + 1, k = n - 1;
            while (j < k) {
                sum = nums[i] + nums[j] + nums[k];
                if (sum == target) {
                    // 如果和等于目标值,直接返回
                    return target;
                }
                if (sum > target) {
                    // 如果和大于目标值,判断差值是否更小,若是则更新结果
                    if (sum - target < minDiff) {
                        closestSum = sum;
                        minDiff = sum - target;
                    }
                    k--;  // 缩小右侧指针的范围
                } else {
                    // 如果和小于目标值,判断差值是否更小,若是则更新结果
                    if (target - sum < minDiff) {
                        closestSum = sum;
                        minDiff = target - sum;
                    }
                    j++;  // 增大左侧指针的范围
                }
            }
        }
        return closestSum;  // 返回最接近目标值的和
    }
};

每个版本的代码所需要的基础知识:

Go 版本代码所需基础知识:

  1. 基本语法: 理解 Go 语言的基本语法,包括变量声明、循环、条件语句、函数定义等。

  2. 切片和排序: 了解切片的概念和使用方法,以及如何使用标准库的 sort 包对切片进行排序。

  3. 双指针法: 理解双指针法的思想,即使用两个指针来在数组或切片中快速查找或计算。

  4. 数学库和常量: 知道如何使用数学库的常量 math.MaxInt 来表示整型最大值。

Python 版本代码所需基础知识:

  1. 基本语法: 熟悉 Python 的基本语法,包括变量声明、循环、条件语句、函数定义等。

  2. 列表和排序: 理解列表的概念和使用方法,以及如何使用内置函数 sorted() 对列表进行排序。

  3. 双指针法: 了解双指针法的思想,即使用两个指针来在列表中快速查找或计算。

  4. 浮点数表示: 知道如何使用 float('inf') 来表示正无穷大。

Java 版本代码所需基础知识:

  1. 基本语法: 掌握 Java 的基本语法,包括变量声明、循环、条件语句、方法定义等。

  2. 数组和排序: 理解数组的概念和使用方法,以及如何使用 Arrays 类的 sort() 方法对数组进行排序。

  3. 双指针法: 熟悉双指针法的思想,即使用两个指针来在数组中快速查找或计算。

  4. 常量: 知道如何使用 Integer.MAX_VALUE 来表示整型最大值。

C++ 版本代码所需基础知识:

  1. 基本语法: 熟悉 C++ 的基本语法,包括变量声明、循环、条件语句、函数定义等。

  2. 向量和排序: 了解向量(vector)的概念和使用方法,以及如何使用标准库的 sort() 函数对向量进行排序。

  3. 双指针法: 掌握双指针法的思想,即使用两个指针来在向量中快速查找或计算。

  4. 整型常量: 知道如何使用 INT_MAX 来表示整型最大值。

17. Letter Combinations of a Phone Number

题目

Given a string containing digits from2-9inclusive, return all possible letter combinations that the number could
represent.

A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any
letters.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xi2AO9kk-1693116589374)(http://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Telephone-keypad2.svg/200px-Telephone-keypad2.svg.png)]

Example:

Input: "23"
Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

Note:

Although the above answer is in lexicographical order, your answer could be in any order you want.

题目大意

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

解题思路

当使用不同的编程语言来实现 “Letter Combinations of a Phone Number” 这个问题时,虽然解法的思路是相同的,但由于语言的差异,实现细节会有所不同。下面将分别介绍每个版本的解题思路:

解题思路(Go 版本):

解法一:DFS(深度优先搜索)

  1. 创建一个 letterMap 数组,其中每个索引对应一个数字,存储了该数字对应的可能字母集合。
  2. 创建一个结果切片 res,用于存储最终的字母组合结果。
  3. 定义一个递归函数 findCombination,该函数会根据当前数字的索引递归地生成字母组合。
  4. letterCombinations 函数中,如果输入的数字串为空,直接返回空切片。
  5. 调用 findCombination 函数,将初始状态的数字串、索引和空字符串传入。递归过程中,每次都会将当前数字对应的字母加入当前组合 s 中,直到处理完所有数字。
  6. 当递归深度达到数字串的长度时,将当前组合 s 加入结果切片 res 中。
  7. 返回最终的结果切片 res

解法二:非递归

  1. 创建一个 letterMap 数组,其中每个索引对应一个数字,存储了该数字对应的可能字母集合。
  2. 创建一个结果切片 res,用于存储最终的字母组合结果。
  3. 遍历输入的数字串,依次处理每个数字。
  4. 对于第一个数字,获取其对应的字母集合,初始化一个初始结果切片 res,将其中的每个字母作为起始元素。
  5. 对于后续的数字,获取其对应的字母集合,将当前结果切片 res 中的每个元素与字母集合中的每个字母组合,生成新的结果切片 tmp
  6. 更新结果切片 res 为新生成的结果切片 tmp
  7. 返回最终的结果切片 res

解法三:回溯

  1. 创建一个 letterMap 数组,其中每个索引对应一个数字,存储了该数字对应的可能字母集合。
  2. 创建一个结果切片 res,用于存储最终的字母组合结果。
  3. 创建一个 dict 字典,将每个数字映射到其对应的可能字母集合。
  4. 定义一个回溯函数 letterFunc,该函数会递归地生成字母组合。
  5. letterCombinations 函数中,如果输入的数字串为空,直接返回空切片。
  6. 调用 letterFunc 函数,将初始状态的空组合 "" 和数字串传入。递归过程中,每次将当前数字对应的每个字母加入组合 res 中,继续处理下一个数字。
  7. 当递归深度达到数字串的长度时,将当前组合加入结果切片 res 中。
  8. 返回最终的结果切片 res

解题思路(Python 版本):

与 Go 版本的解法思路相同,只是在 Python 中使用了不同的语法来实现相同的逻辑。

解题思路(Java 版本):

与 Go 版本的解法思路相同,只是在 Java 中使用了不同的语法来实现相同的逻辑。在 Java 版本中,数组和集合的操作稍微有所不同,需要使用 ArrayList 或其他集合类来存储最终结果。

解题思路(C++ 版本):

与 Go 版本的解法思路相同,只是在 C++ 中使用了不同的语法来实现相同的逻辑。在 C++ 版本中,需要使用 vector 来存储最终结果。

总的来说,不同版本的解法思路都是基于递归、深度优先搜索和回溯的思想,通过在每个数字上选择其中一个字母,然后递归地处理剩余数字,直到生成完整的字母组合。差异主要体现在语言的语法、数据结构和函数调用上。

代码

Go

// 解法一 DFS
var (
    letterMap = []string{
        " ",    //0
        "",     //1
        "abc",  //2
        "def",  //3
        "ghi",  //4
        "jkl",  //5
        "mno",  //6
        "pqrs", //7
        "tuv",  //8
        "wxyz", //9
    }
    res   = []string{} // 存储最终结果的切片
    final = 0          // 标记当前处理的组合数
)

func letterCombinations(digits string) []string {
    if digits == "" {
        return []string{} // 若输入为空,则返回空切片
    }
    res = []string{} // 重置结果切片
    findCombination(&digits, 0, "") // 调用递归函数生成字母组合
    return res
}

func findCombination(digits *string, index int, s string) {
    if index == len(*digits) {
        res = append(res, s) // 所有数字已经处理完,将当前组合加入结果切片
        return
    }
    num := (*digits)[index]
    letter := letterMap[num-'0'] // 获取当前数字对应的字母集合
    for i := 0; i < len(letter); i++ {
        findCombination(digits, index+1, s+string(letter[i])) // 递归生成下一个数字对应的字母组合
    }
    return
}

// 解法二 非递归
var (
    letterMap = []string{
        " ",    //0
        "",     //1
        "abc",  //2
        "def",  //3
        "ghi",  //4
        "jkl",  //5
        "mno",  //6
        "pqrs", //7
        "tuv",  //8
        "wxyz", //9
    }
    res   = []string{} // 存储最终结果的切片
    final = 0          // 标记当前处理的组合数
)

func letterCombinations(digits string) []string {
    if digits == "" {
        return []string{} // 若输入为空,则返回空切片
    }
    index := digits[0] - '0' // 获取第一个数字对应的索引
    letter := letterMap[index] // 获取对应的字母集合
    tmp := []string{} // 临时存储新生成的字母组合
    for i := 0; i < len(letter); i++ {
        if len(res) == 0 {
            res = append(res, "")
        }
        for j := 0; j < len(res); j++ {
            tmp = append(tmp, res[j]+string(letter[i])) // 将当前字母与已有组合进行拼接
        }
    }
    res = tmp // 更新结果切片
    final++
    letterCombinations(digits[1:]) // 继续处理剩余的数字
    final--
    if final == 0 {
        tmp = res
        res = []string{} // 重置结果切片
    }
    return tmp
}

// 解法三 回溯(参考回溯模板,类似DFS)
var (
    letterMap = []string{
        " ",    //0
        "",     //1
        "abc",  //2
        "def",  //3
        "ghi",  //4
        "jkl",  //5
        "mno",  //6
        "pqrs", //7
        "tuv",  //8
        "wxyz", //9
    }
    res   = []string{} // 存储最终结果的切片
    final = 0          // 标记当前处理的组合数
)
var result []string // 用于存储结果的全局切片
var dict = map[string][]string{ // 数字到字母的映射
    "2": []string{"a", "b", "c"},
    "3": []string{"d", "e", "f"},
    "4": []string{"g", "h", "i"},
    "5": []string{"j", "k", "l"},
    "6": []string{"m", "n", "o"},
    "7": []string{"p", "q", "r", "s"},
    "8": []string{"t", "u", "v"},
    "9": []string{"w", "x", "y", "z"},
}

func letterCombinations(digits string) []string {
    result = []string{} // 重置结果切片
    if digits == "" {
        return result
    }
    letterFunc("", digits) // 调用回溯函数生成字母组合
    return result
}

func letterFunc(res string, digits string) {
    if digits == "" {
        result = append(result, res) // 所有数字已经处理完,将当前组合加入结果切片
        return
    }

    k := digits[0:1]
    digits = digits[1:]
    for i := 0; i < len(dict[k]); i++ {
        res += dict[k][i] // 添加当前字母到组合
        letterFunc(res, digits) // 继续处理下一个数字
        res = res[0:len(res)-1] // 回溯,移除最后一个字母
    }
}

Python

class Solution:
    def __init__(self):
        # 字母映射表,与之前的Java代码中的letterMap相同
        self.letterMap = [
            " ",    #0
            "",     #1
            "abc",  #2
            "def",  #3
            "ghi",  #4
            "jkl",  #5
            "mno",  #6
            "pqrs", #7
            "tuv",  #8
            "wxyz"  #9
        ]

    # 解法一 DFS
    def letterCombinations(self, digits: str) -> List[str]:
        result = []
        if not digits:
            return result
        self.findCombination(result, digits, 0, "")
        return result

    def findCombination(self, result: List[str], digits: str, index: int, s: str):
        if index == len(digits):
            result.append(s)
            return
        num = int(digits[index])
        letter = self.letterMap[num]
        for char in letter:
            self.findCombination(result, digits, index + 1, s + char)

    # 解法二 非递归
    def letterCombinations(self, digits: str) -> List[str]:
        result = []
        if not digits:
            return result

        letter = self.letterMap[int(digits[0])]
        res = [char for char in letter]

        for digit in digits[1:]:
            letter = self.letterMap[int(digit)]
            tmp = []
            for prefix in res:
                for char in letter:
                    tmp.append(prefix + char)
            res = tmp
        return res

    # 解法三 回溯
    def letterCombinations(self, digits: str) -> List[str]:
        result = []
        if not digits:
            return result

        dict = {
            "2": ["a", "b", "c"],
            "3": ["d", "e", "f"],
            "4": ["g", "h", "i"],
            "5": ["j", "k", "l"],
            "6": ["m", "n", "o"],
            "7": ["p", "q", "r", "s"],
            "8": ["t", "u", "v"],
            "9": ["w", "x", "y", "z"]
        }

        self.letterFunc(result, "", digits, dict)
        return result

    def letterFunc(self, result: List[str], res: str, digits: str, dict: Dict[str, List[str]]):
        if not digits:
            result.append(res)
            return

        k = digits[0]
        digits = digits[1:]
        for letter in dict[k]:
            self.letterFunc(result, res + letter, digits, dict)

Java

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class Solution {
    // 字母映射表,与之前的Go代码中的letterMap相同
    private static final String[] letterMap = {
        " ",    //0
        "",     //1
        "abc",  //2
        "def",  //3
        "ghi",  //4
        "jkl",  //5
        "mno",  //6
        "pqrs", //7
        "tuv",  //8
        "wxyz"  //9
    };

    // 解法一 DFS
    public List letterCombinations(String digits) {
        List result = new ArrayList<>();
        if (digits.isEmpty()) {
            return result;
        }
        findCombination(result, digits, 0, "");
        return result;
    }

    private void findCombination(List result, String digits, int index, String s) {
        if (index == digits.length()) {
            result.add(s);
            return;
        }
        int num = digits.charAt(index) - '0';
        String letter = letterMap[num];
        for (int i = 0; i < letter.length(); i++) {
            findCombination(result, digits, index + 1, s + letter.charAt(i));
        }
    }

    // 解法二 非递归
    public List letterCombinations(String digits) {
        List result = new ArrayList<>();
        if (digits.isEmpty()) {
            return result;
        }

        char[] digitsArray = digits.toCharArray();
        String letter = letterMap[digitsArray[0] - '0'];

        List res = new ArrayList<>();
        for (int i = 0; i < letter.length(); i++) {
            res.add("" + letter.charAt(i));
        }

        for (int i = 1; i < digitsArray.length; i++) {
            letter = letterMap[digitsArray[i] - '0'];
            List tmp = new ArrayList<>();
            for (String prefix : res) {
                for (int j = 0; j < letter.length(); j++) {
                    tmp.add(prefix + letter.charAt(j));
                }
            }
            res = tmp;
        }
        return res;
    }

    // 解法三 回溯
    public List letterCombinations(String digits) {
        List result = new ArrayList<>();
        if (digits.isEmpty()) {
            return result;
        }
        Map dict = new HashMap<>();
        dict.put("2", new String[]{"a", "b", "c"});
        dict.put("3", new String[]{"d", "e", "f"});
        dict.put("4", new String[]{"g", "h", "i"});
        dict.put("5", new String[]{"j", "k", "l"});
        dict.put("6", new String[]{"m", "n", "o"});
        dict.put("7", new String[]{"p", "q", "r", "s"});
        dict.put("8", new String[]{"t", "u", "v"});
        dict.put("9", new String[]{"w", "x", "y", "z"});

        letterFunc(result, "", digits, dict);
        return result;
    }

    private void letterFunc(List result, String res, String digits, Map dict) {
        if (digits.isEmpty()) {
            result.add(res);
            return;
        }

        String k = digits.substring(0, 1);
        digits = digits.substring(1);
        for (String letter : dict.get(k)) {
            letterFunc(result, res + letter, digits, dict);
        }
    }
}

Cpp

class Solution {
public:
    // 字母映射表,与之前的Java代码中的letterMap相同
    vector letterMap = {
        " ",    //0
        "",     //1
        "abc",  //2
        "def",  //3
        "ghi",  //4
        "jkl",  //5
        "mno",  //6
        "pqrs", //7
        "tuv",  //8
        "wxyz"  //9
    };

    // 解法一 DFS
    vector letterCombinations(string digits) {
        vector result;
        if (digits.empty()) {
            return result;
        }
        string combination;
        findCombination(result, digits, 0, combination);
        return result;
    }

    void findCombination(vector& result, const string& digits, int index, string& s) {
        if (index == digits.length()) {
            result.push_back(s);
            return;
        }
        int num = digits[index] - '0';
        string letter = letterMap[num];
        for (int i = 0; i < letter.length(); i++) {
            s.push_back(letter[i]);
            findCombination(result, digits, index + 1, s);
            s.pop_back(); // 回溯,移除最后一个字母
        }
    }

    // 解法二 非递归
    vector letterCombinations(string digits) {
        vector result;
        if (digits.empty()) {
            return result;
        }

        string letter = letterMap[digits[0] - '0'];
        vector res;
        for (int i = 0; i < letter.length(); i++) {
            res.push_back(string(1, letter[i]));
        }

        for (int i = 1; i < digits.length(); i++) {
            letter = letterMap[digits[i] - '0'];
            vector tmp;
            for (const string& prefix : res) {
                for (char c : letter) {
                    tmp.push_back(prefix + c);
                }
            }
            res = tmp;
        }
        return res;
    }

    // 解法三 回溯
    vector letterCombinations(string digits) {
        vector result;
        if (digits.empty()) {
            return result;
        }
        unordered_map> dict = {
            {"2", {"a", "b", "c"}},
            {"3", {"d", "e", "f"}},
            {"4", {"g", "h", "i"}},
            {"5", {"j", "k", "l"}},
            {"6", {"m", "n", "o"}},
            {"7", {"p", "q", "r", "s"}},
            {"8", {"t", "u", "v"}},
            {"9", {"w", "x", "y", "z"}}
        };

        letterFunc(result, "", digits, dict);
        return result;
    }

    void letterFunc(vector& result, string res, string digits, unordered_map>& dict) {
        if (digits.empty()) {
            result.push_back(res);
            return;
        }

        string k = digits.substr(0, 1);
        digits = digits.substr(1);
        for (const string& letter : dict[k]) {
            letterFunc(result, res + letter, digits, dict);
        }
    }
};

当使用不同的编程语言来实现同一个问题时,虽然解法的思路是相同的,但由于语言的差异,有些基础知识是需要掌握的。下面将逐个介绍每个版本所需要的基础知识:

Go 版本基础知识:

  1. 变量和数据类型: 需要了解如何声明变量、赋值以及基本的数据类型,如字符串、整数等。
  2. 切片: 切片是 Go 中动态数组的数据结构,用于存储字符串结果集,需要了解切片的创建、操作和使用。
  3. 递归: 了解递归的概念和使用方法,因为第一种解法使用了深度优先搜索(DFS)的递归思路。
  4. 函数和方法: 了解如何定义函数和方法,以及函数的参数和返回值。
  5. 字符串操作: 掌握字符串的拼接、分割等基本操作。

Python 版本基础知识:

  1. 变量和数据类型: 需要了解如何声明变量、赋值以及基本的数据类型,如字符串、整数等。
  2. 列表(List): 列表是 Python 中的动态数组,用于存储字符串结果集,需要了解列表的创建、操作和使用。
  3. 递归: 了解递归的概念和使用方法,因为第一种解法使用了深度优先搜索(DFS)的递归思路。
  4. 函数定义和调用: 掌握如何定义函数和调用函数,以及函数的参数和返回值。
  5. 字符串操作: 掌握字符串的拼接、分割等基本操作。

Java 版本基础知识:

  1. 变量和数据类型: 需要了解如何声明变量、赋值以及基本的数据类型,如字符串、整数等。
  2. 数组和集合: Java 中的数组和集合(如 ArrayList)用于存储字符串结果集,需要了解数组和集合的创建、操作和使用。
  3. 递归: 了解递归的概念和使用方法,因为第一种解法使用了深度优先搜索(DFS)的递归思路。
  4. 方法定义和调用: 掌握如何定义方法和调用方法,以及方法的参数和返回值。
  5. 字符串操作: 掌握字符串的拼接、分割等基本操作。

C++ 版本基础知识:

  1. 变量和数据类型: 需要了解如何声明变量、赋值以及基本的数据类型,如字符串、整数等。
  2. 向量(Vector): C++ 中的向量(vector)用于存储字符串结果集,需要了解向量的创建、操作和使用。
  3. 递归: 了解递归的概念和使用方法,因为第一种解法使用了深度优先搜索(DFS)的递归思路。
  4. 函数定义和调用: 掌握如何定义函数和调用函数,以及函数的参数和返回值。
  5. 字符串操作: 掌握字符串的拼接、分割等基本操作。

18. 4Sum

题目

Given an array nums of n integers and an integer target, are there elements a, b, c, and d in nums such that a + b + c +
d = target? Find all unique quadruplets in the array which gives the sum of target.

Note:

The solution set must not contain duplicate quadruplets.

Example:

Given array nums = [1, 0, -1, 0, -2, 2], and target = 0.

A solution set is:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]

题目大意

给定一个数组,要求在这个数组中找出 4 个数之和为 0 的所有组合。

解题思路

用 map 提前计算好任意 3 个数字之和,保存起来,可以将时间复杂度降到 O(n^3)
。这一题比较麻烦的一点在于,最后输出解的时候,要求输出不重复的解。数组中同一个数字可能出现多次,同一个数字也可能使用多次,但是最后输出解的时候,不能重复。例如 [-1,1,2, -2]
和 [2, -1, -2, 1]、[-2, 2, -1, 1] 这 3 个解是重复的,即使 -1, -2 可能出现 100 次,每次使用的 -1, -2 的数组下标都是不同的。

这一题是第 15 题的升级版,思路都是完全一致的。这里就需要去重和排序了。map 记录每个数字出现的次数,然后对 map 的 key
数组进行排序,最后在这个排序以后的数组里面扫,找到另外 3 个数字能和自己组成 0 的组合。

第 15 题和第 18 题的解法一致。
当解释每个版本的解题思路时,我们将专注于每个版本中使用的方法和算法。

Go 版本的解题思路

  1. 双指针法: 这个解法主要使用了双指针法,通过对数组进行排序,然后使用指针来确定数组中的元素。通过设置两个指针,一个指向当前元素,另一个指向数组的末尾,逐步移动指针以找到满足条件的四元组。

  2. 通用的 kSum 解法: 除了双指针法,这个解法还使用了一个通用的 kSum 解法。这个解法基于递归,将问题逐步转化为更小规模的 k-1 Sum 问题,直到最终转化为 2 Sum 问题,然后再使用双指针法来解决。这种通用性的解法可以用于解决不同规模的 kSum 问题。

Python 版本的解题思路

  1. 双指针法: 这个版本的解法主要使用了双指针法。通过对数组进行排序,然后设置两个指针,一个从数组起始位置向后移动,另一个从数组末尾位置向前移动,根据当前指针指向的元素之和与目标值的比较结果,决定指针的移动方向,从而找到满足条件的四元组。

Java 版本的解题思路

  1. 双指针法: 这个版本的解法同样使用了双指针法。通过对数组进行排序,然后设置两个指针,一个从数组起始位置向后移动,另一个从数组末尾位置向前移动,根据当前指针指向的元素之和与目标值的比较结果,决定指针的移动方向,从而找到满足条件的四元组。

  2. 封装 nSum 函数: 这个版本的解法将 nSum 问题的求解封装在一个单独的函数中,这样可以在 3Sum 和 4Sum 问题中重复使用。nSum 函数会根据传入的参数来决定是计算 2Sum、3Sum 还是其他的 nSum 问题。

C++ 版本的解题思路

  1. 双指针法: 这个版本的解法同样使用了双指针法。通过对向量进行排序,然后设置两个指针,一个从向量起始位置向后移动,另一个从向量末尾位置向前移动,根据当前指针指向的元素之和与目标值的比较结果,决定指针的移动方向,从而找到满足条件的四元组。

代码

Go

import "sort"  // 导入排序库

// 解法一 双指针
func fourSum(nums []int, target int) (quadruplets [][]int) {
    sort.Ints(nums)  // 对输入数组进行排序
    n := len(nums)
    for i := 0; i < n-3 && nums[i]+nums[i+1]+nums[i+2]+nums[i+3] <= target; i++ {
        // 跳过重复的起始数字,或者如果当前四个最小数之和已经大于目标值,也跳过
        if i > 0 && nums[i] == nums[i-1] || nums[i]+nums[n-3]+nums[n-2]+nums[n-1] < target {
            continue
        }
        for j := i + 1; j < n-2 && nums[i]+nums[j]+nums[j+1]+nums[j+2] <= target; j++ {
            // 跳过重复的第二个数字,或者如果当前四个数最小和大于目标值,也跳过
            if j > i+1 && nums[j] == nums[j-1] || nums[i]+nums[j]+nums[n-2]+nums[n-1] < target {
                continue
            }
            // 使用双指针查找剩下的两个数字
            for left, right := j+1, n-1; left < right; {
                if sum := nums[i] + nums[j] + nums[left] + nums[right]; sum == target {
                    quadruplets = append(quadruplets, []int{nums[i], nums[j], nums[left], nums[right]})
                    // 跳过重复的数字
                    for left++; left < right && nums[left] == nums[left-1]; left++ {
                    }
                    for right--; left < right && nums[right] == nums[right+1]; right-- {
                    }
                } else if sum < target {
                    left++
                } else {
                    right--
                }
            }
        }
    }
    return
}

// 解法二 kSum
func fourSum1(nums []int, target int) [][]int {
    res, cur := make([][]int, 0), make([]int, 0)
    sort.Ints(nums)
    kSum(nums, 0, len(nums)-1, target, 4, cur, &res)
    return res
}

// 通用的 kSum 函数,用于计算 k 个数之和等于目标值
func kSum(nums []int, left, right int, target int, k int, cur []int, res *[][]int) {
    if right-left+1 < k || k < 2 || target < nums[left]*k || target > nums[right]*k {
        return
    }
    if k == 2 {
        // 2 sum
        twoSum(nums, left, right, target, cur, res)
    } else {
        for i := left; i < len(nums); i++ {
            if i == left || (i > left && nums[i-1] != nums[i]) {
                next := make([]int, len(cur))
                copy(next, cur)
                next = append(next, nums[i])
                kSum(nums, i+1, len(nums)-1, target-nums[i], k-1, next, res)
            }
        }
    }
}

// 计算两数之和为目标值的函数
func twoSum(nums []int, left, right int, target int, cur []int, res *[][]int) {
    for left < right {
        sum := nums[left] + nums[right]
        if sum == target {
            cur = append(cur, nums[left], nums[right])
            temp := make([]int, len(cur))
            copy(temp, cur)
            *res = append(*res, temp)
            // 恢复 cur 到之前状态
            cur = cur[:len(cur)-2]
            left++
            right--
            // 跳过重复的数字
            for left < right && nums[left] == nums[left-1] {
                left++
            }
            for left < right && nums[right] == nums[right+1] {
                right--
            }
        } else if sum < target {
            left++
        } else {
            right--
        }
    }
}

Python

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()  # 对输入数组进行排序
        ans = []  # 存储结果的列表
        n = len(nums)

        for a in range(n - 3):
            x = nums[a]  # 第一个数
            if a and x == nums[a - 1]:  # 避免重复解
                continue
            if x + nums[a + 1] + nums[a + 2] + nums[a + 3] > target:  # 剪枝,如果最小的四个数之和大于目标值,退出循环
                break
            if x + nums[-3] + nums[-2] + nums[-1] < target:  # 剪枝,如果最大的三个数和当前数的和小于目标值,继续下一个数
                continue
            for b in range(a + 1, n - 2):
                y = nums[b]  # 第二个数
                if b > a + 1 and y == nums[b - 1]:  # 避免重复解
                    continue
                if x + y + nums[b + 1] + nums[b + 2] > target:  # 剪枝,如果最小的三个数和当前数的和大于目标值,退出循环
                    break
                if x + y + nums[-2] + nums[-1] < target:  # 剪枝,如果最大的两个数和当前数的和小于目标值,继续下一个数
                    continue
                c = b + 1
                d = n - 1
                while c < d:
                    s = x + y + nums[c] + nums[d]  # 四数之和
                    if s > target:
                        d -= 1
                    elif s < target:
                        c += 1
                    else:  # s == target,找到满足条件的四元组
                        ans.append([x, y, nums[c], nums[d]])
                        c += 1
                        while c < d and nums[c] == nums[c - 1]:  # 跳过重复数字
                            c += 1
                        d -= 1
                        while d > c and nums[d] == nums[d + 1]:  # 跳过重复数字
                            d -= 1
        return ans

Java

// 导入需要的 Java 类
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

// 定义一个名为 Solution 的类
class Solution {
    // 定义一个方法用于解决 3Sum 问题
    public List> threeSum(int[] nums) {
        return nSum(nums, 3, 0);  // 调用 nSum 方法来解决问题
    }

    // 定义一个方法用于解决 4Sum 问题
    public List> fourSum(int[] nums, int target) {
        return nSum(nums, 4, target);  // 调用 nSum 方法来解决问题
    }

    // nSum 方法实现了通用的 nSum 问题解决方案
    List> nSum(int[] a, int n, long target) {
        return new AbstractList>() {
            final List> res = new ArrayList<>();  // 存储结果的列表
            final List path = new ArrayList<>();       // 存储路径的列表

            @Override
            public int size() {
                init();  // 初始化并计算结果
                return res.size();  // 返回结果列表的大小
            }

            @Override
            public List get(int index) {
                init();  // 初始化并计算结果
                return res.get(index);  // 返回指定索引处的结果列表
            }

            // 初始化函数,在结果为空时进行计算
            void init() {
                if (res.isEmpty()) {
                    Arrays.sort(a);  // 对输入数组进行排序
                    dfs(a, 0, a.length - 1, n, target);  // 调用深度优先搜索来计算结果
                }
            }

            // 深度优先搜索函数,计算 nSum 的结果
            void dfs(int[] a, int i, int j, int n, long target) {
                if (n == 2) {
                    two(a, i, j, target);  // 如果 n 为 2,调用 two 函数计算结果
                } else if (n > 2) {
                    hit(a, i, j, n, target);  // 如果 n 大于 2,调用 hit 函数计算结果
                }
            }

            // 计算 twoSum 的结果
            void two(int[] a, int i, int j, long target) {
                if (i >= j) {
                    return;
                }
                long max = 0;
                long min = 0;
                for (int k = 0; k < 2; k++) {
                    min += a[i + k];
                    max += a[j - k];
                }
                if (target < min || target > max) {
                    return;
                }
                while (j > i) {
                    long sum = a[i] + a[j];
                    if (sum < target) {
                        i++;
                    } else if (sum > target) {
                        j--;
                    } else {
                        path.add(a[i]);
                        path.add(a[j]);
                        res.add(new ArrayList<>(path));
                        path.remove(path.size() - 1);
                        path.remove(path.size() - 1);
                        while (j > i && a[i] == a[i + 1]) {
                            i++;
                        }
                        while (j > i && a[i] == a[j - 1]) {
                            j--;
                        }
                        i++;
                        j--;
                    }
                }
            }

            // 计算 nSum(n > 2)的结果
            void hit(int[] a, int i, int j, int n, long target) {
                int begin = i;
                int end = j;
                if (i + n - 2 >= j) {
                    return;
                }
                long max = 0;
                long min = 0;
                for (int k = 0; k < n; k++) {
                    min += a[i + k];
                    max += a[j - k];
                }
                if (target < min || target > max) {
                    return;
                }
                while (j > i + n - 2) {
                    long sufMax = 0;
                    long preMin = 0;
                    for (int k = 0; k < n - 1; k++) {
                        preMin += a[i + k];
                        sufMax += a[j - k];
                    }
                    preMin += a[j];
                    sufMax += a[i];
                    if (sufMax < target) {
                        i++;
                    } else if (preMin > target) {
                        j--;
                    } else {
                        while (i != begin && j > i + n - 2 && a[i] == a[i - 1]) {
                            i++;
                        }
                        while (j != end && j > i + n - 2 && a[j] == a[j + 1]) {
                            j--;
                        }
                        path.add(a[i]);
                        dfs(a, i + 1, j, n - 1, target - a[i]);
                        path.remove(path.size() - 1);
                        i++;
                    }
                }
            }
        };
    }
}

Cpp

class Solution {
public:
    vector> fourSum(vector& nums, int target) {
        sort(nums.begin(), nums.end());  // 对输入数组进行排序
        vector> result;  // 存储结果的二维向量
        int n = nums.size();
        
        for (int i = 0; i < n - 3; ++i) {
            if (i > 0 && nums[i] == nums[i - 1])  // 避免重复解
                continue;

            for (int j = i + 1; j < n - 2; ++j) {
                if (j > i + 1 && nums[j] == nums[j - 1])  // 避免重复解
                    continue;

                int left = j + 1;
                int right = n - 1;

                while (left < right) {
                    long long sum = static_cast(nums[i]) + nums[j] + nums[left] + nums[right];

                    if (sum < target) {
                        ++left;
                    } else if (sum > target) {
                        --right;
                    } else {
                        result.push_back({nums[i], nums[j], nums[left], nums[right]});
                        
                        while (left < right && nums[left] == nums[left + 1])
                            ++left;
                        while (left < right && nums[right] == nums[right - 1])
                            --right;

                        ++left;
                        --right;
                    }
                }
            }
        }

        return result;
    }
};

Go 版本的基础知识

  • 基本语法: 熟悉 Go 语言的基本语法,包括变量声明、循环、条件语句等。
  • 切片和数组: 了解切片和数组的使用方式,它们在 Go 中用于存储数据。
  • 排序: 了解如何使用 sort 包来对切片进行排序。
  • 递归: 理解递归的概念和用法,因为 kSum 解法使用了递归。

Python 版本的基础知识

  • 基本语法: 熟悉 Python 的基本语法,包括变量声明、循环、条件语句等。
  • 列表和数组: 了解列表的使用方式,它在 Python 中常用于存储数据。
  • 排序: 了解如何使用内置的 sorted() 函数来对列表进行排序。
  • 双指针法: 理解双指针法的基本思想,它在这个问题的解决中起到了关键作用。

Java 版本的基础知识

  • 基本语法: 熟悉 Java 的基本语法,包括变量声明、循环、条件语句等。
  • 集合和列表: 了解集合和列表的使用方式,Java 中的 ArrayListList 接口在这个代码中被使用。
  • 排序: 了解如何使用 Arrays.sort() 方法来对数组进行排序。
  • 类和对象: 对 Java 中的类和对象有基本的了解,因为代码中定义了一个名为 Solution 的类。

C++ 版本的基础知识

  • 基本语法: 熟悉 C++ 的基本语法,包括变量声明、循环、条件语句等。
  • 向量和数组: 了解向量的使用方式,它在 C++ 中常用于存储数据。
  • 排序: 了解如何使用 std::sort() 函数来对向量进行排序。
  • 双指针法: 理解双指针法的基本思想,它在这个问题的解决中起到了关键作用。

19. Remove Nth Node From End of List

题目

Given the head of a linked list, remove the nth node from the end of the list and return its head.

Follow up: Could you do this in one pass?

Example 1:

Go-Python-Java-C-LeetCode高分解法-第三周合集_第1张图片

Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]

Example 2:

Input: head = [1], n = 1
Output: []

Example 3:

Input: head = [1,2], n = 1
Output: [1]

Constraints:

  • The number of nodes in the list is sz.
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

题目大意

删除链表中倒数第 n 个结点。

解题思路

Go 版本解题思路

  1. 首先,创建一个虚拟节点 dummy,将其下一个节点指向链表的头节点 head,这是为了处理删除头节点的情况。
  2. 初始化两个指针 firstsecond,分别指向链表的头部和虚拟节点。
  3. 让指针 first 先移动 n 步,这样 first 指针将会比 second 指针领先 n 个节点。
  4. 接下来,同时移动 firstsecond 指针,直到 first 到达链表末尾。
  5. 此时,second 指针停在倒数第 n+1 个节点,然后将其 next 指针指向下下个节点,即跳过倒数第 n 个节点,实现了删除操作。
  6. 最后,返回虚拟节点的下一个节点,即处理后的链表头部。

Python 版本解题思路

  1. 创建一个虚拟节点 dummy,将其 next 指向链表的头节点 head,以便处理删除头节点的情况。
  2. 初始化两个指针 firstsecond,分别指向链表的头部和虚拟节点。
  3. 让指针 first 先移动 n 步,这样 first 指针将会领先 second 指针 n 个节点。
  4. 接下来,使用一个 while 循环,同时移动 firstsecond 指针,直到 first 到达链表末尾。
  5. 此时,second 指针停在倒数第 n+1 个节点,然后将其 next 指针指向下下个节点,即跳过倒数第 n 个节点,实现了删除操作。
  6. 最后,返回虚拟节点的下一个节点,即处理后的链表头部。

Java 版本解题思路

  1. 创建一个虚拟节点 dummy,将其 next 指向链表的头节点 head,以便处理删除头节点的情况。
  2. 初始化两个指针 firstsecond,分别指向链表的头部和虚拟节点。
  3. 让指针 first 先移动 n 步,这样 first 指针将会领先 second 指针 n 个节点。
  4. 接下来,使用一个 while 循环,同时移动 firstsecond 指针,直到 first 到达链表末尾。
  5. 此时,second 指针停在倒数第 n+1 个节点,然后将其 next 指针指向下下个节点,即跳过倒数第 n 个节点,实现了删除操作。
  6. 最后,返回虚拟节点的下一个节点,即处理后的链表头部。

C++ 版本解题思路

  1. 创建一个虚拟节点 dummy,将其 next 指向链表的头节点 head,以便处理删除头节点的情况。
  2. 初始化两个指针 firstsecond,分别指向链表的头部和虚拟节点。
  3. 让指针 first 先移动 n 步,这样 first 指针将会领先 second 指针 n 个节点。
  4. 接下来,使用一个 while 循环,同时移动 firstsecond 指针,直到 first 到达链表末尾。
  5. 此时,second 指针停在倒数第 n+1 个节点,然后将其 next 指针指向下下个节点,即跳过倒数第 n 个节点,实现了删除操作。
  6. 最后,返回虚拟节点的下一个节点,即处理后的链表头部。

以上就是每个版本的解题思路,它们都使用了双指针技巧,通过一次遍历就能找到并删除倒数第 n 个节点。

代码

Go

/**
 * 单链表的定义
 * type ListNode struct {
 *     Val int             // 当前节点的值
 *     Next *ListNode      // 指向下一个节点的指针
 * }
 */
func removeNthFromEnd(head *ListNode, n int) *ListNode {
    dummy := &ListNode{Next: head}  // 创建一个虚拟节点,它的下一个节点是链表的头节点
    first, second := head, dummy    // 定义两个指针,分别称为 first 和 second,初始指向链表的头部和虚拟节点

    for i := 0 ; i < n ; i++ {      // 将 first 指针向前移动 n 步
        first = first.Next 
    }

    for ; first != nil ; first, second = first.Next, second.Next {}  // 同时移动 first 和 second 指针,直到 first 到达链表末尾

    second.Next = second.Next.Next   // 删除倒数第 n 个节点,即将 second 的下一个节点指针直接跳过第 n 个节点,指向第 n+1 个节点

    return dummy.Next  // 返回虚拟节点的下一个节点,即处理后的链表头部
}

Python

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        dummy = ListNode()  # 创建一个虚拟节点
        dummy.next = head  # 虚拟节点的下一个节点是链表的头节点
        first, second = head, dummy  # 定义两个指针,first 和 second,分别指向头节点和虚拟节点

        for i in range(n):
            first = first.next  # 将 first 指针向前移动 n 步

        while first:
            first = first.next
            second = second.next  # 同时移动 first 和 second 指针,直到 first 到达链表末尾

        second.next = second.next.next  # 删除倒数第 n 个节点

        return dummy.next  # 返回虚拟节点的下一个节点,即处理后的链表头部

Java

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode();  // 创建一个虚拟节点
        dummy.next = head;  // 虚拟节点的下一个节点是链表的头节点
        ListNode first = head, second = dummy;  // 定义两个指针,first 和 second,分别指向头节点和虚拟节点

        for (int i = 0; i < n; i++) {
            first = first.next;  // 将 first 指针向前移动 n 步
        }

        while (first != null) {
            first = first.next;
            second = second.next;  // 同时移动 first 和 second 指针,直到 first 到达链表末尾
        }

        second.next = second.next.next;  // 删除倒数第 n 个节点

        return dummy.next;  // 返回虚拟节点的下一个节点,即处理后的链表头部
    }
}

Cpp

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode();  // 创建一个虚拟节点
        dummy->next = head;  // 虚拟节点的下一个节点是链表的头节点
        ListNode* first = head, *second = dummy;  // 定义两个指针,first 和 second,分别指向头节点和虚拟节点

        for (int i = 0; i < n; i++) {
            first = first->next;  // 将 first 指针向前移动 n 步
        }

        while (first != nullptr) {
            first = first->next;
            second = second->next;  // 同时移动 first 和 second 指针,直到 first 到达链表末尾
        }

        second->next = second->next->next;  // 删除倒数第 n 个节点

        return dummy->next;  // 返回虚拟节点的下一个节点,即处理后的链表头部
    }
};

Go 版本的代码使用了 Go 语言来实现链表节点的删除操作。以下是相关的基础知识:

  1. 定义结构体: Go 使用结构体来定义自定义类型。在代码中,ListNode 是一个结构体,用于表示链表节点。结构体的成员包括 Val(节点的值)和 Next(指向下一个节点的指针)。

  2. 指针操作: Go 支持指针,可以通过指针来修改变量的值。在代码中,dummy 是一个指向虚拟节点的指针,firstsecond 是指向链表节点的指针,通过指针操作来进行链表的遍历和删除操作。

  3. 循环: Go 使用 for 关键字来进行循环操作。在代码中,通过 for 循环来将 first 指针移动 n 步,并且在第二个 for 循环中,同时移动 firstsecond 指针直到 first 到达链表末尾。

Python 版本的代码使用了 Python 语言来实现链表节点的删除操作。以下是相关的基础知识:

  1. 类和对象: Python 是面向对象的语言,通过类来定义对象的结构和行为。在代码中,ListNode 是一个类,用于表示链表节点。它有成员变量 val(节点的值)和 next(指向下一个节点的引用)。

  2. 循环: Python 使用 for 循环进行迭代。在代码中,通过 for 循环来将 first 指针移动 n 步,并在第二个 while 循环中,同时移动 firstsecond 指针直到 first 到达链表末尾。

Java 版本的代码使用了 Java 语言来实现链表节点的删除操作。以下是相关的基础知识:

  1. 类和对象: Java 也是面向对象的语言,使用类来定义对象的属性和方法。在代码中,ListNode 是一个类,用于表示链表节点。它有成员变量 val(节点的值)和 next(指向下一个节点的引用)。

  2. 循环: Java 使用 forwhile 循环进行迭代。在代码中,通过 for 循环来将 first 指针移动 n 步,并在第二个 while 循环中,同时移动 firstsecond 指针直到 first 到达链表末尾。

C++ 版本的代码使用了 C++ 语言来实现链表节点的删除操作。以下是相关的基础知识:

  1. 结构体: C++ 使用结构体来定义自定义类型。在代码中,ListNode 是一个结构体,用于表示链表节点。结构体的成员包括 val(节点的值)和 next(指向下一个节点的指针)。

  2. 指针操作: C++ 支持指针,可以通过指针来访问和修改变量。在代码中,dummy 是一个指向虚拟节点的指针,firstsecond 是指向链表节点的指针,通过指针操作来进行链表的遍历和删除操作。

  3. 循环: C++ 使用 forwhile 循环进行迭代。在代码中,通过 for 循环来将 first 指针移动 n 步,并在第二个 while 循环中,同时移动 firstsecond 指针直到 first 到达链表末尾。

20. Valid Parentheses

题目

Given a string containing just the characters ‘(’, ‘)’, ‘{’, ‘}’, ‘[’ and ‘]’, determine if the input string is valid.

An input string is valid if:

Open brackets must be closed by the same type of brackets.
Open brackets must be closed in the correct order.
Note that an empty string is also considered valid.

Example 1:

Input: "()"
Output: true

Example 2:

Input: "()[]{}"
Output: true

Example 3:

Input: "(]"
Output: false

Example 4:

Input: "([)]"
Output: false

Example 5:

Input: "{[]}"
Output: true

题目大意

括号匹配问题。

解题思路

遇到左括号就进栈push,遇到右括号并且栈顶为与之对应的左括号,就把栈顶元素出栈。最后看栈里面还有没有其他元素,如果为空,即匹配。

需要注意,空字符串是满足括号匹配的,即输出 true。

每个版本解释解题思路。

Go 版本解题思路:

  • 创建一个空的 rune 类型切片,用作栈。
  • 遍历字符串中的每个字符。
  • 如果字符是左括号 [, (, 或 {,则将其压入栈中。
  • 如果字符是右括号 ], ), 或 },且栈非空,且栈顶元素与当前字符匹配,则将栈顶元素弹出,表示找到一对匹配的括号。
  • 如果字符不是上述情况之一,说明括号不匹配,返回 false
  • 遍历结束后,如果栈为空,表示所有括号都匹配成功,返回 true,否则返回 false

Python 版本解题思路:

  • 创建一个空列表,用作栈。
  • 创建一个映射字典,存储括号的配对关系(例如:")": "(")。
  • 遍历输入字符串中的每个字符。
  • 如果字符是右括号,尝试从栈中弹出一个元素,如果栈为空,将 left 设为空字符串。
  • 如果字符是左括号,将其压入栈。
  • 遍历结束后,如果栈为空,说明所有括号都匹配成功,返回 True,否则返回 False

ava 版本解题思路:

  • 初始化一个字符数组,用于存储下一个应匹配的字符。
  • 遍历输入字符串中的每个字符。
  • 如果字符是左括号,将相应的右括号压入字符数组。
  • 如果字符是右括号,且字符数组非空,尝试与字符数组中的字符匹配。
  • 若匹配失败,或字符数组为空,返回 false
  • 遍历结束后,如果字符数组为空,则所有括号都匹配成功,返回 true,否则返回 false

C++ 版本解题思路:

  • 创建一个 std::stack 数据结构,用作栈。
  • 遍历输入字符串中的每个字符。
  • 如果字符是左括号 (, {, 或 [,则将其压入栈。
  • 如果字符是右括号 ), }, 或 ],且栈非空,且与栈顶元素匹配,则弹出栈顶元素。
  • 如果字符不匹配或栈为空,返回 false
  • 遍历结束后,如果栈为空,则所有括号都匹配成功,返回 true,否则返回 false

以上是每个版本解题思路的详细描述。不同版本的代码在表达上可能有细微差异,但核心思想都是基于栈的括号匹配。希望这些解题思路能够帮助你更好地理解每个版本的代码。如果你还有任何疑问,欢迎随时提问!

代码

Go

func isValid(s string) bool {
    // 空字符串直接返回 true
    if len(s) == 0 {
        return true
    }
    stack := make([]rune, 0)  // 创建一个空的 rune 类型切片,用作栈

    // 遍历字符串 s 中的每个字符
    for _, v := range s {
        // 如果字符是 '[', '(', 或者 '{',则将其压入栈中
        if (v == '[') || (v == '(') || (v == '{') {
            stack = append(stack, v)
        } else if ((v == ']') && len(stack) > 0 && stack[len(stack)-1] == '[') ||
            ((v == ')') && len(stack) > 0 && stack[len(stack)-1] == '(') ||
            ((v == '}') && len(stack) > 0 && stack[len(stack)-1] == '{') {
            // 如果字符是 ']', ')' 或 '}',并且栈非空,且栈顶元素与当前字符匹配,
            // 则将栈顶元素弹出,表示找到一对匹配的括号
            stack = stack[:len(stack)-1]
        } else {
            // 如果字符不是上述情况之一,则说明括号不匹配,返回 false
            return false
        }
    }
    // 遍历结束后,如果栈为空,则表示所有括号都匹配成功,返回 true,否则返回 false
    return len(stack) == 0
}

Python

class Solution:
    def isValid(self, s: str) -> bool:
        stack = []  # 创建一个空列表,用作栈

        pair = {
            ")": "(",
            "]": "[",
            "}": "{"
        }  # 创建映射字典,存储括号的配对关系

        for x in s:
            if x in pair:
                left = stack.pop() if stack else ""
                # 如果当前字符是右括号,尝试从栈中弹出一个元素
                # 如果栈为空,将 left 设为空字符串
                if left != pair[x]:
                    return False  # 如果左括号不匹配,返回 False
            else:
                stack.append(x)  # 如果当前字符是左括号,将其压入栈

        if stack:
            return False  # 遍历结束后,如果栈不为空,返回 False,说明括号不匹配
        else:
            return True  # 如果栈为空,返回 True,说明所有括号都匹配成功

Java

class Solution {
    public boolean isValid(String s) {
        int length = s.length();

        // 如果字符串长度为奇数或者长度小于等于 1,直接返回 false
        if (length <= 1 || length % 2 != 0) {
            return false;
        }

        char[] nextChars = new char[length / 2 + 1]; // 创建字符数组,类似栈,用于存储下一个应匹配的字符
        int nextIndex = 0;

        // 遍历输入字符串中的每个字符
        for (int i = 0; i < length && nextIndex < nextChars.length; i++) {
            char c = s.charAt(i);

            // 如果是左括号,将相应的右括号压入 nextChars 数组
            if (c == '(') {
                nextChars[nextIndex++] = ')';
            } else if (c == '[') {
                nextChars[nextIndex++] = ']';
            } else if (c == '{') {
                nextChars[nextIndex++] = '}';
            } else if (i > 0) {
                // 如果是右括号,且 nextChars 数组非空,尝试与 nextChars 数组中的字符匹配
                nextIndex--;

                // 若匹配失败,或者 nextChars 数组为空,返回 false
                if (nextIndex < 0 || nextChars[nextIndex] != c) {
                    return false;
                }
            }
        }

        // 遍历结束后,如果 nextIndex 为 0,则表示所有括号都匹配成功,返回 true,否则返回 false
        return nextIndex == 0;
    }
}

Cpp

#include 
#include 
using namespace std;

class Solution {
public:
    bool isValid(string s) {
        stack st; // 创建一个字符类型的栈

        // 遍历输入字符串中的每个字符
        for (auto c : s) {
            if (c == '(' || c == '{' || c == '[') {
                st.push(c); // 如果字符是左括号,压入栈
            } else if (st.empty()) {
                return false; // 如果字符是右括号且栈为空,说明括号不匹配,返回 false
            } else if (abs(st.top() - c) <= 2) {
                st.pop(); // 如果字符是右括号且与栈顶元素匹配,弹出栈顶元素
            } else {
                return false; // 不匹配的情况,返回 false
            }
        }

        // 遍历结束后,如果栈为空,则所有括号都匹配成功,返回 true,否则返回 false
        return st.empty();
    }
};

每个版本的代码所需的基础知识。

Go 版本:

  • 变量和数据类型: 了解 Go 中的基本数据类型,如整数、字符、字符串等,以及如何声明和初始化变量。
  • 切片(Slices): 理解切片是一种动态数组,可以动态增加和减少大小。
  • 循环和条件语句: 熟悉 Go 中的循环和条件判断语句,如 for 循环和 if 语句。
  • 函数: 知道如何定义和调用函数,以及函数的参数和返回值。
  • 数据结构: 对栈的基本概念有一定了解,知道栈是一种后进先出(LIFO)的数据结构。

Python 版本:

  • 基本语法: 了解 Python 的基本语法,如变量赋值、条件语句、循环语句等。
  • 列表和字典: 知道如何创建和操作列表和字典这两种基本的数据结构。
  • 循环和条件语句: 熟悉 for 循环和 if 条件语句的使用方法。
  • 函数: 知道如何定义和调用函数,了解函数的参数和返回值。
  • 堆栈: 对堆栈的基本概念有一定了解,知道堆栈是一种后进先出(LIFO)的数据结构。

Java 版本:

  • 基本语法: 了解 Java 的基本语法,包括变量声明、条件语句、循环语句等。
  • 数组和集合: 知道如何创建和使用数组和集合这两种常见的数据结构。
  • 循环和条件语句: 熟悉 for 循环和 if 条件语句的使用。
  • 函数: 知道如何定义和调用函数,了解函数的参数和返回值。
  • 栈: 对栈的基本概念有一定了解,知道栈是一种后进先出(LIFO)的数据结构。

C++ 版本:

  • 基本语法: 了解 C++ 的基本语法,包括变量声明、条件语句、循环语句等。
  • 数组和容器: 知道如何创建和使用数组以及容器(如 std::stack)这两种数据结构。
  • 循环和条件语句: 熟悉 for 循环和 if 条件语句的使用。
  • 函数: 知道如何定义和调用函数,了解函数的参数和返回值。
  • 栈: 对栈的基本概念有一定了解,知道栈是一种后进先出(LIFO)的数据结构。

21. Merge Two Sorted Lists

题目

Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of
the first two lists.

Example :

Input: 1->2->4, 1->3->4
Output: 1->1->2->3->4->4

题目大意

合并 2 个有序链表

解题思路

Go 解决方案解题思路:

  1. 如果链表 l1 为空,直接返回链表 l2,无需合并。
  2. 如果链表 l2 为空,直接返回链表 l1,无需合并。
  3. 如果链表 l1 的当前节点值小于链表 l2 的当前节点值,说明链表 l1 的当前节点可以放在合并链表中,并继续合并剩余部分。
  4. 否则,链表 l2 的当前节点可以放在合并链表中,并继续合并剩余部分。
  5. 返回合并后的链表头节点。

Python 解决方案解题思路:

  1. 创建一个虚拟头节点 dummy,它不属于合并链表的一部分,仅用于简化代码。
  2. 创建一个指针 current,初始时指向虚拟头节点,表示合并链表的当前节点。
  3. 通过比较链表 list1list2 的当前节点值,将较小的节点链接到合并链表,并更新相应的链表指针。
  4. 重复步骤 3 直到其中一个链表为空。
  5. 将剩余部分的链表直接链接到合并链表的末尾。
  6. 返回虚拟头节点的下一个节点,即合并后的链表头节点。

Java 解决方案解题思路:

  1. 如果链表 list1 为空,直接返回链表 list2,无需合并。
  2. 如果链表 list2 为空,直接返回链表 list1,无需合并。
  3. 如果链表 list1 的当前节点值小于等于链表 list2 的当前节点值,说明链表 list1 的当前节点可以放在合并链表中,并继续合并剩余部分。
  4. 否则,链表 list2 的当前节点可以放在合并链表中,并继续合并剩余部分。
  5. 返回合并后的链表头节点。

C++ 解决方案解题思路:

  1. 如果链表 l1 为空,直接返回链表 l2,无需合并。
  2. 如果链表 l2 为空,直接返回链表 l1,无需合并。
  3. 如果链表 l1 的当前节点值小于等于链表 l2 的当前节点值,说明链表 l1 的当前节点可以放在合并链表中,并继续合并剩余部分。
  4. 否则,链表 l2 的当前节点可以放在合并链表中,并继续合并剩余部分。
  5. 返回合并后的链表头节点。

代码

Go

/**
 * Definition for singly-linked list.
 * 单链表的定义,每个节点有一个整数值和指向下一个节点的指针。
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */

// mergeTwoLists 函数用于合并两个有序链表,返回合并后的链表头节点。
func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
    // 若链表 l1 为空,直接返回链表 l2,无需合并。
    if l1 == nil {
        return l2
    }
    // 若链表 l2 为空,直接返回链表 l1,无需合并。
    if l2 == nil {
        return l1
    }
    // 若链表 l1 的节点值小于链表 l2 的节点值,
    // 则将链表 l1 的当前节点与合并后的链表继续合并,
    // 并返回合并后的链表头节点为 l1。
    if l1.Val < l2.Val {
        l1.Next = mergeTwoLists(l1.Next, l2)
        return l1
    }
    // 若链表 l2 的节点值小于等于链表 l1 的节点值,
    // 则将链表 l2 的当前节点与合并后的链表继续合并,
    // 并返回合并后的链表头节点为 l2。
    l2.Next = mergeTwoLists(l1, l2.Next)
    return l2
}

Python

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        # 创建一个虚拟头节点,简化操作
        dummy = ListNode(0)
        current = dummy  # 指向当前节点的指针

        # 依次比较两个链表的节点,将较小的节点链接到合并链表
        while list1 and list2:
            if list1.val < list2.val:
                current.next = list1
                list1 = list1.next
            else:
                current.next = list2
                list2 = list2.next
            current = current.next

        # 将剩余部分链接到合并链表
        if list1:
            current.next = list1
        else:
            current.next = list2

        return dummy.next  # 返回合并后的链表头节点

Java

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        // 若其中一个链表为空,直接返回另一个链表
        if (list1 == null) {
            return list2;
        }
        if (list2 == null) {
            return list1;
        }
        
        // 若链表 list1 的节点值小于等于链表 list2 的节点值,
        // 则将链表 list1 的当前节点与合并后的链表继续合并,
        // 并返回合并后的链表头节点为 list1。
        if (list1.val <= list2.val) {
            list1.next = mergeTwoLists(list1.next, list2); // 递归合并剩余部分
            return list1;
        }
        
        // 若链表 list2 的节点值小于链表 list1 的节点值,
        // 则将链表 list2 的当前节点与合并后的链表继续合并,
        // 并返回合并后的链表头节点为 list2。
        list2.next = mergeTwoLists(list1, list2.next); // 递归合并剩余部分
        return list2;
    }
}

Cpp

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        // 若其中一个链表为空,直接返回另一个链表
        if (l1 == NULL) {
            return l2;
        }
        if (l2 == NULL) {
            return l1;
        }
        
        // 若链表 l1 的节点值小于等于链表 l2 的节点值,
        // 则将链表 l1 的当前节点与合并后的链表继续合并,
        // 并返回合并后的链表头节点为 l1。
        if (l1->val <= l2->val) {
            l1->next = mergeTwoLists(l1->next, l2); // 递归合并剩余部分
            return l1;
        }
        
        // 若链表 l2 的节点值小于链表 l1 的节点值,
        // 则将链表 l2 的当前节点与合并后的链表继续合并,
        // 并返回合并后的链表头节点为 l2。
        l2->next = mergeTwoLists(l1, l2->next); // 递归合并剩余部分
        return l2;
    }
};

Go 解决方案所需基础知识:

  • Go 语言基础: 了解 Go 语言的基本语法、变量声明、函数定义等。
  • 递归: 理解递归的概念和用法,能够编写递归函数。
  • 链表: 知道链表的基本概念,理解链表节点的结构以及如何通过指针操作链表。

Python 解决方案所需基础知识:

  • Python 基础: 熟悉 Python 语言的基本语法,包括变量、函数、类的定义等。
  • 递归: 理解递归的原理和应用,能够使用递归解决问题。
  • 链表: 了解链表的基本概念,知道如何定义链表节点并进行操作。

Java 解决方案所需基础知识:

  • Java 基础: 熟悉 Java 语言的基本语法,包括变量、函数、类的定义等。
  • 递归: 理解递归的概念和原理,能够编写递归函数。
  • 链表: 理解链表的概念,知道如何定义链表节点并进行操作。

C++ 解决方案所需基础知识:

  • C++ 基础: 了解 C++ 语言的基本语法,包括变量、函数、类的定义等。
  • 递归: 理解递归的原理和用法,能够编写递归函数。
  • 链表: 知道链表的基本概念,了解如何定义链表节点并进行操作。

你可能感兴趣的:(LeetCode,golang,python,java)