前些天无意中看到一个判断数字的题目,很有意思。

主持人从印有数字2-9的卡片中挑出两张,然后把数字的和告诉甲,数字的积告诉乙,让他们猜测主持人选中的数字,有以下的对话:

甲:我猜不出来两个数字是多少

乙:我也猜不出来

甲:我现在能猜出来了

乙:我现在也能猜出来了

请问这两个数字是多少?

分析起来感觉有点吃力,不能一下子抓住关键的所在,于是就想用编程的方式引导以下自己的思维。

以下是全部程序

 

  1. import java.util.ArrayList; 
  2. import java.util.HashMap; 
  3. import java.util.List; 
  4. import java.util.Map; 
  5. import java.util.Map.Entry; 
  6.  
  7.  
  8. public class Iknow { 
  9.  
  10.     /** 
  11.      * 用来抽象主持人所选择的数对的组合 
  12.      */ 
  13.     public class Pair 
  14.     { 
  15.         int a, b; 
  16.         Pair(int a, int b) 
  17.         { 
  18.             this.a=a; 
  19.             this.b=b; 
  20.         } 
  21.          
  22.         public String toString() 
  23.         { 
  24.             return "("+a+","+b+")"; 
  25.         } 
  26.     } 
  27.      
  28.     /** 
  29.      * 用来抽象甲,乙二人得到结果的方法,一种是和,一种是积  
  30.      */ 
  31.     public enum Operation  
  32.     { 
  33.         SUM{ 
  34.             @Override 
  35.             public int apply(int a, int b) { 
  36.                 return a + b; 
  37.             } 
  38.         },  
  39.         PRODUCT{ 
  40.             @Override 
  41.             public int apply(int a, int b) { 
  42.                 return a * b; 
  43.             } 
  44.         }; 
  45.          
  46.         abstract int apply(int a, int b); 
  47.     } 
  48.      
  49.     /** 
  50.      * 每个和数所对应的可能的组合 
  51.      */ 
  52.     private Map<Integer, List<Pair>> hms = new HashMap<Integer, List<Pair>>(); 
  53.     /** 
  54.      * 每个积数所对应的可能的组合 
  55.      */ 
  56.     private Map<Integer, List<Pair>> hmm = new HashMap<Integer, List<Pair>>(); 
  57.      
  58.      
  59.     /** 
  60.      * 主程序 
  61.      */ 
  62.     public static void main(String[] args)  
  63.     { 
  64.         int min = 2
  65.         int max = 9
  66.  
  67.         String str ; 
  68.         if ((str = System.getProperty("min") ) != null) 
  69.         { 
  70.             try 
  71.             { 
  72.                 min = Integer.parseInt(str); 
  73.             } 
  74.             catch(NumberFormatException nfe) 
  75.             {    
  76.             } 
  77.         } 
  78.         if ((str = System.getProperty("max") ) != null) 
  79.         { 
  80.             try 
  81.             { 
  82.                 max = Integer.parseInt(str); 
  83.             } 
  84.             catch(NumberFormatException nfe) 
  85.             {    
  86.             } 
  87.         } 
  88.          
  89.         Iknow iknow = new Iknow(); 
  90.  
  91.         iknow.init(min, max); 
  92.          
  93.         iknow.kickSingleAnswer(); 
  94.          
  95.         iknow.pickupKnows(); 
  96.          
  97.     } 
  98.  
  99.     /** 
  100.      * 初始化所有组合  
  101.      * @param min 
  102.      * @param max 
  103.      */ 
  104.     public void init(int min, int max) 
  105.     { 
  106.         for (int i=min ; i<max; i++) 
  107.         { 
  108.             for(int j = i+1; j<=max;j++) 
  109.             { 
  110.                 Pair pair = new Pair(i,j); 
  111.                 addValue(Operation.SUM.apply(i, j), pair, hms); 
  112.                 addValue(Operation.PRODUCT.apply(i, j), pair, hmm); 
  113.             } 
  114.         } 
  115.     } 
  116.  
  117.     private static <A,B> void addValue(A key, B value, Map<A, List<B>> hm) 
  118.     { 
  119.         List<B> p = hm.get(key); 
  120.         if (p == null) 
  121.         { 
  122.             p = new ArrayList<B>(); 
  123.             hm.put(key, p); 
  124.         } 
  125.         p.add(value); 
  126.     } 
  127.  
  128.     /** 
  129.      * 剔除能立即得出答案的组合 
  130.      */ 
  131.     public void kickSingleAnswer() 
  132.     { 
  133.         kickSingleAnswer(hms); 
  134.         kickSingleAnswer(hmm); 
  135.     } 
  136.      
  137.     private static <A,B> void kickSingleAnswer(Map<A, List<B>> hm) 
  138.     { 
  139.         List<A> la = new ArrayList<A>(); 
  140.         for (Entry<A, List<B>> entrys: hm.entrySet()) 
  141.         { 
  142.             if (entrys.getValue().size() == 1) 
  143.             { 
  144.                 la.add(entrys.getKey()); 
  145.             } 
  146.         } 
  147.         for (A a: la) 
  148.         { 
  149.             hm.remove(a); 
  150.         } 
  151.         System.out.println("剔除 " + la +", 我们剩下 "  + hm); 
  152.     } 
  153.      
  154.     /** 
  155.      * 找出能得到答案的组合 
  156.      */ 
  157.     public void pickupKnows() 
  158.     { 
  159.         pickupKnows(hms, hmm, Operation.PRODUCT); 
  160.         pickupKnows(hmm, hms, Operation.SUM); 
  161.     } 
  162.      
  163.      
  164.     private static void pickupKnows(Map<Integer, List<Pair>> hm1, Map<Integer, List<Pair>>hm2, Operation p) 
  165.     { 
  166.         List<Integer> removeKeys = new ArrayList<Integer>(); 
  167.         for (Entry<Integer, List<Pair>> entrys: hm1.entrySet()) 
  168.         { 
  169.             List<Pair> values = entrys.getValue(); 
  170.             Pair onlyPosible = null
  171.             for (Pair pair: values) 
  172.             { 
  173.                 boolean contains = hm2.containsKey(p.apply(pair.a, pair.b)); 
  174.                 if (onlyPosible == null && contains) 
  175.                 { 
  176.                     onlyPosible = pair
  177.                 } 
  178.                 else if (onlyPosible != null && contains) 
  179.                 { 
  180.                     onlyPosible = null
  181.                     break; 
  182.                 } 
  183.             } 
  184.             if (onlyPosible == null) 
  185.             { 
  186.                 removeKeys.add(entrys.getKey()); 
  187.             } 
  188.             else 
  189.             { 
  190.                 values.clear(); 
  191.                 values.add(onlyPosible); 
  192.             } 
  193.         } 
  194.         for (Integer a: removeKeys) 
  195.         { 
  196.             hm1.remove(a); 
  197.         } 
  198.  
  199.         System.out.println(removeKeys + " 这些有多个答案是不合理的.\n最后我们有 "  + hm1); 
  200.          
  201.     } 
  202.      
  203.      
  204.  

程序中,用enum型模拟了和与积的操作,用Map模拟了甲、乙两个人得到的结果所对应的多种可能。通过程序的执行,把不合理的结果一步步剔除掉,最终得出可能的结果。

实际执行的第一步,把所有可能填充到Map中,作为基础数据。

根据对话的前两句,甲乙都不能独立猜出答案,所以去除Map中仅有唯一数对的组合。根据程序执行的结果我们发现,甲只是把最大最小两头的和数排除了4个,而乙却真是排除了大部分的乘积,只有三个乘积12,18,24是可能的。

根据第三句,我们把甲的每个可能的和数所对应的数对组合都要梳理一下。因为甲已经可能猜出数对,所以,每个和所对应的数对只有一个是合理的,其他的都要能排除掉。这样,就要把每个数对根据乙现在剩下的合理的乘积进行判断,也就是说数对乘积要是12,18,24的一种。于是我们又排除了5个和数的可能,只有7,8,9,10是可能的和数,而他们每个和数仅对应一个合理的数对,因为甲现在根据和数能猜出数对来。

根据第四句,也就是乙的最后一句,在剩下的乘积的Map中,每个乘积也只能有一个合理的数对。这时我们发现,12不幸的对应(2,6)和(3,4)两个数对。也就是说,如果乙拿着的乘积是12,他没办法猜出数对的组合是哪个来。

最后幸存的数对是(3,6)(4,6),也就是说,最初是这两个数字的话,甲和乙最后都能猜出原来的数对是什么。所以答案不是唯一的。

 


也许有人注意了我把2和9参数化了,也就是说我们可以在更大的范围上来找出可能的答案。比如,最初是从2到99,一共98张卡片的话,会是怎么样。我们只需要用一下命令实行

cmd> java -Dmax=99 Iknow

最后得到的数对是 {24=[(4,6)], 6624=[(69,96)], 7200=[(80,90)]}。

值得注意的是(3,6)淡出了我们的视线,揪其原因,是第三步甲再判断的时候,如果他的和是9,他有(3,6)和(4,5)两种可能,而在前面最大卡片数是9的情况下,(4,5)并不是一个合理的数对;而在最大数字是99的情况下,(4,5)合理了,因为(2,10)也可以得到20,保证了它不会在第二步被排除掉。