与语言无关的随机洗牌问题

时间:2020-03-05 18:51:20  来源:igfitidea点击:

不久前,我在接受采访时要求解决两个非常有趣的问题。我很好奇我们将如何解决这些问题。

问题1:

除当前以外的所有产品的乘积

编写一个函数,该函数将长度为len的两个整数数组,输入和索引作为输入,并生成第三个数组,结果如下:
result [i] =输入中除input [index [i]]以外的所有内容的乘积

例如,如果使用len = 4,input = {2,3,4,5}和index = {1,3,2,0}调用函数,则结果将设置为{40,24,30 ,60}。

重要说明:算法必须在线性时间内运行。

问题2 :(该主题在Jeff的一篇文章中)

均匀洗牌

  • 设计(用C ++或者C#编写)一个Deck类来代表有序纸牌,其中纸牌包含52张纸牌,分为13个等级(A,2、3、4、5、6、7、8、9,四个花色中的10,J,Q,K):黑桃(?),心形(?),菱形(?)和球杆(?)。
  • 基于此类,设计并实现一种有效的算法来洗牌。纸牌必须均匀地混洗,也就是说,原始纸牌中的每张纸牌必须具有相同的概率才能出现在纸牌中的任何可能位置。该算法应在Deck类的shuffle()方法中实现:void shuffle()
  • 算法的复杂度是多少(取决于卡组中n张牌的数量)?
  • 解释如何测试卡是否被方法均匀洗净(黑盒测试)。

P.S.我有两个小时编写解决方案的代码

解决方案

回答

对于第一个,首先计算输入的全部内容的乘积,然后对于索引的每个元素,将计算出的乘积除以input [index [i]],以填充结果数组。

当然,我必须假设输入没有零。

回答

Vaibhav,很遗憾,我们不得不假设输入表中可能有一个0。

回答

第一个问题:

int countZeroes (int[] vec) {
int ret = 0;
foreach(int i in vec) if (i == 0) ret++;

return ret;
}

int[] mysticCalc(int[] values, int[] indexes) {
    int zeroes = countZeroes(values); 
    int[] retval = new int[values.length];
    int product = 1;

    if (zeroes >= 2) { // 2 or more zeroes, all results will be 0
        for (int i = 0; i > values.length; i++) {
            retval[i] = 0;      
        }
        return retval;
    }
    foreach (int i in values) {
        if (i != 0) product *= i; // we have at most 1 zero, dont include in product;
    }
    int indexcounter = 0;
    foreach(int idx in indexes) {
        if (zeroes == 1 && values[idx] != 0) {  // One zero on other index. Our value will be 0
            retval[indexcounter] = 0;
        }
        else if (zeroes == 1) { // One zero on this index. result is product
            retval[indexcounter] = product;
        }
        else { // No zeros. Return product/value at index
            retval[indexcounter] = product / values[idx];
        }
        indexcouter++;
    }   
    return retval;
}

最坏的情况是,该程序将一次执行3个向量。

回答

void product_except_current(int input[], int index[], int out[], 
                            int len) {
  int prod = 1, nzeros = 0, izero = -1;

  for (int i = 0; i < len; ++i) 
    if ((out[i] = input[index[i]]) != 0)
      // compute product of non-zero elements 
      prod *= out[i]; // ignore possible overflow problem
    else {
      if (++nzeros == 2) 
         // if number of zeros greater than 1 then out[i] = 0 for all i
         break; 
      izero = i; // save index of zero-valued element
    }

  //  
  for (int i = 0; i < len; ++i)  
    out[i] = nzeros ? 0 : prod / out[i];                               

  if (nzeros == 1)
    out[izero] = prod; // the only non-zero-valued element
}

回答

Tnilsson,一个很好的解决方案(因为我用完全相同的方法:P)。

我看不到在线性时间内完成此操作的其他方法。有人吗?因为招聘经理告诉我,这种解决方案不够强大。

我们是否错过了一些超级复杂的事物,所有事情都在一个返回线中解决吗?

回答

第二个问题。

public static void shuffle (int[] array) 
    {
        Random rng = new Random();   // i.e., java.util.Random.
        int n = array.length;        // The number of items left to shuffle (loop invariant).
        while (n > 1) 
        {
            int k = rng.nextInt(n);  // 0 <= k < n.
            n--;                     // n is now the last pertinent index;
            int temp = array[n];     // swap array[n] with array[k] (does nothing if k == n).
            array[n] = array[k];
            array[k] = temp;
        }
    }

这是Wikipedia文章中有关Fisher-Yates随机播放的内容的复制/粘贴。 O(n)复杂度

回答

Tnilsson,我同意YXJuLnphcnQ解决方案可以说是更快的,但思想是相同的。我忘了补充,在第一个问题中,该语言是可选的,在第二个问题中,该语言是可选的。

我们是对的,该计算为零,并且乘积int相同的循环会更好。也许那是事实。

回答

Tnilsson,我还使用了Fisher-Yates洗牌:)。我对测试部分非常感兴趣:)

回答

#include <algorithm>

class Deck {
  // each card is 8-bit: 4-bit for suit, 4-bit for value
  // suits and values are extracted using bit-magic
  char cards[52];
  public:
  // ...
  void shuffle() {
    std::random_shuffle(cards, cards + 52);
  }
  // ...
};

复杂度:N中为线性。执行了51次交换。参见http://www.sgi.com/tech/stl/random_shuffle.html

测试:

// ...
  int main() {
    typedef std::map<std::pair<size_t, Deck::value_type>, size_t> Map;
    Map freqs;    
    Deck d;
    const size_t ntests = 100000;

    // compute frequencies of events: card at position
    for (size_t i = 0; i < ntests; ++i) {
      d.shuffle();
      size_t pos = 0;
      for(Deck::const_iterator j = d.begin(); j != d.end(); ++j, ++pos) 
        ++freqs[std::make_pair(pos, *j)]; 
    }

    // if Deck.shuffle() is correct then all frequencies must be similar
    for (Map::const_iterator j = freqs.begin(); j != freqs.end(); ++j)
      std::cout << "pos=" << j->first.first << " card=" << j->first.second 
                << " freq=" << j->second << std::endl;    
  }

像往常一样,仅进行一次测试是不够的。

回答

Trilsson在问题的测试部分做了一个单独的主题

如何测试随机性(以点洗牌为例)

很好的想法Trilsson :)

回答

YXJuLnphcnQ,我也是这样做的。这是最明显的。

但是事实是,如果我们编写算法,则每次调用sort()时,都只会将集合中的所有卡向右移一个位置,即使输出不是随机的,也可以通过测试。

回答

C#3中第一个问题的线性时间解决方案是:-

IEnumerable<int> ProductExcept(List<int> l, List<int> indexes) {
    if (l.Count(i => i == 0) == 1) {
        int singleZeroProd = l.Aggregate(1, (x, y) => y != 0 ? x * y : x);
        return from i in indexes select l[i] == 0 ? singleZeroProd : 0;
    } else {
        int prod = l.Aggregate(1, (x, y) => x * y);
        return from i in indexes select prod == 0 ? 0 : prod / l[i];
    }
}

编辑:考虑到一个零!我上一次的解决方案是在我上班的时候花了2分钟,所以我不会觉得很糟糕:-)

回答

这是C中使用测试方法的第二个答案。 Shuffle在我看来是O(n)。

编辑:查看了Fisher-Yates随机播放后,我发现我不知道它就重新发明了该算法:-)但是很明显。我实现了Durstenfeld方法,该方法将我们从O(n ^ 2)-> O(n)引入,非常聪明!

public enum CardValue { A, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, J, Q, K }
public enum Suit { Spades, Hearts, Diamonds, Clubs }

public class Card {
    public Card(CardValue value, Suit suit) {
        Value = value;
        Suit = suit;
    }

    public CardValue Value { get; private set; }
    public Suit Suit { get; private set; }
}

public class Deck : IEnumerable<Card> {
    public Deck() {
        initialiseDeck();
        Shuffle();
    }

    private Card[] cards = new Card[52];

    private void initialiseDeck() {
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 13; ++j) {
                cards[i * 13 + j] = new Card((CardValue)j, (Suit)i);
            }
        }
    }

    public void Shuffle() {
        Random random = new Random();

        for (int i = 0; i < 52; ++i) {
            int j = random.Next(51 - i);
            // Swap the cards.
            Card temp = cards[51 - i];
            cards[51 - i] = cards[j];
            cards[j] = temp;
        }
    }

    public IEnumerator<Card> GetEnumerator() {
        foreach (Card c in cards) yield return c;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        foreach (Card c in cards) yield return c;
    }
}

class Program {
    static void Main(string[] args) {
        foreach (Card c in new Deck()) {
            Console.WriteLine("{0} of {1}", c.Value, c.Suit);
        }

        Console.ReadKey(true);
    }
}

回答

from numpy import array

def product(input, index):
    a = array(input)[index]

    if a[a == 0].size != 1:
        a = a.prod() / a # product except current
    else:
        # exaclty one non-zero-valued element in `a`
        nzi = a.nonzero() # indices of non-zero-valued elements
        a[a == 0] = a[nzi].prod()
        a[nzi] = 0

    return a

例子:

for input in ([2,3,4,5], [2,0,4,5], [0,3,0,5]):
    print product(input, [1,3,2,0])

输出:

[40 24 30 60]
[40  0  0  0]
[0 0 0 0]

回答

在Haskell中:

import Array

problem1 input index = [(left!i) * (right!(i+1)) | i <- index]
  where left  = scanWith scanl
        right = scanWith scanr
        scanWith scan = listArray (0, length input) (scan (*) 1 input)