1. 两数之和
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
一开始我的解法(太慢了)
1866ms 击败 15.41%
class Solution:
def twoSum(self, nums: List [int], target: int) -> List [int]:
n = len(nums)
for i in range(n-1):
for j in range(i+1, n):
if nums [i] + nums [j] == target:
return [i, j]
return []
现在用的解法(双指针法)
8ms 击败 44.35%
class Solution:
def twoSum(self, nums: List [int], target: int) -> List [int]:
# 构造一个含 index 的数组,同时对其进行排序
matrix = list(enumerate(nums))
matrix.sort(key = lambda x: x [1])
# 构造两个 lambda 函数方便理解
value = lambda x: matrix [x][1]
index = lambda x: matrix [x][0]
# 初始化两个指针
left = 0
right = len(nums)-1
# 循环结束条件为:两指针不相等
while left != right:
if value(left) + value(right) > target:
right-= 1
elif value(left) + value(right) < target:
left+= 1
else:
return [index(left), index(right)]
哈希解法
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
val_dict = dict()
for i, num in enumerate(nums):
other_val = target - num
if other_val not in val_dict.keys():
val_dict[num] = i
else:
return [val_dict[other_val], i]
2. 两数相加
正确解法
class ListNode:
def __init__(self, val = 0, next = None):
self.val = val
self.next = next
class Solution:
def addTwoNumbers(self, l1, l2):
dummy = ListNode() # 创建一个哑节点,方便处理结果链表
cur = dummy
carry = 0
while l1 or l2 or carry:
# 将 carry 作为总数值
if l1:
carry += l1.val
l1 = l1.next
if l2:
carry += l2.val
l2 = l2.next
cur.val = ListNode(carry%10)
cur = cur.next
carry//= 10
return dummy.next # 返回哑节点的下一个节点作为结果链表的头
3. 无重复字符的最长子串
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示:
0 <= s.length <= 5 * 104
s
由英文字母、数字、符号和空格组成
哈希表(set/dict)维护指针法
15ms 击败 91.72%
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
n = len(s)
ans = 0
left = 0
charSet = set() # 使用集合来存储当前窗口内的字符
for right in range(n): # 遍历字符串,right 作为滑动窗口的右边界
while s [right] in charSet: # 如果当前字符已经在集合中,移动左边界直到没有重复字符
charSet.remove(s [left]) # 移除左边界的字符
left += 1
charSet.add(s [right]) # 将当前字符加入集合
ans = max(ans, right - left + 1) # 更新答案
return ans
0x3f 做法
from collections import Counter
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
ans = 0
# 使用 Counter 来记录当前窗口中每个字符的出现次数
cnt = Counter()
left = 0
# 遍历字符串 s,right 作为右指针,c 为当前字符
for right, c in enumerate(s):
cnt [c] += 1
while cnt [c] > 1:
# 减少左指针所指向字符的计数,并将左指针向右移动,缩小窗口
cnt [s[left]] -= 1
left += 1
# 当前窗口的长度是(right - left + 1)
ans = max(ans, right - left + 1)
return ans
维护指针法
23ms 击败 57.96%
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if not s:
return 0
n = len(s)
ans = 0
left = 0
right = 0
while right <= n-1:
if s [right] not in s [left: right]:
ans = max(ans, right - left + 1)
right+= 1
else:
left+= 1
return ans
4. 寻找两个正序数组的中位数
困难
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n))
。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106
维护最大堆最小堆
import heapq
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
max_heap = [] # 存储较小的那一半(用负数模拟最大堆)
min_heap = [] # 存储较大的那一半(直接是最小堆)
# 合并两个有序数组(可以用双指针优化,但这里简单起见直接合并)
nums = nums1 + nums2
nums.sort() # 先排序(如果 nums1 和 nums2 已排序,可以用双指针优化)
for num in nums:
# 先尝试放入 max_heap
if not max_heap or num <= -max_heap[0]:
heapq.heappush(max_heap, -num)
else:
heapq.heappush(min_heap, num)
# 平衡两个堆的大小
if len(max_heap) > len(min_heap) + 1:
heapq.heappush(min_heap, -heapq.heappop(max_heap))
elif len(min_heap) > len(max_heap):
heapq.heappush(max_heap, -heapq.heappop(min_heap))
# 计算中位数
if len(max_heap) > len(min_heap):
return -max_heap[0]
else:
return (-max_heap[0] + min_heap[0]) / 2
---
5. 最长回文子串
中等
给你一个字符串 s
,找到 s
中最长的 回文 子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
提示:
1 <= s.length <= 1000
s
仅由数字和英文字母组成
官方解法
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
if n < 2:
return s
max_len = 1
begin = 0
# dp[i][j] 表示 s[i..j] 是否是回文串
dp = [[False] * n for _ in range(n)]
for i in range(n):
dp[i][i] = True
# 递推开始
# 先枚举子串长度
for L in range(2, n + 1):
# 枚举左边界,左边界的上限设置可以宽松一些
for i in range(n):
# 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
j = L + i - 1
# 如果右边界越界,就可以退出当前循环
if j >= n:
break
if s[i] != s[j]:
dp[i][j] = False
else:
if j - i < 3:
dp[i][j] = True
else:
dp[i][j] = dp[i + 1][j - 1]
# 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if dp[i][j] and j - i + 1 > max_len:
max_len = j - i + 1
begin = i
return s[begin:begin + max_len]
---
6. Z 字形变换
中等
将一个给定字符串 s
根据给定的行数 numRows
,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING"
行数为 3
时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"
。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入:s = "PAYPALISHIRING", numRows = 3
输出:"PAHNAPLSIIGYIR"
示例 2:
输入:s = "PAYPALISHIRING", numRows = 4
输出:"PINALSIGYAHRPI"
解释:
P I N
A L S I G
Y A H R
P I
示例 3:
输入:s = "A", numRows = 1
输出:"A"
提示:
1 <= s.length <= 1000
s
由英文字母(小写和大写)、','
和'.'
组成1 <= numRows <= 1000
模拟
class Solution:
def convert(self, s: str, numRows: int) -> str:
if numRows == 1 or numRows >= len(s):
return s
# 初始化每一行的字符串
rows = [''] * min(numRows, len(s))
# 当前行和方向
current_row = 0
going_down = False
for char in s:
# 将字符放入当前行
rows[current_row] += char
# 如果到达第一行或最后一行,改变方向
if current_row == 0 or current_row == numRows - 1:
going_down = not going_down
# 根据方向更新当前行
current_row += 1 if going_down else -1
# 将所有行的字符串连接起来
return ''.join(rows)
---
7. 整数反转
中等
给你一个 32 位的有符号整数 x
,返回将 x
中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1]
,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
示例 1:
输入:x = 123
输出:321
示例 2:
输入:x = -123
输出:-321
示例 3:
输入:x = 120
输出:21
示例 4:
输入:x = 0
输出:0
提示:
- $-2^{31}<= x <= 2^{31} - 1$
class Solution:
def reverse(self, x: int) -> int:
INT_MAX = 2**31-1
INT_MIN = -2**31
flag=False
if x==0:
return 0
elif x<0:
flag=True
x=-x
count=[]
while x:
count.append(x%10)
x//=10
j=0
ans=0
for i in range(len(count)-1,-1,-1):
ans+=count[i]*pow(10,j)
j+=1
if flag:
ans=-ans
if ans >INT_MAX or ans < INT_MIN:
return 0
return ans
---
8. 字符串转换整数 (atoi)
请你来实现一个 myAtoi(string s)
函数,使其能将字符串转换成一个 32 位有符号整数。
函数 myAtoi(string s)
的算法如下:
- 空格: 读入字符串并丢弃无用的前导空格(
" "
) - 符号: 检查下一个字符(假设还未到字符末尾)为
'-'
还是'+'
。如果两者都不存在,则假定结果为正。 - 转换: 通过跳过前置零来读取该整数,直到遇到非数字字符或到达字符串的结尾。如果没有读取数字,则结果为 0。
- 舍入: 如果整数数超过 32 位有符号整数范围
[−231, 231 − 1]
,需要截断这个整数,使其保持在这个范围内。具体来说,小于−231
的整数应该被舍入为−231
,大于231 − 1
的整数应该被舍入为231 − 1
。
返回整数作为最终结果。
官方解法(利用状态机求解)
INT_MAX = 2 ** 31 - 1
INT_MIN = -2 ** 31
class Automaton:
def __init__(self):
self.state = 'start'
self.sign = 1
self.ans = 0
self.table = {
'start': ['start', 'signed', 'in_number', 'end'],
'signed': ['end', 'end', 'in_number', 'end'],
'in_number': ['end', 'end', 'in_number', 'end'],
'end': ['end', 'end', 'end', 'end'],
}
def get_col(self, c):
if c.isspace():
return 0
if c == '+' or c == '-':
return 1
if c.isdigit():
return 2
return 3
def get(self, c):
self.state = self.table [self.state][self.get_col(c)]
if self.state == 'in_number':
self.ans = self.ans * 10 + int(c)
self.ans = min(self.ans, INT_MAX) if self.sign == 1 else min(self.ans, -INT_MIN)
elif self.state == 'signed':
self.sign = 1 if c == '+' else -1
class Solution:
def myAtoi(self, str: str) -> int:
automaton = Automaton()
for c in str:
automaton.get(c)
return automaton.sign * automaton.ans
我的解法
我的解法是状态机的变体, 但是只能利用在每个状态 顺序已知 的情况下, 不适合 变通 的情况
class Solution:
def myAtoi(self, s: str) -> int:
ture_num = 0
index = 0
negFlag = False
while index < len(s):
while index < len(s) and s [index] == ' ':
index+= 1
while index < len(s) and s [index] in "+-":
if s [index] == '-':
negFlag = True
index+= 1
break
while index < len(s) and (s[index] > = '0' and s [index] <= '9'):
ture_num*= 10
ture_num+= int(s [index])
index+= 1
break
ture_num = -ture_num if negFlag else ture_num
if ture_num > 2**31-1:
ture_num = 2**31-1
elif ture_num < -2**31:
ture_num = -2**31
return ture_num
9.回文数
快速解法
class Solution:
def isPalindrome(self, x: int) -> bool:
return str(x)==str(x)[::-1] #运用了Python的str函数直接转置int数字为字符串,同时利用[::-1]写法直接进行reverse操作
我的解法(没有考虑到 python 自带字符串转置函数)
class Solution:
def isPalindrome(self, x: int) -> bool:
if x < 0:
return False
s = ""
while x:
cur_bit = x%10
s+= str(cur_bit)
x//= 10
while len(s)> 1:
if s [0] != s [-1]:
return False
s = s [1:-1]
return True
10. 正则表达式匹配
给你一个字符串 s
和一个字符规律 p
,请你来实现一个支持 '.'
和 '*'
的正则表达式匹配。
'.'
匹配任意单个字符'*'
匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s
的,而不是部分字符串。
示例 1:
输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:s = "aa", p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
提示:
1 <= s.length <= 20
1 <= p.length <= 20
s
只包含从a-z
的小写字母。p
只包含从a-z
的小写字母,以及字符.
和*
。- 保证每次出现字符
*
时,前面都匹配到有效的字符
Python 写法
class Solution:
def isMatch(self, s: str, p: str) -> bool:
n = len(s)
m = len(p)
s = '0' + s
p = '0' + p
f = [[False for _ in range(m+1)] for _ in range(n+1)]
f [0][0] = True
for j in range(1, m+1):
if p [j] == '*':
f [0][j] = f [0][j-1]
elif j+1 > m or p [j+1]!='*':
break
else:
f [0][j] = True
for i in range(1, n+1):
for j in range(1, m+1):
if p [j] == '*':
f [i][j] = f [i][j-1]
continue
if j+1 <= m and p [j+1] == '*':
f [i][j] = f [i][j-1] or\
(f [i-1][j-1] and (p [j] == '.' or s [i] == p [j])) or\
(f [i-1][j] and (p [j] == '.' or s [i] == p [j]))
else:
f [i][j] = f [i-1][j-1] and (p [j] == '.' or s [i] == p [j])
return f [n][m]
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
该题思路为 任意情况下 给你两条线,该移动谁可能形成更大的容量,同时注意要节省时间性能,实际上可以观察到
面积 = 底部距离 x 最小值(线段长度 1, 线段长度 2) = 底部距离 x 较短线段长度
底部距离的 单调性: 因为指针是从两边向内靠近, 所以底部距离是在不断缩短的, 呈现单调减的特性
较短线段长度的 单调性:
- 固定较短线段 而 移动较长线段 时: 新的较短线段 <= 老的较短线段,所以也呈现单调减的特性
- 固定较长线段 而 移动较短线段 时: 新的较短线段不确定大小关系,所以单调关系也不确定
则:
- 固定较短线段 而 移动较长线段 时: 面积的单调性 = 单调减 x 单调减,所以也呈现单调减的特性
- 固定较长线段 而 移动较短线段 时: 面积的单调性 = 单调减 x 不确定,所以单调关系也不确定
所以不能 固定较短线段移动较长线段,而是要 固定较长线段移动较短线段 尝试是否有更大值
双指针法
class Solution:
def maxArea(self, height: List [int]) -> int:
ans = 0
left = 0
right = len(height)-1
while left < right:
area = (right-left)*min(height [left], height [right])
ans = max(area, ans)
if height [left] < height [right]:
left+= 1
elif height [left] > height [right]:
right-= 1
else:
left+= 1
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 (C) + 100 (C)
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
一开始的做法
class Solution:
def intToRoman(self, num: int) -> str:
my_dict = { 1:"I",
5:"V",
10:"X",
50:"L",
100:"C",
500:"D",
1000:"M",
}
minus_dict = { 4:"IV",
9:"IX",
40:"XL",
90:"XC",
400:"CD",
900:"CM",
}
ans = []
def len_bit(num):
result = 0
while num:
num//=10
result += 1
return result
while num:
now_len = len_bit(num)
now_first = num//(10**(now_len-1))
if now_first == 4 or now_first == 9:
for key in list(minus_dict.keys())[::-1]:
if num >= key:
num -= key
ans.append(minus_dict[key])
break
else:
for key in list(my_dict.keys())[::-1]:
if num >= key:
num -= key
ans.append(my_dict[key])
break
return "".join(ans)
精简版
class Solution:
VALUE_SYMBOLS = [
(1000, "M"),
(900, "CM"),
(500, "D"),
(400, "CD"),
(100, "C"),
(90, "XC"),
(50, "L"),
(40, "XL"),
(10, "X"),
(9, "IX"),
(5, "V"),
(4, "IV"),
(1, "I"),
]
def intToRoman(self, num: int) -> str:
roman = list()
for value, symbol in Solution.VALUE_SYMBOLS:
while num >= value:
num -= value
roman.append(symbol)
if num == 0:
break
return "".join(roman)
---
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 。
- 关于罗马数字的详尽书写规则,可以参考 罗马数字 - 百度百科。
状态机写法
class Solution:
def romanToInt(self, s: str) -> int:
my_dict = {
"I": 1,
"V": 5,
"X": 10,
"L": 50,
"C": 100,
"D": 500,
"M": 1000,
"IV": 4,
"IX": 9,
"XL": 40,
"XC": 90,
"CD": 400,
"CM": 900
}
ans = 0
status = "Default"
for i in range(len(s)):
c = s[i]
if status == "pre_is_I":
if c == "V" or c == "X":
ans += my_dict["I" + c]
status = "Default"
continue
else:
ans += my_dict["I"]
status = "Default"
elif status == "pre_is_X":
if c == "L" or c == "C":
ans += my_dict["X" + c]
status = "Default"
continue
else:
ans += my_dict["X"]
status = "Default"
elif status == "pre_is_C":
if c == "D" or c == "M":
ans += my_dict["C" + c]
status = "Default"
continue
else:
ans += my_dict["C"]
status = "Default"
# Default 状态处理
if c == "I":
status = "pre_is_I"
elif c == "X":
status = "pre_is_X"
elif c == "C":
status = "pre_is_C"
else:
ans += my_dict[c]
# 处理末尾可能遗留的状态
if status == "pre_is_I":
ans += my_dict["I"]
elif status == "pre_is_X":
ans += my_dict["X"]
elif status == "pre_is_C":
ans += my_dict["C"]
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]
如果非空,则仅由小写英文字母组成
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
dp=""
for i in range(min(len(s) for s in strs)):
Flag=True
k=0
for j in range(len(strs)):
if j==0:
dp+=strs[j][i]
else:
if strs[j][i]!=dp[i]:
Flag=False
break
if not Flag:
return dp[0:-1]
return dp
---
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
双指针解法
class Solution:
def threeSum(self, nums: List [int]) -> List [List[int]]:
# 时间复杂度 O(n^2)
# 空间复杂度 O(1)
nums.sort()
ans = []
n = len(nums)
for i in range(n-2):
# i 跳过重复元素 ##################################
x = nums [i]
if i > 0 and x == nums [i-1]:
continue
# 优化一 #########################################
if x + nums [i+1] + nums [i+2] > 0:
break
# 优化二
if x + nums [-2] + nums [-1] < 0:
continue
#################################################
j = i+1
k = n-1
while j < k:
if nums [j] + nums [k] + x > 0:
k-= 1
elif nums [j] + nums [k] + x < 0:
j+= 1
else:
ans.append([x, nums[j], nums [k]])
# j 跳过重复元素 ##########################
while j < k and nums [j] == nums [j + 1]:
j += 1
# k 跳过重复元素
while j < k and nums [k] == nums [k - 1]:
k -= 1
########################################
j += 1
k -= 1
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
双指针解法
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
# 时间复杂度 O(n^2)
# 空间复杂度 O(n)
# 对nums进行排序使得双指针可以使用
nums.sort()
# 使用set防止多次加入相同元素,避免最后的排序时间浪费
ans = set()
n = len(nums)
for i in range(n-2):
# 跳过重复元素
x = nums [i]
if i > 0 and x == nums [i-1]:
continue
j = i+1
k = n-1
while j < k:
tmp = nums [j] + nums [k] + x
if tmp - target > 0:
k-= 1
ans.add(tmp)
elif tmp - target < 0:
j+= 1
ans.add(tmp)
else:
return target
ans = list(ans)
return sorted(ans, key = lambda x:abs(x - target))[0]
---
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']
的一个数字。
字典替换
MAPPING = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
# 定义字典
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
n=len(digits)
if n == 0:
return []
ans = []
path = [""] * n
# 初始化[空路径]
def dfs(i):
# 结束条件
if i == n:
ans.append("".join(path))
return
for c in MAPPING[int(digits[i])]:
# 这一句是替换(相当于每一层固定位置的问题可以用替换一句话来替代添加和删除两句话)
path[i] = c
# 下一层回溯
dfs(i+1)
dfs(0)
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
高效做法(剪枝)
class Solution:
def fourSum(self, nums: List [int], target: int) -> List [List[int]]:
quadruplets = list()
if not nums or len(nums) < 4:
return quadruplets
nums.sort()
length = len(nums)
for i in range(length - 3):
if i > 0 and nums [i] == nums [i - 1]:
continue
if nums [i] + nums [i + 1] + nums [i + 2] + nums [i + 3] > target:
break
if nums [i] + nums [length - 3] + nums [length - 2] + nums [length - 1] < target:
continue
for j in range(i + 1, length - 2):
if j > i + 1 and nums [j] == nums [j - 1]:
continue
if nums [i] + nums [j] + nums [j + 1] + nums [j + 2] > target:
break
if nums [i] + nums [j] + nums [length - 2] + nums [length - 1] < target:
continue
left, right = j + 1, length - 1
while left < right:
total = nums [i] + nums [j] + nums [left] + nums [right]
if total == target:
quadruplets.append([nums[i], nums [j], nums [left], nums [right]])
while left < right and nums [left] == nums [left + 1]:
left += 1
left += 1
while left < right and nums [right] == nums [right - 1]:
right -= 1
right -= 1
elif total < target:
left += 1
else:
right -= 1
return quadruplets
正确做法
from typing import List
class Solution:
def fourSum(self, nums: List [int], target: int) -> List [List[int]]:
nums.sort()
n = len(nums)
ans = []
for i in range(n):
if i > 0 and nums [i] == nums [i - 1]:
continue
for j in range(i + 1, n):
if j > i + 1 and nums [j] == nums [j - 1]:
continue
left, right = j + 1, n - 1
while left < right:
total = nums [i] + nums [j] + nums [left] + nums [right]
if total < target:
left += 1
elif total > target:
right -= 1
else:
ans.append([nums[i], nums [j], nums [left], nums [right]])
while left < right and nums [left] == nums [left + 1]:
left += 1
while left < right and nums [right] == nums [right - 1]:
right -= 1
left += 1
right -= 1
return ans
我的做法
class Solution:
def fourSum(self, nums: List [int], target: int) -> List [List[int]]:
n = len(nums)
if n < 4:
return []
nums.sort()
ans = []
for i1 in range(n-3):
x = nums [i1]
if i1 > 0 and nums [i1-1] == x:
continue
for i2 in range(i1+1, n-2):
y = nums [i2]
if i2 > 0 and nums [i2-1] == y:
continue
j = i2+1
k = n-1
while j < k:
if (x+y) + nums [j] + nums [k] > target:
k-= 1
elif (x+y) + nums [j] + nums [k] < target:
j+= 1
else:
ans.append([x, y, nums[j], nums [k]])
# 跳过重复项
while j < k and nums [j+1] == nums [j]:
j+= 1
while j < k and nums [k-1] == nums [k]:
k-= 1
j+= 1
k-= 1
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
**进阶:**你能尝试使用一趟扫描实现吗?
两趟扫描做法
0ms 击败100.00%
from typing import *
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy = ListNode()
dummy.next = head
cur = dummy.next
length = 0
iterator = 0
# 第一遍记录长度
while cur != None:
length += 1
cur = cur.next
# 然后删除iterator == length - n的节点
cur = dummy.next
pre = dummy
while iterator != length - n:
pre = cur
cur = cur.next
iterator += 1
pre.next = cur.next
cur.next = None
return dummy.next
# def BuildTree(arr):
# def BuildTree_helper(arr):
# if not arr:
# return None
# root = ListNode(arr[-1])
# root.next = BuildTree_helper(arr[:-1])
# return root
# root = BuildTree_helper(arr[::-1])
# return root
# root = BuildTree([1,2,3,4,5])
# sol = Solution()
# sol.removeNthFromEnd(root, 2)
---
20. 有效的括号
简单
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
**输入:**s = "()"
**输出:**true
示例 2:
**输入:**s = "()[]{}"
**输出:**true
示例 3:
**输入:**s = "(]"
**输出:**false
示例 4:
**输入:**s = "([])"
**输出:**true
提示:
1 <= s.length <= 104
s
仅由括号'()[]{}'
组成
栈的进出
class Solution:
def isValid(self, s: str) -> bool:
stack = []
mapping = {')': '(', ']': '[', '}': '{'}
for char in s:
if char in mapping.values():
stack.append(char)
elif char in mapping.keys():
if not stack or mapping[char] != stack.pop():
return False
else:
return False
return not stack
---