惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
MyScale Blog
MyScale Blog
月光博客
月光博客
S
SegmentFault 最新的问题
C
CERT Recently Published Vulnerability Notes
P
Proofpoint News Feed
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
人人都是产品经理
人人都是产品经理
K
Kaspersky official blog
Forbes - Security
Forbes - Security
宝玉的分享
宝玉的分享
爱范儿
爱范儿
V
Visual Studio Blog
博客园 - 聂微东
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
N
News and Events Feed by Topic
阮一峰的网络日志
阮一峰的网络日志
V
V2EX
The Cloudflare Blog
Attack and Defense Labs
Attack and Defense Labs
美团技术团队
L
LangChain Blog
NISL@THU
NISL@THU
IT之家
IT之家
T
Tor Project blog
云风的 BLOG
云风的 BLOG
Security Latest
Security Latest
Apple Machine Learning Research
Apple Machine Learning Research
Cisco Talos Blog
Cisco Talos Blog
I
InfoQ
Help Net Security
Help Net Security
Engineering at Meta
Engineering at Meta
Know Your Adversary
Know Your Adversary
I
Intezer
Recent Commits to openclaw:main
Recent Commits to openclaw:main
TaoSecurity Blog
TaoSecurity Blog
P
Palo Alto Networks Blog
GbyAI
GbyAI
Last Week in AI
Last Week in AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - Franky
L
Lohrmann on Cybersecurity
The Register - Security
The Register - Security
W
WeLiveSecurity
Recorded Future
Recorded Future
大猫的无限游戏
大猫的无限游戏
AWS News Blog
AWS News Blog
G
GRAHAM CLULEY

Kenvix's Blog

实现带有Nvidia GPU+Rootless Podman+Docker+Systemd+自动驱动注入支持的systemd nspawn容器 | Kenvix's Blog 手动保留常用端口,解决 Windows 端口被 Hyper-V / WinNAT 占用的问题 免Telent/TTL屏蔽运营商新版光猫的远控、TR069和RMS,获取动态随机超级管理员密码并固化权限 | Kenvix's Blog 利用Windows卷影副本(Volume Shadow)找回被覆盖和删除的数据 | Kenvix's Blog 在Windows下实现WireGuard动态DNS解析(DDNS)的正确方法:避免无意义的开销 | Kenvix's Blog OpenWRT/DNSMasq 配置DHCP静态路由主动推送 实现流量直达和旁路由流量零代价分载 | Kenvix's Blog 解决 Windows 打开视频/图片文件夹很慢的问题 | Kenvix's Blog AltA2DP - 向支持Sony LDAC协议的耳机提供Windows下蓝牙LDAC音频编码器支持 | Kenvix's Blog (2024更新)修复黑群晖 DSM7.0 + Btrfs 存储空间/磁盘损毁/堪用 的问题 校园网白嫖思路分享:局域网中转-不花钱、不认证、高速上网 | Kenvix's Blog 在 Windows 上配置网卡多个 VLAN、多个虚拟网卡、实现单线多拨网速叠加(无需驱动支持) | Kenvix's Blog 解决视频彩铃、语音通话自动转视频通话导致打电话自动挂断的问题 | Kenvix's Blog 超低成本廉价考研教程:如何用小于¥500甚至¥300的开销考个研 | Kenvix's Blog 在 Ubuntu 21.10 上启用蓝牙 LDAC/AAC/AptX 高质量音频编码支持 在 VMware Workstation 桥接模式的网卡上让虚拟机使用 VLAN 的正确方法 在 Windows 上设置 NAT 或网络共享的正确方法——避免Wi-Fi热点无法使用 自编译 红米 AC2100 OpenWRT R21.7.26 Linux 内核结构和子系统简介 | Kenvix's Blog Java 快速读取文本 (算法竞赛适用) | Kenvix's Blog 利用 ThreadLocal + Lambda,实现有状态变量的单例模式 | Kenvix's Blog 状态压缩的动态规划问题:骨牌完全覆盖棋盘问题 | Kenvix's Blog 我的 Windows 10 2004 新增 Bug 解决办法记录 解决 Android Studio 及 IDEA 中 Gradle 错误信息乱码的问题 Kotlin 的那些骚操作 | Kenvix's Blog 在普通的 Gradle Java/Kotlin 项目中使用 BuildConfig 修复国行 MIUI 打开 Google Play 始终提示 DF-DFERH-01 的问题 解决 VSCode 持续调用 WMIC 导致一个 CPU 核心完全被占满的问题 计算机幻觉从入门到入土 | Kenvix's Blog Java 注解预处理 Annotation Processing & 代码生成 Ubuntu 上通过以太网分享网络连接(NAT) | Kenvix's Blog Windows 选择指定的网卡来开承载网络型热点 | Kenvix's Blog 修复升级 Windows10 版本后所有内置应用闪退+第三方应用参数错误的问题 | Kenvix's Blog 配置用于 Gradle6.x + MySQL 8 的 jOOQ 3.14 代码自动生成 (已更新) 修复 Windows 环境下的程序访问 WSL 中的 MySQL 提示 Access Denied 的问题 修复 WSL 下 PHP+FastCGI 卡死的问题 使用任意磁盘或路径保存 Windows 文件历史记录 | Kenvix's Blog [1.12.2+Mod] MoeCraft :: 自由开放的科技向公益 Mod 服务器 Kenvix's Blog 禁用使用Intel核显的Windows笔记本自动调节亮度功能 | Kenvix's Blog 真正实现Minecraft高级登录(外置登录)的几种方案 | Kenvix's Blog 谈谈神舟的两艘贼船,Z7M-KP7S1 / Z7M-KP7SC USBCopyer: 插上U盘自动按需复制文件 | Kenvix's Blog USBCopyer 回调功能详细说明 | Kenvix's Blog C# 实现自定义"应用程序设置"的配置文件(user.config)存储路径 | Kenvix's Blog Win10 资源管理器为所有格式激活“编辑”按钮并修改文本文件“编辑”按钮的编辑器 | Kenvix's Blog 留言板 | Kenvix's Blog 又一次 Hello world | Kenvix's Blog Java 学习笔记 (仍在更新) | Kenvix's Blog 在Win10 Pro下挂载NFS(网络文件系统) | Kenvix's Blog Nginx 反向代理 Aria2 JSONRPC | Kenvix's Blog (Android6.0~9.0) 清除锁屏密码 | Kenvix's Blog WordPress 更换站点地址后批量修改文章/评论中的旧地址 | Kenvix's Blog 修复一加3/3T因固件过老导致刷入ROM时提示错误7的问题 | Kenvix's Blog 修复Android DM-Verity 警告 | Kenvix's Blog 贴吧云签到 资源索引(下载|文档|插件) | Kenvix's Blog 继续监控!使用树莓派+Motion实现实时视频监控并通过浏览器查看 | Kenvix's Blog 自动获取Pixiv每日排行榜第一张图片(600x600 | 可用于博客背景图) | Kenvix's Blog 使用树莓派实现定时拍照监控并发送邮件到邮箱 | Kenvix's Blog 好压 V2.7 Beta1 绿色版——功能强大,良心的压缩软件 | Kenvix's Blog 任意语言实现读取压缩包注释 | Kenvix's Blog 自己实现QQ群自定义分享(管理员开启了群交易?) | Kenvix's Blog MoeCDN - 加速Gravatar/GoogleAPIs等无法在国内访问的资源 | Kenvix's Blog [Minecraft] WebLogin-连接到你的服务器来检查玩家是否可以登录 | Kenvix's Blog Android卡刷包提示This package is for device: ... this device is ...的解决方案 Kenvix's Blog 给EMLOG评论框加上复选框[√]防止垃圾评论 | Kenvix's Blog 欢迎使用emlog | Kenvix's Blog
扔鸡蛋问题 | Kenvix's Blog
2019-02-14 · via Kenvix's Blog

img 菜鸡第一次看算法题,这篇笔记还是不要看比较好

题目

x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。
各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。

x星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的2楼。

如果手机从第7层扔下去没摔坏,但第8层摔坏了,则手机耐摔指数=7。
特别地,如果手机从第1层扔下去就坏了,则耐摔指数=0。
如果到了塔的最高层第n层扔没摔坏,则耐摔指数=n

为了减少测试次数,从每个厂家抽样3部手机参加测试。

某次测试的塔高为1000层,如果我们总是采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢?

解决方案

这东西就是传统的扔鸡蛋问题。

二分法

通常使用此方法的前提是 鸡蛋个数 ≥ ⌈log2M⌉ (M表示楼层个数)例如,9~16层楼在最坏情况下都需要多达 4 个鸡蛋才能得出结果。

倘若鸡蛋个数少,当只剩最后一个鸡蛋时,只能最保守地在一个较大的区间内逐层向上扔鸡蛋,显然会徒增尝试次数,不宜使用。

动态规划

动态规划思想是利用对已求解的重复子问题进行记忆化存储,然后避免重复计算,利用最优化原理得出结果。说人话就是粗暴地递归找。

此方法需要寻找“状态转移方程式”,并依此“倒推”求解

先设一个函数 f(m,n) m 表示楼层数(不是楼高),n 表示鸡蛋数,返回值为最优解在最恶劣情况下需要尝试的次数。

假设第一个鸡蛋在 x 层 (1≤x≤m) 扔出,将出现两种情况:

  1. 鸡蛋在 x 层抛出后碎了
  2. 鸡蛋在 x 层抛出后没碎

鸡蛋碎了

显然接下来应从 (1≤y≤x-1) 层扔出鸡蛋,由于鸡蛋碎了,鸡蛋个数将 -1,同时尝试过一次了,所以尝试次数 +1

根据上面所设函数,可以将这种情况用同样的函数表示为 f(x-1, n-1) +1

鸡蛋没碎

由于鸡蛋没有碎,可以确保小于等于此层数的都没有问题,并且鸡蛋个数没有减少
剩余待测试层数为 m-x,可以用函数表示为 f(m-x, n) +1

这个函数描述在剩下 m-x 个楼层,n 个鸡蛋,返回值为最优解在最恶劣情况下需要尝试的次数。

为什么是 m-x ?

如果在思考这里的时候,代入的一个 x 值比较大,则容易在这里面绕进去。

受平时写业务代码的影响,此处的 f(m-x, n) 易被理解为具有判断鸡蛋是否摔碎的功能,但之前定义 f(a,b) 只是一个用于计算摔鸡蛋所需次数的函数,并非处理业务逻辑的函数,即并不是判断鸡蛋会不会摔碎的函数。

也就是说,尽管已经知道 在 m-x 层的鸡蛋并不会碎(业务逻辑),但仍需假设鸡蛋会碎,并求出在 m-x 层、有 n 个鸡蛋时需要尝试多少次。

f 的第一个参数压根就不是表示楼高的,而是楼层的个数!个数!

得出公式

在从 x 层扔鸡蛋的条件下,此处尚不知道到底鸡蛋碎还是不碎是更加恶劣的情况(需要进一步尝试的次数多),因此需要同时求出上述两种情况,并取最大者:

f(m,n)=max{ f(x-1, n-1)+1, f(m-x, n)+1 }

题设要求我们解出哪层(x)扔鸡蛋可以在最恶劣的情况下(上式)所得值最小,因此需要遍历x (1≤x≤m),分别代入 1≤x≤m 求出值,取最小者,函数返回值即为最优解在最恶劣情况下需要尝试的次数,而使这个返回值成立的x即为所求x(即所求最优解)

综上,易得公式:

f(m,n)=min{ max{f(1-1, n-1)+1, f(m-1, n)+1}, max{f(2-1, n-1)+1, f(m-2, n)+1}, max{f(3-1, n-1)+1, f(m-3, n)+1}, ..., max{f(m-1, n-1)+1, f(m-m, n)+1} }

代码实现

以从 100 楼下落,持有 2 个鸡蛋为例

public class Main {
    private static final int Floor = 100;
    private static final int Egg = 2;

    private static int f(final int m, final int n) {
        int min = 0;
        boolean isInitialized = false;

        if (m == 0)
            return 0;

        for (int x = 1; x <= m; x++) {
            int result = Math.max(f(x-1, n-1)+1, f(m-x, n)+1);

            if(!isInitialized || min > result) {
                isInitialized = true;
                min = result;
            }
        }

        System.out.printf("progress: m=%d value=%d \n", m, min);
        return min;
    }

    public static void main(String[] args) {
        System.out.println("result: " + f(Floor, Egg));
    }
}

跑一下就可以发现,计算花费了大量的时间,这是由于递归的资源开销很大,时间复杂度大,因此效率较低,需要在上式的基础上进行优化。

不优化可以吗?原题 1000 层楼,3 个鸡蛋,我觉得跑到收卷也跑不完,更何况还是渣机。

简单的优化

尝试重新整理思路以改写成循环的形式。

首先绘制一张表格:

持有的鸡蛋数(n)/楼层数(m) 0层楼 1层楼 2层楼 3层楼 4层楼
0个鸡蛋
1个鸡蛋
2个鸡蛋
3个鸡蛋

显然:

  1. 在只有一个鸡蛋时,楼层数=尝试次数
  2. 在只有一层楼时,不管多少个鸡蛋都只尝试一次
  3. 0 层楼或 0 个鸡蛋时不需要尝试,尝试次数记为 0
持有的鸡蛋数(n)/楼层数(m) 0层楼 1层楼 2层楼 3层楼 4层楼
0个鸡蛋 0 0 0 0 0
1个鸡蛋 0 1 2 3 4
2个鸡蛋 0 1
3个鸡蛋 0 1

使用上面的公式,算出2个鸡蛋时、不同层楼的情况,再算出3个鸡蛋时、不同层楼的情况。

求解过程略,但可以发现:求解需要用到上次求解的结果,例如,在求解2个鸡蛋2层楼时,需要用到0层楼2个鸡蛋、1层楼1个鸡蛋的结果。显然,这个表格的填充顺序应是从上到下、从左到右、填完一行到下一行。

根据公式 f(m,n)=max{ f(x-1, n-1)+1, f(m-x, n)+1 }
得出:表格坐标(m,n)的值为 max{(x-1, n-1)的值+1, (m-x, n)的值+1}

持有的鸡蛋数(n)/楼层数(m) 0层楼 1层楼 2层楼 3层楼 4层楼
0个鸡蛋 0 0 0 0 0
1个鸡蛋 0 1 2 3 4
2个鸡蛋 0 1 2 2 3
3个鸡蛋 0 1 2 2 3

整理思路

整理一下上面的操作,可以分为下面的过程:

  1. 建立一个二维数组模仿上面的表格保存结果
  2. 对只有一个鸡蛋时直接返回楼层数,暂时将其他填充为最大尝试次数
  3. 2个以上的鸡蛋时,首先枚举鸡蛋个数(n)
  4. 枚举鸡蛋个数(n)时,枚举楼层数(m)
  5. 枚举楼层数(m)时,枚举从哪层楼开始扔(x)
  6. 枚举从哪层楼开始扔(x)时,算出这层楼的尝试次数
  7. 对应“坐标”的值为尝试次数的最小者
  8. 最终所求解为表格最右下角的那个值

写成代码:

import java.util.stream.IntStream;

public class Main {
    private static final int Floor = 1000;
    private static final int Egg = 3;

    private static int getMinDropEggTries(final int floorNum, final int eggNum) {
        if (floorNum < 1 || eggNum < 1)
            return 0;

        int[][] result = new int[eggNum+1][floorNum+1]; //坐标为 (鸡蛋,楼层)

        IntStream.range(1, floorNum+1).forEach(value -> result[1][value] = value);
        IntStream.range(1, eggNum+1).forEach(value -> result[value][1] = 1);

        IntStream.range(2, eggNum+1).forEach(egg ->
                IntStream.range(2, floorNum+1).forEach(floor ->
                        IntStream.range(1, floor+1).forEach(x -> {
                                    int value = Math.max(result[egg-1][x-1] + 1, result[egg][floor-x] + 1);

                                    if(result[egg][floor] == 0 || result[egg][floor] > value)
                                        result[egg][floor] = value;
                                })));

        return result[eggNum][floorNum];
    }

    public static void main(String[] args) {
        System.out.println("result: " + getMinDropEggTries(Floor, Egg));
    }
}

进一步优化

不难发现,int[][] result 实际上在后续计算中并没有使用,可以将其简化以降低空间复杂度。