题目
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;
}
总结
没思路的时候,不妨排个序,对特例的情况下寻找答案。