V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
acr0ss
V2EX  ›  算法

[请教] 生活中的算法题:密码尝试次数

  •  
  •   acr0ss · 2023-12-10 23:24:31 +08:00 · 1854 次点击
    这是一个创建于 378 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近想到一个算法题,和生活有关的。

    出租屋最近换了密码锁,6 位密码。实际操作中,在密码前后输入多位其他数字,也能验证成功。既 prefix + password + suffix 的输入,也能解锁。

    那么问题来了,6 位密码最多输入多少次能破解? 假设密码锁的判断方式是最近输入的 12 位字符,既 length(prefix + password + suffix) = 12

    没有思路,请教诸君。

    第 1 条附言  ·  2023-12-10 23:59:31 +08:00
    题意没有表达清楚,应该是:最多试多少次一定破解。
    第 2 条附言  ·  2023-12-11 00:40:19 +08:00
    在换种方式描述一下问题。

    一次输入 12 位,可以拆分位 7 个连续的 6 位密码,即分别为 1-6 ,2-7 ,3-8……7-12 位。意味着能同时验证 7 个密码。

    有没有一种算法,能够计算出,最少尝试多少次 12 位密码,就一定能试出密码。
    第 3 条附言  ·  2023-12-11 17:40:20 +08:00
    算法问题抽象:

    十二位数字用字符串形式表示为:000000000000-999999999999 。六位数用字符串形式表示为:000000-999999 。
    一个十二位数字可以拆分为七个六位数,例如 12345789012 拆分为 123456, 234567, 345678, 456789, 567890, 678901, 789012 七个六位数,即一个十二位数包含七个六位数。

    请问,最少需要多少个十二位数字,包含所有 000000-999999 的六位数。
    33 条回复    2023-12-12 22:51:09 +08:00
    nerkeler
        1
    nerkeler  
       2023-12-10 23:43:25 +08:00 via Android
    所有排列的情况减去 六位正确密码在十二位从头滑动到末尾的情况?
    acr0ss
        2
    acr0ss  
    OP
       2023-12-10 23:57:46 +08:00
    @nerkeler 我题意没说清,应该是最多试多少次一定破解。 你这算法肯定超过 6 位密码 10^6 上界了。
    nuk
        3
    nuk  
       2023-12-11 00:11:35 +08:00
    假设是 12 位密码,然后容许的 12 位密码有 10 的 6 次方*6 个,基本上就是试 6 次密码的 1/6 ,就是 1/10^5 几率
    acr0ss
        4
    acr0ss  
    OP
       2023-12-11 00:17:03 +08:00
    @nuk 我是算次数,不是概率。而且你这算法很奇怪,逻辑很跳跃不知道是什么的概率。
    smdbh
        5
    smdbh  
       2023-12-11 00:17:29 +08:00
    感觉是,大概在 1/6 * 10^6 多一点
    acr0ss
        6
    acr0ss  
    OP
       2023-12-11 00:18:52 +08:00
    @smdbh 有什么思路吗? brutal force 也行
    smdbh
        7
    smdbh  
       2023-12-11 00:25:40 +08:00
    @smdbh 12 位数字,一次可以试 0-6 ,1-7 ,2-8 ,。。。7-12 ,7 个密码,试过的就 hash 查表标记。但这个排列是有相关性的,到后面可能会有重复的,一次查不满 7 个
    acr0ss
        8
    acr0ss  
    OP
       2023-12-11 00:33:43 +08:00
    @smdbh 如果要暴力枚举,计算量是 10**12 ,这个机器扛不住。而且类似贪心,不一定是最少的次数(最优解)。
    TongNianShanHe
        9
    TongNianShanHe  
       2023-12-11 01:14:48 +08:00 via Android
    感觉好像 leetcode 752
    acr0ss
        10
    acr0ss  
    OP
       2023-12-11 01:48:10 +08:00
    @TongNianShanHe 谢谢回复,看了题目,不一样但有启发。可以用广度优先来做,但是时间复杂度数量级是 10**12 ,约等于不行。
    acr0ss
        11
    acr0ss  
    OP
       2023-12-11 01:53:59 +08:00
    @acr0ss 又想了一下,bfs/dfs 的初始状态没法确定,visited 数组得按路径维护,内存消耗太大。
    NoOneNoBody
        12
    NoOneNoBody  
       2023-12-11 02:01:54 +08:00   ❤️ 1
    只要直接输入密码也能打开,就意味着真密码前后的几位,对防暴力破解是没有意义的,它只是对于写下来,即使被别人看到了,也难以猜到而已
    例如真密码是六个 0 ,000000+六个任意数字能打开不?能打开的话,那第一次就成功了,意味着最大次数就只和真密码有关
    geelaw
        13
    geelaw  
       2023-12-11 02:40:03 +08:00
    你需要搜索的是 de Brujin 序列 (sequence)
    jjyy1008
        14
    jjyy1008  
       2023-12-11 03:06:27 +08:00 via iPhone
    @NoOneNoBody 这才是正解!楼上的钻书里去了?门禁密码锁设置前缀和后缀的目的就是为了防偷窥用的,哪怕背后有人瞟到了一部分真正密码或者全部真正密码,你接着瞎按一串再确认,这样对防人肉破解很有效。
    huibosa
        15
    huibosa  
       2023-12-11 08:21:21 +08:00
    如果想自己编程实现这种机制该怎么做呢,比如强制服务端只存储密码的哈希,如果想达到最优性能需要用到哪些算法呢(当然题主讨论的密码锁应该用的不是这种方式)
    xuhai951753
        16
    xuhai951753  
       2023-12-11 10:47:55 +08:00
    function recur(pre, times) {
    const cur = times === 0 ? 1 / (9 ** 6) : (1 - pre) / 9;

    return times < 6 ? (cur + recur(cur, times + 1)) : cur;
    }

    recur(0, 0);
    acr0ss
        17
    acr0ss  
    OP
       2023-12-11 10:51:55 +08:00
    @NoOneNoBody 按我的体重描述,“000000+六个任意数字”可以打开,对应位 len(prefix)=0, (suffix)=6 。如果知道真密码,那一次就行。我只是借着这个场景,实际想要表达的问题是:需要多少个 12 位,就能穷举 6 位数的所有可能。
    acr0ss
        18
    acr0ss  
    OP
       2023-12-11 12:41:53 +08:00
    @xuhai951753 不理解,能否解释一下?问题是求次数,但为什么结果是一个小 1 的小数?
    acr0ss
        19
    acr0ss  
    OP
       2023-12-11 12:47:40 +08:00
    @jjyy1008 不知道可以不回答,不用趾高气扬、指手划脚。
    NoOneNoBody
        20
    NoOneNoBody  
       2023-12-11 14:22:09 +08:00
    @acr0ss #17
    不用想那么多,假设身旁没有人,你会前面多按几个无用的数字么?你只会按 6 位,所以密码就是前 6 位匹配
    别人来暴力破解,也是前 6 位对了就可以了

    所以,决定因素就是对方是否知道密码为 6 位,知道的话,他也不会那么傻试第 7 位,如果不知道,只能硬着头皮输入 12 位的话,那就是穷举到“密码+000000”的位置结束,按这个思路解
    如果已经知道 12 个数字,不知道顺序,就是这 12 个数字的组合,按某个顺序把其结果排序,第一个出现前六位匹配的位置结束
    如果知道 12 个数字,且知道顺序,那就更简单了,每次错误去掉第一个数,到成功时,最多应该仅仅只是 prefix 的长度而已,所以,这说明无论多少位,还是不要让别人知道顺序为好,这是最重要的

    从上面几点看,穷举后排序的方式是很重要的,它影响了到达“前六位匹配”的距离,从电脑角度看都是 0-->9 正序,但实际意义上这个排序方式是不定的,所以就看题面“最多”是怎么理解了,它是表示求最短距离,还是求正序穷举个数,还是有区别的,“中文博大精深”
    acr0ss
        21
    acr0ss  
    OP
       2023-12-11 17:46:29 +08:00
    @NoOneNoBody 介于我“最多最少”表述不明确,且你总是沉浸在密码场景,我已经将问题抽象为算法描述,作为第三条文章附言。

    我仔细看了你的回复,你加了预设条件,和我探究的算法问题不同。
    NoOneNoBody
        22
    NoOneNoBody  
       2023-12-11 18:23:52 +08:00
    @acr0ss #20
    好吧,我直接给你结果就是,你六位密码是多少,就是多少次
    假设你的密码是 123456 ,结果就是 123456+1 次

    000000000000 开门失败
    000000000001 开门失败
    ...
    000000123455 开门失败
    000000123456 开门成功

    这个过程就是 123456 次,再加上 12 个 0 一次
    这样能明白么?还是我理解错了题意?
    TongNianShanHe
        23
    TongNianShanHe  
       2023-12-11 22:32:45 +08:00
    @acr0ss 我的锅,我没有阐明“只需要猜六位数”的前提条件,昨天就是因为想到了这个前提条件,才会想到这道题的(上面也有老哥提出来了)
    TongNianShanHe
        24
    TongNianShanHe  
       2023-12-11 22:35:12 +08:00
    @NoOneNoBody 本质没错,他的意思是密码前后加无意义数字,其实只需要把前面六个 0 的三个 0 ,移动到最后就行了
    TongNianShanHe
        25
    TongNianShanHe  
       2023-12-11 22:42:39 +08:00
    @NoOneNoBody @acr0ss 哦,我重新看了一下补充,当我没说,如果我没理解错的话,这个可以用滑动窗口?还有楼上说的 de Brujin 序列?(其他其他大佬的解答)
    yw9381
        26
    yw9381  
       2023-12-11 22:48:50 +08:00 via Android
    我理解你这个要表达的是
    想要完全包含所有的 6 位数。最少需要多少个 12 位数才可以做到
    最差情况为不复用任何数字。即一个 12 位当成两个 6 位。也就是需要 50w 个
    最优情况应该就是尽可能的复用数字。算法这块我不太懂。但感觉应该可以把尝试次数压到 10w 以内
    acr0ss
        27
    acr0ss  
    OP
       2023-12-12 01:39:23 +08:00
    @NoOneNoBody 老哥我都抽象成纯算法问题了,咋还揪着密码不放呢!?

    题意是用十二位表示所有六位数,所有六位数,所有六位数。
    acr0ss
        28
    acr0ss  
    OP
       2023-12-12 01:44:14 +08:00
    @yw9381 是的,就是想表达这个问题。
    首先想到的上界肯定是 50W ,但也肯定能往下缩减。
    由于一个十二位能表示七个六位,所以下界肯定是 10**6 / 7 ,当然这个应该是达不到的。

    究竟怎么求具体值,我没有算法思路,就连暴力算法也没思路。
    acr0ss
        29
    acr0ss  
    OP
       2023-12-12 17:21:18 +08:00
    @geelaw 看了简介,拙笨无法应用于此问题,烦请指教。
    geelaw
        30
    geelaw  
       2023-12-12 18:53:13 +08:00   ❤️ 1
    @geelaw #13
    @acr0ss #29

    de Bruijn 序列 B(k, n) 的定义是 { 0, 1, ..., k-1 } 的有限序列 X = X[1]X[2] ... X[L] 使 L >= n 且所有 { 0, 1, ..., k-1 }^n 的元素都在 X' = X[1] X[2] ... X[L] X[1] X[2] ... X[n-1] 恰好作为子串出现一次。已经知道 B(k, n) 的长度是 L = k^n ,注意 X ' 长度为 n 的子串恰好有 L = k^n 个,且 { 0, 1, ..., k-1 }^n 恰好就有 k^n 个元素。

    在你的情况里 k=10, n=6 ,但 de Bruijn 序列并不是你直接要的答案,不过已经非常接近了。

    你希望寻找一系列长度是 12 的串 Y(1), ..., Y(z) 使得诸 Y(i) 的所有长度为 6 的子串覆盖了 { 0, ..., 9 }^6 ,很明显 z >= ceiling(10^6/7) = 142858 ,而取

    Y(i) = X'[7(i-1)+1] ... X[7(i-1)+12]
    1 <= i <= 142857
    Y(142858) = X'[7(142858-1)+1 = 10^6] ... X'[10^6+(10-1)] 0 0

    则诸 Y(i) 的所有长度为 6 的子串当然就包括了 X' 所有长度为 6 的子串,后者就是所有长度为 6 的串。这说明需要的 z 可以是 142858 。

    暂且称上面长度为 12 的串是“窗口”。类似地,可以算出密码字符有 k 个,密码长度是 n ,窗口长度是 m 的时候需要的最小的窗口数就是 ceiling(k^n/m)。

    计算最小窗口数和计算这一系列窗口是两回事儿,不过后者也不难,de Bruijn 序列有算法可以生成,再练习一下使用搜索引擎的能力就好。
    geelaw
        31
    geelaw  
       2023-12-12 19:00:33 +08:00
    @geelaw #30 Ugh, 应该是 ceiling(k^n/(m-n+1)).
    NoOneNoBody
        32
    NoOneNoBody  
       2023-12-12 20:14:07 +08:00
    好吧,是我读错题了
    12 个抽取连续 6 个连续的,只有 7 种可能,每种可能都能满足一个 10**6
    10**6/7≈142857.14285714287
    acr0ss
        33
    acr0ss  
    OP
       2023-12-12 22:51:09 +08:00
    @geelaw 多谢老哥指点,数学真是奇妙,没想道这个问题已经有答案了。
    我还有一个问题: 能确保对于任意 n,k 和窗口 m ,都存在对应的 de bruijn 序列吗?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1150 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 17:53 · PVG 01:53 · LAX 09:53 · JFK 12:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.