202008leetcode刷题记录
19. 删除链表的倒数第N个节点
题目要求:
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
思路:
添加一个头结点,以便删除第一个结点。让一个指针先走 n 下,然后两个指针一起走直到后面的指针走到最后一个结点。这时候前面那个指针就指向要删除的结点的前一个结点,就可以删了。
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
L = ListNode(0)
if not head.next:
return head.next
L.next = head
pre, p = L, L
while n > 0:
p = p.next
n -= 1
while p.next:
pre = pre.next
p = p.next
pre.next = pre.next.next
return L.next
17. 电话号码的字母组合
题目要求:
给定一个仅包含数字2-9
的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意1
不对应任何字母。
思路:
其实就是返回每个数字对应字母的笛卡尔积,python内置的product就可以实现。
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits:
return []
hashmap = {
"2": "abc",
"3": "def",
"4": "ghi",
"5": "jkl",
"6": "mno",
"7": "pqrs",
"8": "tuv",
"9": "wxyz",
}
combinations = [hashmap[digit] for digit in digits]
return ["".join(_) for _ in itertools.product(*combinations)]
20. 有效的括号
题目要求:
给定一个只包括'(',')','{','}','[',']'
的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
思路:
栈。遇到左括号就进栈,遇到右括号就出栈并判断是否可以匹配。注意栈不空才能出栈。
class Solution:
def isValid(self, s: str) -> bool:
stack = []
hashmap = {'(': ')', '[': ']', '{': '}'}
for ch in s:
if ch in hashmap:
# 遇到左括号,进栈
stack.append(ch)
elif not stack:
# 遇到右括号,如果栈空,说明不匹配
return False
elif hashmap[stack.pop()] != ch:
# 遇到右括号且栈不空,但是左右括号不是一个类型
return False
return not stack
43. 字符串相乘
题目要求:
给定两个以字符串形式表示的非负整数num1
和num2
,返回num1
和num2
的乘积,它们的乘积也表示为字符串形式。
思路:
模拟法。模拟我们平时用的乘法,就是从个位数开始相乘,最后相加(当然还有进位)。加法的实现可以直接照搬415. 字符串相加的实现。
class Solution(object):
def multiply(self, num1, num2):
if num1 == "0" or num2 == "0":
return "0"
l1, l2 = len(num1), len(num2)
if l1 < l2:
num1, num2 = num2, num1
l1, l2 = l2, l1
num2 = num2[::-1]
ans = "0"
for i, digit in enumerate(num2):
tmp = self.StringMultiplyDigit(num1, int(digit)) + "0" * i
ans = self.addStrings(ans, tmp) #计算res和tmp的和
return ans
def StringMultiplyDigit(self,string, n):
s = string[::-1]
res = []
for i, char in enumerate(s):
num = int(char)
res.append(num * n)
res = self.CarrySolver(res)
res = res[::-1]
return "".join(str(x) for x in res)
def CarrySolver(self, nums):
i = 0
while i < len(nums):
if nums[i] >= 10:
carrier = nums[i] // 10
if i == len(nums) - 1:
nums.append(carrier)
else:
nums[i + 1] += carrier
nums[i] %= 10
i += 1
return nums
def addStrings(self, num1: str, num2: str) -> str:
ans = ''
i, j, carry = len(num1) - 1, len(num2) - 1, 0
while i > -1 or j > -1:
a = int(num1[i]) if i > -1 else 0
b = int(num2[j]) if j > -1 else 0
res = a + b + carry
carry = res // 10
ans = str(res % 10) + ans
i, j = i - 1, j - 1
return "1" + ans if carry else ans
80. 删除排序数组中的重复项 II
题目要求:
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(1)
额外空间的条件下完成。
思路:
双指针。题目里面的最多出现两次,意思就是如果出现了三次,就得删到只剩两次,而不是一次,有点坑。一个指针走得快,一个走得慢。快指针一边走一边对重复元素进行计数,如果重复不超过两次,就放到慢指针那里,超过两次,快指针就接着走。
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
n = len(nums)
left, right = 1, 1
cnt = 1
while right < n:
if nums[right] == nums[right - 1]:
cnt += 1
else:
cnt = 1
if cnt < 3:
nums[left] = nums[right]
left += 1
right += 1
return left
88. 合并两个有序数组
题目要求:
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明:
- 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
- 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
思路:
双指针。从前向后遍历,而nums1
数组的后面都是0,此时要么将nums2
的元素插入进来(就得把nums1
中的元素向后移动,代价很大),要么将nums1
中的元素与后面的0交换(会打乱顺序,反而使数组变得无序)。于是从前往后遍历只能是牺牲空间,再开辟一个数组用来存储结果。
class Solution(object):
def merge(self, nums1, m, nums2, n):
nums1_copy = nums1[:m]
nums1[:] = []
p1, p2 = 0, 0
while p1 < m and p2 < n:
if nums1_copy[p1] < nums2[p2]:
nums1.append(nums1_copy[p1])
p1 += 1
else:
nums1.append(nums2[p2])
p2 += 1
if p1 < m:
nums1[p1 + p2:] = nums1_copy[p1:]
if p2 < n:
nums1[p1 + p2:] = nums2[p2:]
如果想要不牺牲空间和速度,可以从后往前遍历。由于nums1
之后的0的个数是可以把nums2
中的元素全部放入其中的,因此从后往前遍历不会涉及到元素的移动或者交换。
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
p1 = m - 1
p2 = n - 1
p = len(nums1) - 1 # 用来插入的位置
while p1 >= 0 and p2 >= 0:
if nums1[p1] < nums2[p2]:
nums1[p] = nums2[p2]
p2 -= 1
else:
nums1[p] = nums1[p1]
p1 -= 1
p -= 1
if p2 > -1:
nums1[:p2 + 1] = nums2[:p2 + 1]
100. 相同的树
题目要求:
给定两个二叉树,编写一个函数来检验它们是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
思路:
递归先序遍历。
class Solution:
def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
if not p and not q:
return True
elif (not p and q) or (p and not q):
return False
elif p.val != q.val:
return False
else:
return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
206. 反转链表
题目要求:
反转一个单链表。
思路:
加一个头结点,辅助链表的反转。
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
L = ListNode(0)
L.next = head
p = head.next
while p:
q = L.next
L.next = p
head.next = p.next
p.next = q
p = head.next
return L.next
递归。当只剩一个结点时。就不需要反转了,此时结束递归。
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
newHead = self.reverseList(head.next)
head.next.next = head
head.next = None
return newHead
225. 用队列实现栈
题目要求:
使用队列实现栈的下列操作:
- push(x) -- 元素 x 入栈
- pop() -- 移除栈顶元素
- top() -- 获取栈顶元素
- empty() -- 返回栈是否为空
思路:
用一个队列来模拟栈的操作。入栈就直接入队列就行;出栈就麻烦一些了,队列是先进先出,也就是说要完成出栈操作得让队尾的元素出队,那就让出队的元素重新入队,直到队头是原来的队尾元素,这时候出队就是出栈了。python的Queue()好像没有获取队头或者队尾元素的方法,要获取栈顶元素,就出栈再重新入栈。判断栈是否为空即判断这个队列是否为空。
from queue import Queue
class MyStack:
def __init__(self):
"""
Initialize your data structure here.
"""
self.que = Queue()
def push(self, x: int) -> None:
"""
Push element x onto stack.
"""
self.que.put(x)
def pop(self) -> int:
"""
Removes the element on top of the stack and returns that element.
"""
n = self.que.qsize()
while n > 1:
self.que.put(self.que.get())
n -= 1
return self.que.get()
def top(self) -> int:
"""
Get the top element.
"""
x = self.pop()
self.push(x)
return x
def empty(self) -> bool:
"""
Returns whether the stack is empty.
"""
return self.que.empty()
232. 用栈实现队列
题目要求:
使用栈实现队列的下列操作:
- push(x) -- 将一个元素放入队列的尾部。
- pop() -- 从队列首部移除元素。
- peek() -- 返回队列首部的元素。
- empty() -- 返回队列是否为空。
思路:
一个栈肯定是没办法模拟队列的,至少要用到两个栈。stack1
用来模拟入队操作,stack2
模拟出队操作。入队操作比较简单,直接把元素压入stack1
即可;出队操作要分成两种情况,stack2
为空时,需要将stack1
中的元素逐一弹出并压入stack2
,这样一来stack1
的栈底元素,即队列的队头就跑到了stack2
的栈顶,也就是将stack1
翻转了一遍,这时只要弹出stack2
的栈顶即为出队操作。当stack2
不空时,直接出栈就行了;要返回队头的元素,就返回stack2[-1]
或者stack1[0]
;当两个栈都为空时,队列为空。
class MyQueue:
def __init__(self):
"""
Initialize your data structure here.
"""
self.stack1 = []
self.stack2 = []
def push(self, x: int) -> None:
"""
Push element x to the back of queue.
"""
self.stack1.append(x)
def pop(self) -> int:
"""
Removes the element from in front of queue and returns that element.
"""
if self.stack2:
return self.stack2.pop()
else:
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
def peek(self) -> int:
"""
Get the front element.
"""
if self.stack2:
return self.stack2[-1]
else:
return self.stack1[0]
def empty(self) -> bool:
"""
Returns whether the queue is empty.
"""
return not self.stack1 and not self.stack2
345. 反转字符串中的元音字母
题目要求:
编写一个函数,以字符串作为输入,反转该字符串中的元音字母。
思路:
双指针。先将字符串转为数组,因为python中字符串是不可变类型。从头尾开始遍历这个数组,遇到元音字母就交换顺序。
class Solution:
def reverseVowels(self, s: str) -> str:
str_list = list(s)
n = len(str_list)
left, right = 0, n - 1
hashmap = {'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'}
while left < right:
while left < right and str_list[left] not in hashmap:
left += 1
while left < right and str_list[right] not in hashmap:
right -= 1
str_list[left], str_list[right] = str_list[right], str_list[left]
left += 1
right -= 1
return ''.join(str_list)
524. 通过删除字母匹配到字典里最长单词
题目要求:
给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。
思路:
双指针。题目中说“删除给定字符串的某些字符”,其实不用真的删除,只要用指针,如果不相等就跳过就好了。把字符串字典中的每个字符串拿出来与题中的字符串进行比较,如果可以过删除给定字符串的某些字符来得到,就保存到ans
中,在保存之前要比较一下这个答案和之前的ans
谁“更加”符合题目的要求,即长度最长且字典顺序最小的字符串。
class Solution:
def findLongestWord(self, s: str, d: List[str]) -> str:
ans = ""
n = len(s)
for string in d:
j = 0
for i in range(n):
if s[i] == string[j]:
j += 1
if j == len(string):
if j > len(ans) or (j == len(ans) and string < ans):
ans = string
break
return ans
557. 反转字符串中的单词 III
题目要求:
给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
思路:
字符串中没有多余的空格,就以空格为界,将每个单词分开,再将其分别反转。
class Solution:
def reverseWords(self, s: str) -> str:
ans = []
n = len(s)
start = 0
for i in range(n):
if s[i] == ' ':
ans.append(s[start:i])
start = i + 1
ans.append(s[start:])
m = len(ans)
for i in range(m):
ans[i] = ans[i][::-1]
return ' '.join(ans)
633. 平方数之和
题目要求:
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得\(a^2 + b^2 = c\)。
思路:
双指针。
class Solution:
def judgeSquareSum(self, c: int) -> bool:
if c == 0:
return True
lo, hi = 0, int(math.sqrt(c) + 1)
while lo <= hi:
if lo ** 2 + hi ** 2 > c:
hi -= 1
elif lo ** 2 + hi ** 2 < c:
lo += 1
else:
return True
return False
647. 回文子串
题目要求:
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
思路:
动态规划。用一个二维数组来记录字符串的子串s[j: i]
是否是回文串,当s[j: i]
的长度为 1 时,它一定是回文串;当s[i] == s[j]
时,如果s[j + 1: i - 1]
也是回文串,那么它也是回文串。其余情况都不是回文串。
class Solution:
def countSubstrings(self, s: str) -> int:
n = len(s)
ans = 0
dp = [[0] * n for _ in range(n)]
for i in range(n):
for j in range(i + 1):
if s[i] == s[j] and (i - j < 2 or dp[j + 1][i - 1]):
dp[j][i] = 1
ans += 1
return ans
657. 机器人能否返回原点
题目要求:
在二维平面上,有一个机器人从原点 (0, 0) 开始。给出它的移动顺序,判断这个机器人在完成移动后是否在 (0, 0) 处结束。
移动顺序由字符串表示。字符move[i]
表示其第i
次移动。机器人的有效动作有 R(右),L(左),U(上)和 D(下)。如果机器人在完成所有动作后返回原点,则返回true
。否则,返回false
。
注意:机器人“面朝”的方向无关紧要。 “R” 将始终使机器人向右移动一次,“L” 将始终向左移动等。此外,假设每次移动机器人的移动幅度相同。
思路:
模拟法,左右移动次数要相同,上下也是。
class Solution:
def judgeCircle(self, moves: str) -> bool:
hrz, vti = 0, 0
for s in moves:
if s == 'L':
hrz += 1
elif s == 'R':
hrz -= 1
if s == 'U':
vti += 1
elif s == 'D':
vti -= 1
return hrz == 0 and vti == 0
696. 计数二进制子串
题目要求:
给定一个字符串s
,计算具有相同数量0和1的非空(连续)子字符串的数量,并且这些子字符串中的所有0和所有1都是组合在一起的。重复出现的子串要计算它们出现的次数。
思路:
按照题意,这些0和1必须是相邻的,而且0和1的数量必须是相等的。于是只要分段统计0和1的个数,相邻计数中较小的那个就是连续二进制子串的个数。
class Solution:
def countBinarySubstrings(self, s: str) -> int:
n = len(s)
p = pre = ans = cnt = 0
while p < n:
cur = s[p]
while p < n and s[p] == cur:
p += 1
cnt += 1
ans += min(cnt, pre)
pre = cnt
cnt = 0
return ans
925. 长按键入
题目要求:
你的朋友正在使用键盘输入他的名字name
。偶尔,在键入字符c
时,按键可能会被长按,而字符可能被输入1
次或多次。
你将会检查键盘输入的字符typed
。如果它对应的可能是你的朋友的名字(其中一些字符可能被长按),那么就返回True
。
思路:
本来以为很简单,但是发现起始各种条件的限制是挺多的,不能相等了就移动一下了事。
class Solution:
def isLongPressedName(self, name: str, typed: str) -> bool:
m, n = len(name), len(typed)
if m > n:
return False
i, j = 0, 0
while i < m and j < n:
if name[i] == typed[j]:
i += 1
j += 1
elif j > 0 and typed[j] == typed[j - 1]:
j += 1
else:
return False
while j < n:
if typed[j] != typed[j - 1]:
return False
j += 1
return i == m
930. 和相同的二元子数组
题目要求:
在由若干0
和1
组成的数组A
中,有多少个和为S
的非空子数组。
思路:
前缀和可以算出任意一个切片的子数组的和,再用一个字典来计数,就可以在O(1)
的时间内找到相应和的数量。实际上就是求满足S = preSum[j] - preSum[i]
的子数组,也即preSum[j] = preSum[i] + S
。
class Solution(object):
def numSubarraysWithSum(self, A, S):
preSum = [0]
for x in A:
preSum.append(preSum[-1] + x)
cnt = collections.Counter()
ans = 0
for x in preSum:
ans += cnt[x]
cnt[x + S] += 1
return ans
977. 有序数组的平方
题目要求:
给定一个按非递减顺序排序的整数数组A
,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。
思路:
可以直接平方了再放进去排序。
class Solution:
def sortedSquares(self, A: List[int]) -> List[int]:
ans = []
for x in A:
ans.append(x ** 2)
ans.sort()
return ans
用双指针速度会更快一些。由于数组是非降序排列的,因此两边的元素一定是绝对值最大的。于是双指针由两边开始,且新元素也往新数组的后面放。
class Solution:
def sortedSquares(self, A: List[int]) -> List[int]:
n = len(A)
if n == 1:
return [A[0] ** 2]
ans = [0] * n
lo, hi = 0, n - 1
while lo <= hi and n > 0:
n -= 1
if abs(A[lo]) > abs(A[hi]):
ans[n] = A[lo] ** 2
lo += 1
else:
ans[n] = A[hi] ** 2
hi -= 1
return ans
986. 区间列表的交集
题目要求:
给定两个由一些闭区间组成的列表,每个区间列表都是成对不相交的,并且已经排序。返回这两个区间列表的交集。
(形式上,闭区间[a, b]
(其中a <= b
)表示实数x
的集合,而a <= x <= b
。两个闭区间的交集是一组实数,要么为空集,要么为闭区间。例如,[1, 3] 和 [2, 4] 的交集为 [2, 3]。)
思路:
双指针。两个指针分别指向两个区间列表中的一个区间,两个闭区间中较大的左边界作为交集的左边界,较小的右边界作为交集的右边界(要注意,指针指向的两个区间可能是不相交的,因此输出时需要检查左右边界的大小关系);对于右边界较小的区间,它不可能再与别的区间有交集,因此指针后移,这就是指针更新的规则。
class Solution:
def intervalIntersection(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]:
m, n = len(A), len(B)
i = j = 0
ans = []
while i < m and j < n:
start = max(A[i][0], B[j][0])
if A[i][1] <= B[j][1]:
end = A[i][1]
i += 1
else:
end = B[j][1]
j += 1
if start <= end:
ans.append([start, end])
return ans
剑指 Offer 09. 用两个栈实现队列
题目要求:
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail
和deleteHead
,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead
操作返回-1
)
思路:
跟232一样,就是多个判断。
class CQueue:
def __init__(self):
self.stack1 = []
self.stack2 = []
def appendTail(self, value: int) -> None:
self.stack1.append(value)
def deleteHead(self) -> int:
if self.stack2:
return self.stack2.pop()
elif self.stack1:
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
else:
return -1
剑指 Offer 22. 链表中倒数第k个节点
题目要求:
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。
思路:
双指针。
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
L = ListNode(0)
L.next = head
pre = q = L
while k > 0:
q = q.next
k -= 1
while q:
pre = pre.next
q = q.next
return pre
面试题 10.01. 合并排序的数组
题目要求:
给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。编写一个方法,将 B 合并入 A 并排序。初始化 A 和 B 的元素数量分别为 m 和 n。
思路:
双指针。跟前面的88题是一个思路的。
class Solution:
def merge(self, A: List[int], m: int, B: List[int], n: int) -> None:
"""
Do not return anything, modify A in-place instead.
"""
while m > 0 and n > 0:
if A[m - 1] >= B[n - 1]:
m -= 1
A[m + n] = A[m]
else:
n -= 1
A[m + n] = B[n]
if n > 0:
A[:n] = B[:n]
面试题 10.09. 排序矩阵查找
题目要求:
给定M×N
矩阵,每一行、每一列都按升序排列,请编写代码找出某元素。
思路:
从右上角开始遍历矩阵,如果目标值大于当前元素,说明只可能在其下方找到目标值(当然也包括左下方),就往下走一行;如果目标值小于当前元素,说明不可能在当前列找到它了,往左走一列。
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
if not matrix or not matrix[0]:
return False
m, n = len(matrix), len(matrix[0])
row, col = 0, n - 1
while row < m and col > -1:
if target == matrix[row][col]:
return True
elif target < matrix[row][col]:
col -= 1
elif target > matrix[row][col]:
row += 1
return False