698. 划分为 K 个总和相等的子集(Medium)/ 473. 火柴拼正方形(Medium)

698. 划分为 K 个总和相等的子集

在这里插入图片描述

class Solution:
	# 回溯法
    def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
        s = sum(nums)
        if s % k != 0: # 数组总和除以k除不尽,返回False
            return False

        self.target = s // k
        for n in nums: # 若数组中存在一个数大于每份之和,返回False
            if n > self.target:
                return False
        
        nums.sort()
        visited = [0] * len(nums)

        def backtrack(nums, k, visited, begin, temp):
            if temp == 0: # 若当前的总和已经达到target,则继续寻找下一个子集(k-1)
                return backtrack(nums, k-1, visited, 0, self.target)

            if k == 0: # k份都分完了,则成功
                return True

            for i in range(begin, len(nums)):
                if visited[i] == 1: # 若当前值已经用过,则继续
                    continue
                if nums[i] > temp: # 若某一个值大于当前的和,则失败
                    break

                visited[i] = 1 # 标记当前值为已使用
                temp -= nums[i] # 去掉当前值后,对剩余值进行回溯
                if backtrack(nums, k, visited, i+1, temp):
                    return True
                visited[i] = 0 # 回溯完成,则需要加回去还原
                temp += nums[i]

            return False

        return backtrack(nums, k, visited, 0, self.target)
  • 升级版:存在负数的情况
class Solution:
    def canPartitionKSubsets(self, nums, k):
        # 划分子集的目标值
        target, rem = divmod(sum(nums), k)
        # 如果不能整除,可以直接返回
        if rem: return False

        # 递归调用,判断是否可以用当前的nums数组去凑到k个target
        # 注意,这里有递归调用,再次到这里的时候,groups还是那个groups,但是nums不是当初的nums了
        def search(groups):
            # 整个nums数组已经全用完了,还没有从递归中return上来,且groups里的每组数都等于target
            # 说明可以完成这个任务,所以return True
            if not nums and all(map(lambda g : True if g == target else False,groups)): 
            	return True
            # 整个nums数组已经全用完了,还没有从递归中return上来,但groups里的每组数不完全等于target
            # 说明不能完成这个任务,所以return False
            if not nums and not all(map(lambda g : True if g == target else False,groups)): 
            	return False
            
            # 判断放到哪个组里面
            for i, group in enumerate(groups):
                ## 取出末尾并删除1个元素
                v = nums.pop()
                ## 如果group(上一层group)等于target,则找到一组,继续找下一组。必须把v放回去原来的数组
                if group == target:
                    nums.append(v)
                    continue

                # 把这个数放到这个组里
                groups[i] += v
                # 继续判断后续能否完成任务,如果可以,直接返回True,这会在递归中一路返回上去
                # 在这里递归找到v应该放到哪个组里面
                if search(groups): return True
                # 否则,这个数不应该放到这个组里,把v从这个组里面拿出来,继续尝试其他的组。
                # 由于上面已经将v从nums取出,故要将v放回nums
                groups[i] -= v
                nums.append(v)

                if not group:
                    # print("break ... ")
                    break
                    
            # 并且返回False,表示这次回溯是失败的,这个v没有找到对应位置
            return False

        # 如果有相等的,那么相等的必然自己成组,也不需要判断,从nums中去除,并且减少组的数目
        for index in range(len(nums) - 1,-1,-1):
            v = nums[index]
            if v == target:
                nums.pop(index)
                k -= 1

        nums.sort()    # 排序,有助于快速拿到结果

        return search([0] * k)

473. 火柴拼正方形

在这里插入图片描述

class Solution:
    def makesquare(self, matchsticks: List[int]) -> bool:
        totalLen = sum(matchsticks)
        if totalLen % 4:
            return False
        
        def dfs(idx: int) -> bool:
            if idx == len(matchsticks):
                return True
            
            for i in range(4):
                edges[i] += matchsticks[idx]
                if edges[i] <= totalLen // 4 and dfs(idx + 1):
                    return True
                edges[i] -= matchsticks[idx]

            return False

        matchsticks.sort(reverse=True)
        edges = [0] * 4
        
        return dfs(0)

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值