题目来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems
https://www.nowcoder.com/activity/oj
特别鸣谢:来自夸夸群的 醉笑陪公看落花@知乎,王不懂不懂@知乎,QFIUNE@csdn
感谢醉笑陪公看落花@知乎 倾囊相授,感谢小伙伴们督促学习,一起进步
感谢醉笑陪公看落花@知乎 对本文进行勘误
文章目录
tips:
- 二维数组转一维列表
- map reduce 用法
- 系统内置函数 combinations 求排列组合
- 数组深拷贝 deepcopy()和数组切片[:]
- 与或非的python表示
78. 求子集的四种方法:深度优先,广度优先,动态规划,系统内置函数
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/subsets
此题要考虑空集的加入
方法1和2:深度优先和广度优先
把子集的求解过程看作一棵树的生成过程,具体内容参考 如何求集合的子集-从回溯暴力搜索到伪动态规划再到数学求解:力扣1863. 找出所有子集的异或总和再求和
树的第n层,表示长度为n的的子集
左栏蓝色标记表示深度优先的遍历顺序
中栏蓝色标记表示广度优先的遍历顺序
右栏表示广度优先遍历时,队列内容的变化情况,队列中存的是从根到该结点形成的集合
from copy import deepcopy
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
self.all_subs = [[]]
self.DFS(nums,-1,len(nums),[])
# self.BFS(nums)
# self.BFS_1(nums)
return self.all_subs
'''
递归实现的深度优先搜索
'''
def DFS(self,nums,index,n,cur_sub):
'''
深度优先
:param nums: 目标数组
:param index: 起始下标
:param n: 子集长度
:param cur_sub: 当前子集
:return:
nums = [0,1,2,3]
[0],[0, 1],[0, 1, 2],[0, 1, 2, 3],[0, 1, 3],[0, 2],[0, 2, 3],[0, 3],[1],[1, 2],[1, 2, 3],[1, 3],[2],[2, 3],[3]
'''
if index == n:return
for i in range(index+1,n):
cur_sub.append(nums[i])
self.all_subs.append(deepcopy(cur_sub))
self.DFS(nums,i,n,cur_sub)
cur_sub.pop()
'''
队列实现的广度优先搜索
'''
def BFS(self,nums):
from queue import Queue
q = Queue()
q.put([-1])
while not q.empty():
indexs = q.get()
i_s = indexs[-1]
if i_s ==-1:indexs = []
else:self.all_subs.append([nums[i] for i in indexs])
for i in range(i_s+1,len(nums)):
indexs.append((i))
q.put(indexs[:])
q.put(deepcopy(indexs))
indexs.pop()
def BFS_1(self,nums):
'''
广度优先
如:nums = [0,1,2,3]
得到集合的顺序为:[0],[1],[2],[3],[0, 1],[0, 2],[0, 3],[1, 2],[1, 3],[2, 3],[0, 1, 2],[0, 1, 3],[0, 2, 3],[1, 2, 3],[0, 1, 2, 3]
:param nums:
:return:
'''
q = []
q.append([-1])
while len(q)>0:
indexs = q.pop(0)
i_s =indexs[-1]
if i_s ==-1:indexs = []
else:self.all_subs.append([nums[i] for i in indexs])
for i in range(i_s + 1, len(nums)):
indexs.append(i)
q.append(deepcopy(indexs))
# print(indexs,end=',')
indexs.pop()
深度优先的剪枝优化
实现代码:
class Solution:
def subsetXORSum(self,nums):
self.nums = nums
self.all_subs = []
self.xor = 0
self.memor = set() # 备忘录
return self.DFS(-1), self.memor
def DFS(self,start):
if start in self.memor: return [] # 检查是否被备忘录记录
else:
dset = []
if start == -1:dset = []
else:
dset.append([start])
for i in range(start+1, len(self.nums)):
ans = self.DFS(i)
if start!=-1:
dset.extend([x+[start] for x in ans])
dset.extend(ans)
self.memor.add(start) # 记录,表示该节点已经被使用过
return dset
方法3 动态规划1
先求长度为1的子集,然后在这个子集基础上增加一个元素,得到长度为2的子集,依次类推
初始状态:dp[1] : 长度为1的子集列表
转移函数:dp[i-1] ==> dp[ i ]:长度为i-1的子集列表 1<i<=n,增加一个元素得到长度为i的子集列表
终止条件:i == n, 返回dp 中的所有集合
class Solution:
def subsetXORSum(self, nums: List[int]) -> int:
subsets = [[]]
ans = 0
dp = []
dp.append([]) # dp[0]
subsets.extend(dp[0])
dp.append([])
# 求子集
for i in range(len(nums)):
dp[1].append([i])
subsets.extend(dp[1])
for i in range(2,len(nums)+1):
dp.append([])
dp[i] = self.merge(dp[i-1],dp[1],i)
subsets.extend(dp[i])
return subsets
def merge(self,dp0,dp1,n):
dp2 = []
for subi in dp0:
for subj in dp1:
suba = set(subi) | set(subj)
if len(suba) == n and suba not in dp2:dp2.append(suba)
return dp2
优化merge函数
原merge函数中,查找是否在集合中比较费时
suba not in dp2
修改之后,集合每次只添加更大的下标,不必再次去重
def merge(self,nums,dppi):
return [j+[k] for j in dppi for k in range(j[-1]+1,len(nums))]
优化之后,整体代码简化为:
class Solution:
# 求子集
def subsets(self, nums: List[int]) -> List[List[int]]:
dp = []
dp.append([[i] for i in range(len(nums))]) # dp[1]
for i in range(1,n):
dp.append([])
dp[i] =self.merge(nums,dp[i-1]) # dp[2~n]
subset = [[nums[v] for v in k] for k in [i for j in dp for i in j]]
return subset+[[]]
def merge(self,nums,dppi):
return [j+[k] for j in dppi for k in range(j[-1]+1,len(nums))]
方法4 动态规划2
方法1和2中深度优先的剪枝优化后,只剩一条垂直生成的树枝,自底向上遍历,即是本次动态规划的主要思想。
循环遍历nums数组,将当前元素视作一个子集,并将前面的集合拷贝一份,为每一个集合添加当前元素
class Solution:
def subsetXORSum(self, nums):
allset = []
for i in nums:
cur_set = deepcopy(allset)
for j in range(len(cur_set)):
cur_set[j] = cur_set[j] +[i]
cur_set.append([i])
allset.extend(deepcopy(cur_set))
return allset
方法5 系统内置函数combinations
首先是官方文档对combinations方法的介绍说的是:Elements are treated as unique based on their position, not on their value
.,意思是combinations处理传入的可迭代参数时是根据迭代元素的位置来确定是否唯一的,和元素的值是否唯一没有关系
from itertools import combinations
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
# ans = []
# for i in range(len(nums)):
# for c in combinations(nums,i+1):
# ans.append(list(c))
# return ans
return [c for i in range(len(nums)) for c in combinations(nums,i+1)]+[[]]
tips
如果需要组合的时候去除重复元素值,用 {},相当于用set对初始元素进行了去重,不用考虑去重的时候,用[]
https://blog.csdn.net/cloume/article/details/76399093
data = {1,2,3,2}
data
Out[6]: {1, 2, 3}
data = [1,2,3,2]
data
Out[6]: [1, 2, 3, 2]
二维数组转一维列表
https://blog.csdn.net/STR_Liang/article/details/109246859
# 第一种写法 拆解的写法
a = []
ab = [[1,2,3], [5,8], [7,8,9]]
for item in ab :
for i in item:
a.append(i)
print(a)
# 第二种写法,合并在一起的写法
ab = [[1,2,3], [5,8], [7,8,9]]
a1 = [i for item in ab for i in item]
1863. 找出所有子集的异或总和再求和
一个数组的 异或总和 定义为数组中所有元素按位 XOR 的结果;如果数组为 空 ,则异或总和为 0 。
例如,数组 [2,5,6] 的 异或总和 为 2 XOR 5 XOR 6 = 1 。
给你一个数组 nums ,请你求出 nums 中每个 子集 的 异或总和 ,计算并返回这些值相加之 和 。
注意:在本题中,元素 相同 的不同子集应 多次 计数。
数组 a 是数组 b 的一个 子集 的前提条件是:从 b 删除几个(也可能不删除)元素能够得到 a 。
示例 1:
输入:nums = [1,3]
输出:6
解释:[1,3] 共有 4 个子集:
- 空子集的异或总和是 0 。
- [1] 的异或总和为 1 。
- [3] 的异或总和为 3 。
- [1,3] 的异或总和为 1 XOR 3 = 2 。
0 + 1 + 3 + 2 = 6
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sum-of-all-subset-xor-totals
方法1 先求集合后求异或
求集合可以直接用上题的五种方式,得到子集的情况下,遍历子集,求异或和
'''
求异或和
'''
ans = 0
for sub in subset:
tp = 0
for s in sub:
tp ^= s
ans += tp
return ans
如:
class Solution:
# 求子集
def subsets(self, nums: List[int]) -> List[List[int]]:
dp = []
dp.append([[i] for i in range(len(nums))]) # dp[1]
for i in range(1,n):
dp.append([])
dp[i] =self.merge(nums,dp[i-1]) # dp[2~n]
subset = [[nums[v] for v in k] for k in [i for j in dp for i in j]]
'''
求异或和
'''
ans = 0
for sub in subset:
tp = 0
for s in sub:
tp ^= s
ans += tp
return ans
def merge(self,nums,dppi):
return [j+[k] for j in dppi for k in range(j[-1]+1,len(nums))]
方法2 用系统内置函数求子集再求异或 简洁写法
from itertools import combinations
from functools import reduce
from itertools import combinations
from operator import xor
class Solution:
def subsetXORSum(self,nums):
# [i for i in range(len(nums))] i : 每一个下标 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
# [combination for combination in combinations(nums, i + 1)] combination = nums 里面选择i+1个形成的子集 [(10,), (5,), (8,), (3,), (6,), (12,), (2,), (4,), (11,), (7,), (1,), (9,)]
# reduce(lambda x, y: x ^ y, combination) 异或结果
# [m for m in map(lambda combination: reduce(lambda x, y: x ^ y, combination), combinations(nums, i + 1))] [10, 5, 8, 3, 6, 12, 2, 4, 11, 7, 1, 9]
return sum([sum(map(lambda combination: reduce(lambda x, y: x ^ y, combination), combinations(nums, i + 1))) for i in range(len(nums))])
map用法
https://zhuanlan.zhihu.com/p/77311224
将集合中的元素,映射为其他的元素,第一个参数是映射函数(接收一个参数),第二个参数是需要映射的集合
map(function_to_apply, list_of_inputs)
reduce
reduce 将集合中的元素,映射为其他的元素,第一个参数是映射函数(接收两个参数),第二个参数是需要映射的集合
reduce(function, iterable[, initializer])
带初始值的reduce
result = 100*1*3*5*7*9
reduce(f, [1, 3, 5, 7, 9], 100)
https://cloud.tencent.com/developer/article/1390899
filter
filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
https://www.runoob.com/python/python-func-filter.html
filter(function, iterable)
map reduce 使用示例
'''
求异或和
'''
ans = 0
for sub in subset:
tp = 0
for s in sub:
tp ^= s
ans += tp
return ans
from functools import reduce
return sum(map(lambda x: reduce(lambda i,j:i^j, x), subset))
导入 xor, 进一步简化
from functools import reduce
from operator import xor
return sum(map(lambda x: reduce(xor, x), subset))
方法3 数学规律
class Solution:
def subsetXORSum(self, nums: List[int]) -> int:
ans = 0
for num in nums:
ans |= num
return ans << (len(nums)-1)
tips:
- 统计每一bit上,0和1的出现情况
- 二项式定理
- 二项式系数:奇数项之和=偶数项之和=总项数的一半
- 含有某个元素的子集,占总子集数目的一半
- 如果某个元素的某位上是1,则这1位在一半的总子集上出现
当前ans可以反映,在所有位上,哪些位出现过1,哪些位没出现过1
ans = 0
for num in nums:
ans |= num
ans << (len(nums)-1)
# ans * (2**一半子集数目)
python中的位运算 与或非,移位等
& 都为1 时,结果为1
| 有1,结果为1
^ 相异为1 ,奇数个1 结果为1,偶数个1,结果为0
<< a<<b == a*(2**b)
>> a>>b == a/(2**b)
& 与,两个位都为1时,结果才为1 (统计奇数) 全1为1
| 或,两个位都为0时,结果才为0 (统计偶数) 全0为0 # ^ # 异或 # 两个位相同为0,相异为1 (常用统计不相同数) 不同为1
~ 取反,0变1,1变0
<< 左移,各二进位全部左移若干位,高位丢弃,低位补0
>> 右移