卡洗牌(SPOJ / Interviewstreet)

时间:2012-08-20 22:31:48

标签: java algorithm shuffle

之前已经提出过这个问题,但是没有一个问题得到明确答复,我尝试编译我在这里找到的所有信息。如有必要,请随意合并/移动到另一个stackexchange站点。

以下是我发现的与此相关的问题:

问题最初是作为Interviewstreet Code Sprint发布的,但现在它被列为a practice problem。它也是ported to SPOJ

以下是问题陈述:

  

这是一种改组N卡的算法:

     

1)卡被分成K个相等的桩。

     

2)底部N / K卡   属于堆1的顺序相同(所以最初的底卡   桩是桩1)的底卡。

     

3)来自的下一张N / K卡   底部属于桩2,依此类推。

     

4)现在,洗牌堆的顶牌是堆1的顶牌。   下一张牌是桩2的顶牌,...,洗牌桩的第K张牌是顶部>堆K的卡。然后(K + 1)卡是现在位于顶部的卡   桩1,(K + 2)nd是现在位于桩2顶部的卡   等等。

     

例如,如果N = 6且K = 3,则一副牌“ABCDEF”的顺序   (自上而下)洗牌后一次会改为“ECAFDB”。

     

给定N和K,之后需要的最少洗牌次数是多少   堆是否恢复到原来的顺序?

     

输入:第一行包含测试用例数T.下一个T.   行包含两个整数,每个N和K.

     

输出:输出T行,每个包含最小值的测试用例一行   需要的洗牌次数。如果甲板永远不会回到它的   原始订单,输出-1。

     

约束:

     
      
  • K将是N的一个因子。
  •   
  • T <= 10000
  •   
  • 2&lt; = K&lt; = N&lt; = 10 ^ 9
  •   

扰流警报 - 如果您想自己解决,请不要阅读以下内容。

问题可以翻译为:

  

查找需要执行K-way(完美)in-shuffle的次数   将一副N张牌恢复到最初的订购状态。

我采取了两种方法来解决这个问题。想到的第一种方法是:

  • 找到一个公式,给定初始顺序中的位置将生成卡的下一个位置
  • 使用公式确定每张卡从第一堆(大小为n / k)返回其初始位置所需的洗牌次数
  • 返回
  • 之前确定的shuffle数的最小公倍数

此解决方案的复杂性为O(n / k + max_number_of_suhffles)。 Here's the actual implementation。这个问题是它超过了最大时间,所以我开始寻找一个允许我在接近恒定时间内获得数字的公式。

我在这里可以优化的最多(例如,使用一些地图在相同的排列周期中缓存计算值等)是为了让它在面试街上通过3/10测试。


我找到this implementation,假设返回初始状态所需的shuffle数是K相对于N + 1的multiplicative order。来自wiki:

As a consequence of Lagrange's theorem, ordn(a) always divides φ(n). 

φ(n)是Euler totient function,ordn是group order - 我们正在寻找的东西。我发现this paper使用φ来计算shuffle的数量,但它只适用于2-in-shuffle,而不是k-way。

以下是此实施的步骤:

  • 预先计算了素数列表&lt; 100 000
  • 从其主要因素计算φ(N+1)
  • 通过以各种可能的方式组合其主要因素来确定所有φ(N + 1)个因素。
  • 依次尝试每个因素并获得最小的因子x,以验证k ^ x % N + 1 = 1

此实施也是posted on GitHub

这种运行速度非常快,但自动分级器为SPOJ和Interviewstreet的10个测试中的9个提供了“错误答案”分类。

我尝试比较两个实现的输出,但对于我输入的测试用例(已知结果和随机),这两个实现总是输出相同的东西。这很奇怪,因为我很确定第一个算法是正确的我假设第二个算法也应该是正确的。

“错误答案”分类可能来自代码中的运行时错误,但没有任何内容可能导致此错误。

我没有考虑到没有数字洗牌可以将牌组恢复到初始状态的情况 - 我的理解是这是不可能的。有限数量的完美洗牌最终将恢复初始排序,即使洗牌次数可能非常高。

如果您花时间阅读本文,谢谢。 :)我很好奇这个问题,我想让它解决。

2 个答案:

答案 0 :(得分:3)

这是我在纸上完成的一些观察后想出的。

class CardShuffle {
    private long k;
    private long n;
    private long kn;
    private long kn2;

    public CardShuffle(long k, long n) {
            //I omitted some checks done here
        this.k = k;
        this.n = n;
        this.kn = k / n;
        this.kn2 = k - kn;
    }

    public long shuffle() {
        long count = 0L;
        long next = 0L;
        do {
               //this can be further optimized
           next = kn2 - kn * (next % n) + (next / n); 
           ++count;
        } while((next != 0L) && (count < k));
        if(count > k)
           return -1;
        return count;
    }
}

结果是......

Testing 1000000 : 2
#ms: 3.121905
#ms: 1424.487191
#1: 9900 #2: 9900
Testing 1000000 : 5
#ms: 1.409955
#ms: 556.329366
#1: 2475 #2: 2475
Testing 1000000 : 10
#ms: 0.007823
#ms: 186.797204
#1: 12 #2: 12
Testing 1000000 : 20
#ms: 0.590298
#ms: 275.751527
#1: 4950 #2: 4950
Testing 1000000 : 25
#ms: 0.298642
#ms: 260.559372
#1: 2475 #2: 2475
Testing 1000000 : 40
#ms: 1.187581
#ms: 241.956729
#1: 9900 #2: 9900
Testing 1000000 : 50
#ms: 1.187581
#ms: 241.015548
#1: 9900 #2: 9900
Testing 9999999 : 41841
#ms: 14.499887
#ms: 1829.868042
#1: 125000 #2: 125000
Testing 9999999 : 3333333
#ms: 58.119398
#ms: 311.005728
#1: 500000 #2: 500000
Testing 9999999 : 13947
#ms: 52.704185
#ms: 2095.336418
#1: 500000 #2: 500000

针对此输入进行测试...

10
1000000 2
1000000 5
1000000 10
1000000 20
1000000 25
1000000 40
1000000 50
9999999 41841
9999999 3333333
9999999 13947

首先#ms是时间,以毫秒为单位,我的方法,第二个是你的。 #1#2分别是结果。

此输入的位置......

15
1000000000 2
1000000000 5
1000000000 10
1000000000 20
1000000000 25
1000000000 40
1000000000 50
1000000000 1000
1000000000 200000000
1000000000 250000000
1000000000 500000000
1000000000 50000000
999999999 1001001
999999999 37037037
999999999 333333333

我的方法在

中找到解决方案
Testing 1000000000 : 2
#ms: 71.360466
#1: 525780
Testing 1000000000 : 5
#ms: 68.987259
#1: 525780
Testing 1000000000 : 10
#ms: 0.008381
#1: 18
Testing 1000000000 : 20
#ms: 75.608492
#1: 525780
Testing 1000000000 : 25
#ms: 31.843154
#1: 262890
Testing 1000000000 : 40
#ms: 33.014531
#1: 262890
Testing 1000000000 : 50
#ms: 84.27384
#1: 525780
Testing 1000000000 : 1000
#ms: 0.006705
#1: 6
Testing 1000000000 : 200000000
#ms: 53.991778
#1: 525780
Testing 1000000000 : 250000000
#ms: 43.765898
#1: 262890
Testing 1000000000 : 500000000
#ms: 54.457201
#1: 525780
Testing 1000000000 : 50000000
#ms: 68.080999
#1: 525780
Testing 999999999 : 1001001
#ms: 115.060154
#1: 1000000
Testing 999999999 : 37037037
#ms: 5783.539528
#1: 50000000
Testing 999999999 : 333333333
#ms: 5391.880532
#1: 50000000

虽然你的旧笔记本电脑上的第一台笔记本电脑内存不足,但你的内存却很慢。

我还没有验证过这种方法,但我觉得它很有效。 您可以尝试查看某些输入是否失败。我很感激。

如果您对我如何开发公式感兴趣,请发表评论。

我也提交了 interviewstreet 的解决方案,但由于时间限制,它在第4个测试用例上失败了。

我很快会尝试使用c程序,并在此处报告。

答案 1 :(得分:0)

给定N和K,计算出产生的排列,并计算出http://en.wikipedia.org/wiki/Cycle_notation中的样子。在循环符号中,置换是不相交循环的乘积,例如, ABCDEF =&gt; ECAFDB是(AEDFBC),因为A-> E-> D-> F-> B-> C-> A.如果您的permuation是这样的单个循环,则循环的长度是您需要重复它以返回到同一位置的次数。如果permuation是多个不相交循环的乘积,例如(ABC)(DE)(F),则需要重复的次数是各个循环长度的http://en.wikipedia.org/wiki/Least_common_multiple