leetcode 78,1863,子集,异或和,动态规划,DFS和BFS,map,reduce,filter

题目来源:力扣(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)

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 数学规律

O(n)算法简明讲解

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
>> 右移
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值