Leetcode: 0011-0020题速览
本文材料来自于LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
遵从开源协议为知识共享 版权归属-相同方式共享 4.0 国际 公共许可证
研一在读备战就业,制此集合便于空闲时间浏览,有任何疑惑问题欢迎讨论,共同进步
目录
- Leetcode: 0011-0020题速览
- [11. 盛最多水的容器](https://leetcode.cn/problems/container-with-most-water)
- 题目描述
- 解法
- 方法一:双指针
- Python3
- Java
- C++
- [12. 整数转罗马数字](https://leetcode.cn/problems/integer-to-roman)
- 题目描述
- 解法
- 方法一:贪心
- Python3
- Java
- C++
- [13. 罗马数字转整数](https://leetcode.cn/problems/roman-to-integer)
- 题目描述
- 解法
- 方法一:哈希表 + 模拟
- Python3
- Java
- C++
- [14. 最长公共前缀](https://leetcode.cn/problems/longest-common-prefix)
- 题目描述
- 解法
- 方法一:字符比较
- Python3
- Java
- C++
- [15. 三数之和](https://leetcode.cn/problems/3sum)
- 题目描述
- 解法
- 方法一:排序 + 双指针
- Python3
- Java
- C++
- [16. 最接近的三数之和](https://leetcode.cn/problems/3sum-closest)
- 题目描述
- 解法
- 方法一:排序 + 双指针
- Python3
- Java
- C++
- [17. 电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number)
- 题目描述
- 解法
- 方法一:遍历
- Python3
- Java
- C++
- [18. 四数之和](https://leetcode.cn/problems/4sum)
- 题目描述
- 解法
- 方法一:排序 + 双指针
- Python3
- Java
- C++
- [19. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list)
- 题目描述
- 解法
- 方法一:快慢指针
- Python3
- Java
- C++
- [20. 有效的括号](https://leetcode.cn/problems/valid-parentheses)
- 题目描述
- 解法
- 方法一:栈
- Python3
- Java
- C++
11. 盛最多水的容器
题目描述
给定一个长度为 n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
。
找出其中的两条线,使得它们与 x
轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入:height = [1,1]
输出:1
提示:
n == height.length
2 <= n <= 105
0 <= height[i] <= 104
难度:中等
标签:贪心,数组,双指针
解法
方法一:双指针
一开始,我们考虑相距最远的两个柱子所能容纳水的容量。水的宽度是两根柱子之间的距离,而水的高度取决于两根柱子之间较短的那个。
当前柱子是最两侧的柱子,水的宽度最大,其他的组合,水的宽度都比这个小。不妨假设左侧柱子的高度小于等于右侧柱子的高度,那么水的高度就是左侧柱子的高度。如果我们移动右侧柱子,那么水的宽度就减小了,而水的高度却不会增加,因此水的容量一定减少。所以我们移动左侧柱子,更新最大容量。
循环此过程,直到两个柱子相遇。
时间复杂度
O
(
n
)
O(n)
O(n),其中
n
n
n 是数组 height
的长度。空间复杂度
O
(
1
)
O(1)
O(1)。
Python3
class Solution:
def maxArea(self, height: List[int]) -> int:
i, j = 0, len(height) - 1
ans = 0
while i < j:
t = (j - i) * min(height[i], height[j])
ans = max(ans, t)
if height[i] < height[j]:
i += 1
else:
j -= 1
return ans
Java
class Solution {
public int maxArea(int[] height) {
int i = 0, j = height.length - 1;
int ans = 0;
while (i < j) {
int t = Math.min(height[i], height[j]) * (j - i);
ans = Math.max(ans, t);
if (height[i] < height[j]) {
++i;
} else {
--j;
}
}
return ans;
}
}
C++
class Solution {
public:
int maxArea(vector<int>& height) {
int i = 0, j = height.size() - 1;
int ans = 0;
while (i < j) {
int t = min(height[i], height[j]) * (j - i);
ans = max(ans, t);
if (height[i] < height[j]) {
++i;
} else {
--j;
}
}
return ans;
}
};
12. 整数转罗马数字
题目描述
七个不同的符号代表罗马数字,其值如下:
符号 | 值 |
---|---|
I | 1 |
V | 5 |
X | 10 |
L | 50 |
C | 100 |
D | 500 |
M | 1000 |
罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则:
- 如果该值不是以 4 或 9 开头,请选择可以从输入中减去的最大值的符号,将该符号附加到结果,减去其值,然后将其余部分转换为罗马数字。
- 如果该值以 4 或 9 开头,使用 减法形式,表示从以下符号中减去一个符号,例如 4 是 5 (
V
) 减 1 (I
):IV
,9 是 10 (X
) 减 1 (I
):IX
。仅使用以下减法形式:4 (IV
),9 (IX
),40 (XL
),90 (XC
),400 (CD
) 和 900 (CM
)。 - 只有 10 的次方(
I
,X
,C
,M
)最多可以连续附加 3 次以代表 10 的倍数。你不能多次附加 5 (V
),50 (L
) 或 500 (D
)。如果需要将符号附加4次,请使用 减法形式。
给定一个整数,将其转换为罗马数字。
示例 1:
输入:num = 3749
输出: "MMMDCCXLIX"
解释:
3000 = MMM 由于 1000 (M) + 1000 (M) + 1000 (M)
700 = DCC 由于 500 (D) + 100 © + 100 ©
40 = XL 由于 50 (L) 减 10 (X)
9 = IX 由于 10 (X) 减 1 (I)
注意:49 不是 50 (L) 减 1 (I) 因为转换是基于小数位
示例 2:
输入:num = 58
输出:"LVIII"
解释:
50 = L
8 = VIII
示例 3:
输入:num = 1994
输出:"MCMXCIV"
解释:
1000 = M
900 = CM
90 = XC
4 = IV
提示:
1 <= num <= 3999
难度:中等
标签:哈希表,数学,字符串
解法
方法一:贪心
我们可以先将所有可能的符号 c s cs cs 和对应的数值 v s vs vs 列出来,然后从大到小枚举每个数值 v s [ i ] vs[i] vs[i],每次尽可能多地使用该数值对应的符号 c s [ i ] cs[i] cs[i],直到数字 n u m num num 变为 0 0 0。
时间复杂度为 O ( m ) O(m) O(m),空间复杂度为 O ( m ) O(m) O(m)。其中 m m m 为符号的个数。
Python3
class Solution:
def intToRoman(self, num: int) -> str:
cs = ('M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I')
vs = (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1)
ans = []
for c, v in zip(cs, vs):
while num >= v:
num -= v
ans.append(c)
return ''.join(ans)
Java
class Solution {
public String intToRoman(int num) {
List<String> cs
= List.of("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I");
List<Integer> vs = List.of(1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1);
StringBuilder ans = new StringBuilder();
for (int i = 0, n = cs.size(); i < n; ++i) {
while (num >= vs.get(i)) {
num -= vs.get(i);
ans.append(cs.get(i));
}
}
return ans.toString();
}
}
C++
class Solution {
public:
string intToRoman(int num) {
vector<string> cs = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
vector<int> vs = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
string ans;
for (int i = 0; i < cs.size(); ++i) {
while (num >= vs[i]) {
num -= vs[i];
ans += cs[i];
}
}
return ans;
}
};
13. 罗马数字转整数
题目描述
罗马数字包含以下七种字符: I
, V
, X
, L
,C
,D
和 M
。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2
写做 II
,即为两个并列的 1 。12
写做 XII
,即为 X
+ II
。 27
写做 XXVII
, 即为 XX
+ V
+ II
。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII
,而是 IV
。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX
。这个特殊的规则只适用于以下六种情况:
I
可以放在V
(5) 和X
(10) 的左边,来表示 4 和 9。X
可以放在L
(50) 和C
(100) 的左边,来表示 40 和 90。C
可以放在D
(500) 和M
(1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。
示例 1:
输入: s = “III”
输出: 3
示例 2:
输入: s = “IV”
输出: 4
示例 3:
输入: s = “IX”
输出: 9
示例 4:
输入: s = “LVIII”
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: s = “MCMXCIV”
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
提示:
1 <= s.length <= 15
s
仅含字符('I', 'V', 'X', 'L', 'C', 'D', 'M')
- 题目数据保证
s
是一个有效的罗马数字,且表示整数在范围[1, 3999]
内 - 题目所给测试用例皆符合罗马数字书写规则,不会出现跨位等情况。
- IL 和 IM 这样的例子并不符合题目要求,49 应该写作 XLIX,999 应该写作 CMXCIX 。
- 关于罗马数字的详尽书写规则,可以参考 罗马数字 - 百度百科。
难度:简单
标签:哈希表,数学,字符串
解法
方法一:哈希表 + 模拟
我们先用哈希表 d d d 记录每个字符对应的数值,然后从左到右遍历字符串 s s s,如果当前字符对应的数值小于右边字符对应的数值,则减去当前字符对应的数值,否则加上当前字符对应的数值。
时间复杂度 ( n ) (n) (n),空间复杂度 O ( m ) O(m) O(m)。其中 n n n 和 m m m 分别为字符串 s s s 的长度和字符集的大小。
Python3
class Solution:
def romanToInt(self, s: str) -> int:
d = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
return sum((-1 if d[a] < d[b] else 1) * d[a] for a, b in pairwise(s)) + d[s[-1]]
Java
class Solution {
public int romanToInt(String s) {
String cs = "IVXLCDM";
int[] vs = {1, 5, 10, 50, 100, 500, 1000};
Map<Character, Integer> d = new HashMap<>();
for (int i = 0; i < vs.length; ++i) {
d.put(cs.charAt(i), vs[i]);
}
int n = s.length();
int ans = d.get(s.charAt(n - 1));
for (int i = 0; i < n - 1; ++i) {
int sign = d.get(s.charAt(i)) < d.get(s.charAt(i + 1)) ? -1 : 1;
ans += sign * d.get(s.charAt(i));
}
return ans;
}
}
C++
class Solution {
public:
int romanToInt(string s) {
unordered_map<char, int> nums{
{'I', 1},
{'V', 5},
{'X', 10},
{'L', 50},
{'C', 100},
{'D', 500},
{'M', 1000},
};
int ans = nums[s.back()];
for (int i = 0; i < s.size() - 1; ++i) {
int sign = nums[s[i]] < nums[s[i + 1]] ? -1 : 1;
ans += sign * nums[s[i]];
}
return ans;
}
};
14. 最长公共前缀
题目描述
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
。
示例 1:
输入:strs = [“flower”,“flow”,“flight”]
输出:“fl”
示例 2:
输入:strs = [“dog”,“racecar”,“car”]
输出:“”
解释:输入不存在公共前缀。
提示:
1 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i]
仅由小写英文字母组成
难度:简单
标签:字典树,字符串
解法
方法一:字符比较
我们以第一个字符串 s t r s [ 0 ] strs[0] strs[0] 为基准,依次比较后面的字符串的第 i i i 个字符是否与 s t r s [ 0 ] strs[0] strs[0] 的第 i i i 个字符相同,如果相同则继续比较下一个字符,否则返回 s t r s [ 0 ] strs[0] strs[0] 的前 i i i 个字符。
遍历结束,说明所有字符串的前 i i i 个字符都相同,返回 s t r s [ 0 ] strs[0] strs[0] 即可。
时间复杂度 ( n × m ) (n \times m) (n×m),其中 n n n 和 m m m 分别为字符串数组的长度以及字符串的最小长度。空间复杂度 O ( 1 ) O(1) O(1)。
Python3
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
for i in range(len(strs[0])):
for s in strs[1:]:
if len(s) <= i or s[i] != strs[0][i]:
return s[:i]
return strs[0]
Java
class Solution {
public String longestCommonPrefix(String[] strs) {
int n = strs.length;
for (int i = 0; i < strs[0].length(); ++i) {
for (int j = 1; j < n; ++j) {
if (strs[j].length() <= i || strs[j].charAt(i) != strs[0].charAt(i)) {
return strs[0].substring(0, i);
}
}
}
return strs[0];
}
}
C++
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
int n = strs.size();
for (int i = 0; i < strs[0].size(); ++i) {
for (int j = 1; j < n; ++j) {
if (strs[j].size() <= i || strs[j][i] != strs[0][i]) {
return strs[0].substr(0, i);
}
}
}
return strs[0];
}
};
15. 三数之和
题目描述
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
难度:中等
标签:数组,双指针,排序
解法
方法一:排序 + 双指针
我们注意到,题目不要求我们按照顺序返回三元组,因此我们不妨先对数组进行排序,这样就可以方便地跳过重复的元素。
接下来,我们枚举三元组的第一个元素 n u m s [ i ] nums[i] nums[i],其中 0 ≤ i < n − 2 0 \leq i \lt n - 2 0≤i<n−2。对于每个 i i i,我们可以通过维护两个指针 j = i + 1 j = i + 1 j=i+1 和 k = n − 1 k = n - 1 k=n−1,从而找到满足 n u m s [ i ] + n u m s [ j ] + n u m s [ k ] = 0 nums[i] + nums[j] + nums[k] = 0 nums[i]+nums[j]+nums[k]=0 的 j j j 和 k k k。在枚举的过程中,我们需要跳过重复的元素,以避免出现重复的三元组。
具体判断逻辑如下:
如果 i > 0 i \gt 0 i>0 并且 n u m s [ i ] = n u m s [ i − 1 ] nums[i] = nums[i - 1] nums[i]=nums[i−1],则说明当前枚举的元素与上一个元素相同,我们可以直接跳过,因为不会产生新的结果。
如果 n u m s [ i ] > 0 nums[i] \gt 0 nums[i]>0,则说明当前枚举的元素大于 0 0 0,则三数之和必然无法等于 0 0 0,结束枚举。
否则,我们令左指针 j = i + 1 j = i + 1 j=i+1,右指针 k = n − 1 k = n - 1 k=n−1,当 j < k j \lt k j<k 时,执行循环,计算三数之和 x = n u m s [ i ] + n u m s [ j ] + n u m s [ k ] x = nums[i] + nums[j] + nums[k] x=nums[i]+nums[j]+nums[k],并与 0 0 0 比较:
- 如果 x < 0 x \lt 0 x<0,则说明 n u m s [ j ] nums[j] nums[j] 太小,我们需要将 j j j 右移一位。
- 如果 x > 0 x \gt 0 x>0,则说明 n u m s [ k ] nums[k] nums[k] 太大,我们需要将 k k k 左移一位。
- 否则,说明我们找到了一个合法的三元组,将其加入答案,并将 j j j 右移一位,将 k k k 左移一位,同时跳过所有重复的元素,继续寻找下一个合法的三元组。
枚举结束后,我们即可得到三元组的答案。
时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( log n ) O(\log n) O(logn)。其中 n n n 为数组的长度。
Python3
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
n = len(nums)
ans = []
for i in range(n - 2):
if nums[i] > 0:
break
if i and nums[i] == nums[i - 1]:
continue
j, k = i + 1, n - 1
while j < k:
x = nums[i] + nums[j] + nums[k]
if x < 0:
j += 1
elif x > 0:
k -= 1
else:
ans.append([nums[i], nums[j], nums[k]])
j, k = j + 1, k - 1
while j < k and nums[j] == nums[j - 1]:
j += 1
while j < k and nums[k] == nums[k + 1]:
k -= 1
return ans
Java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<>();
int n = nums.length;
for (int i = 0; i < n - 2 && nums[i] <= 0; ++i) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int j = i + 1, k = n - 1;
while (j < k) {
int x = nums[i] + nums[j] + nums[k];
if (x < 0) {
++j;
} else if (x > 0) {
--k;
} else {
ans.add(List.of(nums[i], nums[j++], nums[k--]));
while (j < k && nums[j] == nums[j - 1]) {
++j;
}
while (j < k && nums[k] == nums[k + 1]) {
--k;
}
}
}
}
return ans;
}
}
C++
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
int n = nums.size();
for (int i = 0; i < n - 2 && nums[i] <= 0; ++i) {
if (i && nums[i] == nums[i - 1]) {
continue;
}
int j = i + 1, k = n - 1;
while (j < k) {
int x = nums[i] + nums[j] + nums[k];
if (x < 0) {
++j;
} else if (x > 0) {
--k;
} else {
ans.push_back({nums[i], nums[j++], nums[k--]});
while (j < k && nums[j] == nums[j - 1]) {
++j;
}
while (j < k && nums[k] == nums[k + 1]) {
--k;
}
}
}
}
return ans;
}
};
16. 最接近的三数之和
题目描述
给你一个长度为 n
的整数数组 nums
和 一个目标值 target
。请你从 nums
中选出三个整数,使它们的和与 target
最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
示例 1:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2)。
示例 2:
输入:nums = [0,0,0], target = 1
输出:0
解释:与 target 最接近的和是 0(0 + 0 + 0 = 0)。
提示:
3 <= nums.length <= 1000
-1000 <= nums[i] <= 1000
-104 <= target <= 104
难度:中等
标签:数组,双指针,排序
解法
方法一:排序 + 双指针
我们将数组排序,然后遍历数组,对于每个元素 n u m s [ i ] nums[i] nums[i],我们使用指针 j j j 和 k k k 分别指向 i + 1 i+1 i+1 和 n − 1 n-1 n−1,计算三数之和,如果三数之和等于 t a r g e t target target,则直接返回 t a r g e t target target,否则根据与 t a r g e t target target 的差值更新答案。如果三数之和大于 t a r g e t target target,则将 k k k 向左移动一位,否则将 j j j 向右移动一位。
时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( log n ) O(\log n) O(logn)。其中 n n n 为数组长度。
Python3
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
n = len(nums)
ans = inf
for i, v in enumerate(nums):
j, k = i + 1, n - 1
while j < k:
t = v + nums[j] + nums[k]
if t == target:
return t
if abs(t - target) < abs(ans - target):
ans = t
if t > target:
k -= 1
else:
j += 1
return ans
Java
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int ans = 1 << 30;
int n = nums.length;
for (int i = 0; i < n; ++i) {
int j = i + 1, k = n - 1;
while (j < k) {
int t = nums[i] + nums[j] + nums[k];
if (t == target) {
return t;
}
if (Math.abs(t - target) < Math.abs(ans - target)) {
ans = t;
}
if (t > target) {
--k;
} else {
++j;
}
}
}
return ans;
}
}
C++
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
int ans = 1 << 30;
int n = nums.size();
for (int i = 0; i < n; ++i) {
int j = i + 1, k = n - 1;
while (j < k) {
int t = nums[i] + nums[j] + nums[k];
if (t == target) return t;
if (abs(t - target) < abs(ans - target)) ans = t;
if (t > target)
--k;
else
++j;
}
}
return ans;
}
};
17. 电话号码的字母组合
题目描述
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
示例 2:
输入:digits = “”
输出:[]
示例 3:
输入:digits = “2”
输出:[“a”,“b”,“c”]
提示:
0 <= digits.length <= 4
digits[i]
是范围['2', '9']
的一个数字。
难度:中等
标签:哈希表,字符串,回溯
解法
方法一:遍历
我们先用一个数组或者哈希表存储每个数字对应的字母,然后遍历每个数字,将其对应的字母与之前的结果进行组合,得到新的结果。
时间复杂度 O ( 4 n ) O(4^n) O(4n)。空间复杂度 O ( 4 n ) O(4^n) O(4n)。其中 n n n 是输入数字的长度。
Python3
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits:
return []
d = ["abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"]
ans = [""]
for i in digits:
s = d[int(i) - 2]
ans = [a + b for a in ans for b in s]
return ans
Java
class Solution {
public List<String> letterCombinations(String digits) {
List<String> ans = new ArrayList<>();
if (digits.length() == 0) {
return ans;
}
ans.add("");
String[] d = new String[] {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
for (char i : digits.toCharArray()) {
String s = d[i - '2'];
List<String> t = new ArrayList<>();
for (String a : ans) {
for (String b : s.split("")) {
t.add(a + b);
}
}
ans = t;
}
return ans;
}
}
C++
class Solution {
public:
vector<string> letterCombinations(string digits) {
if (digits.empty()) {
return {};
}
vector<string> d = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
vector<string> ans = {""};
for (auto& i : digits) {
string s = d[i - '2'];
vector<string> t;
for (auto& a : ans) {
for (auto& b : s) {
t.push_back(a + b);
}
}
ans = move(t);
}
return ans;
}
};
18. 四数之和
题目描述
给你一个由 n
个整数组成的数组 nums
,和一个目标值 target
。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a
、b
、c
和d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
提示:
1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109
难度:中等
标签:数组,双指针,排序
解法
方法一:排序 + 双指针
我们注意到,题目中要求找到不重复的四元组,那么我们可以先对数组进行排序,这样就可以方便地跳过重复的元素。
接下来,我们枚举四元组的前两个元素 n u m s [ i ] nums[i] nums[i] 和 n u m s [ j ] nums[j] nums[j],其中 i < j i \lt j i<j,在枚举的过程中,我们跳过重复的 n u m s [ i ] nums[i] nums[i] 和 n u m s [ j ] nums[j] nums[j]。然后,我们用两个指针 k k k 和 l l l 分别指向 n u m s [ i ] nums[i] nums[i] 和 n u m s [ j ] nums[j] nums[j] 后面的两端,令 x = n u m s [ i ] + n u m s [ j ] + n u m s [ k ] + n u m s [ l ] x = nums[i] + nums[j] + nums[k] + nums[l] x=nums[i]+nums[j]+nums[k]+nums[l],我们将 x x x 与 t a r g e t target target 比较,进行如下操作:
- 如果 x < t a r g e t x \lt target x<target,则更新 k = k + 1 k = k + 1 k=k+1 以得到更大的 x x x;
- 如果 x > t a r g e t x \gt target x>target,则更新 l = l − 1 l = l - 1 l=l−1 以得到更小的 x x x;
- 否则,说明找到了一个四元组 ( n u m s [ i ] , n u m s [ j ] , n u m s [ k ] , n u m s [ l ] ) (nums[i], nums[j], nums[k], nums[l]) (nums[i],nums[j],nums[k],nums[l]),将其加入答案,然后我们更新指针 k k k 和 l l l,并跳过所有重复的元素,防止答案中包含重复的四元组,继续寻找下一个四元组。
时间复杂度为 O ( n 3 ) O(n^3) O(n3),空间复杂度为 O ( log n ) O(\log n) O(logn),其中 n n n 是数组的长度。
Python3
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
n = len(nums)
ans = []
if n < 4:
return ans
nums.sort()
for i in range(n - 3):
if i and nums[i] == nums[i - 1]:
continue
for j in range(i + 1, n - 2):
if j > i + 1 and nums[j] == nums[j - 1]:
continue
k, l = j + 1, n - 1
while k < l:
x = nums[i] + nums[j] + nums[k] + nums[l]
if x < target:
k += 1
elif x > target:
l -= 1
else:
ans.append([nums[i], nums[j], nums[k], nums[l]])
k, l = k + 1, l - 1
while k < l and nums[k] == nums[k - 1]:
k += 1
while k < l and nums[l] == nums[l + 1]:
l -= 1
return ans
Java
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
int n = nums.length;
List<List<Integer>> ans = new ArrayList<>();
if (n < 4) {
return ans;
}
Arrays.sort(nums);
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 k = j + 1, l = n - 1;
while (k < l) {
long x = (long) nums[i] + nums[j] + nums[k] + nums[l];
if (x < target) {
++k;
} else if (x > target) {
--l;
} else {
ans.add(List.of(nums[i], nums[j], nums[k++], nums[l--]));
while (k < l && nums[k] == nums[k - 1]) {
++k;
}
while (k < l && nums[l] == nums[l + 1]) {
--l;
}
}
}
}
}
return ans;
}
}
C++
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
int n = nums.size();
vector<vector<int>> ans;
if (n < 4) {
return ans;
}
sort(nums.begin(), nums.end());
for (int i = 0; i < n - 3; ++i) {
if (i && 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 k = j + 1, l = n - 1;
while (k < l) {
long long x = (long long) nums[i] + nums[j] + nums[k] + nums[l];
if (x < target) {
++k;
} else if (x > target) {
--l;
} else {
ans.push_back({nums[i], nums[j], nums[k++], nums[l--]});
while (k < l && nums[k] == nums[k - 1]) {
++k;
}
while (k < l && nums[l] == nums[l + 1]) {
--l;
}
}
}
}
}
return ans;
}
};
19. 删除链表的倒数第 N 个结点
题目描述
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
提示:
- 链表中结点的数目为
sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
进阶:你能尝试使用一趟扫描实现吗?
难度:中等
标签:链表,双指针
解法
方法一:快慢指针
我们定义两个指针 fast
和 slow
,初始时都指向链表的虚拟头结点 dummy
。
接着 fast
指针先向前移动
n
n
n 步,然后 fast
和 slow
指针同时向前移动,直到 fast
指针到达链表的末尾。此时 slow.next
指针指向的结点就是倒数第 n
个结点的前驱结点,将其删除即可。
时间复杂度 O ( n ) O(n) O(n),其中 n n n 为链表的长度。空间复杂度 O ( 1 ) O(1) O(1)。
Python3
## 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(next=head)
fast = slow = dummy
for _ in range(n):
fast = fast.next
while fast.next:
slow, fast = slow.next, fast.next
slow.next = slow.next.next
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(0, head);
ListNode fast = dummy, slow = dummy;
while (n-- > 0) {
fast = fast.next;
}
while (fast.next != null) {
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
C++
/**
* 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(0, head);
ListNode* fast = dummy;
ListNode* slow = dummy;
while (n--) {
fast = fast->next;
}
while (fast->next) {
slow = slow->next;
fast = fast->next;
}
slow->next = slow->next->next;
return dummy->next;
}
};
20. 有效的括号
题目描述
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
示例 4:
输入:s = "([])"
输出:true
提示:
1 <= s.length <= 104
s
仅由括号'()[]{}'
组成
难度:简单
标签:栈,字符串
解法
方法一:栈
遍历括号字符串
s
s
s,遇到左括号时,压入当前的左括号;遇到右括号时,弹出栈顶元素(若栈为空,直接返回 false
),判断是否匹配,若不匹配,直接返回 false
。
也可以选择遇到左括号时,将右括号压入栈中;遇到右括号时,弹出栈顶元素(若栈为空,直接返回 false
),判断是否是相等。若不匹配,直接返回 false
。
两者的区别仅限于括号转换时机,一个是在入栈时,一个是在出栈时。
遍历结束,若栈为空,说明括号字符串有效,返回 true
;否则,返回 false
。
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)。其中 n n n 为括号字符串 s s s 的长度。
Python3
class Solution:
def isValid(self, s: str) -> bool:
stk = []
d = {'()', '[]', '{}'}
for c in s:
if c in '({[':
stk.append(c)
elif not stk or stk.pop() + c not in d:
return False
return not stk
Java
class Solution {
public boolean isValid(String s) {
Deque<Character> stk = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '{' || c == '[') {
stk.push(c);
} else if (stk.isEmpty() || !match(stk.pop(), c)) {
return false;
}
}
return stk.isEmpty();
}
private boolean match(char l, char r) {
return (l == '(' && r == ')') || (l == '{' && r == '}') || (l == '[' && r == ']');
}
}
C++
class Solution {
public:
bool isValid(string s) {
string stk;
for (char c : s) {
if (c == '(' || c == '{' || c == '[')
stk.push_back(c);
else if (stk.empty() || !match(stk.back(), c))
return false;
else
stk.pop_back();
}
return stk.empty();
}
bool match(char l, char r) {
return (l == '(' && r == ')') || (l == '[' && r == ']') || (l == '{' && r == '}');
}
};