本题解Go语言部分基于 LeetCode-Go
其他部分基于本人实践学习
个人题解GitHub连接:LeetCode-Go-Python-Java-C
Go-Python-Java-C-LeetCode高分解法-第一周合集
Go-Python-Java-C-LeetCode高分解法-第二周合集
本文部分内容来自网上搜集与个人实践。如果任何信息存在错误,欢迎读者批评指正。本文仅用于学习交流,不用作任何商业用途。
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 版本):
a
),将问题转化为在剩余数组中查找两个数之和等于 -a
的问题。解题思路(Python 版本):
counts
)来记录每个数字的出现次数。解题思路(Java 版本):
a
),将问题转化为在剩余数组中查找两个数之和等于 -a
的问题。解题思路(C++ 版本):
a
),将问题转化为在剩余数组中查找两个数之和等于 -a
的问题。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 // 返回结果二维切片
}
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 # 返回结果列表
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; // 返回结果列表
}
}
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 版本:
切片(Slice): 在 Go 中,切片是动态数组,是一种常见的数据结构。你需要了解如何创建切片、访问切片元素、切片的长度和容量等基本操作。
排序(Sorting): Go 提供了排序函数 sort.Ints
来对整数切片进行排序。了解如何使用排序函数对切片进行排序。
循环和条件语句: 掌握 Go 中的 for
循环和 if
条件语句,以及如何在循环中使用 continue
和 break
。
切片的添加元素: 学习如何使用 append
函数在切片末尾添加元素。
双指针技巧: 了解如何使用双指针技巧来解决查找问题,例如在本问题中的左右指针用法。
Python 版本:
列表(List): 在 Python 中,列表是一种动态数组。你需要了解如何创建列表、访问列表元素、列表的方法和属性等。
字典(Dictionary): 字典是键值对的集合,类似于其他编程语言中的哈希表或映射。了解如何使用字典存储数字出现次数。
排序: 学习如何使用内置函数 sorted
对列表进行排序。
循环和条件语句: 掌握在 Python 中的 for
循环和 if
条件语句,以及如何在循环中使用 continue
和 break
。
双指针技巧: 学习如何使用双指针技巧来解决问题,例如在本问题中的左右指针用法。
Java 版本:
数组(Array): 了解如何创建数组、访问数组元素以及数组的属性。
排序: 掌握如何使用 Java 中的 Arrays.sort
方法对数组进行排序。
循环和条件语句: 学习在 Java 中的 for
循环和 if
条件语句,以及如何使用 break
和 continue
控制循环。
列表(List): Java 中的 List
是一种常见的数据结构,类似于动态数组。了解如何使用 List
存储数据。
双指针技巧: 掌握在 Java 中如何使用双指针技巧来解决问题,例如在本问题中的左右指针用法。
C++ 版本:
向量(Vector): C++ 中的 vector
类似于其他编程语言中的动态数组。了解如何创建向量、访问向量元素以及向量的方法。
排序: 学习如何使用 sort
函数对向量进行排序。
循环和条件语句: 掌握在 C++ 中的 for
循环和 if
条件语句,以及如何使用 break
和 continue
控制循环。
向量的添加元素: 了解如何使用 push_back
函数在向量末尾添加元素。
双指针技巧: 学习如何使用双指针技巧来解决问题,例如在本问题中的左右指针用法。
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 版本解题思路:
首先,获取数组的长度,然后对数组进行升序排序,以便于后续的双指针法查找。
初始化一个最小差值 minDiff
为整型最大值,并定义一个变量 ans
来存储最接近目标的和。
使用循环遍历数组,从第一个元素到倒数第三个元素。在循环中,检查是否需要跳过重复的元素。
尝试以当前元素为基准,计算三个元素的和 sum
,分情况讨论:
sum
大于目标值 target
,判断差值是否更小,若是则更新 ans
。sum
小于目标值 target
,判断差值是否更小,若是则更新 minDiff
。如果 sum
小于目标值 target
,使用双指针法在剩余区间内查找最接近目标值的和:
j
为当前元素的下一个元素,右指针 k
为数组末尾元素。sum
,若和等于目标值,直接返回目标值。ans
和 minDiff
,然后左指针右移。ans
和 minDiff
,然后右指针左移。最后,返回存储最接近目标的和 ans
。
Python 版解题思路:
Python 版本的思路与 Go 版本类似,只是使用了 Python 特有的列表和内置函数,具体如下:
获取列表的长度,然后对列表进行升序排序。
初始化一个最小差值 minDiff
为正无穷大,并定义一个变量 closestSum
来存储最接近目标的和。
使用循环遍历列表,从第一个元素到倒数第三个元素。在循环中,检查是否需要跳过重复的元素。
尝试以当前元素为基准,计算三个元素的和 sum_
,分情况讨论:
sum_
大于目标值 target
,判断差值是否更小,若是则更新 closestSum
。sum_
小于目标值 target
,判断差值是否更小,若是则更新 minDiff
。如果 sum_
小于目标值 target
,使用双指针法在剩余区间内查找最接近目标值的和:
j
为当前元素的下一个元素,右指针 k
为列表末尾元素。sum_
,若和等于目标值,直接返回目标值。closestSum
和 minDiff
,然后右指针左移。closestSum
和 minDiff
,然后左指针右移。最后,返回存储最接近目标的和 closestSum
。
Java 版解题思路:
Java 版本的解题思路与其他版本类似,同样使用了双指针法和数组,具体如下:
获取数组的长度,然后对数组进行升序排序。
初始化一个最小差值 minDiff
为整型最大值,并定义一个变量 closestSum
来存储最接近目标的和。
使用循环遍历数组,从第一个元素到倒数第三个元素。在循环中,检查是否需要跳过重复的元素。
尝试以当前元素为基准,计算三个元素的和 sum
,分情况讨论:
sum
大于目标值 target
,判断差值是否更小,若是则更新 closestSum
。sum
小于目标值 target
,判断差值是否更小,若是则更新 minDiff
。如果 sum
小于目标值 target
,使用双指针法在剩余区间内查找最接近目标值的和:
j
为当前元素的下一个元素,右指针 k
为数组末尾元素。sum
,若和等于目标值,直接返回目标值。closestSum
和 minDiff
,然后右指针左移。closestSum
和 minDiff
,然后左指针右移。最后,返回存储最接近目标的和 closestSum
。
C++ 版解题思路:
C++ 版本的解题思路与其他版本类似,同样使用了双指针法和向量(vector),具体如下:
获取向量的长度,然后对向量进行升序排序。
初始化一个最小差值 minDiff
为整型最大值,并定义一个变量 closestSum
来存储最接近目标的和。
使用循环遍历向量,从第一个元素到倒数第三个元素。在循环中,检查是否需要跳过重复的元素。
尝试以当前元素为基准,计算三个元素的和 sum
,分情况讨论:
sum
大于目标值 target
,判断差值是否更小,若是则更新 closestSum
。sum
小于目标值 target
,判断差值是否更小,若是则更新 minDiff
。如果 sum
小于目标值 target
,使用双指针法在剩余区间内查找最接近目标值的和:
j
为当前元素的下一个元素,右指针 k
为向量末尾元素。sum
,若和等于目标值,直接返回目标值。closestSum
和 minDiff
,然后右指针左移。closestSum
和 minDiff
,然后左指针右移。最后,返回存储最接近目标的和 closestSum
。
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 // 返回最接近目标值的和
}
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 # 返回最接近目标值的和
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; // 返回最接近目标值的和
}
}
#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 版本代码所需基础知识:
基本语法: 理解 Go 语言的基本语法,包括变量声明、循环、条件语句、函数定义等。
切片和排序: 了解切片的概念和使用方法,以及如何使用标准库的 sort
包对切片进行排序。
双指针法: 理解双指针法的思想,即使用两个指针来在数组或切片中快速查找或计算。
数学库和常量: 知道如何使用数学库的常量 math.MaxInt
来表示整型最大值。
Python 版本代码所需基础知识:
基本语法: 熟悉 Python 的基本语法,包括变量声明、循环、条件语句、函数定义等。
列表和排序: 理解列表的概念和使用方法,以及如何使用内置函数 sorted()
对列表进行排序。
双指针法: 了解双指针法的思想,即使用两个指针来在列表中快速查找或计算。
浮点数表示: 知道如何使用 float('inf')
来表示正无穷大。
Java 版本代码所需基础知识:
基本语法: 掌握 Java 的基本语法,包括变量声明、循环、条件语句、方法定义等。
数组和排序: 理解数组的概念和使用方法,以及如何使用 Arrays
类的 sort()
方法对数组进行排序。
双指针法: 熟悉双指针法的思想,即使用两个指针来在数组中快速查找或计算。
常量: 知道如何使用 Integer.MAX_VALUE
来表示整型最大值。
C++ 版本代码所需基础知识:
基本语法: 熟悉 C++ 的基本语法,包括变量声明、循环、条件语句、函数定义等。
向量和排序: 了解向量(vector
)的概念和使用方法,以及如何使用标准库的 sort()
函数对向量进行排序。
双指针法: 掌握双指针法的思想,即使用两个指针来在向量中快速查找或计算。
整型常量: 知道如何使用 INT_MAX
来表示整型最大值。
Given a string containing digits from2-9
inclusive, 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(深度优先搜索)
letterMap
数组,其中每个索引对应一个数字,存储了该数字对应的可能字母集合。res
,用于存储最终的字母组合结果。findCombination
,该函数会根据当前数字的索引递归地生成字母组合。letterCombinations
函数中,如果输入的数字串为空,直接返回空切片。findCombination
函数,将初始状态的数字串、索引和空字符串传入。递归过程中,每次都会将当前数字对应的字母加入当前组合 s
中,直到处理完所有数字。s
加入结果切片 res
中。res
。解法二:非递归
letterMap
数组,其中每个索引对应一个数字,存储了该数字对应的可能字母集合。res
,用于存储最终的字母组合结果。res
,将其中的每个字母作为起始元素。res
中的每个元素与字母集合中的每个字母组合,生成新的结果切片 tmp
。res
为新生成的结果切片 tmp
。res
。解法三:回溯
letterMap
数组,其中每个索引对应一个数字,存储了该数字对应的可能字母集合。res
,用于存储最终的字母组合结果。dict
字典,将每个数字映射到其对应的可能字母集合。letterFunc
,该函数会递归地生成字母组合。letterCombinations
函数中,如果输入的数字串为空,直接返回空切片。letterFunc
函数,将初始状态的空组合 ""
和数字串传入。递归过程中,每次将当前数字对应的每个字母加入组合 res
中,继续处理下一个数字。res
中。res
。解题思路(Python 版本):
与 Go 版本的解法思路相同,只是在 Python 中使用了不同的语法来实现相同的逻辑。
解题思路(Java 版本):
与 Go 版本的解法思路相同,只是在 Java 中使用了不同的语法来实现相同的逻辑。在 Java 版本中,数组和集合的操作稍微有所不同,需要使用 ArrayList 或其他集合类来存储最终结果。
解题思路(C++ 版本):
与 Go 版本的解法思路相同,只是在 C++ 中使用了不同的语法来实现相同的逻辑。在 C++ 版本中,需要使用 vector 来存储最终结果。
总的来说,不同版本的解法思路都是基于递归、深度优先搜索和回溯的思想,通过在每个数字上选择其中一个字母,然后递归地处理剩余数字,直到生成完整的字母组合。差异主要体现在语言的语法、数据结构和函数调用上。
// 解法一 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] // 回溯,移除最后一个字母
}
}
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)
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);
}
}
}
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 版本基础知识:
Python 版本基础知识:
Java 版本基础知识:
C++ 版本基础知识:
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 版本的解题思路
双指针法: 这个解法主要使用了双指针法,通过对数组进行排序,然后使用指针来确定数组中的元素。通过设置两个指针,一个指向当前元素,另一个指向数组的末尾,逐步移动指针以找到满足条件的四元组。
通用的 kSum 解法: 除了双指针法,这个解法还使用了一个通用的 kSum 解法。这个解法基于递归,将问题逐步转化为更小规模的 k-1 Sum 问题,直到最终转化为 2 Sum 问题,然后再使用双指针法来解决。这种通用性的解法可以用于解决不同规模的 kSum 问题。
Python 版本的解题思路
Java 版本的解题思路
双指针法: 这个版本的解法同样使用了双指针法。通过对数组进行排序,然后设置两个指针,一个从数组起始位置向后移动,另一个从数组末尾位置向前移动,根据当前指针指向的元素之和与目标值的比较结果,决定指针的移动方向,从而找到满足条件的四元组。
封装 nSum 函数: 这个版本的解法将 nSum 问题的求解封装在一个单独的函数中,这样可以在 3Sum 和 4Sum 问题中重复使用。nSum 函数会根据传入的参数来决定是计算 2Sum、3Sum 还是其他的 nSum 问题。
C++ 版本的解题思路
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--
}
}
}
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 类
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++;
}
}
}
};
}
}
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 版本的基础知识
sort
包来对切片进行排序。Python 版本的基础知识
sorted()
函数来对列表进行排序。Java 版本的基础知识
ArrayList
和 List
接口在这个代码中被使用。Arrays.sort()
方法来对数组进行排序。Solution
的类。C++ 版本的基础知识
std::sort()
函数来对向量进行排序。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:
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:
sz
.1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
删除链表中倒数第 n 个结点。
Go 版本解题思路
dummy
,将其下一个节点指向链表的头节点 head
,这是为了处理删除头节点的情况。first
和 second
,分别指向链表的头部和虚拟节点。first
先移动 n 步,这样 first
指针将会比 second
指针领先 n 个节点。first
和 second
指针,直到 first
到达链表末尾。second
指针停在倒数第 n+1 个节点,然后将其 next
指针指向下下个节点,即跳过倒数第 n 个节点,实现了删除操作。Python 版本解题思路
dummy
,将其 next
指向链表的头节点 head
,以便处理删除头节点的情况。first
和 second
,分别指向链表的头部和虚拟节点。first
先移动 n 步,这样 first
指针将会领先 second
指针 n 个节点。while
循环,同时移动 first
和 second
指针,直到 first
到达链表末尾。second
指针停在倒数第 n+1 个节点,然后将其 next
指针指向下下个节点,即跳过倒数第 n 个节点,实现了删除操作。Java 版本解题思路
dummy
,将其 next
指向链表的头节点 head
,以便处理删除头节点的情况。first
和 second
,分别指向链表的头部和虚拟节点。first
先移动 n 步,这样 first
指针将会领先 second
指针 n 个节点。while
循环,同时移动 first
和 second
指针,直到 first
到达链表末尾。second
指针停在倒数第 n+1 个节点,然后将其 next
指针指向下下个节点,即跳过倒数第 n 个节点,实现了删除操作。C++ 版本解题思路
dummy
,将其 next
指向链表的头节点 head
,以便处理删除头节点的情况。first
和 second
,分别指向链表的头部和虚拟节点。first
先移动 n 步,这样 first
指针将会领先 second
指针 n 个节点。while
循环,同时移动 first
和 second
指针,直到 first
到达链表末尾。second
指针停在倒数第 n+1 个节点,然后将其 next
指针指向下下个节点,即跳过倒数第 n 个节点,实现了删除操作。以上就是每个版本的解题思路,它们都使用了双指针技巧,通过一次遍历就能找到并删除倒数第 n 个节点。
/**
* 单链表的定义
* 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 // 返回虚拟节点的下一个节点,即处理后的链表头部
}
# 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 # 返回虚拟节点的下一个节点,即处理后的链表头部
/**
* 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; // 返回虚拟节点的下一个节点,即处理后的链表头部
}
}
/**
* 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 语言来实现链表节点的删除操作。以下是相关的基础知识:
定义结构体: Go 使用结构体来定义自定义类型。在代码中,ListNode
是一个结构体,用于表示链表节点。结构体的成员包括 Val
(节点的值)和 Next
(指向下一个节点的指针)。
指针操作: Go 支持指针,可以通过指针来修改变量的值。在代码中,dummy
是一个指向虚拟节点的指针,first
和 second
是指向链表节点的指针,通过指针操作来进行链表的遍历和删除操作。
循环: Go 使用 for
关键字来进行循环操作。在代码中,通过 for
循环来将 first
指针移动 n 步,并且在第二个 for
循环中,同时移动 first
和 second
指针直到 first
到达链表末尾。
Python 版本的代码使用了 Python 语言来实现链表节点的删除操作。以下是相关的基础知识:
类和对象: Python 是面向对象的语言,通过类来定义对象的结构和行为。在代码中,ListNode
是一个类,用于表示链表节点。它有成员变量 val
(节点的值)和 next
(指向下一个节点的引用)。
循环: Python 使用 for
循环进行迭代。在代码中,通过 for
循环来将 first
指针移动 n 步,并在第二个 while
循环中,同时移动 first
和 second
指针直到 first
到达链表末尾。
Java 版本的代码使用了 Java 语言来实现链表节点的删除操作。以下是相关的基础知识:
类和对象: Java 也是面向对象的语言,使用类来定义对象的属性和方法。在代码中,ListNode
是一个类,用于表示链表节点。它有成员变量 val
(节点的值)和 next
(指向下一个节点的引用)。
循环: Java 使用 for
和 while
循环进行迭代。在代码中,通过 for
循环来将 first
指针移动 n 步,并在第二个 while
循环中,同时移动 first
和 second
指针直到 first
到达链表末尾。
C++ 版本的代码使用了 C++ 语言来实现链表节点的删除操作。以下是相关的基础知识:
结构体: C++ 使用结构体来定义自定义类型。在代码中,ListNode
是一个结构体,用于表示链表节点。结构体的成员包括 val
(节点的值)和 next
(指向下一个节点的指针)。
指针操作: C++ 支持指针,可以通过指针来访问和修改变量。在代码中,dummy
是一个指向虚拟节点的指针,first
和 second
是指向链表节点的指针,通过指针操作来进行链表的遍历和删除操作。
循环: C++ 使用 for
和 while
循环进行迭代。在代码中,通过 for
循环来将 first
指针移动 n 步,并在第二个 while
循环中,同时移动 first
和 second
指针直到 first
到达链表末尾。
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 版本解题思路:
[
, (
, 或 {
,则将其压入栈中。]
, )
, 或 }
,且栈非空,且栈顶元素与当前字符匹配,则将栈顶元素弹出,表示找到一对匹配的括号。false
。true
,否则返回 false
。Python 版本解题思路:
")": "("
)。True
,否则返回 False
。ava 版本解题思路:
false
。true
,否则返回 false
。C++ 版本解题思路:
std::stack
数据结构,用作栈。(
, {
, 或 [
,则将其压入栈。)
, }
, 或 ]
,且栈非空,且与栈顶元素匹配,则弹出栈顶元素。false
。true
,否则返回 false
。以上是每个版本解题思路的详细描述。不同版本的代码在表达上可能有细微差异,但核心思想都是基于栈的括号匹配。希望这些解题思路能够帮助你更好地理解每个版本的代码。如果你还有任何疑问,欢迎随时提问!
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
}
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,说明所有括号都匹配成功
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;
}
}
#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 版本:
for
循环和 if
语句。Python 版本:
for
循环和 if
条件语句的使用方法。Java 版本:
for
循环和 if
条件语句的使用。C++ 版本:
std::stack
)这两种数据结构。for
循环和 if
条件语句的使用。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 解决方案解题思路:
l1
为空,直接返回链表 l2
,无需合并。l2
为空,直接返回链表 l1
,无需合并。l1
的当前节点值小于链表 l2
的当前节点值,说明链表 l1
的当前节点可以放在合并链表中,并继续合并剩余部分。l2
的当前节点可以放在合并链表中,并继续合并剩余部分。Python 解决方案解题思路:
dummy
,它不属于合并链表的一部分,仅用于简化代码。current
,初始时指向虚拟头节点,表示合并链表的当前节点。list1
和 list2
的当前节点值,将较小的节点链接到合并链表,并更新相应的链表指针。Java 解决方案解题思路:
list1
为空,直接返回链表 list2
,无需合并。list2
为空,直接返回链表 list1
,无需合并。list1
的当前节点值小于等于链表 list2
的当前节点值,说明链表 list1
的当前节点可以放在合并链表中,并继续合并剩余部分。list2
的当前节点可以放在合并链表中,并继续合并剩余部分。C++ 解决方案解题思路:
l1
为空,直接返回链表 l2
,无需合并。l2
为空,直接返回链表 l1
,无需合并。l1
的当前节点值小于等于链表 l2
的当前节点值,说明链表 l1
的当前节点可以放在合并链表中,并继续合并剩余部分。l2
的当前节点可以放在合并链表中,并继续合并剩余部分。/**
* 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
}
# 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 # 返回合并后的链表头节点
/**
* 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;
}
}
/**
* 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 解决方案所需基础知识:
Python 解决方案所需基础知识:
Java 解决方案所需基础知识:
C++ 解决方案所需基础知识: