76 KiB
76 KiB
两指针
27. 移除元素
快慢指针法
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
# 快慢指针
fast = 0 # 快指针
slow = 0 # 慢指针
size = len(nums)
while fast < size: # 不加等于是因为,a = size 时,nums[a] 会越界
# slow 用来收集不等于 val 的值,如果 fast 对应值不等于 val,则把它与 slow 替换
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
# 15. 三数之和
# 判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 请你找出所有满足条件且不重复的三元组。两指针
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
for i in range(len(nums)):
# 如果第一个元素已经大于0,不需要进一步检查
if nums[i] > 0:
return result
# 跳过相同的元素以避免重复
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = len(nums) - 1
while right > left:
sum_ = nums[i] + nums[left] + nums[right]
if sum_ < 0:
left += 1
elif sum_ > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
# 跳过相同的元素以避免重复
while right > left and nums[right] == nums[right - 1]:
right -= 1
while right > left and nums[left] == nums[left + 1]:
left += 1
right -= 1
left += 1
return result
# 1 4Sum/四数之和 (Medium)
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
n = len(nums)
res = []
for i in range(n):
if i > 0 and nums[i] == nums[i - 1]: continue
for k in range(i+1, n):
if k > i + 1 and nums[k] == nums[k-1]: continue
p = k + 1
q = n - 1
while p < q:
if nums[i] + nums[k] + nums[p] + nums[q] > target: q -= 1
elif nums[i] + nums[k] + nums[p] + nums[q] < target: p += 1
else:
res.append([nums[i], nums[k], nums[p], nums[q]])
while p < q and nums[p] == nums[p + 1]: p += 1
while p < q and nums[q] == nums[q - 1]: q -= 1
p += 1
q -= 1
return res
# 26 Remove Duplicates from Sorted Array
def removeDuplicates(nums):
if not nums:
return 0
# 使用双指针法
slow = 0 # 慢指针,指向不重复的元素
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1 # 返回新数组的长度
#R 75 Sort Colors/颜色分类 (Medium)
# p0:指向 0 应该放置的位置;p0左边全是0, p0本身并不包括0
# p2:指向 2 应该放置的位置;p2右边全是0, p2本身并不包括2
# i:当前遍历位置
class Solution:
def sortColors(self, nums: List[int]) -> None:
p0, i , p2 = 0,0,len(nums) - 1
while i <= p2:
if nums[i] == 0:
nums[i], nums[p0] = nums[p0], nums[i]
p0 += 1
i += 1
elif nums[i] == 2:
nums[i], nums[p2] = nums[p2], nums[i]
p2 -= 1
# 注意:i 不增加,因为换过来的 nums[i] 还要检查
else:
i += 1
# 88 Merge Sorted Array/合并两个有序数组 (Easy)
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
tail = len(nums1) - 1
i1 = m - 1
i2 = n - 1
# 个人经常用 while true,然后在循环内部列出所有情况
while True:
# 都左移到头,退出
if i1 == -1 and i2 == -1:
break
# 一方左移到头,选取另一方赋值,然后左移
elif i1 == -1:
nums1[tail] = nums2[i2]
i2 -= 1
elif i2 == -1:
nums1[tail] = nums1[i1] # 也可以省去,直接 i1 -= 1;
i1 -= 1
# 选取大的元素赋值,然后左移
elif nums1[i1] > nums2[i2]:
nums1[tail] = nums1[i1]
i1 -= 1
else:
nums1[tail] = nums2[i2]
i2 -= 1
tail -= 1
# 167 Two Sum II - Input array is sorted $/两数之和 II - 输入有序数组 (Medium)
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
i, j = 0, len(numbers) - 1
while i < j:
s = numbers[i] + numbers[j]
if s > target: j -= 1
elif s < target: i += 1
else: return i + 1, j + 1
return []
# 283 Move Zeroes/移动零 (Easy) 将所有 0 移动到数组的末尾
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
i0 = 0
for i in range(len(nums)):
if nums[i]:
nums[i], nums[i0] = nums[i0], nums[i]
i0 += 1
# 345 "Reverse Vowels of a String"
def reverseVowels(s):
vowels = set("aeiouAEIOU")
s = list(s)
left, right = 0, len(s) - 1
while left < right:
while left < right and s[left].lower() not in vowels:
left += 1
while left < right and s[right].lower() not in vowels:
right -= 1
# 交换左右两侧的元音字母
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
return ''.join(s)
# 415 Add Strings/字符串相加 (Easy)
class Solution:
def addStrings(self, num1: str, num2: str) -> str:
res = ""
i, j, carry = len(num1) - 1, len(num2) - 1, 0
while i >= 0 or j >= 0:
n1 = int(num1[i]) if i >= 0 else 0
n2 = int(num2[j]) if j >= 0 else 0
tmp = n1 + n2 + carry
carry = tmp // 10
res = str(tmp % 10) + res
i, j = i - 1, j - 1
return "1" + res if carry else res
# 443 String Compression/压缩字符串 (Easy)
# ["a","2","b","2","c","3"]
# 三指针:指针1当前字符,指针2找这个字符有连续多少个,指针3当前在数组中的读写位置。
class Solution:
def compress(self, chars: List[str]) -> int:
n = len(chars)
i = 0
write = 0
while i < n:
j = i
while j < n and chars[j] == chars[i]:
j += 1
chars[write] = chars[i]
write += 1
if j - i > 1:
for c in str(j-i):
chars[write] = c
write += 1
i = j
return write
# 0 Split Array with Equal Sum $/将数组分割成和相等的子数组 (Medium)
# 子数组 (0, i - 1) , (i + 1, j - 1) , (j + 1, k - 1) , (k + 1, n - 1) 的相等,左闭右闭
class Solution:
def splitArray(self, nums: List[int]) -> bool:
n = len(nums)
presum = [0 for _ in range(n + 1)] #用的虚指(从1开始计数)
for i in range(n):
presum[i + 1] = presum[i] + nums[i]
#(1) L (2) M (3) R (4) 3个指针,4个区域
for M in range(3, n - 3):
memo = set()
for L in range(1, M - 1):
zoom1 = presum[L]
zoom2 = presum[M] - presum[L + 1]
if zoom1 == zoom2:
memo.add(zoom1)
for R in range(M + 2, n - 1):
zoom3 = presum[R] - presum[M + 1]
zoom4 = presum[n] - presum[R + 1]
if zoom3 == zoom4 and zoom3 in memo:
return True
return False
# 633 Sum of Square Numbers/平方数之和 (Easy)
class Solution:
def judgeSquareSum(self, c: int) -> bool:
low, high = 0, int(c**0.5)
while low<=high:
sumOf = low*low+high*high
if sumOf==c: return True
elif sumOf<c: low += 1
else: high -= 1
return False
#R 259 3Sum Smaller $/较小的三数之和 (Medium)
# satisfy the condition nums[i] + nums[j] + nums[k] < target
class Solution:
def threeSumSmaller(self, nums, target):
if len(nums) < 3:
return 0
res = 0
nums.sort()
n = len(nums)
for i in range(n - 2):
left, right = i + 1, n - 1
while left < right:
if nums[i] + nums[left] + nums[right] < target:
res += right - left # 所有 (nums[i], nums[left], nums[left+1]...nums[right]) 都满足条件
left += 1
else:
right -= 1
return res
# 977.有序数组的平方
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
l, r, i = 0, len(nums)-1, len(nums)-1
res = [float('inf')] * len(nums) # 需要提前定义列表,存放结果
while l <= r:
if nums[l] ** 2 < nums[r] ** 2: # 左右边界进行对比,找出最大值
res[i] = nums[r] ** 2
r -= 1 # 右指针往左移动
else:
res[i] = nums[l] ** 2
l += 1 # 左指针往右移动
i -= 1 # 存放结果的指针需要往前平移一位
return res
# 845 "Longest Mountain in Array" 给定一个整数数组 A,找出数组中的最长山脉的长度
# 遍历数组中的每个可能的山顶,然后从山顶向左右两侧扩展,找到山脉的左侧和右侧
def longestMountain(A):
n = len(A)
if n < 3:
return 0
max_length = 0
for peak in range(1, n - 1):
left, right = peak, peak
# 找到山脉的左侧
while left > 0 and A[left - 1] < A[left]:
left -= 1
# 找到山脉的右侧
while right < n - 1 and A[right + 1] < A[right]:
right += 1
# 确保是山脉
if left < peak < right:
max_length = max(max_length, right - left + 1)
return max_length
# 985.9 Interval List Intersections/区间列表的交集 (Medium)
class Solution:
def intervalIntersection(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]:
ans = []
i = j = 0
while i < len(A) and j < len(B):
lo = max(A[i][0], B[j][0])
hi = min(A[i][1], B[j][1])
if lo <= hi:
ans.append([lo, hi])
if A[i][1] < B[j][1]:
i += 1
else:
j += 1
return ans
# 1155.9 Swap For Longest Repeated Character Substring/单字符重复子串的最大长度 (Medium)
# 你只能交换其中两个字符一次或者什么都不做,返回单字符最长的子串的长度。
# j 指向 i,并不断地向右移动 j,直到 j 指向的字符与 i 指向的字符不同,跳过指针 j 指向的字符,用指针 k 继续向右移动,直到 k 指向的字符与 i 指向的字符不同
class Solution:
def maxRepOpt1(self, text: str) -> int:
cnt = Counter(text)
n = len(text)
ans = i = 0
while i < n:
j = i
while j < n and text[j] == text[i]:
j += 1
l = j - i
k = j + 1
while k < n and text[k] == text[i]:
k += 1
r = k - j - 1
ans = max(ans, min(l + r + 1, cnt[text[i]]))
i = j
return ans
#H 1163 Last Substring in Lexicographical Order/按字典序排在最后的子串 (Hard)
# 找出它的所有子串并按字典序排列,返回排在最后的那个子串。
# i指向的是当前找到字典序最大的字符,j指向的是当前要进行比较的字符。使用一个位移指针k,来比较i和j构成的子串[i,..,i + k]和[j,...,j + k]的顺序。i始终指向当前找到字典序最大的字符
class Solution:
def lastSubstring(self, s: str) -> str:
n = len(s)
i = 0
j = 1
k = 0
while j + k < n:
if s[i + k] == s[j + k]:
k += 1
elif s[i + k] < s[j + k]:
i += k + 1
k = 0
if i >= j:
j = i + 1
else:
j += k + 1
k = 0
return s[i:]
滑动窗口
209.长度最小的子数组
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
l = len(nums)
left = 0
right = 0
min_len = float('inf')
cur_sum = 0 #当前的累加值
while right < l:
cur_sum += nums[right]
while cur_sum >= s: # 当前累加值大于目标值
min_len = min(min_len, right - left + 1)
cur_sum -= nums[left]
left += 1
right += 1
return min_len if min_len != float('inf') else 0
# Max Consecutive Ones II 你可以改变其中最多 k 个 0 的位,求该数组中最大连续1的个数,滑动窗口
def findMaxConsecutiveOnes(nums, k):
left, right = 0, 0
max_length = 0
zero_count = 0
while right < len(nums):
if nums[right] == 0:
zero_count += 1
while zero_count > k:
if nums[left] == 0:
zero_count -= 1
left += 1
max_length = max(max_length, right - left + 1)
right += 1
return max_length
# 340 "Longest Substring with At Most K Distinct Characters" 给定一个字符串 s,找出至多包含 k 个不同字符的最长子串的长度,滑动窗口
def lengthOfLongestSubstringKDistinct(s, k):
if k == 0:
return 0
left, right = 0, 0
char_count = {}
max_length = 0
while right < len(s):
char_count[s[right]] = char_count.get(s[right], 0) + 1
while len(char_count) > k:
char_count[s[left]] -= 1
if char_count[s[left]] == 0:
del char_count[s[left]]
left += 1
max_length = max(max_length, right - left + 1)
right += 1
return max_length
#R 1358 Number of Substrings Containing All Three Characters/包含所有三种字符的子字符串数目 (Medium)
# 请你返回 a,b 和 c 都 至少 出现过一次的子字符串数目。
# 滑动窗口的内层循环结束时,右端点固定在 right,左端点在 0,1,2,…,left−1 的所有子串都是合法的,这一共有 left 个,加入答案。
class Solution:
def numberOfSubstrings(self, s: str) -> int:
ans = left = 0
cnt = defaultdict(int)
for c in s:
cnt[c] += 1
while len(cnt) == 3:
out = s[left] # 离开窗口的字母
cnt[out] -= 1
if cnt[out] == 0:
del cnt[out]
left += 1
ans += left
return ans
#R 395 Longest Substring with At Least K Repeating Characters/至少有 K 个重复字符的最长子串 (Medium) s = "ababbc", k = 2 最长子串为 "ababb"
class Solution:
def longestSubstring(self, s: str, k: int) -> int:
ans = 0
for i in range(1,27): #分治:遍历可能的字符种类数量(1~26)
cnt = Counter() #计数
unique = 0 #字符种类
num_k = 0 #满足出现次数大于等于k的字符串数量
left = 0
for right,char in enumerate(s):
if cnt[char] == 0:
unique += 1
cnt[char] += 1
if cnt[char] == k:
num_k += 1
#当字符串种类超过i时,移动左窗口
while unique > i:
left_char = s[left]
if cnt[left_char] == k:
num_k -= 1 #因为要一直移动左窗口,所以计数会一直减少,当进入循环时刚好==k时,再减一后,num_k就要减一了
cnt[left_char] -= 1
if cnt[left_char] == 0: #如果减完了,种类就少一个
unique -= 1
left += 1
if unique == i and num_k == i: #当种类满足要求,且子串里的数量都满足>=k时,就更新ans
ans = max(ans ,right - left +1)
return ans
#R 424 Longest Repeating Character Replacement/替换后的最长重复字符 (Medium) 可替换字符k次
class Solution:
def characterReplacement(self, s: str, k: int) -> int:
count = [0 for _ in range(26)] #记录当前窗口的字母出现次数
left = 0 #滑动窗口左边界
right = 0 #滑动窗口右边界
retval = 0 #最长窗口长度
while right < len(s):
count[ord(s[right])-ord('A')] += 1
benchmark = max(count) #选择出现次数最多的字母为基准
others = sum(count) - benchmark #则其他字母需要通过替换操作来变为基准
if others <= k: #通过与K进行比较来判断窗口是进行扩张?
right += 1
retval = max(retval, right-left)#记录当前有效窗口长度
else: #通过与K进行比较来判断窗口还是进行位移?
count[ord(s[left])-ord('A')] -= 1
left += 1
right += 1 #这里注意:位移操作需要整个向右移,不仅仅只是left向右
return retval #返回最长窗口长度
# 486.99 Max Consecutive Ones II $/最大连续1的个数 II (Medium)
# 最多可以翻转一个 0 ,则返回数组中连续 1 的最大个数
class Solution:
def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
res=l=count_zero = 0
for r, num in enumerate(nums):
if not num :
count_zero +=1
while count_zero >1 :
if not nums[l]:
count_zero-=1
l+=1
res = max(res, r-l+1)
return res
# 1100 Find K-Length Substrings With No Repeated Characters/长度为 K 的无重复字符子串 (Medium)
# 找出所有长度为 K 且不含重复字符的子串,返回子串数目。
class Solution:
def numKLenSubstrNoRepeats(self, s: str, k: int) -> int:
ans = 0
cnt = Counter(s[: k - 1])
for i, (in_, out) in enumerate(zip(s[k - 1 :], s)):
cnt[in_] += 1
if len(cnt) == k:
ans += 1
cnt[out] -= 1
if cnt[out] == 0:
del cnt[out]
return ans
# 验证子列不带顺序 + 连续 permutation-in-string,定长滑动窗口
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
m, n = len(s1), len(s2)
if m > n:
return False
cnt = collections.Counter(s1) # 哈希表:记录需要匹配到的各个字符的数目
need = m # 记录需要匹配到的字符总数【need=0表示匹配到了】
for right in range(n):
# 窗口右边界
ch = s2[right] # 窗口中新加入的字符
if ch in cnt: # 新加入的字符ch位于s1中
if cnt[ch] > 0: # 此时新加入窗口中的字符ch对need有影响
need -= 1
cnt[ch] -= 1
# 窗口左边界
left = right - m
if left >= 0:
ch = s2[left]
if ch in cnt: # 刚滑出的字符位于s1中
if cnt[ch] >= 0: # 此时滑出窗口的字符ch对need有影响
need += 1
cnt[ch] += 1
if need == 0: # 找到了一个满足题意的窗口
return True
return False
# 1.最长连续公共子列,2.最长不连续公共子列,3.S 中最长重复的子列==718?
#? 4.允许swap一次的最长重复子列,5.两个字交错和
# 3. Longest Substring Without Repeating Characters 子串必须连续,不同于子序列
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
dic, res, i = {}, 0, -1
for j in range(len(s)):
if s[j] in dic:
i = max(dic[s[j]], i) # 更新左指针 i
dic[s[j]] = j # 哈希表记录
res = max(res, j - i) # 更新结果
return res
二分法
# 0. 二分查找
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1 # 定义target在左闭右闭的区间里,[left, right]
while left <= right:
middle = left + (right - left) // 2
if nums[middle] > target:
right = middle - 1 # target在左区间,所以[left, middle - 1]
elif nums[middle] < target:
left = middle + 1 # target在右区间,所以[middle + 1, right]
else:
return middle # 数组中找到目标值,直接返回下标
return -1 # 未找到目标值
# 34 Search for a Range/在排序数组中查找元素的第一个和最后一个位置 (Medium) 二分法
# 实现 upper_bound (bisect.bisect_right)和 lower_bound(bisect.bisect_left)
def upper_bound(vec, target):
l, r = 0, len(vec) - 1
while l <= r:
m = (l + r) // 2
if vec[m] > target:
r = m - 1
else:
l = m + 1
return l
def lower_bound(vec, target):
l, r = 0, len(vec) - 1
while l <= r:
m = (l + r) // 2
if vec[m] >= target:
r = m - 1
else:
l = m + 1
return l
vec,tgt=[0,2,2,2,2,3,4,5,6],2
# print(upper_bound(vec,tgt),lower_bound(vec,tgt)) # ans=5,1
# 56.9 Insert Interval/插入区间 (Hard)
class Solution:
def insert(
self, intervals: List[List[int]], newInterval: List[int]
) -> List[List[int]]:
new_start, new_end = newInterval
n = len(intervals)
left = bisect.bisect_left(
range(n),
True,
key=lambda i: intervals[i][1] >= new_start,
)
right = bisect.bisect_left(
range(n),
True,
key=lambda i: intervals[i][0] > new_end,
)
if left < n:
new_start = min(new_start, intervals[left][0])
if right > 0:
new_end = max(new_end, intervals[right - 1][1])
return intervals[:left] + [[new_start, new_end]] + intervals[right:]
#H LeetCode 33 "Search in Rotated Sorted Array"
def search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
# 判断哪一部分是有序的
if nums[left] <= nums[mid]: # 左半段有序
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
else: # 右半段有序
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1 # 没有找到目标值
#H 81 Search in Rotated Sorted Array II/搜索旋转排序数组 II (Medium)
# 数组中的值不必互不相同,基于 33 题的简洁写法,只需增加一个 if
class Solution:
def search(self, nums: List[int], target: int) -> bool:
if not nums:
return False
n = len(nums)
if n == 1:
return nums[0] == target
l, r = 0, n - 1
while l <= r:
mid = (l + r) // 2
if nums[mid] == target:
return True
if nums[l] == nums[mid] and nums[mid] == nums[r]:
l += 1
r -= 1
elif nums[l] <= nums[mid]:
if nums[l] <= target and target < nums[mid]:
r = mid - 1
else:
l = mid + 1
else:
if nums[mid] < target and target <= nums[n - 1]:
l = mid + 1
else:
r = mid - 1
return False
# Search a 2D Matrix
class Solution(object):
def searchMatrix(self, matrix, target):
m = len(matrix)
if m == 0:
return False
n = len(matrix[0])
l,r = 0, m*n-1
while l <= r:
mid = (l+r)//2
i = mid//n
j = mid%n
pivot = matrix[i][j]
if pivot == target:
return True
if pivot < target:
l = mid + 1
else:
r = mid -1
return False
# LeetCode 240 "Search a 2D Matrix II"
def searchMatrix(matrix, target):
if not matrix or not matrix[0]:
return False
rows, cols = len(matrix), len(matrix[0])
row, col = 0, cols - 1 # 从矩阵的右上角开始搜索
while row < rows and col >= 0:
if matrix[row][col] == target:
return True
elif matrix[row][col] < target:
row += 1 # 如果当前元素小于目标值,说明当前行不可能存在目标值,向下移动一行
else:
col -= 1 # 如果当前元素大于目标值,说明当前列不可能存在目标值,向左移动一列
return False
# 1237 枚举 xxx,然后在二分查找;或下面的相向双指针方法
class Solution:
def findSolution(self, customfunction: 'CustomFunction', z: int) -> List[List[int]]:
ans = []
x, y = 1, 1000
while x <= 1000 and y:
res = customfunction.f(x, y)
if res < z:
x += 1
elif res > z:
y -= 1
else:
ans.append([x, y])
x += 1
y -= 1
return ans
#R 378 Kth Smallest Element in a Sorted Matrix/有序矩阵中第 K 小的元素 (Medium)
class Solution:
def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
n = len(matrix)
def check(mid):
"""遍历获取较小元素部分元素总数,并与k值比较"""
i, j = n-1, 0
num = 0
while i >= 0 and j < n:
if matrix[i][j] <= mid:
# 当前元素小于mid,则此元素及上方元素均小于mid
num += i + 1
# 向右移动
j += 1
else:
# 当前元素大于mid,则向上移动,直到找到比mid小的值,或者出矩阵
i -= 1
return num >= k
left, right = matrix[0][0], matrix[-1][-1]
while left < right:
mid = (left + right) >> 1
if check(mid):
# 满足 num >= k,范围太大,移动right至mid, 范围收缩
right = mid
else:
left = mid + 1
return left
#R 410 Split Array Largest Sum/分割数组的最大值 (Hard)
# 分成 k 个非空的连续子数组,使得这 k 个子数组各自和的最大值 最小。
class Solution:
def splitArray(self, nums: List[int], k: int) -> int:
def check(mx):
s, cnt = inf, 0
for x in nums:
s += x
if s > mx:
s = x
cnt += 1
return cnt <= k
left, right = max(nums), sum(nums)
return left + bisect_left(range(left, right + 1), True, key=check)
#R 中位数可以奇偶统一表示
def get_median_universal(nums):
sorted_nums = sorted(nums)
n = len(sorted_nums)
median = (sorted_nums[(n - 1) // 2] + sorted_nums[n // 2]) / 2.0
return median
# Median of Two Sorted Arrays
import bisect
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
n = len(nums1) + len(nums2)
n1, n2 = len(nums1), len(nums2)
if n1 == 0: return (nums2[n2//2] + nums2[(n2-1)//2]) / 2
if n2 == 0: return (nums1[n1//2] + nums1[(n1-1)//2]) / 2
minN, maxN = min(nums1[0], nums2[0]), max(nums1[-1], nums2[-1])
nums = list(range(minN, maxN+1))
def func(x):
# nums1和nums2中<=x的数的个数(记为y),和x单调
k1 = bisect.bisect_right(nums1, x)
k2 = bisect.bisect_right(nums2, x)
return k1 + k2
k1 = bisect_right(nums, n//2, key=func)
if n % 2: k2 = k1
else: k2 = bisect_right(nums, (n-1)//2, key=func)
return (nums[k1] + nums[k2]) / 2
# 611 Valid Triangle Number/有效三角形的个数 (Medium)
# ni<=nj 找出最大的满足 nums[k]<nums[i]+nums[j] 的下标 k,这样一来,在 [j+1,k] 范围内的下标都可
class Solution:
def triangleNumber(self, nums: List[int]) -> int:
n = len(nums)
nums.sort()
ans = 0
for i in range(n):
for j in range(i + 1, n):
left, right, k = j + 1, n - 1, j
while left <= right:
mid = (left + right) // 2
if nums[mid] < nums[i] + nums[j]:
k = mid
left = mid + 1
else:
right = mid - 1
ans += k - j
return ans
# 981 Time Based Key-Value Store/基于时间的键值存储 (Medium)
# void set( key, value, timestamp) 存储给定时间戳 timestamp 时的键 key 和值 value。
# String get( key, timestamp) 返回一个值,该值在之前调用了 set,其中 timestamp_prev <= timestamp 。如果有多个这样的值,它将返回与最大 timestamp_prev 的值。如果没有值,则返回空字符串("")。
class TimeMap:
def __init__(self):
self.dct = defaultdict(list)
def set(self, key: str, value: str, timestamp: int) -> None:
self.dct[key].append([timestamp, value])
def get(self, key: str, timestamp: int) -> str:
a = bisect_right(self.dct[key], [timestamp, "z"*101])
if a == 0:
return ""
return (self.dct[key])[a-1][1]
# 1901 Find a Peak Element II/寻找峰值 II (中等)
# 任意两个相邻格子的值都 不相同 。我们考虑第 i 行的最大值,不妨将其下标记为 j。如果 mat[i][j]>mat[i+1][j],那么第 [0,..i] 行中必然存在一个峰值,我们只需要在第 [0,..i] 行中找到最大值即可。
class Solution:
def findPeakGrid(self, mat: List[List[int]]) -> List[int]:
l, r = 0, len(mat) - 1
while l < r:
mid = (l + r) >> 1
j = mat[mid].index(max(mat[mid]))
if mat[mid][j] > mat[mid + 1][j]:
r = mid
else:
l = mid + 1
return [l, mat[l].index(max(mat[l]))]
字符串&哈希表&数组
# 验证子列,顺序+不连续(ab, acbd):392,如果考虑有多少个这样的子列要用动态规划(115. Distinct Subsequences)
# 验证子列不带顺序 + 连续 permutation-in-string,参考滑动窗口部分
# 392. Is Subsequence 动态规划看mycodes, 双指针解法如下
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if not s: return True
i = 0
for c in t:
if s[i] == c:
i += 1
# 若已经遍历完 s ,则提前返回 true
if i == len(s):
return True
return False
# 验证s, t是不是最多一个字母不同(即|s.size-t.size|<=1):不同字母出现时只需要验证后半段是否一样
def isOneEditDistance(s, t):
# 遍历短的字符串
for i in range(min(len(s), len(t))):
# 比较字符
if s[i] != t[i]:
# 验证后半段是否一样
if len(s) == len(t):
return s[i + 1:] == t[i + 1:]
elif len(s) < len(t):
return s[i:] == t[i + 1:]
else:
return s[i + 1:] == t[i:]
# 前段完全一样,则最后必然多一个,这里假设 s, t 一定不同
return abs(len(s) - len(t)) == 1
# 205. Isomorphic String 如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。
def isIsomorphic(self, s, t):
d1, d2 = {}, {}
for c1, c2 in zip(s, t):
if c1 in d1 and d1[c1] != c2: return False
else: d1[c1] = c2
if c2 in d2 and d2[c2] != c1: return False
else: d2[c2] = c1
return True
# string中获得多位数字
def extract_number_loop(num):
ct = 0
for char in num:
if '0' <= char <= '9':
ct = ct * 10 + int(char)
return ct
# 242.有效的字母异位词
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record = [0] * 26
for i in s:
#并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
record[ord(i) - ord("a")] += 1
for i in t:
record[ord(i) - ord("a")] -= 1
for i in range(26):
if record[i] != 0:
#record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return False
return True
# 1002. 查找常用字符
# 给你一个字符串数组 words ,请你找出所有在 words 的每个字符串中都出现的共用字符( 包括重复字符),并以数组形式返回。你可以按 任意顺序 返回答案。
# 示例:输入:words = ["bella","label","roller"] 输出:["e","l","l"]
class Solution:
def commonChars(self, words: List[str]) -> List[str]:
if not words: return []
result = []
hash = [0] * 26 # 用来统计所有字符串里字符出现的最小频率
for i, c in enumerate(words[0]): # 用第一个字符串给hash初始化
hash[ord(c) - ord('a')] += 1
# 统计除第一个字符串外字符的出现频率
for i in range(1, len(words)):
hashOtherStr = [0] * 26
for j in range(len(words[i])):
hashOtherStr[ord(words[i][j]) - ord('a')] += 1
# 更新hash,保证hash里统计26个字符在所有字符串里出现的最小次数
for k in range(26):
hash[k] = min(hash[k], hashOtherStr[k])
# 将hash统计的字符次数,转成输出形式
for i in range(26):
while hash[i] != 0: # 注意这里是while,多个重复的字符
result.extend(chr(i + ord('a')))
hash[i] -= 1
return result
# 349. 两个数组的交集
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 使用哈希表存储一个数组中的所有元素
table = {}
for num in nums1:
table[num] = table.get(num, 0) + 1
# 使用集合存储结果
res = set()
for num in nums2:
if num in table:
res.add(num)
del table[num]
return list(res)
# 1. 快乐数
# 快乐数:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1。如果 可以变为 1,那么这个数就是快乐数。
class Solution:
def isHappy(self, n: int) -> bool:
record = set()
while True:
n = self.get_sum(n)
if n == 1:
return True
# 如果中间结果重复出现,说明陷入死循环了,该数不是快乐数
if n in record:
return False
else:
record.add(n)
def get_sum(self,n: int) -> int:
new_num = 0
while n:
n, r = divmod(n, 10)
new_num += r ** 2
return new_num
# 1. 两数之和,使用字典
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
records = dict()
for index, value in enumerate(nums):
if target - value in records: # 遍历当前元素,并在map中寻找是否有匹配的key
return [records[target- value], index]
records[value] = index # 如果没找到匹配对,就把访问过的元素和下标加入到map中
return []
# 453.99 四数相加II
# 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。
class Solution(object):
def fourSumCount(self, nums1, nums2, nums3, nums4):
# 使用字典存储nums1和nums2中的元素及其和
hashmap = dict()
for n1 in nums1:
for n2 in nums2:
if n1 + n2 in hashmap:
hashmap[n1+n2] += 1
else:
hashmap[n1+n2] = 1
# 如果 -(n1+n2) 存在于nums3和nums4, 存入结果
count = 0
for n3 in nums3:
for n4 in nums4:
key = - n3 - n4
if key in hashmap:
count += hashmap[key]
return count
# 1198.给定一个二维整数数组 mat,其中每一行都是升序排列的,找到一个最小的正整数,使得每一行中都存在这个数。
def smallestCommonElement(mat):
# 哈希表,记录每个元素在各行中的出现次数
element_count = {}
# 遍历每一行,统计每个元素的出现次数
for row in mat:
for num in row:
if num not in element_count:
element_count[num] = 1
else:
element_count[num] += 1
# 遍历哈希表,找到所有行中都存在的最小正整数
for num, count in sorted(element_count.items()):
if count == len(mat):
return num
return -1 # 如果没有找到符合条件的元素,返回 -1
#R 9 回文数
class Solution:
def isPalindrome(self, x: int) -> bool:
# 同样地,如果数字的最后一位是 0,为了使该数字为回文,则其第一位数字也应该是 0
if x < 0 or (x % 10 == 0 and x != 0):
return False
reverted_number = 0
while x > reverted_number:
reverted_number = reverted_number * 10 + x % 10
x //= 10
# 当数字长度为奇数时,我们可以通过 reverted_number // 10 去除处于中位的数字。
# 例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,reverted_number = 123,
# 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
return x == reverted_number or x == reverted_number // 10
# 12 整数转罗马数字
class Solution:
def intToRoman(self, num: int) -> str:
symbol = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I']
value = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
result = ""
temp = num
for i in range(len(value)):
d = temp // value[i]
if d > 0:
result += symbol[i]*d
temp -= d*value[i]
return result
# 0 Longest Common Prefix/最长公共前缀 (Easy)
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
s0 = strs[0]
for j, c in enumerate(s0):
for s in strs:
if j == len(s) or s[j] != c:
return s0[:j]
return s0
# 36 Valid Sudoku/有效的数独 (Easy)
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
# 初始化行、列、宫的集合
row = [set() for _ in range(9)]
col = [set() for _ in range(9)]
palace = [[set() for _ in range(3)] for _ in range(3)]
# 双重循环遍历数独
for i in range(9):
for j in range(9):
# 获取当前位置的数字
num = board[i][j]
# 跳过空格
if num == ".":
continue
# 重复数字检查
if(num in row[i] or num in col[j] or num in palace[i//3][j//3]):
return False
# 更新数字集合
row[i].add(num)
col[j].add(num)
palace[i//3][j//3].add(num)
return True
# 43 Multiply Strings/字符串相乘 (Medium)
class Solution:
def multiply(self, num1: str, num2: str) -> str:
if num1 == "0" or num2 == "0":
return "0"
m, n = len(num1), len(num2)
l = m+n
ans = [0]*l
for i in range(m-1,-1,-1):
a = int(num1[i])
for j in range(n-1,-1,-1):
b = int(num2[j])
c = a*b + ans[i+j+1]
ans[i+j+1] = c%10
ans[i+j] += c//10
if ans[0]==0:
ans.remove(0)
return ''.join(str(i) for i in ans)
# 49 Anagrams/字母异位词分组 (Medium)
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
d = defaultdict(list)
for s in strs:
d[''.join(sorted(s))].append(s) # sorted(s) 相同的字符串分到同一组
return list(d.values())
# 53.9 Spiral Matrix/螺旋矩阵 (Medium)
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
ans = list()
# 通过次数控制变量控制循环步数
n = len(matrix[0])
m = len(matrix)
# 初始化索引变量
i = 0
j = -1
# 移动方向变量
step = 1
while m > 0 and n > 0: # m n 变化的代表每一横纵可以行走的步数
# 横向循环
for jcnt in range(n):
# 先加再走,避免拐角重复
j += step
ans.append(matrix[i][j])
# 横向走完减少的是纵向可走的次数
m -= 1
# 纵向循环
for icnt in range(m):
# 先加再走,避免拐角重复
i += step
ans.append(matrix[i][j])
# 纵向走完减少的是横向可走的次数
n -= 1
# 改变方向
step = -step
return ans
# 60 Permutation Sequence/排列序列 (Hard)
# 选择第一个元素为 1 后的排列有 (𝑛−1)! 个
class Solution:
def getPermutation(self, n: int, k: int) -> str:
# 初始化数字列表
nums = list(range(1, n + 1))
# 转换为从 0 开始的索引
k -= 1
result = []
# 构造排列
for i in range(n, 0, -1):
fact = factorial(i - 1)
index = k // fact
result.append(str(nums.pop(index)))
k %= fact
return ''.join(result)
# 119 Pascal's Triangle II/杨辉三角 II (Easy)
class Solution:
def getRow(self, rowIndex: int) -> List[int]:
f = [1] * (rowIndex + 1)
for i in range(2, rowIndex + 1):
for j in range(i - 1, 0, -1):
f[j] += f[j - 1]
return f
# 0 反转字符串II/Reverse String II (easy)
# 给定一个字符串 s 和一个整数 k,从字符串开头算起, 每计数至 2k 个字符,就反转这 2k 个字符中的前 k 个字符;如果剩余字符少于 k 个,则将剩余字符全部反转;如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
class Solution:
def reverseStr(self, s: str, k: int) -> str:
# 字符串末尾如果超过最大长度,则会返回至字符串最后一个值,这个特性可以避免一些边界条件的处理。
def reverse_substring(text):
left, right = 0, len(text) - 1
while left < right:
text[left], text[right] = text[right], text[left]
left += 1
right -= 1
return text
res = list(s)
for cur in range(0, len(s), 2 * k):
res[cur: cur + k] = reverse_substring(res[cur: cur + k])
return ''.join(res)
# 344.反转字符串/Reverse String (easy)
class Solution:
def reverseString(self, s: List[str]) -> None:
# Do not return anything, modify s in-place instead.
left, right = 0, len(s) - 1
# 该方法已经不需要判断奇偶数,经测试后时间空间复杂度比用 for i in range(len(s)//2)更低
# 因为while每次循环需要进行条件判断,而range函数不需要,直接生成数字,因此时间复杂度更低。推荐使用range
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
# 151 翻转字符串中的单词 Reverse Words in a String II $/? (Medium)
class Solution:
def reverseWords(self, str):
left = 0
n = len(str)
for i in range(n + 1):
if i == n or str[i] == ' ':
self.reverse(str, left, i - 1)
left = i + 1
self.reverse(str, 0, n - 1)
def reverse(self, str, left, right):
while left < right:
str[left], str[right] = str[right], str[left]
left += 1
right -= 1
#R 28. 实现 strStr(),next数组:最长的相同的真前后缀长度
class Solution:
def getNext(self, next, s):
j = -1
next[0] = j
for i in range(1, len(s)):
while j >= 0 and s[i] != s[j+1]:
j = next[j]
if s[i] == s[j+1]:
j += 1
next[i] = j
def strStr(self, haystack: str, needle: str) -> int:
if not needle:
return 0
next = [0] * len(needle)
self.getNext(next, needle)
j = -1
for i in range(len(haystack)):
while j >= 0 and haystack[i] != needle[j+1]:
j = next[j]
if haystack[i] == needle[j+1]:
j += 1
if j == len(needle) - 1:
return i - len(needle) + 1
return -1
# 459.重复的子字符串
class Solution:
def repeatedSubstringPattern(self, s: str) -> bool:
if len(s) == 0:
return False
nxt = [0] * len(s)
self.getNext(nxt, s)
if nxt[-1] != -1 and len(s) % (len(s) - (nxt[-1] + 1)) == 0:
return True
return False
def getNext(self, nxt, s):
nxt[0] = -1
j = -1
for i in range(1, len(s)):
while j >= 0 and s[i] != s[j+1]:
j = nxt[j]
if s[i] == s[j+1]:
j += 1
nxt[i] = j
return nxt
# 162 Find Peak Element/寻找峰值 (Medium)
# 如果nums[i]<nums[i+1],那么在下标 [i+1,n−1] 中一定存在至少一个峰值。
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
left, right = -1, len(nums) - 1 # 开区间 (-1, n-1)
while left + 1 < right: # 开区间不为空
mid = (left + right) // 2
if nums[mid] > nums[mid + 1]:
right = mid
else:
left = mid
return right
# 166 Fraction to Recurring Decimal/分数到小数 (Medium)
class Solution:
def fractionToDecimal(self, numerator: int, denominator: int) -> str:
if numerator == 0: return "0"
res = []
# 首先判断结果正负, 异或作用就是 两个数不同 为 True 即 1 ^ 0 = 1 或者 0 ^ 1 = 1
if (numerator > 0) ^ (denominator > 0):
res.append("-")
numerator, denominator = abs(numerator), abs(denominator)
# 判读到底有没有小数
a, b = divmod(numerator, denominator)
res.append(str(a))
# 无小数
if b == 0:
return "".join(res)
res.append(".")
# 处理余数
# 把所有出现过的余数记录下来
loc = {b: len(res)}
while b:
b *= 10
a, b = divmod(b, denominator)
res.append(str(a))
# 余数前面出现过,说明开始循环了,加括号
if b in loc:
res.insert(loc[b], "(")
res.append(")")
break
# 在把该位置的记录下来
loc[b] = len(res)
return "".join(res)
# 169 Majority Element/多数元素 (Easy)
# 摩尔投票法: 核心理念为 票数正负抵消 。
class Solution:
def majorityElement(self, nums: List[int]) -> int:
votes = 0
for num in nums:
if votes == 0: x = num
votes += 1 if num == x else -1
return x
# 243 Shortest Word Distance $/最短单词距离 (Easy)
# shortest distance between these two words in the list
class Solution:
def shortestDistance(self, words, word1, word2):
p1, p2 = -1, -1
res = float('inf') # 使用 float('inf') 表示正无穷
for i in range(len(words)):
if words[i] == word1:
p1 = i
elif words[i] == word2:
p2 = i
if p1 != -1 and p2 != -1:
res = min(res, abs(p1 - p2))
return res
# 204 Count Primes/计数质数 (Easy)
def count_primes_py(n):
# 最小的质数是 2
if n < 2:
return 0
isPrime = [1] * n
isPrime[0] = isPrime[1] = 0 # 0和1不是质数,先排除掉
# 埃式筛,把不大于根号 n 的所有质数的倍数剔除
for i in range(2, int(n ** 0.5) + 1):
if isPrime[i]:
isPrime[i * i:n:i] = [0] * ((n - 1 - i * i) // i + 1)
return sum(isPrime)
# 0. Kth Largest Element in an Array/数组中的第K个最大元素 (Medium)
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
pq = [] # 将数组加入小顶堆,堆中维护当前值最大的k个数
for num in nums:
heapq.heappush(pq, num) # 当前元素入堆
if len(pq) > k:
heapq.heappop(pq) # 堆中元素超过k个,弹出最小的那个
return pq[0] # 最后堆顶的即为第k大的数
# 241 Different Ways to Add Parentheses/为运算表达式设计优先级 (Medium)
# 输入:expression = "2-1-1" 输出:[0,2]
# 解释: ((2-1)-1) = 0 (2-(1-1)) = 2
class Solution:
def diffWaysToCompute(self, input: str) -> List[int]:
# 如果只有数字,直接返回
if input.isdigit():
return [int(input)]
res = []
for i, char in enumerate(input):
if char in ['+', '-', '*']:
# 1.分解:遇到运算符,计算左右两侧的结果集
# 2.解决:diffWaysToCompute 递归函数求出子问题的解
left = self.diffWaysToCompute(input[:i])
right = self.diffWaysToCompute(input[i+1:])
# 3.合并:根据运算符合并子问题的解
for l in left:
for r in right:
if char == '+':
res.append(l + r)
elif char == '-':
res.append(l - r)
else:
res.append(l * r)
return res
# 264 Ugly Number II/丑数 II (Medium)
# 计算下一个丑数的候选集 res[a]⋅2 , res[b]⋅3 , res[c]⋅5 。
# 选择丑数候选集中最小的那个作为下一个丑数,填入 res 。
# 将被选中的丑数对应的指针向右移动一格。
class Solution:
def nthUglyNumber(self, n: int) -> int:
res, a, b, c = [1] * n, 0, 0, 0
for i in range(1, n):
n2, n3, n5 = res[a] * 2, res[b] * 3, res[c] * 5
res[i] = min(n2, n3, n5)
if res[i] == n2: a += 1
if res[i] == n3: b += 1
if res[i] == n5: c += 1
return res[-1]
# 289 Game of Life/生命游戏 (Medium)
# 2表示活细胞变成死细胞,3表示死细胞变成活细胞。
class Solution:
def gameOfLife(self, board: List[List[int]]) -> None:
m = len(board) # 行数
n = len(board[0]) # 列数
for i in range(m):
for j in range(n):
count = 0 # 统计每个格子周围八个位置的活细胞数,每个格子计数重置为0
for x in range(-1, 2):
for y in range(-1, 2):
# 枚举周围八个位置,其中去掉本身(x = y = 0)和越界的情况
if (x == 0 and y == 0) or i + x < 0 or i + x >= m or j + y < 0 or j + y >= n: continue
# 如果周围格子是活细胞(1)或者是活细胞变死细胞(2)的,都算一个活细胞
if board[i + x][j + y] == 1 or board[i + x][j + y] == 2: count += 1
if board[i][j] == 1 and (count < 2 or count > 3): board[i][j] = 2 # 格子本身是活细胞,周围满足变成死细胞的条件,标记为2
if board[i][j] == 0 and count == 3: board[i][j] = 3 # 格子本身是死细胞,周围满足复活条件,标记为3
for i in range(m):
for j in range(n):
# 死细胞为0,活细胞变成死细胞为2,都为偶数,模2为0,刚好是死细胞
# 活细胞为1,死细胞变成活细胞为3,都为奇数,模2为1,刚好是活细胞
board[i][j] %= 2
# 346 Moving Average from Data Stream $/数据流中的移动平均值 (Easy)
class MovingAverage:
def __init__(self, size: int):
self.l = size
self.sum = 0
self.r = []
def next(self, val: int) -> float:
if len(self.r)==self.l:
self.sum -= self.r.pop(0) #移除队头元素,这种简单情况可以用python list
self.sum += val
self.r.append(val)
return self.sum/len(self.r)
# 498 Diagonal Traverse/对角线遍历 (Medium)
# 右斜对角线是横纵坐标和为定值,一共有m+n−1个对角线
class Solution:
def findDiagonalOrder(self, mat: List[List[int]]) -> List[int]:
m, n, ans = len(mat), len(mat[0]), []
for k in range(m + n - 1):
if not k % 2:
ans += [mat[x][k-x] for x in range(min(m - 1, k), max(-1, k - n),-1)]
else:
ans += [mat[x][k-x] for x in range(max(0, k - n + 1), min(k + 1, m))]
return ans
# 697 Degree of an Array/数组的度 (Easy)
# 数组的 度 的定义是指数组里任一元素出现频数的最大值,找到与 nums 拥有相同大小的度的最短连续子数组
# 要求的最短子数组的起始和终止位置,由出现次数最多的元素 第一次和最后一次出现的位置 确定。
class Solution:
def findShortestSubArray(self, nums: List[int]) -> int:
left, right = dict(), dict()
counter = collections.Counter()
for i, num in enumerate(nums):
if num not in left:
left[num] = i
right[num] = i
counter[num] += 1
degree = max(counter.values())
res = len(nums)
for k, v in counter.items():
if v == degree:
res = min(res, right[k] - left[k] + 1)
return res
# 0 Shortest Completing Word/最短补全词 (Medium)
# 找到最短的包含所有字母的单词
class Solution:
def shortestCompletingWord(self, licensePlate: str, words: List[str]) -> str:
tmp=[]
for i in licensePlate:
if 'a'<=i<='z' or 'A'<=i<='Z':
tmp.append(i.lower())
words=sorted(words, key=lambda x:len(x))
for word in words:
l=tmp.copy()
for i in word:
if i in l:
l.remove(i)
if not l:
return word
# 835 Image Overlap/图像重叠 (Medium)
# 转换 其中一个图像,将所有的 1 向左,右,上,或下滑动任何数量的单位;最大可能的重叠数量是多少
# 计算两个图像的1之间的移动距离,取计数最大的移动方式
class Solution:
def largestOverlap(self, img1: List[List[int]], img2: List[List[int]]) -> int:
n = len(img1)
cnt = defaultdict(int)
one = []
for i in range(n):
for j in range(n):
if img1[i][j]:
one.append([i, j])
for i in range(n):
for j in range(n):
if img2[i][j]:
for a, b in one:
cnt[(i - a, j - b)] += 1
return max(cnt.values()) if cnt else 0
# 892 Surface Area of 3D Shapes/三维形体的表面积 (Easy)
# 对于四个侧面的表面积,只有在相邻位置的高度小于 v 时,对应的那个侧面才会贡献表面积,且贡献的数量为 v - nv
class Solution:
def surfaceArea(self, grid: List[List[int]]) -> int:
N = len(grid)
ans = 0
for r in range(N):
for c in range(N):
if grid[r][c]:
ans += 2
for nr, nc in ((r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)):
if 0 <= nr < N and 0 <= nc < N:
nval = grid[nr][nc]
else:
nval = 0
ans += max(grid[r][c] - nval, 0)
return ans
# 970 Powerful Integers/强整数 (Easy)
# 整数可以表示为 x^i + y^j
class Solution:
def powerfulIntegers(self, x: int, y: int, bound: int) -> List[int]:
ans = set()
a = 1
while a <= bound:
b = 1
while a + b <= bound:
ans.add(a + b)
b *= y
if y == 1:
break
if x == 1:
break
a *= x
return list(ans)
# 1010 Pairs of Songs With Total Durations Divisible by 60/总持续时间可被 60 整除的歌曲对 (Medium)
# 可被 60 整除的歌曲对的数量,长度为 60 的数组 cnt 记录每个余数 x 出现的次数。
class Solution:
def numPairsDivisibleBy60(self, time: List[int]) -> int:
cnt = Counter()
ans = 0
for x in time:
x %= 60
y = (60 - x) % 60
ans += cnt[y]
cnt[x] += 1
return ans
# 1013 Partition Array Into Three Parts With Equal Sum/将数组分成和相等的三个部分 (Easy)
# 依次寻找切分点i j
class Solution:
def canThreePartsEqualSum(self, A: List[int]) -> bool:
s = sum(A)
if s % 3 != 0:
return False
target = s // 3
n, i, cur = len(A), 0, 0
while i < n:
cur += A[i]
if cur == target:
break
i += 1
if cur != target:
return False
j = i + 1
while j + 1 < n: # 需要满足最后一个数组非空
cur += A[j]
if cur == target * 2:
return True
j += 1
return False
#R 767 Reorganize String/重构字符串 (Medium)
# 重新排布其中的字母,使得两相邻的字符不同。
# 对于每一种元素,循环在不同桶中进行填充,由于桶的个数等于字符串中最多的元素的数目,因此每个桶中不会出现相同的元素,填充完毕后将桶依次相连即为答案
class Solution:
def reorganizeString(self, s: str) -> str:
cnt, idx = Counter(s), 0
bucketNum = cnt.most_common(1)[0][1] # 桶的数目等于字符串中最多的元素的数目
if bucketNum > (len(s) + 1) // 2: return ""
buckets = [[] for _ in range(bucketNum)]
for c, num in cnt.most_common():
for _ in range(num):
buckets[idx].append(c)
idx = (idx + 1) % bucketNum # 循环在不同桶中进行填充
return "".join(["".join(bucket) for bucket in buckets])
#R 1053.9 Distant Barcodes/距离相等的条形码 (Medium)
# 请你重新排列这些条形码,使其中任意两个相邻的条形码不能相等。
# 我们先统计数组barcodes中各个数出现的次数,然后按照它们出现的次数从大到小排序,依次填入偶数后奇数
class Solution:
def rearrangeBarcodes(self, barcodes: List[int]) -> List[int]:
cnt = Counter(barcodes)
barcodes.sort(key=lambda x: (-cnt[x], x))
n = len(barcodes)
ans = [0] * len(barcodes)
ans[::2] = barcodes[: (n + 1) // 2]
ans[1::2] = barcodes[(n + 1) // 2:]
return ans
# 1232.9 Remove Sub-Folders from the Filesystem/删除子文件夹 (Medium)
# 删除该列表中的所有子文件夹
class Solution:
def removeSubfolders(self, folder: List[str]) -> List[str]:
folder.sort()
ans = [folder[0]]
for f in folder[1:]:
m, n = len(ans[-1]), len(f)
if m >= n or not (ans[-1] == f[:m] and f[m] == '/'):
ans.append(f)
return ans
# 1282 Group the People Given the Group Size They Belong To/用户分组 (Medium)
# groupSizes[i] 是第 i 个人所在的组的大小,返回用户分组[[5],[0,1,2],[3,4,6]]
class Solution:
def groupThePeople(self, groupSizes: List[int]) -> List[List[int]]:
mp, ans = defaultdict(list), []
for i, v in enumerate(groupSizes):
mp[v].append(i)
for k, lt in mp.items():
ans.extend(lt[i:i+k] for i in range(0, len(lt), k))
return ans
# 1369.9 Increasing Decreasing String/上升下降字符串 (简单)
# 从 s 剩余字符中选出比上一个添加字符更大的 最小 字符,将它 接在 结果字符串后面。
# 创建一个大小为 26 的桶记录每种字符的数量。每次提取最长的上升或下降字符串时,我们直接顺序或逆序遍历这个桶。
class Solution:
def sortString(self, s: str) -> str:
num = [0] * 26
for ch in s:
num[ord(ch) - ord('a')] += 1
ret = list()
while len(ret) < len(s):
for i in range(26):
if num[i]:
ret.append(chr(i + ord('a')))
num[i] -= 1
for i in range(25, -1, -1):
if num[i]:
ret.append(chr(i + ord('a')))
num[i] -= 1
return "".join(ret)
# 1452 People Whose List of Favorite Companies Is Not a Subset of Another List/收藏清单 (medium)
# 找出不是其他任何人收藏的公司清单的子集的收藏清单。 暴力匹配。中间加一点剪枝
class Solution:
def peopleIndexes(self, favoriteCompanies: List[List[str]]) -> List[int]:
a = list(set(f) for f in favoriteCompanies)
n = len(a)
res = []
for i in range(n):
for j in range(n):
flag = True
if i != j and len(a[i]) <= len(a[j]):
if a[i].issubset(a[j]): #### 偷懒,直接调库
flag = False
break
if flag == True:
res.append(i)
return res
# 1966 未排序数组中的可被二分搜索的数 (中等)
# 二分搜索的方法是否可以判断 target 是否存在 sequence中。输出所有可以判断的数量
# 需要找到一个下标,他左边的数比他小,右边的数比他大,返回所有这样下标的数量。
# 分别从左往右和从右往左遍历,分别记录起点到i最大值和j到终点的最小值,如果一个数同时是从左往右的最大值和从右往左的最小值,那么符合题意。
class Solution:
def binarySearchableNumbers(self, nums: List[int]) -> int:
n = len(nums)
l, r = float('-inf'), float('inf')
has = [0] * n
for i in range(n):
j = n-1-i
vi, vj = nums[i], nums[j]
if vi > l:
l = vi
has[i] += 1
if vj < r:
r = vj
has[j] += 1
return sum(i==2 for i in has)
# 681.最近时刻 中等
# "01:34" 和 "12:09" 是合法的,“1:34” 和 “12:9” 是不合法的。
# 利用当前出现过的数字构造下一个距离当前时间最近的时刻。每个出现数字都可以被无限次使用。
class Solution:
def nextClosestTime(self, time: str) -> str:
nums, hour, minute = set(time), int(time[:2]), int(time[3:])
m = hour * 60 + minute
while True:
m += 1
hour = m // 60 % 24
minute = m % 60
if hour < 10:
hour = '0' + str(hour)
else:
hour = str(hour)
if minute < 10:
minute = '0' + str(minute)
else:
minute = str(minute)
for i, ch in enumerate(hour + minute):
if ch not in nums:
break
if i == 3:
return hour + ':' + minute
链表
# 203.移除链表元素
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
# 创建虚拟头部节点以简化删除过程
dummy_head = ListNode(next = head)
# 遍历列表并删除值为val的节点
current = dummy_head
while current.next:
if current.next.val == val:
current.next = current.next.next
else:
current = current.next
return dummy_head.next
# 707.设计链表
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummy_head = ListNode()
self.size = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.size:
return -1
current = self.dummy_head.next
for i in range(index):
current = current.next
return current.val
def addAtHead(self, val: int) -> None:
self.dummy_head.next = ListNode(val, self.dummy_head.next)
self.size += 1
def addAtTail(self, val: int) -> None:
current = self.dummy_head
while current.next:
current = current.next
current.next = ListNode(val)
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index > self.size:
return
current = self.dummy_head
for i in range(index):
current = current.next
current.next = ListNode(val, current.next)
self.size += 1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.size:
return
current = self.dummy_head
for i in range(index):
current = current.next
current.next = current.next.next
self.size -= 1
# 206.反转链表
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
cur = head
pre = None
while cur:
temp = cur.next # 保存一下 cur的下一个节点,因为接下来要改变cur->next
cur.next = pre #反转
#更新pre、cur指针
pre = cur
cur = temp
return pre
# 24. 两两交换链表中的节点
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
dummy_head = ListNode(next=head)
current = dummy_head
# 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
while current.next and current.next.next:
temp = current.next # 防止节点修改
temp1 = current.next.next.next
current.next = current.next.next
current.next.next = temp
temp.next = temp1
current = current.next.next
return dummy_head.next
19.删除链表的倒数第N个节点
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
# 创建一个虚拟节点,并将其下一个指针设置为链表的头部
dummy_head = ListNode(0, head)
# 创建两个指针,慢指针和快指针,并将它们初始化为虚拟节点
slow = fast = dummy_head
# 快指针比慢指针快 n+1 步
for i in range(n+1):
fast = fast.next
# 移动两个指针,直到快速指针到达链表的末尾
while fast:
slow = slow.next
fast = fast.next
# 通过更新第 (n-1) 个节点的 next 指针删除第 n 个节点
slow.next = slow.next.next
return dummy_head.next
# 02.07. 链表相交
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
lenA, lenB = 0, 0
cur = headA
while cur: # 求链表A的长度
cur = cur.next
lenA += 1
cur = headB
while cur: # 求链表B的长度
cur = cur.next
lenB += 1
curA, curB = headA, headB
if lenA > lenB: # 让curB为最长链表的头,lenB为其长度
curA, curB = curB, curA
lenA, lenB = lenB, lenA
for _ in range(lenB - lenA): # 让curA和curB在同一起点上(末尾位置对齐)
curB = curB.next
while curA: # 遍历curA 和 curB,遇到相同则直接返回
if curA == curB:
return curA
else:
curA = curA.next
curB = curB.next
return None
# 142.环形链表II
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# If there is a cycle, the slow and fast pointers will eventually meet
if slow == fast:
# Move one of the pointers back to the start of the list
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
# If there is no cycle, return None
return None
2. Add Two Numbers(两数相加)
def addTwoNumbers(l1, l2):
dummy = ListNode() # 创建一个哑结点
current = dummy # 初始化指针,指向哑结点
carry = 0 # 进位值
# 遍历两个链表,直到两个链表都遍历完且没有进位
while l1 or l2 or carry:
# 获取当前位置的数字
val1 = l1.val if l1 else 0
val2 = l2.val if l2 else 0
# 计算当前位置的和,考虑进位
total = val1 + val2 + carry
carry, remainder = divmod(total, 10) # 计算进位和当前位置的数字
# 创建新的结点,并将其添加到结果链表中
current.next = ListNode(remainder)
current = current.next
# 移动指针到下一个位置
if l1:
l1 = l1.next
if l2:
l2 = l2.next
return dummy.next # 返回哑结点的下一个结点,即相加结果的链表的头结点
#R 92 Reverse Linked List II/反转链表 II (Medium)
class Solution:
def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
p0 = dummy = ListNode(next=head)
for _ in range(left - 1):
p0 = p0.next
pre = None
cur = p0.next
for _ in range(right - left + 1):
nxt = cur.next
cur.next = pre # 每次循环只修改一个 next,方便大家理解
pre = cur
cur = nxt
p0.next.next = cur
p0.next = pre
return dummy.next
#R 146 LRU Cache/LRU 缓存机制 (Medium)
# get 如果关键字 key 存在于缓存中,则返回关键字的值,否则 -1 。
# put 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该逐出最久未使用的关键字。
class Node:
# 提高访问属性的速度,并节省内存
__slots__ = 'prev', 'next', 'key', 'value'
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.dummy = Node() # 哨兵节点 dummy.prev指向尾部,dummy.next指向头部
self.dummy.prev = self.dummy
self.dummy.next = self.dummy
self.key_to_node = {}
# 获取 key 对应的节点,同时把该节点移到链表头部
def get_node(self, key: int) -> Optional[Node]:
if key not in self.key_to_node: # 没有这本书
return None
node = self.key_to_node[key] # 有这本书
self.remove(node) # 把这本书抽出来
self.push_front(node) # 放在最上面
return node
def get(self, key: int) -> int:
# 返回关键字的值
node = self.get_node(key) # get_node 会把对应节点移到链表头部
return node.value if node else -1
def put(self, key: int, value: int) -> None:
# 插入key-value 。如果关键字数量超过 capacity ,则应该逐出最久未使用的关键字
node = self.get_node(key)
if node: # 有这本书
node.value = value # 更新 value
return
self.key_to_node[key] = node = Node(key, value) # 新书
self.push_front(node) # 放在最上面
if len(self.key_to_node) > self.capacity: # 书太多了
back_node = self.dummy.prev
del self.key_to_node[back_node.key]
self.remove(back_node) # 去掉最后一本书
# 删除一个节点(抽出一本书)
def remove(self, x: Node) -> None:
x.prev.next = x.next
x.next.prev = x.prev
# 在链表头添加一个节点(把一本书放在最上面)
def push_front(self, x: Node) -> None:
x.prev = self.dummy
x.next = self.dummy.next
x.prev.next = x
x.next.prev = x
# 234 Palindrome Linked List/回文链表 (Easy)
class Solution:
# 876. 链表的中间结点
def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
# 206. 反转链表
def reverseList(self, head):
def isPalindrome(self, head: Optional[ListNode]) -> bool:
mid = self.middleNode(head)
head2 = self.reverseList(mid)
while head2:
if head.val != head2.val: # 不是回文链表
return False
head = head.next
head2 = head2.next
return True
# 287 Find the Duplicate Number/寻找重复数 (Medium) nums 只有 一个重复的整数
# 将 i 和 nums[i] 视为链表中的父子节点。可转换为环形链表
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
slow = 0
fast = 0
while True:
# fast 前进两次,slow 前进一次
fast = nums[fast]
fast = nums[fast]
slow = nums[slow]
if slow == fast:
break
# ptr == slow 时说明检测到重复元素,两个重复元素同时指向环的入口。
ptr = 0
while ptr != slow:
ptr = nums[ptr]
slow = nums[slow]
return ptr