【LeetCode】升级打怪之路 Day 26:回溯算法 — 集合划分问题

news2024/11/14 8:31:27

今日题目:

  • 698. 划分为k个相等的子集 | LeetCode
  • 473. 火柴拼正方形 | LeetCode

参考文章:

  • 经典回溯算法:集合划分问题

目录

      • LC 698. 划分为k个相等的子集 【classic,有难度】
        • 数据预处理:计算 target
        • 基本回溯
        • 优化 1:跳过某些 bucket 的选择
        • 优化 2:事先对 nums 排序
        • 最终代码事先
      • LC 473. 火柴拼正方形 【练习】

集合划分问题是使用回溯算法来解决的一类问题。这类问题的抽象描述是:给定 n 个数,让我们划分成 k 组,使得这 k 组的每组数的 sum 一样大。这类问题也有固定的套路思路,学会就行了。

LC 698. 划分为k个相等的子集 【classic,有难度】

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

我们可以理解为有 k 个桶,我们需要尝试将各个数字分别放入所有桶中,使得每个桶的 sum 都相等。

基本思路就是采用回溯算法,在“做选择”这一步,就是将一个数字分别选择放入各个不同的桶中,这样回溯决策树的第 i 层就是决定将 nums[i] 放入哪个 bucket 中。

但这种基本的回溯就等同于暴力搜索了,在 LeetCode 中提交后会出现超时错误,解决方法就是优化某些步骤,尽可能地剪枝

数据预处理:计算 target

这一步很重要。因为我们是让每个 bucket 中的 sum 都相等,那自然每个 bucket 的 sum 就等于 所有数字的加和 / bucket 数量,所以我们先计算出 target,也就是最终每个 bucket 中的所有数字的累加需要达到的目标。

int sum = Arrays.stream(sum).sum();
// 如果 sum 不能平分,则直接可以判定找不到答案
if (sum % k != 0) {
	return false;
}
int target = sum / k;  // 每个 bucket 的累加需要达到的目标

通过上面我们计算出了 target,就可以在回溯时提前判断当 bucket 的数字累加超过了 target 时,就可以提前剪枝了

基本回溯

由此,我们可以写出如下的解决代码:

class Solution {

    private boolean backtrack(int[] buckets, int target, int[] nums, int k, int level) {
        if (level >= nums.length) {
            for (int bucket: buckets) {
                if (bucket != target) {
                    return false;
                }
            }
            return true;
        }

        int num = nums[level];
        for (int i = 0; i < k; i++) {  // 遍历各个 bucket
            int sum = buckets[i] + num;  // 如果做出选择后,这个 bucket 的累加和
            if (sum > target) {  // 如果超出了 target,就提前剪枝
                continue;
            }
            buckets[i] = sum;
            boolean ok = backtrack(buckets, target, nums, k, level + 1);
            if (ok) {
                return true;
            }
            buckets[i] -= num;
        }

        return false;
    }

    public boolean canPartitionKSubsets(int[] nums, int k) {
        int sum = Arrays.stream(nums).sum();
        if (sum % k != 0) {
            return false;
        }
        int target = sum / k;
        int[] buckets = new int[k];
        Arrays.fill(buckets, 0);
        return backtrack(buckets, target, nums, k, 0);
    }
}

这个思路是没问题了,但是很遗憾,还是复杂度过高,超时。

优化 1:跳过某些 bucket 的选择

这里存在一个重要优化:当你做选择想把某个 num 放入一个 bucket 时,如果这个 bucket 的累加和与上一个 bucket 的累加和相同,那把这个 num 放入当前这个 bucket 的结果与放入上一个 bucket 的结果是一样的,上一个选择没有让我们找出答案,那这一次选择也不会让我们找出答案,因此可以直接剪枝跳过

由此,在上面的代码中,我们可以加入这样一个优化:

优化
这个优化只需要让我们加一个小判断,就能剪掉很多枝。

优化 2:事先对 nums 排序

因为我们会判断当前 bucket 的和是否超过了 target 进而剪枝,那事先对 nums 逆序排序,将大的数字放在前面,就更快地出现剪枝,从而减小复杂度。所以,我们可以在一开始先对 nums 进行逆序排序

逆序排序

最终代码事先

在基本的回溯代码再加上上面两个优化后,就可以通过 LeetCode 的检测了。最终代码如下:

class Solution {

    private boolean backtrack(int[] buckets, int target, int[] nums, int k, int level) {
        if (level >= nums.length) {
            for (int bucket: buckets) {
                if (bucket != target) {
                    return false;
                }
            }
            return true;
        }

        int num = nums[level];
        // 遍历各个 bucket 做选择
        for (int i = 0; i < k; i++) {
            // 如果当前桶和上一个桶内的元素和相等,则跳过
            // 原因:如果元素和相等,那么 nums[index] 选择上一个桶和选择当前桶可以得到的结果是一致的
            if (i != 0 && buckets[i] == buckets[i - 1]) {
                continue;
            }
            int sum = buckets[i] + num;  // 如果做出选择后,这个 bucket 的累加和
            if (sum > target) {   // 如果超出了 target,就提前剪枝
                continue;
            }
            buckets[i] = sum;
            boolean ok = backtrack(buckets, target, nums, k, level + 1);
            if (ok) {
                return true;
            }
            buckets[i] -= num;
        }

        return false;
    }

    public boolean canPartitionKSubsets(int[] nums, int k) {
        // 逆序排序 nums
        Arrays.sort(nums);
        for (int low = 0, high = nums.length - 1; low < high; low++, high--) {
            int temp = nums[low];
            nums[low] = nums[high];
            nums[high] = temp;
        }
        int sum = Arrays.stream(nums).sum();
        if (sum % k != 0) {
            return false;
        }
        int target = sum / k;
        int[] buckets = new int[k];
        Arrays.fill(buckets, 0);
        return backtrack(buckets, target, nums, k, 0);
    }
}

LC 473. 火柴拼正方形 【练习】

[473. 火柴拼正方形 | LeetCode]

这个题目本质上和上个题目一样,可以当作练习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1539253.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

限时免费!Unity 资源商店威尔房间精品资源等你来领!

Unity 资源商店威尔房间精品资源分享 资源介绍资源特色免费领取 Unity 商店资源&#xff1a;Will’s Room Environment, Dormitory Room Environment 免费获取。 资源介绍 好消息来啦&#xff01;Unity 资源商店的威尔房间精品资源正在限时免费中&#xff01;这是一个非常受欢迎…

一维前缀和一维差分(下篇讲解二维前缀和二维差分)(超详细,python版,其他语言也很轻松能看懂)

本篇博客讲解一维前缀和&#xff0c;一维差分&#xff0c;还会给出一维差分的模板题&#xff0c;下篇博客讲解 二维前缀和&二维差分。 一维前缀和&#xff1a; 接触过算法的小伙伴应该都了解前缀和&#xff0c;前缀和在算法中应用很广&#xff0c;不了解也没有关系&#…

【算法】小强爱数学(迭代公式+数论取模)

文章目录 1. 问题2. 输入3. 输出4. 示例5. 分析6. 思路7. 数论&#xff0c;取模相关公式8. 数论&#xff0c;同余定理9. 代码 1. 问题 小强发现当已知 x y B xyB xyB以及 x y A xyA xyA时,能很轻易的算出 x n x_ {n} xn​ y n y_ {n} yn​ 的值.但小强想请你在已知A和B的…

【No.16】蓝桥杯动态规划下|线性DP装箱问题|计数DP0/1背包的方案数|过河卒|完全背包小明的背包2|最长公共子序列|蓝桥骑士|推荐练习题(C++)

线性DP&#xff0c;0/1背包简化版&#xff0c;装箱问题 【题目描述】有一个箱子容量为V(正整数&#xff0c; 0 ≤ V ≤ 20000 0 \le V \le 20000 0≤V≤20000)&#xff0c;同时有n个物品( 0 < n ≤ 30 0 < n \le 30 0<n≤30)&#xff0c;每个物品有一个体积(正整数)。…

ubuntu20.04安裝輸入法

文章目录 前言一、操作過程1、安装fcitx-googlepinyin2、配置language support 前言 參考文獻 一、操作過程 1、安装fcitx-googlepinyin sudo apt-get install fcitx-googlepinyin2、配置language support 第一次點擊進去&#xff0c;會讓你安裝 點擊ctrl和空格切換中英文…

Gin入门指南:从零开始快速掌握Go Web框架Gin

官网:https://gin-gonic.com/ GitHub:https://github.com/gin-gonic 了解 Gin Gin 是一个使用 Go 语言开发的 Web 框架,它非常轻量级且具有高性能。Gin 提供了快速构建 Web 应用程序所需的基本功能和丰富的中间件支持。 以下是 Gin 框架的一些特点和功能: 快速而高效:…

000_coolprop_in_matlab在Matlab中使用CoolProp

在Matlab中使用CoolProp 简介 CoolProp是一个开源的热力学性质库&#xff0c;可以计算多种流体的热力学性质。CoolProp支持多种编程语言&#xff0c;包括Python、C、Matlab等。本文将介绍如何在Matlab中使用CoolProp。 CoolProp官网 本文所使用的Matlab版本为R2021a。 在Ma…

Zabbix与Prometheus区别简述

Zabbix与Prometheus区别简述 历史沿革 一、监控工具简介 1、Zabbix https://www.zabbix.com/cn/download Zabbix是传统的监控系统&#xff0c;出现比云原生早&#xff0c;使用的是SQL关系型数据库&#xff1b;开源监控软件&#xff0c;遵守 GPLv2开源协议&#xff0c;起源于…

为什么说 C/C++ 不适合做 Web 开发?

Web开发是当今信息技术领域的重要组成部分&#xff0c;涉及创建、维护和优化供浏览器访问的网站和应用程序。在众多编程语言中&#xff0c;C/C因其高效、灵活和接近硬件的特性而备受推崇&#xff0c;但在Web开发领域&#xff0c;它们并不被视为首选工具。本文将从以下几个方面探…

Linux Sftp和Scp

scp 和 sftp 区别 1 scp 能将远程文件复制到另一个远程机&#xff0c;sftp 不能。sftp为 SSH的其中一部分&#xff0c;是一种传输档案至 Blogger 伺服器的安全方式 2.scp 没有删除/创建远程目录功能&#xff0c;sftp 有。scp 在需要进行验证时会要求你输入密码或口令。 3. FT…

【算法】回溯与深搜

方法论 1.构建决策树 2.设计代码&#xff1a;全局变量、dfs函数 3.剪枝&#xff0c;回溯 全排列 给定一个不含重复数字的整数数组 nums &#xff0c;返回其 所有可能的全排列 。可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff…

力扣---随机链表的复制

给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的值。新节点的 n…

进程和线程的关系与区别

目录&#xff1a; 导言&#xff1a; 正文&#xff1a; 一.进程 1.概念 2.进程控制块&#xff08;Process Control Block&#xff0c;PCB) 3.状态 4.优缺点 二.线程 1.概念 2.线程的设计理由与优点 3.缺点 三.进程与线程的区别与联系 导言&#xff1a; 在现代计算机…

ctf__

title: ctf 2.题目类别 3.题目的细分 4.例题 misc 0x05. 明文攻击 明文攻击是一种较为高效的攻击手段&#xff0c;大致原理是当你不知道一个zip的密码&#xff0c;但是你有zip中的一个已知文件&#xff08;文件大小要大于12Byte&#xff09;时&#xff0c;因为同一个zip压缩…

java数据结构与算法刷题-----LeetCode215. 数组中的第K个最大元素

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 解题思路&#xff1a;时间复杂度O( n n n)&#xff0c;空间复杂度…

大屏可视化综合展示解决方案

1.系统概述 1.1.需求分析 1.2.重难点分析 1.3.重难点解决措施 2.系统架构设计 2.1.系统架构图 2.2.关键技术 2.3.接口及要求 3.系统功能设计 3.1.功能清单列表 3.2.数据源管理 3.3.数据集管理 3.4.视图管理 3.5.仪表盘管理 3.6.移动端设计 3.1.系统权限设计 3.…

STM32 CAN的位时序和波特率

STM32 CAN的位时序和波特率 位时序 STM32的CAN外设位时序中只包含3段&#xff0c;分别是同步段SYNC_SEG、位段BS1及位段BS2&#xff0c;采样点位于BS1及BS2段的交界处。其中SYNC_SEG段固定长度为1Tq&#xff0c;而BS1及BS2段可以在位时序寄存器CAN_BTR设置它们的时间长度&…

CISP 4.2备考之《安全支撑技术》知识点总结

文章目录 第一节 密码技术第二节 标识和身份鉴别技术第三节 访问控制技术 第一节 密码技术 密码学发展阶段&#xff1a;古典、近代、现代和公钥密码学及特点。 密码系统组成&#xff1a;明文、加密、密钥、解密、密文。 柯克霍夫原则&#xff1a;密钥保密&#xff0c;算法公开…

Stable Diffusion 本地训练端口与云端训练端口冲突解决办法

方法之一&#xff0c;修改本地训练所用的端口 1 首先&#xff0c;进入脚本训练器的根目录 例如&#xff1a;C:\MarkDeng\lora-scripts-v1.7.3 找到gui.py 2 修改端口号 因为云端训练器也是占用28000和6006端口 那么本地改成27999和6007也是可以的 保存退出&#xff0c;运行启动…

NX二次开发常用函数:UF_MODL_ask_feat_......(一)

最近学习NX二次开发发现有一些函数经常使用&#xff0c;俗话说得好&#xff0c;好记性不如烂笔头&#xff0c;现在做一下笔记&#xff0c;帮助理解。 UF_MODL_ask_feat_......所在头文件uf_modl.h 1、UF_MODL_ask_feat_body&#xff08;通过特征查询体&#xff09; 概述&…