350.两个数组的交集 II
https://leetcode.cn/problems/intersection-of-two-arrays-ii/
数组 哈希表 双指针 二分查找
给你两个整数数组
nums1
和nums2
,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
我的思路:首先考虑哈希表,然后可以考虑排序和双指针
方法一:哈希表
1、用哈希表(字典)实现collections.Counter()
的功能,即返回键值为数组元素,值为数组元素个数的字典。
2、首先遍历nums1数组,即较短的数组,可以降低空间复杂度。定义一个字典count=dict()
记录nums1数组中的元素与元素个数。
3、遍历nums2数组,判断nums2数组中的元素是否在count中并且该元素个数不为0,如果是则将该元素记录到结果数组中,然后count中该元素的个数减1。
from collections import Counter
class Solution:
def intersect(self, nums1: List[int], nums: List[int]) -> List[int]:
# 降低空间复杂度,先遍历较短的数组,再遍历较长的数组得到交集
if len(nums1) > len(nums2):
return intersect(nums2, nums1)
# count = Counter(nums1)
count = {}
# 自己试着实现Counter的功能
for num in nums1:
if num in count:
count[num] += 1
else:
count[num] = 1
ans = []
for num in nums2:
if num in count and count[num]:
ans.append(num)
count[num] -= 1
return ans
方法二:利用Counter方法
import collections
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
num1 = collections.Counter(nums1)
num2 = collections.Counter(nums2)
num = num1 & num2 # &求两个字典中key的交集
return list(num.elements()) # Counter对象elements()方法
方法三:排序+双指针
如果两个数组是有序的,则可以使用双指针的方法得到两个数组的交集。首先对两个数组进行排序,然后使用两个指针遍历两个数组。
初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位
,如果两个数字相等,将该数字添加到答案,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1.sort()
nums2.sort()
p1, p2 = 0, 0
ans = []
while p1 < len(nums1) and p2 < len(nums2):
if nums1[p1] < nums2[p2]:
p1 += 1
elif nums1[p1] == nums2[p2]:
ans.append(nums1[p1])
p1 += 1
p2 += 1
else:
p2 += 1
return ans
推荐方法一,占用空间较少。
拓展:349.两个数组的交集
相当于350求交集需要去重
给定两个数组 nums1 和 nums2 ,返回
它们的交集
。输出结果中的每个元素一定是唯一
的。我们可以 不考虑输出结果的顺序 。
我的思路:1、字典记录数组元素的值和个数,然后判断两个字典的键值是否有相同;2、可以用set实现。
方法一:字典实现(哈希表)
# 运行时间很长,可能复杂度比较高
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
def Counter(nums):
count = {}
for num in nums:
if num in count:
count[num] += 1
else:
count[num] = 1
return count
# 去重
count1 = Counter(nums1)
count2 = Counter(nums2)
ans = count1.keys() & count2.keys() # 得到的是一个集合set
return list(ans)
方法二:用set实现:(官方解法一:两个集合)
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 先遍历短的数组,降低复杂度
if len(nums1) > len(nums2):
return self.intersection(nums2, nums1)
set1 = set(nums1)
set2 = set(nums2)
return [x for x in set1 if x in set2]
# ans = set1 & set2
# return list(ans)
复杂度分析
时间复杂度:O(m+n)O(m+n)O(m+n),其中 m 和 n 分别是两个数组的长度。使用两个集合分别存储两个数组中的元素需要 O(m+n)O(m+n)O(m+n) 的时间,遍历较小的集合并判断元素是否在另一个集合中需要 O(min(m,n))O(\min(m,n))O(min(m,n)) 的时间,因此总时间复杂度是 O(m+n)O(m+n)O(m+n)。
空间复杂度:O(m+n)O(m+n)O(m+n),其中 m 和 n 分别是两个数组的长度。空间复杂度主要取决于两个集合。
方法三:排序+双指针(官方解法二)
加入答案的数组的元素一定是递增的,为了保证加入元素的唯一性,我们需要额外记录变量 preprepre 表示上一次加入答案数组的元素。
如果两个数字相等,且该数字不等于 preprepre ,将该数字添加到答案并更新变量 preprepre ,同时将两个指针都右移一位。……
变量 preprepre 可以用 ans[−1]ans[-1]ans[−1] 来表示,添加元素时自动更新。
重点:保证唯一性:if not ans or nums[p1] != ans[-1]:
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1.sort()
nums2.sort()
ans = []
p1, p2 = 0, 0
while p1 < len(nums1) and p2 < len(nums2):
if nums1[p1] < nums2[p2]:
p1 += 1
elif nums1[p1] == nums2[p2]:
# 保证加入元素的唯一性!
if not ans or nums1[p1] != ans[-1]:
ans.append(nums1[p1])
p1 += 1
p2 += 1
else:
p2 += 1
return ans
补充:求交集,差集,并集。
在python3环境下,字典的keys()或者items()可以求交集、差集和并集,values()不可以。如果相对values()方法进行集合操作,必须先将值转化为集合。set(a.values())
a = {'x' : 1, 'y' : 2, 'z' : 3}
b = {'w' : 10, 'x' : 11, 'y' : 2}
print('Common keys:', a.keys() & b.keys()) # & 求交集
print('Keys in a not in b:', a.keys() - b.keys()) # - 求差集
print('Keys in a or in b',a.keys() | b.keys()) # | 求并集
print('(key,value) pairs in common:', a.items() & b.items())
print('(key,value) pairs in a not in b:', a.items() - b.items())
print('(key,value) pairs in a or in b:', a.items() | b.items())
121. 买卖股票的最佳时机
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/
数组 动态规划
给定一个数组
prices
,它的第i
个元素prices[i]
表示一支给定股票第i
天的价格。你只能选择某一天
买入这只股票,并选择在未来的某一个不同的日子
卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
方法一:一次遍历(可以理解为动态规划的优化)
思路(官方题解方法二:一次遍历)
遍历一遍数组,计算每次到当天为止
的最小股票价格和最大利润。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 一次遍历(可以理解为动态规划的优化)
minPrice = float("inf") # 定义一个极大值
# minprice = int(1e9)
maxProfit = 0
for price in prices:
minPrice = min(price, minPrice)
maxProfit = max(price - minPrice, maxProfit)
return maxProfit
复杂度分析
时间复杂度:O(n)\mathcal{O}(n)O(n),遍历了一遍数组。
空间复杂度:O(1)\mathcal{O}(1)O(1),使用了有限的变量。
方法二:动态规划
动态规划一般分为一维、二维、多维(使用状态压缩),对应形式为dp(i)dp(i)dp(i)、dp(i)(j)dp(i)(j)dp(i)(j)、二进制dp(i)(j)dp(i)(j)dp(i)(j)。
1. 动态规划做题步骤
- 明确 dp(i)dp(i)dp(i) 应该表示什么(二维情况:dp(i)(j)dp(i)(j)dp(i)(j));
- 根据 dp(i)dp(i)dp(i) 和 dp(i−1)dp(i-1)dp(i−1) 的关系得出状态转移方程;
- 确定初始条件,如 dp(0)dp(0)dp(0)。
2. 本题思路
其实方法一的思路不是凭空想象的,而是由动态规划的思想演变而来。这里介绍一维动态规划思想。
dp[i]dp[i]dp[i] 表示前 iii 天的最大利润,因为我们始终要使利润最大化,则:
dp[i]=max(dp[i−1],prices[i]−minprice)dp[i] = max(dp[i-1], prices[i]-minprice)dp[i]=max(dp[i−1],prices[i]−minprice)
class Solution:
def maxProfit(self, prices: List[int]) -> int:
# 一维动态规划思想
n = len(prices)
if n == 0: return 0 # 边界条件
# 1、初始化dp[0]
dp = [0] * n
dp[0] = 0
minPrice = prices[0]
# 2、递推公式
for i in range(1, n):
minPrice = min(minPrice, prices[i])
dp[i] = max(dp[i - 1], prices[i] - minPrice)
return dp[-1] # 等价于 return dp[n - 1]
# 返回 dp数组的最后一项;如果直接取 dp(n)是越界的