剑指Offer-47-扑克牌顺子

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dawn_after_dark/article/details/81917168

题目

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…..LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

解析

预备知识

题目意思其实就是给定5个数,其中最多有4个0,而0可以表示任意数字,问这5个数可否组成连续序列。 比如0,3,2,4,6,若0取5,则表明可以组成有序序列。

思路一

题目数字很少才5个,所以如果采用暴力的深搜也是可以的。思路为从数组的第一个数字开始遍历,若该位为0,则遍历取值为所有的未出现的数字,然后在此基础,继续考察后序序列,对于每一位数字都这样处理,直到遍历完毕。我们考察此次得到的数组是否可以为顺子,这里我们不采用排序,而是利用连续序列的2个特性:
1. 最大值减去最小值为4
2. 序列的和应等于(max + min) * 2 + (max + min) / 2

若此次合法,则返回true,回溯时若得知已成功,则不断回溯给上层结果,最终结束。如果此次不成功,则回溯给上层,开始下一轮的取值。我们注意到各个0的取值通常是一种组合的形式,故不存在顺序问题。所以我们可以在深搜时带上上一次使用的值,之后的递归考察该数之后的值即可,不必从1开始。因为如果假设,两个0取2,4 不合法,那么取4,2肯定也不合法。所以我们只需按序来取值即可保证所有的组合。

    /**
     * dfs做法
     * @param numbers
     * @return
     */
    public static boolean isContinuous(int [] numbers) {
        if(numbers == null || numbers.length < 5) {
            return false;
        }
        boolean[] visited = new boolean[16];
        for(int i = 0; i < numbers.length; i++) {
            visited[numbers[i]] = true;
        }
        return dfs(numbers, 0, visited, 0);
    }

    public static boolean dfs(int[] numbers, int index, boolean[] visited, int previous) {
        if(index == 5) {
            if (isContinuousHelper(numbers)) {
                return true;
            }
            return false;
        }
        //如果为0,这里就遍历取值考察
        if(numbers[index] == 0) {
            for (int i = previous + 1; i < 16; i++) {
                if(!visited[i]) {
                    numbers[index] = i;
                    if (dfs(numbers, index + 1, visited, i)) {
                        return true;
                    }
                }
            }
            return false;
        } else {
            //如果是固定的值,直接跳过
            return dfs(numbers, index + 1, visited, previous);
        }
    }

    public static boolean isContinuousHelper(int[] numbers) {
        int min = findMin(numbers);
        int max = findMax(numbers);
        int actualSum = 0;
        for(int i = 0; i < numbers.length; i++) {
            actualSum += numbers[i];
        }
        int targetSum = (min + max) * 2 + (min + max) / 2;
        return findMax(numbers) - findMin(numbers) == 4 && actualSum == targetSum;
    }

    public static int findMin(int[] numbers) {
        int result = 16;
        for(int item : numbers) {
            result = Math.min(result, item);
        }
        return result;
    }

    public static int findMax(int[] numbers) {
        int result = 0;
        for(int item : numbers) {
            result = Math.max(result, item);
        }
        return result;
    }

思路二

我们观察这样的数列,3,6,0,0,7。如果我们对他排序,得到0,0,3,6,7。对于这样的序列,如果想要连续,那么6与3之间要插入2个数才行,而此时我们正好有2个0,所以可以补上去。7与6连续,故而此序列合法。对于0,7,0,0,0这样的序列,排序后为0,0,0,0,7,只有7一个,因此我们可以随意向左补充或向右补充即可。由此可以得出结论:只要0出现的次数能够填充排序后的序列的所有间隔,那么就可以组成顺子。

    public static boolean isContinuous2(int [] numbers) {
        if(numbers == null || numbers.length < 5) {
            return false;
        }
        int zeroCount = 0;
        Arrays.sort(numbers);
        for(int i = 0; i < numbers.length - 1; i++) {
            //统计0的个数
            if (numbers[i] == 0) {
                zeroCount++;
            } else if(numbers[i] == numbers[i + 1]) {
                //不为0的两个数间隔为0,说明是一个对子,不满足顺子的要求
                return false;
            } else {
                //拿0的个数补充序列中出现的间隔
                zeroCount -= (numbers[i + 1] - numbers[i] - 1);
            }
        }
        //最后若间隔不小于0,那么说明可以组成顺子
        return zeroCount >= 0;
    }

总结

没思路的时候,不妨排个序,对特例的情况下寻找答案。

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/81917168