week15
文章目录
- week15
- Easy:
- Medium:
- [633. 平方数之和](https://leetcode.cn/problems/sum-of-square-numbers/)
- [524. 通过删除字母匹配到字典里最长单词](https://leetcode.cn/problems/longest-word-in-dictionary-through-deleting/)
- [34. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/)
- [81. 搜索旋转排序数组 II](https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/)
- [153. 寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/)
- [540. 有序数组中的单一元素](https://leetcode.cn/problems/single-element-in-a-sorted-array/)
- Hard:
Easy:
680. 验证回文串 II
题目描述:
给你一个字符串 s
,最多 可以从中删除一个字符。
请你判断 s
是否能成为回文字符串:如果能,返回 true
;否则,返回 false
。
示例 1:
输入:s = "aba"
输出:true
题解:双指针
解题思路:可以利用双指针的方法来判断是否能构成回文字符串。初始化两个指针左和右,分别指向字符串的开头和结尾。在循环中,比较 s[left] 和 s[right]是否符合,如果符合,则将两个指针分别向中间移动一位。如果不符合,则有两种情况:
- 可以删除左边的角色,即将左指针右移一位;
- 可以删除右边的角色字符,即将向右指针左移一位。
然后继续判断剩余部分是否构成回文串。如果循环结束时没有找到不符合的字符,则说明可以构成回文串。
class Solution:
def validPalindrome(self, s: str) -> bool:
def is_palindrome(left, right):
while left < right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
left, right = 0, len(s) - 1 # 左右指针
while left < right:
if s[left] != s[right]:
return is_palindrome(left + 1, right) or is_palindrome(left, right - 1) # 左删除或右删除
left += 1
right -= 1
return True
88. 合并两个有序数组
题目描述:
给你两个按 非递减顺序 排列的整数数组 nums1
和 nums2
,另有两个整数 m
和 n
,分别表示 nums1
和 nums2
中的元素数目。
请你 合并 nums2
到 nums1
中,使合并后的数组同样按 非递减顺序 排列。
**注意:**最终,合并后数组不应由函数返回,而是存储在数组 nums1
中。为了应对这种情况,nums1
的初始长度为 m + n
,其中前 m
个元素表示应合并的元素,后 n
个元素为 0
,应忽略。nums2
的长度为 n
。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
题解:双指针
解题思路:由于 nums1 内存有足够的空间来承载 nums2 中的元素,可以从后往前遍历,逐比较个 nums1 和 nums2 中的元素,然后将增加的元素放到 nums1 中的元素。之前浏览,可以避免在 nums1 中插入元素时的移动操作。
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, p2 = m - 1, n - 1 # 从末尾开始
p = m + n - 1 # 最后的位置
while p1 >= 0 and p2 >= 0:
if nums1[p1] > nums2[p2]:
nums1[p] = nums1[p1]
p1 -= 1
else:
nums1[p] = nums2[p2]
p2 -= 1
p -= 1
while p2 >= 0: # 如果 nums2 中还有剩余元素,将其放入 nums1
nums1[p] = nums2[p2]
p2 -= 1
p -= 1
2682. 找出转圈游戏输家
题目描述:
n
个朋友在玩游戏。这些朋友坐成一个圈,按 顺时针方向 从 1
到 n
编号。从第 i
个朋友的位置开始顺时针移动 1
步会到达第 (i + 1)
个朋友的位置(1 <= i < n
),而从第 n
个朋友的位置开始顺时针移动 1
步会回到第 1
个朋友的位置。
游戏规则如下:
第 1
个朋友接球。
- 接着,第
1
个朋友将球传给距离他顺时针方向k
步的朋友。 - 然后,接球的朋友应该把球传给距离他顺时针方向
2 * k
步的朋友。 - 接着,接球的朋友应该把球传给距离他顺时针方向
3 * k
步的朋友,以此类推。
换句话说,在第 i
轮中持有球的那位朋友需要将球传递给距离他顺时针方向 i * k
步的朋友。
当某个朋友第 2 次接到球时,游戏结束。
在整场游戏中没有接到过球的朋友是 输家 。
给你参与游戏的朋友数量 n
和一个整数 k
,请按升序排列返回包含所有输家编号的数组 answer
作为答案。
示例 1:
输入:n = 5, k = 2
输出:[4,5]
解释:以下为游戏进行情况:
1)第 1 个朋友接球,第 1 个朋友将球传给距离他顺时针方向 2 步的玩家 —— 第 3 个朋友。
2)第 3 个朋友将球传给距离他顺时针方向 4 步的玩家 —— 第 2 个朋友。
3)第 2 个朋友将球传给距离他顺时针方向 6 步的玩家 —— 第 3 个朋友。
4)第 3 个朋友接到两次球,游戏结束。
题解:模拟
使用模拟的方式来解决,需要注意的是根据规则进行传递球的过程
class Solution:
def circularGameLosers(self, n: int, k: int) -> List[int]:
visit = [False] * n # 创建一个记录是否被访问过的列表,初始都为 False
i = k # 初始化步长
j = 0 # 初始位置
while not visit[j]: # 当位置未被访问过时继续
visit[j] = True # 标记当前位置为已访问
j = (i + j) % n # 计算下一个位置
i += k # 更新步长
ans = []
for i in range(n):
if not visit[i]:
ans.append(i+1) # 将未访问过的位置加入答案列表
return ans
69. x 的平方根
题目描述:
给你一个非负整数 x
,计算并返回 x
的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
**注意:**不允许使用任何内置指数函数和算符,例如 pow(x, 0.5)
或者 x ** 0.5
。
示例 1:
输入:x = 4
输出:2
题解:二分查找
我们可以把这道题想象成,给定一个非负整数a,求 f ( x ) = x 2 − a = 0 f(x)=x^2-a=0 f(x)=x2−a=0 的解。因为我们只考虑 x ≥ 0 x\geq0 x≥0,所以 f ( x ) f(x) f(x) 在定义域上是单调递增的。考虑到 f ( x ) = − a < 0 f(x)=-a<0 f(x)=−a<0, f ( x ) = a 2 − a ≥ 0 f(x) = a^2-a\geq0 f(x)=a2−a≥0我们可以对 [ 0 , a ] [0, a] [0,a] 区间使用二分法找到 f ( x ) = 0 f(x)=0 f(x)=0的解。
class Solution:
def mySqrt(self, x: int) -> int:
l, r, ans = 0, x, -1 # 初始化左边界 l 为 0,右边界 r 为 x,ans 为初始值 -1
while l <= r: # 当左边界小于等于右边界时进行循环
mid = (l + r) // 2 # 计算中间值 mid
if mid * mid <= x: # 如果 mid 的平方小于等于 x
ans = mid # 更新 ans 为当前 mid,因为 mid 可能是更接近平方根的值
l = mid + 1 # 调整左边界为 mid 的下一个数,继续搜索更大的值
else:
r = mid - 1 # 调整右边界为 mid 的前一个数,将搜索范围调整到更小的一半
return ans # 返回最终的 ans 作为平方根的整数部分
题解:牛顿迭代法
牛顿迭代法——其公式为 x n + 1 = x n − f ( x n ) / f / ( x n ) x_{n+1}=x_n-f(x_n)/f^/(x_n) xn+1=xn−f(xn)/f/(xn)。给定 f ( x ) = x 2 − a = 0 f(x) = x^2-a=0 f(x)=x2−a=0,这里的迭代公式为 x n + 1 = ( x n + a / x n ) / 2 x_{n+1}=(x_n+a/x_n)/2 xn+1=(xn+a/xn)/2
class Solution:
def mySqrt(self, x: int) -> int:
if x == 0:
return 0
c = float(x)
x0 = float(x)
# 迭代计算 x 的平方根。
while True:
xi = 0.5 * (x0 + c / x0)
if abs(x0 - xi) < 1e-7:
break
x0 = xi
return int(x0)
Medium:
633. 平方数之和
题目描述:
给定一个非负整数 c
,你要判断是否存在两个整数 a
和 b
,使得 a2 + b2 = c
。
示例 1:
输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5
题目描述:双指针
双解题思路:这道题可以用指针的方法来解决。初始化一个指针left和right,分别指向0和sqrt©。然后在循环中,计算 l e f t 2 + r i g h t 2 left^2 + right^2 left2+right2的值,如果等于c,则找到了解,返回True。如果小于c,则将向左向右移动一位,使左增加。如果大于c,则将右向左移动一位,使右增加。循环终止条件是left <= right。如果循环结束后仍没有找到解,则返回False。
class Solution:
def judgeSquareSum(self, c: int) -> bool:
a, b = 0, int(c ** 0.5) # 左右数
while a <= b:
current_sum = a ** 2 + b ** 2
if current_sum == c: # 大小相同
return True
elif current_sum < c: # 过小,左指针右移
a += 1
else: # 过大,右指针左移
b -= 1
return False
524. 通过删除字母匹配到字典里最长单词
题目描述:
给你一个字符串 s
和一个字符串数组 dictionary
,找出并返回 dictionary
中最长的字符串,该字符串可以通过删除 s
中的某些字符得到。
如果答案不止一个,返回长度最长且字母序最小的字符串。如果答案不存在,则返回空字符串。
示例 1:
输入:s = "abpcplea", dictionary = ["ale","apple","monkey","plea"]
输出:"apple"
题解:双指针
解题思路:检索批量 d 中的每个字符串,然后判断当前字符串是否可以通过删除 s 中的一些字符得到。可以通过双指针来判断两个字符串是否匹配。
class Solution:
def findLongestWord(self, s: str, dictionary: List[str]) -> str:
def Astringexists(s, target):
i = j = 0 # 分别检索s和target
while i < len(s) and j < len(target):
if s[i] == target[j]: # targt指针移动
j += 1
i += 1 # s指针移动
return j == len(target) # 满足条件
# 先长度,在字典序排序
dictionary.sort(key=lambda x: (-len(x), x)) # 从大到小,所以添加负号
for word in dictionary:
if Astringexists(s, word): # 判断是否是最长
return word
return ""
34. 在排序数组中查找元素的第一个和最后一个位置
题目描述:
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
题解:二分查找
解决方案:
- 使用二分查找找到第一个等于 target 的元素的索引。
- 使用二分查找找到最后一个等于 target 的元素的索引。
- 返回一个包含两个索引的列表。
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# 两次二分查找,找左右边界
def searchLeft(nums, target): # 左
left, right = 0, len(nums) - 1 # 指针
while left <= right:
mid = (right - left) // 2 + left # 防止溢出
if nums[mid] == target: # 找到target
if mid == 0 or nums[mid-1] < target: # 判断是否是左边界
return mid
else:
right = mid - 1 # 不是,右指针左移
elif nums[mid] > target: # 过大
right = mid - 1
else: # 过小
left = mid + 1
return -1
def searchRight(nums, target): # 右
left, right = 0 ,len(nums) - 1 # 防止溢出
while left <= right: # 找到target
mid = (right - left) // 2 + left
if nums[mid] == target:
if mid == len(nums) - 1 or nums[mid+1] > target: # 判断是否是右边界
return mid
else:
left = mid + 1 # 不是,左指针右移
elif nums[mid] > target: # 过大
right = mid - 1
else: # 过小
left = mid + 1
return -1
return [searchLeft(nums, target), searchRight(nums, target)]
81. 搜索旋转排序数组 II
题目描述:
已知存在一个按非降序排列的整数数组 nums
,数组中的值不必互不相同。
在传递给函数之前,nums
在预先未知的某个下标 k
(0 <= k < nums.length
)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
(下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7]
在下标 5
处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4]
。
给你 旋转后 的数组 nums
和一个整数 target
,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums
中存在这个目标值 target
,则返回 true
,否则返回 false
。
你必须尽可能减少整个操作步骤。
示例 1:
输入:nums = [2,5,6,0,0,1,2], target = 0
输出:true
题解:二分查找
即使数组被旋转过,我们仍然可以利用这个数组的递增性,使用二分查找。对于当前的中点,如果它指向的值小于等于右端,那么说明右区间是排好序的;反之,那么说明左区间是排好序的。如果目标值位于排好序的区间内,我们可以对这个区间继续二分查找;反之,我们对于另一半区间继续二分查找。
注意,因为数组存在重复数字,如果中点和左端的数字相同,我们并不能确定是左区间全部相同,还是右区间完全相同。在这种情况下,我们可以简单地将左端点右移一位,然后继续进行二分查找。
class Solution:
def search(self, nums: List[int], target: int) -> bool:
# 二分查找
left, right = 0, len(nums) - 1 # 左右指针
while left <= right: # 开始循环
mid = (right - left) // 2 + left # 中间点
if nums[mid] == target: # 存在目标数
return True
if nums[left] == nums[right]: # 无法判断那个区间是递增的
left += 1
elif nums[mid] <= nums[right]: # 当前[mid, right]
if nums[mid] < target and nums[right] >= target: # 检索右区间
left = mid + 1
else:
right = mid - 1
else:
if nums[mid] > target and nums[left] <= target: # 检索左区间
right = mid - 1
else:
left = mid + 1
return False
153. 寻找旋转排序数组中的最小值
题目描述:
已知一个长度为 n
的数组,预先按照升序排列,经由 1
到 n
次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7]
在变化后可能得到:
- 若旋转
4
次,则可以得到[4,5,6,7,0,1,2]
- 若旋转
7
次,则可以得到[0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]]
旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]
。
给你一个元素值 互不相同 的数组 nums
,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n)
的算法解决此问题。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
题解:二分查找
数组分布如下图,中间值在左递增区间,向右靠拢,在右边递增区间,向左靠拢。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KSW4ioLY-1692539210236)(C:\Users\86152\Desktop\1.png)]
class Solution:
def findMin(self, nums: List[int]) -> int:
start, end = 0, len(nums) - 1 # 首尾两个指针
minNum = float('inf') # 记录最小值
while start <= end: # 开始搜索
mid = (end - start) // 2 + start # 中值 防止溢出
if nums[mid] < minNum:
minNum = nums[mid]
if nums[mid] < nums[end]: # mid落在右半部分的递增区间中
end = mid # 向左边靠拢
else: # mid落在左半部分的递增区间中
start = mid + 1 # 向右边靠拢
return minNum
class Solution:
def findMin(self, nums: List[int]) -> int:
start, end = 0, len(nums) - 1 # 首尾两个指针
while start < end: # 开始搜索
mid = (end - start) // 2 + start # 中值 防止溢出
if nums[mid] < nums[end]: # mid落在右半部分的递增区间中
end = mid # 向左边靠拢
else: # mid落在左半部分的递增区间中
start = mid + 1 # 向右边靠拢
return nums[start]
540. 有序数组中的单一元素
题目描述:
给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。
请你找出并返回只出现一次的那个数。
你设计的解决方案必须满足 O(log n)
时间复杂度和 O(1)
空间复杂度。
示例 1:
输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2
题解:二分查找
当 m i d mid mid是偶数时,则比较 n u m s [ m i d ] 和 n u m s [ m i d + 1 ] nums[mid]和nums[mid+1] nums[mid]和nums[mid+1]是否相等;
如果 m i d mid mid是奇数,则比较 n u m s [ m i d ] 和 n u m s [ m i d + 1 ] nums[mid]和nums[mid+1] nums[mid]和nums[mid+1]是否相等。
当 m i d mid mid是偶数时, m i d + 1 = m i d ⨁ 1 mid+1=mid\bigoplus1 mid+1=mid⨁1;
当 m i d mid mid是奇数时, m i d − 1 = m i d ⨁ 1 mid-1=mid\bigoplus1 mid−1=mid⨁1;
mid = 2
mid ^ 1
3
mid = 1
mid ^ 1
0
class Solution:
def singleNonDuplicate(self, nums: List[int]) -> int:
# 现象 在出现独立数之前和之后,奇偶位数的值发生了变化
# 偶+奇 ---> 奇+偶
# mid满足那个 就更加靠近那个指针
low, high = 0, len(nums) - 1 # 左右指针
while low < high:
# 找到中间元素
mid = (high - low) // 2 + low
# 如果偶+奇
if nums[mid] == nums[mid ^ 1]: # 合并写法
low = mid + 1
# 如果奇+偶
else:
high = mid
# 返回右指针指向的元素
return nums[high]
Hard:
76. 最小覆盖子串
题目描述:
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
题解:滑动窗口
该算法使用了滑动窗口的思想,通过left
和right
两个right
,直t
中的字符。然后通过移动left
指针缩小窗口,直到无法再满足条件,然后再继续滑动``right`,循环迭代,最终找到
class Solution:
def minWindow(self, s: str, t: str) -> str:
need = {} # 哈希表统计字符的数量
for c in t:
need[c] = need.get(c, 0) + 1 # 键c不存在,返回默认值 0
left = right = 0 # 滑动窗口的左右指针
valid_num = 0 # 统计窗口的达到条件的字符数量
min_stat = 0 # 最小串的起始位置
min_len = float('inf') # 最小串的长度
window = {} # 记录窗口内字符出现的数量
while right < len(s):
c = s[right]
right += 1 # 为窗口添加字符
if c in need:
window[c] = window.get(c, 0) + 1
if window[c] == need[c]: # 某个字符达到条件
valid_num += 1
while valid_num == len(need): # 某一个子串达到条件
if min_len > right - left: # 字串更小可以更改数据
min_len = right - left
min_start = left
# 开始靠右移动左指针
d = s[left] # 当前左指针指向的字符
left += 1
if d in need:
if window[d] == need[d]:
valid_num -= 1
window[d] -= 1
return "" if min_len == float('inf') else s[min_start:min_start + min_len]
154. 寻找旋转排序数组中的最小值 II
题目描述:
已知一个长度为 n
的数组,预先按照升序排列,经由 1
到 n
次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7]
在变化后可能得到:
- 若旋转
4
次,则可以得到[4,5,6,7,0,1,4]
- 若旋转
7
次,则可以得到[0,1,4,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]]
旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]
。
给你一个可能存在 重复 元素值的数组 nums
,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须尽可能减少整个过程的操作步骤。
示例 1:
输入:nums = [1,3,5]
输出:1
解题思路:二分查找
思路还是和81. 搜索旋转排序数组 II差不多的,多的是对相同数的处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DA7gklDv-1692539210241)(C:\Users\86152\Desktop\1.png)]
class Solution:
def findMin(self, nums: List[int]) -> int:
low, high = 0, len(nums) - 1 # 左右指针
while low <= high: # 开始搜索
mid = (high - low) // 2 + low
if nums[low] == nums[high]:
low += 1
elif nums[mid] <= nums[high]:
high = mid
else:
low = mid + 1
return nums[high]
输入:nums = [1,3,5]
输出:1
解题思路:二分查找
思路还是和81. 搜索旋转排序数组 II差不多的,多的是对相同数的处理
[外链图片转存中…(img-DA7gklDv-1692539210241)]
class Solution:
def findMin(self, nums: List[int]) -> int:
low, high = 0, len(nums) - 1 # 左右指针
while low <= high: # 开始搜索
mid = (high - low) // 2 + low
if nums[low] == nums[high]:
low += 1
elif nums[mid] <= nums[high]:
high = mid
else:
low = mid + 1
return nums[high]