前些天无意中看到一个判断数字的题目,很有意思。
主持人从印有数字2-9的卡片中挑出两张,然后把数字的和告诉甲,数字的积告诉乙,让他们猜测主持人选中的数字,有以下的对话:
甲:我猜不出来两个数字是多少
乙:我也猜不出来
甲:我现在能猜出来了
乙:我现在也能猜出来了
请问这两个数字是多少?
分析起来感觉有点吃力,不能一下子抓住关键的所在,于是就想用编程的方式引导以下自己的思维。
以下是全部程序
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Map.Entry;
- public class Iknow {
- /**
- * 用来抽象主持人所选择的数对的组合
- */
- public class Pair
- {
- int a, b;
- Pair(int a, int b)
- {
- this.a=a;
- this.b=b;
- }
- public String toString()
- {
- return "("+a+","+b+")";
- }
- }
- /**
- * 用来抽象甲,乙二人得到结果的方法,一种是和,一种是积
- */
- public enum Operation
- {
- SUM{
- @Override
- public int apply(int a, int b) {
- return a + b;
- }
- },
- PRODUCT{
- @Override
- public int apply(int a, int b) {
- return a * b;
- }
- };
- abstract int apply(int a, int b);
- }
- /**
- * 每个和数所对应的可能的组合
- */
- private Map<Integer, List<Pair>> hms = new HashMap<Integer, List<Pair>>();
- /**
- * 每个积数所对应的可能的组合
- */
- private Map<Integer, List<Pair>> hmm = new HashMap<Integer, List<Pair>>();
- /**
- * 主程序
- */
- public static void main(String[] args)
- {
- int min = 2;
- int max = 9;
- String str ;
- if ((str = System.getProperty("min") ) != null)
- {
- try
- {
- min = Integer.parseInt(str);
- }
- catch(NumberFormatException nfe)
- {
- }
- }
- if ((str = System.getProperty("max") ) != null)
- {
- try
- {
- max = Integer.parseInt(str);
- }
- catch(NumberFormatException nfe)
- {
- }
- }
- Iknow iknow = new Iknow();
- iknow.init(min, max);
- iknow.kickSingleAnswer();
- iknow.pickupKnows();
- }
- /**
- * 初始化所有组合
- * @param min
- * @param max
- */
- public void init(int min, int max)
- {
- for (int i=min ; i<max; i++)
- {
- for(int j = i+1; j<=max;j++)
- {
- Pair pair = new Pair(i,j);
- addValue(Operation.SUM.apply(i, j), pair, hms);
- addValue(Operation.PRODUCT.apply(i, j), pair, hmm);
- }
- }
- }
- private static <A,B> void addValue(A key, B value, Map<A, List<B>> hm)
- {
- List<B> p = hm.get(key);
- if (p == null)
- {
- p = new ArrayList<B>();
- hm.put(key, p);
- }
- p.add(value);
- }
- /**
- * 剔除能立即得出答案的组合
- */
- public void kickSingleAnswer()
- {
- kickSingleAnswer(hms);
- kickSingleAnswer(hmm);
- }
- private static <A,B> void kickSingleAnswer(Map<A, List<B>> hm)
- {
- List<A> la = new ArrayList<A>();
- for (Entry<A, List<B>> entrys: hm.entrySet())
- {
- if (entrys.getValue().size() == 1)
- {
- la.add(entrys.getKey());
- }
- }
- for (A a: la)
- {
- hm.remove(a);
- }
- System.out.println("剔除 " + la +", 我们剩下 " + hm);
- }
- /**
- * 找出能得到答案的组合
- */
- public void pickupKnows()
- {
- pickupKnows(hms, hmm, Operation.PRODUCT);
- pickupKnows(hmm, hms, Operation.SUM);
- }
- private static void pickupKnows(Map<Integer, List<Pair>> hm1, Map<Integer, List<Pair>>hm2, Operation p)
- {
- List<Integer> removeKeys = new ArrayList<Integer>();
- for (Entry<Integer, List<Pair>> entrys: hm1.entrySet())
- {
- List<Pair> values = entrys.getValue();
- Pair onlyPosible = null;
- for (Pair pair: values)
- {
- boolean contains = hm2.containsKey(p.apply(pair.a, pair.b));
- if (onlyPosible == null && contains)
- {
- onlyPosible = pair;
- }
- else if (onlyPosible != null && contains)
- {
- onlyPosible = null;
- break;
- }
- }
- if (onlyPosible == null)
- {
- removeKeys.add(entrys.getKey());
- }
- else
- {
- values.clear();
- values.add(onlyPosible);
- }
- }
- for (Integer a: removeKeys)
- {
- hm1.remove(a);
- }
- System.out.println(removeKeys + " 这些有多个答案是不合理的.\n最后我们有 " + hm1);
- }
- }
程序中,用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,保证了它不会在第二步被排除掉。
本文出自 “哈维” 博客,谢绝转载!