week12
文章目录
- week12
- 知识点记录:
- 简单类型:
- 中等类型:
- [238. 除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/)
- [78. 子集](https://leetcode.cn/problems/subsets/)
- [47. 全排列 II](https://leetcode.cn/problems/permutations-ii/)
- [209. 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/)
- [713. 乘积小于 K 的子数组](https://leetcode.cn/problems/subarray-product-less-than-k/)
- 其他:
- 其他:
知识点记录:
pop():
在Python中,pop()
是列表(List)的一个方法,用于移除列表中的元素,并返回被移除的元素值。pop()
方法可以接受一个可选的参数,用于指定要移除的元素的索引位置,默认为最后一个元素。
例如,对于列表nums = [1, 2, 3, 4, 5]
:
-
nums.pop()
将移除并返回最后一个元素5,列表变为[1, 2, 3, 4]
。 -
nums.pop(1)
将移除并返回索引位置为1的元素2,列表变为[1, 3, 4, 5]
。
注意:如果使用pop()
方法时不指定索引位置,并且列表为空,将会引发IndexError
异常。因此,在使用pop()
方法之前,最好先检查列表是否为空。
简单类型:
160. 相交链表
题目描述:
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
图示两个链表在节点 c1
开始相交**:**
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal
- 相交的起始节点的值。如果不存在相交节点,这一值为0
listA
- 第一个链表listB
- 第二个链表skipA
- 在listA
中(从头节点开始)跳到交叉节点的节点数skipB
- 在listB
中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA
和 headB
传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
code_1:
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
def get_length(head): # 链表长度的获取
length = 0
while head:
length += 1
head = head.next
return length
lenA, lenB = get_length(headA), get_length(headB)
p1, p2 = headA, headB
# 调整链表的起始位置,使得它们从同样长度开始
while lenA > lenB:
p1 = p1.next
lenA -= 1
while lenB > lenA:
p2 = p2.next
lenB -= 1
# 同时移动指针p1和p2,直到它们相遇或者都指向null
while p1 and p2:
if p1 == p2:
return p1
p1 = p1.next
p2 = p2.next
return None
解题思路:
双指针,这次判断的条件是第一个地址相同的地方,也就是两个指针相遇。思路还是比较简单的
- 使指针的首地址相同
- 让指针相遇
- 否则:
None
code_2:
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
if not headA or not headB:
return None # 如果其中一个链表为空,直接返回None,因为不可能相交
pA, pB = headA, headB # 初始化两个指针分别指向两个链表的头节点
while pA != pB:
pA = pA.next if pA else headB # 如果pA指针到达链表末尾,则重新从headB开始遍历
pB = pB.next if pB else headA # 如果pB指针到达链表末尾,则重新从headA开始遍历
return pA # 返回相交的起始节点,若没有相交,则pA会在遍历完两个链表后都为None
解题思路:
这个也是双指针算法,根据官方题解写的,时间开销和空间开销是差不多的。提供学习
206. 反转链表
题目描述:
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
code:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
p = None # 用于记录当前已经反转的链表的头节点,初始值为None
current_node = head # 遍历指针,初始值为链表的头节点
while current_node:
next_node = current_node.next # 记录当前节点的下一个节点,以便后续迭代
current_node.next = p # 当前节点的next指针指向已经反转的链表
p = current_node # 更新p为当前节点,表示已经反转的链表的头节点
current_node = next_node # 更新遍历指针为下一个节点,继续迭代
return p # 返回p作为反转后的链表的头节点
解题思路:
使用迭代的方法进行链表反转。定义一个新的变量p
来记录当前已经反转的链表,初始值为None
。然后,遍历原链表,每次迭代都将当前节点的next
指针指向前一个节点,实现链表的反转。在遍历的过程中,需要保存当前节点的下一个节点next_node
,以防止丢失后续节点。最后,p
节点将指向新的头节点,返回p
作为反转后的链表头节点就是结果。
中等类型:
238. 除自身以外数组的乘积
题目描述
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。
题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请**不要使用除法,**且在 O(*n*)
时间复杂度内完成此题。
示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
code:
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
length = len(nums) # 长度的获取
answer = [0]*length # 结果数组
answer[0] = 1 # 设置为一不改变大小
for i in range(1, length): # 每个元素左边的累积
answer[i] = nums[i - 1] * answer[i - 1]
R = 1; # 记录元素右边的累积
for i in reversed(range(length)): # 从右向左扫描
answer[i] = answer[i] * R
R *= nums[i]
return answer
解题思路:
请**不要使用除法,**且在 O(*n*)
时间复杂度内完成此题。有要求限制,而且考虑到0的存在也无法使用除号。
先记录每个元素左边的累积,在从右向左扫描,乘以元素右边的累积。
78. 子集
题目描述:
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
code:
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
n = len(nums) # 数组的长度
res = [[]] # 初始化结果列表,空集也是子集
def backtrack(curr, choices):
if not choices: # 当前选择列表为空,即没有更多元素可选,返回
return
for i in range(len(choices)): # 遍历每个元素
num = choices[i] # 选择的数字
res.append(curr + [num]) # 将当前选择加入结果列表中
backtrack(curr + [num], choices[i+1:]) # 继续递归,将当前选择后面的元素作为新的选择列表
backtrack([], nums) # 调用递归函数开始生成子集
return res # 返回结果列表
解题思路:
和46. 全排列的思路差不多,回溯算法,不同的是,该题需要的是不重复的所有节点,所以在记录节点的方式上有所不同。
47. 全排列 II
题目描述:
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
code:
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
# 先排序保证相同的数字相邻
nums.sort()
# 结果
res = []
# 长度
n = len(nums)
def backtrack(curr, choices):
"""
回溯函数
Args:
curr: 当前排列
choices: 剩余的数字
Returns:
无
"""
# 如果没有数字了,就添加到结果中
if not choices:
res.append(curr)
return
# 遍历所有的数字
for i in range(len(choices)):
# 如果当前数字已经被使用过了,就跳过
if i > 0 and choices[i] == choices[i-1]:
continue
# 选择当前数字
num = choices[i]
# 递归地调用函数,选择剩余的数字
backtrack(curr + [num], choices[:i] + choices[i+1:])
# 从空数组开始
backtrack([], nums)
return res
解题思路:
和46. 全排列的思路差不多,回溯算法,不同的是,该题需要的是所有不重复的全排列,所以先是基础的回溯算法
,根据题目经行剪枝,判断条件是如果当前数字是否使用过
。
209. 长度最小的子数组
题目描述:
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其和 ≥ target
的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度**。**如果不存在符合条件的子数组,返回 0
。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
code:
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
n = len(nums) # 获取数组的长度
ans = n + 1 # 取一个最大值作为比较的初始值
l = 0 # 为左指针初始化
s = 0 # 窗口的和
for r, x in enumerate(nums): # 开始移动右指针x=nums[r]
s += x
while s - nums[l] >= target: # 左指针移动
s -= nums[l]
l += 1
if s >= target:
ans = min(ans, r - l + 1)
return ans if ans <= n else 0 # 注意可能数组的值不会大于target
解题思路:
比较简单的窗口移动,利用题目数组都是整数的特点经行左指针的移动。
713. 乘积小于 K 的子数组
题目描述:
给你一个整数数组 nums
和一个整数 k
,请你返回子数组内所有元素的乘积严格小于 k
的连续子数组的数目。
示例 1:
输入:nums = [10,5,2,6], k = 100
输出:8
解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2],、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。
code:
class Solution:
def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
if k <= 1: # 考虑到数组的数都是正整数
return 0
ans = 0 # 记录结果
p = 1 # 统计窗口乘积
l = 0 # 左端点
for r, x in enumerate(nums): # 右指针遍历
p *= x # 先乘上右指针的数
while p >= k: # 移动左指针
p /= nums[l]
l += 1
ans += r - l + 1
return ans
解题思路:
解法和209. 长度最小的子数组还是相同的不同的是ans
的计算,这里使用的是ans += r - l + 1
if p[l, r] < k: # p数组内所有元素的乘积
p[l+1, r], p[l+2, r]... < k # 所以ans += r - l + 1
找到右指针不变满足条件最长的字串。
其他:
本周写的leetcode比较少,开始写C++的题和看算法书,下周就是每天最少三道python的,要加快进度。大部分都有思路并且可以自己在30min
以内独立完成(在学的部分),剩下的就是如何优化,写题解。会一点还是比什么都不会要好写一点,也带着看些视频,书籍,和一些巨佬的博客。
部分题开始使用Google的聊天机器人Bard经行代码的注释添加(我写的比较简单),优化算法以及算法解释(比我写的详细),我也在向它的标准化学习。个人感觉他比openai
的chatgpt
要好用一点(当然可能是因为我没为它升级)
l + 1`
if p[l, r] < k: # p数组内所有元素的乘积
p[l+1, r], p[l+2, r]... < k # 所以ans += r - l + 1
找到右指针不变满足条件最长的字串。
其他:
本周写的leetcode比较少,开始写C++的题和看算法书,下周就是每天最少三道python的,要加快进度。大部分都有思路并且可以自己在30min
以内独立完成(在学的部分),剩下的就是如何优化,写题解。会一点还是比什么都不会要好写一点,也带着看些视频,书籍,和一些巨佬的博客。
部分题开始使用Google的聊天机器人Bard经行代码的注释添加(我写的比较简单),优化算法以及算法解释(比我写的详细),我也在向它的标准化学习。个人感觉他比openai
的chatgpt
要好用一点(当然可能是因为我没为它升级)