LeetCode1-5
LeetCode: https://leetcode-cn.com/problemset/all/.
1.两数之和
难度:简单
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。
示例1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]:
示例2:
输入:nums = [3,3], target = 6
输出:[0,1]
方法1(暴力求解):
时间复杂度:
O
(
N
2
)
O(N^2)
O(N2),空间复杂度:
O
(
1
)
O(1)
O(1)
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
for i in range(n):
for j in range(i + 1, n):
if nums[i] + nums[j] == target:
return [i, j]
return []
方法2(哈希表):
创建一个哈希表,对于每一个 num,我们首先查询哈希表中是否存在 target - num,然后将 num 插入到哈希表中,即可保证不会让 num 和自己匹配。
时间复杂度:
O
(
N
)
O(N)
O(N),空间复杂度:
O
(
N
)
O(N)
O(N)
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
hashtable={}
for i,num in enumerate(nums):
if target - num in hashtable:
return [hashtable[target - num],i]
hashtable[num] = i
2.两数相加
难度:中等
给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字0之外,这两个数都不会以0开头。
示例1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
解决方法:
由于输入的两个链表都是逆序存储数字的位数的,因此两个链表中同一位置的数字可以直接相加。
我们同时遍历两个链表,逐位计算它们的和,并与当前位置的进位值相加。具体而言,如果当前两个链表处相应位置的数字为
n
1
,
n
2
n_1,n_2
n1,n2进位值为
c
a
r
r
y
carry
carry,则它们的和为
n
1
+
n
2
+
c
a
r
r
y
n_1+n_2+carry
n1+n2+carry;其中,答案链表处相应位置的数字为
(
n
1
+
n
2
+
c
a
r
r
y
)
m
o
d
10
(n_1+n_2+carry) \bmod 10
(n1+n2+carry)mod10,而新的进位值为
⌊
n
1
+
n
2
+
c
a
r
r
y
10
⌋
\lfloor\dfrac{n_1+n_2+carry}{10}\rfloor
⌊10n1+n2+carry⌋。
此外,如果链表遍历结束后,有
c
a
r
r
y
>
0
carry>0
carry>0,还需要在答案链表的后面附加一个节点,节点的值为
c
a
r
r
y
carry
carry。
时间复杂度:
O
(
m
a
x
(
m
,
n
)
)
O(max(m,n))
O(max(m,n)),空间复杂度:
O
(
1
)
O(1)
O(1)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
head = curr = ListNode()
carry = val = 0
while carry or l1 or l2:
val = carry
if l1:
val += l1.val
l1 = l1.next
if l2:
val += l2.val
l2 = l2.next
carry, val = divmod(val, 10)
curr.next = ListNode(val)
curr = curr.next
if carry > 0:
curr.next = ListNode(carry)
return head.next
3.无重复字符的最长子串
难度:中等
给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。
示例1:
输入: s = “abcabcbb”
输出: 3 (最长子串“abc”)
示例2:
输入: s = “pwwkew”
输出: 3(最长子串"kew")
示例3:
输入: s = “abcdefagh”
输出: 8(最长子串"bcdefagh")
方法1(暴力求解):
时间复杂度:
O
(
N
2
)
O(N^2)
O(N2),空间复杂度:
O
(
N
)
O(N)
O(N)
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
lengthOfSubstring = 0
lens = len(s)
for index1, char1 in enumerate(s):
Substring = ""
Substring += char1
for index2, char2 in enumerate(s[index1 + 1:]):
if char2 in Substring:
lengthOfSubstring = max(lengthOfSubstring, len(Substring))
break
else:
Substring += char2
if index2 == lens - index1-2:
lengthOfSubstring = max(lengthOfSubstring, len(Substring))
break
return lengthOfSubstring
方法2(滑动窗口):
时间复杂度:
O
(
N
)
O(N)
O(N),空间复杂度:
O
(
∣
Σ
∣
)
O(|\Sigma|)
O(∣Σ∣)
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if not s:return 0
left = 0
slidingWin = set()
lens = len(s)
maxLen = 0
curLen = 0
for i in range(lens):
curLen += 1
while s[i] in slidingWin:
slidingWin.remove(s[left])
left += 1
curLen -= 1
if curLen > maxLen:
maxLen = curLen
slidingWin.add(s[i])
return maxLen
4.寻找两个正序数组的中位数
难度:困难
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。
要求时间复杂度为
l
o
g
(
m
+
n
)
log(m+n)
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
示例3:
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例4:
输入:nums1 = [], nums2 = [1]
输出:1.00000
方法1(直接求解):
归并后再找中位数。
时间复杂度:
O
(
N
)
O(N)
O(N),空间复杂度:
O
(
1
)
O(1)
O(1)
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
nums1.extend(nums2)
nums1.sort()
n = len(nums1)
if n % 2 == 1 :
return nums1[n // 2]
else :
return (nums1[n // 2] + nums1[n // 2 - 1])/2
方法2(对分查找):
题解:假设两个有序数组分别是A和B。要找到第k个元素,我们可以比较A[k/2−1] 和B[k/2−1],其中/表示整数除法。由于A[k/2−1] 和B[k/2−1] 的前面分别有A[0…k/2−2] 和B[0…k/2−2],即k/2−1 个元素,对于A[k/2−1] 和B[k/2−1] 中的较小值,最多只会有(k/2−1)+(k/2−1)≤k−2个元素比它小,那么它就不能是第k小的数了,则之前的数都可排除。
因此我们有以下三种情况需要特殊处理:
- 如果A[k/2−1] 或者B[k/2−1] 越界,那么我们可以选取对应数组中的最后一个元素。在这种情况下,我们必须根据排除数的个数减少k 的值,而不能直接将k减去k/2。
- 如果一个数组为空,说明该数组中的所有元素都被排除,我们可以直接返回另一个数组中第k 小的元素。
- 如果k=1,我们只要返回两个数组首元素的最小值即可。
时间复杂度: O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n)),空间复杂度: O ( 1 ) O(1) O(1)
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
def getKthNum(k):#找到第k个数
index1, index2 = 0, 0
while True:
# 特殊情况
if index1 == m:
return nums2[index2 + k - 1]
if index2 == n:
return nums1[index1 + k - 1]
if k == 1:
return min(nums1[index1], nums2[index2])
# 正常情况
newIndex1 = min(index1 + k // 2 - 1, m - 1)
newIndex2 = min(index2 + k // 2 - 1, n - 1)
if nums1[newIndex1] <= nums2[newIndex2]:
k -= newIndex1 - index1 + 1
index1 = newIndex1 + 1
else:
k -= newIndex2 - index2 + 1
index2 = newIndex2 + 1
m, n = len(nums1), len(nums2)
totalLength = m + n
if totalLength % 2 == 1:
return getKthNum((totalLength + 1) // 2)
else:
return (getKthNum(totalLength // 2) + getKthNum(totalLength // 2 + 1)) / 2
方法3(划分数组):
题解
时间复杂度:
O
(
l
o
g
(
m
i
n
(
m
,
n
)
)
)
O(log(min(m,n)))
O(log(min(m,n))),空间复杂度:
O
(
1
)
O(1)
O(1)
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
if len(nums1) > len(nums2):
return self.findMedianSortedArrays(nums2, nums1)
infinty = 2**40
m, n = len(nums1), len(nums2)
left, right = 0, m
# median1:前一部分的最大值
# median2:后一部分的最小值
median1, median2 = 0, 0
while left <= right:
# 前一部分包含 nums1[0 .. i-1] 和 nums2[0 .. j-1]
# // 后一部分包含 nums1[i .. m-1] 和 nums2[j .. n-1]
i = (left + right) // 2
j = (m + n + 1) // 2 - i
# nums_im1, nums_i, nums_jm1, nums_j 分别表示 nums1[i-1], nums1[i], nums2[j-1], nums2[j]
nums_im1 = (-infinty if i == 0 else nums1[i - 1])
nums_i = (infinty if i == m else nums1[i])
nums_jm1 = (-infinty if j == 0 else nums2[j - 1])
nums_j = (infinty if j == n else nums2[j])
if nums_im1 <= nums_j:
median1, median2 = max(nums_im1, nums_jm1), min(nums_i, nums_j)
left = i + 1
else:
right = i - 1
return (median1 + median2) / 2 if (m + n) % 2 == 0 else median1
5.最长回文子串
难度:中等
给你一个字符串 s,找到 s 中最长的回文子串。
示例1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例2:
输入:s = “cabbad”
输出:“abba”
示例3:
输入:s = “a”
输出:“a”
方法1(动态规划):
我们用
P
(
i
,
j
)
P(i,j)
P(i,j)表示字符串
s
s
s的第
i
i
i到
j
j
j个字母组成的串(下文表示成
s
[
i
,
j
]
s[i,j]
s[i,j])是否为回文串:
P
(
i
,
j
)
=
{
t
r
u
e
,
s
[
i
,
j
]
是
回
文
串
f
a
l
s
e
,
s
[
i
,
j
]
不
是
回
文
串
或
i
>
j
P(i,j) = \left\{\begin{matrix} true,&s[i,j]是回文串\qquad\qquad \\ false,&s[i,j]不是回文串或i>j \end{matrix}\right.
P(i,j)={true,false,s[i,j]是回文串s[i,j]不是回文串或i>j
那么我们就可以写出动态规划的状态转移方程:
P
(
i
,
j
)
=
P
(
i
+
1
,
j
−
1
)
∧
(
S
i
=
=
S
j
)
P(i,j)=P(i+1,j-1)\wedge (S_i==S_j)
P(i,j)=P(i+1,j−1)∧(Si==Sj)
也就是说,只有
s
[
i
+
1
:
j
−
1
]
s[i+1:j−1]
s[i+1:j−1]是回文串,并且
s
s
s的第
i
i
i和
j
j
j个字母相同时,
s
[
i
:
j
]
s[i:j]
s[i:j]才会是回文串。
另外动态规划的边界条件:
{
P
(
i
,
i
)
=
t
r
u
e
P
(
i
,
i
+
1
)
=
(
S
i
=
=
S
i
+
1
)
\left\{\begin{array}{l} P(i,i)=true \\ P(i,i+1)=(S_i==S_{i+1}) \end{array}\right.
{P(i,i)=trueP(i,i+1)=(Si==Si+1)
最终的答案即为所有
P
(
i
,
j
)
=
t
r
u
e
P(i,j)=true
P(i,j)=true中
j
−
i
+
1
j−i+1
j−i+1(即子串长度)的最大值
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2),空间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
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]
方法2(最长公共子串):
将原字符串倒置后求出最长公共子串即可。
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2),空间复杂度:
O
(
n
)
O(n)
O(n)
if not s:
return ""
length = len(s)
sRev = s[::-1]
maxLen, maxEnd = 0, 0
arr = [0 for _ in range(length)]
for i in range(length):
for j in range(length - 1, 0, -1):
if sRev[j] == s[i]:
if i == 0 or j == 0:
arr[j] = 1
else:
arr[j] = arr[j - 1] + 1
else:
arr[j] = 0
if arr[j] > maxLen:
indexBef = length - 1 - j
if indexBef + arr[j] - 1 == i:
maxLen = arr[j]
maxEnd = i
return s[maxEnd - maxLen + 1:maxEnd + 1]
方法3(中心扩展算法):
我们枚举每一种子串长度为 1 或 2 的情况,并从对应的子串开始不断地向两边扩展。如果两边的字母相同,我们就可以继续扩展,例如从
P
(
i
+
1
,
j
−
1
)
P(i+1,j−1)
P(i+1,j−1) 扩展到
P
(
i
,
j
)
P(i,j)
P(i,j);如果两边的字母不同,我们就可以停止扩展,因为在这之后的子串都不能是回文串了。
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2),空间复杂度:
O
(
1
)
O(1)
O(1)
class Solution:
def expandAroundCenter(self, s, left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return left + 1, right - 1
def longestPalindrome(self, s: str) -> str:
start, end = 0, 0
for i in range(len(s)):
left1, right1 = self.expandAroundCenter(s, i, i)
left2, right2 = self.expandAroundCenter(s, i, i + 1)
if right1 - left1 > end - start:
start, end = left1, right1
if right2 - left2 > end - start:
start, end = left2, right2
return s[start: end + 1]
方法4(Manacher算法(马拉车算法)):
如果位置
j
j
j 的臂长为
l
e
n
g
t
h
length
length,并且有
j
+
l
e
n
g
t
h
>
i
j + length > i
j+length>i,
当在位置
i
i
i 开始进行中心拓展时,我们可以先找到
i
i
i 关于
j
j
j 的对称点
2
∗
j
−
i
2 * j - i
2∗j−i。那么如果点
2
∗
j
−
i
2 * j - i
2∗j−i 的臂长等于
n
n
n,我们就可以知道,点
i
i
i 的臂长至少为
m
i
n
(
j
+
l
e
n
g
t
h
−
i
,
n
)
min(j + length - i, n)
min(j+length−i,n)。那么我们就可以直接跳过
i
i
i到
i
+
m
i
n
(
j
+
l
e
n
g
t
h
−
i
,
n
)
i + min(j + length - i, n)
i+min(j+length−i,n) 这部分,从
i
+
m
i
n
(
j
+
l
e
n
g
t
h
−
i
,
n
)
+
1
i + min(j + length - i, n) + 1
i+min(j+length−i,n)+1 开始拓展。
如何处理长度为偶数的回文字符串呢?
我们可以通过一个特别的操作将奇偶数的情况统一起来:我们向字符串的头尾以及每两个字符中间添加一个特殊字符
#
\#
#,比如字符串
a
a
b
a
aaba
aaba 处理后会变成
#
a
#
a
#
b
#
a
#
\#a\#a\#b\#a\#
#a#a#b#a#。那么原先长度为偶数的回文字符串
a
a
aa
aa 会变成长度为奇数的回文字符串
#
a
#
a
#
\#a\#a\#
#a#a#,而长度为奇数的回文字符串
a
b
a
aba
aba 会变成长度仍然为奇数的回文字符串
#
a
#
b
#
a
#
\#a\#b\#a\#
#a#b#a#,我们就不需要再考虑长度为偶数的回文字符串了。
时间复杂度:
O
(
n
)
O(n)
O(n),空间复杂度:
O
(
n
)
O(n)
O(n)
class Solution:
def expand(self, s, left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return (right - left - 2) // 2
def longestPalindrome(self, s: str) -> str:
end, start = -1, 0
s = '#' + '#'.join(list(s)) + '#'
arm_len = []
right = -1
j = -1
for i in range(len(s)):
if right >= i:
i_sym = 2 * j - i
min_arm_len = min(arm_len[i_sym], right - i)
cur_arm_len = self.expand(s, i - min_arm_len, i + min_arm_len)
else:
cur_arm_len = self.expand(s, i, i)
arm_len.append(cur_arm_len)
if i + cur_arm_len > right:
j = i
right = i + cur_arm_len
if 2 * cur_arm_len + 1 > end - start:
start = i - cur_arm_len
end = i + cur_arm_len
return s[start+1:end+1:2]