首页 > 其他分享 >698. 划分为k个相等的子集

698. 划分为k个相等的子集

时间:2022-12-29 19:46:40浏览次数:37  
标签:相等 False nums 698 dfs 满足 子集 line total

题目描述

给出一个数组nums,问能不能分为k份,每份的和都相等,数组的长度不超过16

f1-状态压缩+记忆化搜索

基本分析

  1. 数组长度不超过16?暗示可以对选择情况用二进制进行表达
  2. dfs的过程需要怎么实现?(1)参数就是当前的选择情况和对应的和;(2)最终退出条件就是s取到了mask,可以返回True;(3)对当前的s,需要在nums中逐个去遍历i和v,需要满足一些条件(s中i位置还是0并且p+v不能超均值),如果以上都满足了,那么dfs(s,p)就和dfs(ns, np)一条战线了;(4)如果找完了nums,都不能满足,就返回False
  3. 其他一些预处理的技巧?(1)和要整除(2)某个元素不能超过均值(3)对nums排序某些情况能减枝

代码

class Solution:
    def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
        n = len(nums)
        total = sum(nums)
        if total % k != 0:
            return False
        line = total // k
        nums.sort()
        if nums[-1] > line:
            return False
        mask = (1<<n) - 1

        @cache
        def dfs(s, p):
            # 选完了
            if s == mask:
                return True
            for i, v in enumerate(nums):
                # s中i处需要没选,是0
                if s>>i & 1:
                    continue
                # 大可以直接减枝,后面肯定不满足
                if p + v > line:
                    break
                # 如果后续能满足,那么s时候可以满足
                if dfs(s | 1<<i, (p+v)%line):
                    return True
            return False
        
        return dfs(0, 0)

复杂度

时间:状态个数是\(2^n, 每个状态尝试n次,最终是O(n \times 2^n)\)
空间:\(状态数组的开销是O(2^n)\)

总结

  1. 其实还是从没选到选的顺序进行递推,只不过把后面当做了已知
f2-状态压缩+动态规划

基本分析

  1. 这里和存在哪里?用f[s]来表示s当前对应的%line的值,-1表示还没有选或者不能选,其余值对应dfs里面的p
  2. 往后递推的思路?和dfs中是类似的,(1)这里先遍历每个可能的s,s不满足就去下一个s;(2)如果s满足,去遍历可能的i和v,需要满足s中i处是0,且f[s]的值+v不超过均值,如果以上都满足了,如果新的ns没有更新,那么就更新他
  3. 最后怎么判断是不是满足要求?最后看填满时候f[-1]的值,如果是-1或者其他非0的值,那就不满足

代码

class Solution:
    def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
        n = len(nums)
        total = sum(nums)
        if total % k != 0:
            return False
        line = total // k
        nums.sort()
        if nums[-1] > line:
            return False
        mask = 1<<n
        f = [-1] * mask
        f[0] = 0

        # 对当前所有的状态进行遍历
        for s in range(mask):
            # 如果s状态不能满足,那么就下一个s
            if f[s] == -1:
                continue
            # 遍历可能的数字
            for i, v in enumerate(nums):
                # 首先还是需要考虑s中不为1的位置
                if s >> i & 1:
                    continue
                # 其次和不能超
                if f[s] + v > line:
                    continue
                # 都满足了,就对新的ns进行更新,为什么需要判断f[ns]==-1的情况?可能是结果唯一,再更新没意义
                ns = s | 1<<i
                if f[ns] == -1:
                    f[ns] = (f[s] + v) % line
        return f[-1] == 0

复杂度

时间:\(状态个数是2^n, 每个状态尝试n次,因此O(n \times 2^n)\)
空间:\(状态数组空间开销是O(2^n)\)

总结

  1. 和dfs的区别在哪?(1)总的递推过程是类似的;(2)这里需要显式的先去遍历可能的所有s,dfs只是在最后和他的后续可能建立关系;(2)dfs中对和直接用参数进行传递,这里显式的定义了f数组,兼顾存和和判断是当前否当前可行的作用;(3)结束的时候,这个f[-1]需要判断和0的关系,dfs里面直接取到mask就返回True了?

标签:相等,False,nums,698,dfs,满足,子集,line,total
From: https://www.cnblogs.com/zk-icewall/p/17013358.html

相关文章