【问题标题】:shuffle card deck issues in language agnostic [closed]语言不可知的洗牌卡组问题[关闭]
【发布时间】:2010-09-08 13:05:04
【问题描述】:

不久前,我在接受采访,这需要解决两个非常有趣的问题。我很好奇您将如何处理解决方案。

问题 1:

除当前之外的一切的产物

编写一个函数,将两个长度为 len、input 和 index 的整数数组作为输入,并生成第三个数组 result,这样: result[i] = input 中除 input[index[i]] 之外的所有内容的乘积

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

重要提示:您的算法必须在线性时间内运行。

问题 2:(主题在 Jeff 的一篇帖子中)

均匀洗牌

  1. 设计(在 C++ 或 C# 中)一个 Deck 类来表示有序的一副牌,其中一副牌包含 52 张牌,分为 13 个等级(A、2、3、4、5、6、 7, 8, 9, 10, J, Q, K) 四种花色:黑桃 (?)、红心 (?)、方块 (?) 和梅花 (?)。

  2. 基于这个类,设计并实现一种高效的算法来洗牌。牌必须均匀洗牌,也就是说,原始牌组中的每张牌必须有相同的概率最终出现在洗牌牌组中的任何可能位置。 该算法应该在类 Deck 的方法 shuffle() 中实现: 无效洗牌()

  3. 您的算法的复杂度是多少(作为一副牌中卡片数量 n 的函数)?

  4. 说明您将如何测试卡片是否被您的方法均匀洗牌(黑盒测试)。

附:我有两个小时来编写解决方案

【问题讨论】:

    标签: language-agnostic


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

      Vaibhav,不幸的是,我们不得不假设输入表中可能有一个 0。

      【讨论】:

        【解决方案3】:

        第一个问题:

        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 个向量一次。

        【讨论】:

        • 嗯,最后一个else(没有零)应该没有,retval[indexcounter] = product/values[index[i]]?
        • 呸,我的意思是根据上述评论的这个程序的“索引”,而不是“索引”。
        【解决方案4】:

        C 中除当前所有内容的乘积

        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
        }
        

        【讨论】:

          【解决方案5】:

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

          我没有看到任何其他方法可以在线性时间内做到这一点。有人吗?因为招聘经理告诉我,这个解决方案不够强大。

          我们是否遗漏了一些超级复杂的,在一条返回线上完成所有事情的解决方案?

          【讨论】:

          • YXJuLnphcnQ 具有几乎相同的算法,但他用 C 语言做到了这一切。他所做的可以说更快的是在同一个循环中计算零和乘积。
          • 你们两个在数组上迭代了一个额外的时间来计算零……我写的 YXJuLnphcnQ 基本上是一样的,但是在 python 中,因为已经有三个解决方案,所以懒得发布它跨度>
          • 另外,线性时间,即 O(n),与单程不同。
          • 或者我们中没有一个人足够强大,无法在那家公司工作,这是一个非常现实的可能性
          • 不,o(n) 与单次传递不同。呃,如果我能用 300 个字符解释大 O 微积分,我会很富有。让我侥幸逃脱 - 不,不是。
          【解决方案6】:

          第二个问题。

              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;
                  }
              }
          

          这是来自维基百科关于Fisher-Yates shuffle 的文章的复制/粘贴。 O(n) 复杂度

          【讨论】:

            【解决方案7】:

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

            您是对的,计算为零,并且同一循环中的产品更好。也许就是这样。

            【讨论】:

            • 速度更快。可能不会快多少,但仍然如此。我仍然会像我一样写它,因为我相信它更清楚
            【解决方案8】:

            Tnilsson,我还使用了 Fisher-Yates shuffle :)。我很感兴趣面团,关于测试部分:)

            【讨论】:

            • 嗯。测试...我需要考虑一下。您不能仅通过洗牌一次或两次进行测试,它需要花费大量时间(而且我准备声称可以在一次洗牌后完成的人不理解随机性)
            • 我“偷”了这部分问题,并在beta.stackoverflow.com/questions/56411/… 提出了一个新问题。这确实是一个棘手的问题。希望你不要介意
            【解决方案9】:

            在 C++ 中均匀洗牌

            #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;    
              }
            

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

            【讨论】:

              【解决方案10】:

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

              How to test randomness (case in point - Shuffling)

              特里尔森的好主意:)

              【讨论】:

                【解决方案11】:

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

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

                【讨论】:

                • 我同意一项测试是不够的。需要额外的测试。一次测试总比没有好。
                【解决方案12】:

                第一个问题在 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 分钟,所以我感觉还不错:-)

                【讨论】:

                  【解决方案13】:

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

                  编辑:在查看了 Fisher-Yates shuffle 之后,我发现我在不知情的情况下重新发明了该算法 :-) 然而这很明显。我实现了 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);
                      }
                  }
                  

                  【讨论】:

                  • 在那次洗牌中,每张牌都可能原地不动吗?这可能是您的解决方案的问题,例如那张牌[51] 将被移动并且永远无法落回,因此它有可能最终出现在牌组的任何位置。
                  【解决方案14】:

                  在 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)
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-06-22
                    • 1970-01-01
                    相关资源
                    最近更新 更多