【模拟】德州扑克(许老师题解)
【模拟】德州扑克(许老师题解)
题目描述与示例
本题练习地址:https://www.algomooc.com/problem/P2496
题目描述
五张牌,每张牌由牌大小和花色组成,牌大小2~10、J、Q、K、A,牌花色为红桃、黑桃、梅花、方块四种花色之一。
判断牌型:
牌型1,同花顺:同一花色的顺子,如红桃2红桃3红桃4红桃5红桃6。
牌型2,四条:四张相同数字 + 单张,如红桃A黑桃A梅花A方块A + 黑桃K。
牌型3,葫芦:三张相同数字 + 一对,如红桃5黑桃5梅花5 + 方块9梅花9。
牌型4,同花:同一花色,如方块3方块7方块10方块J方块Q。
牌型5,顺子:花色不一样的顺子,如红桃2黑桃3红桃4红桃5方块6。
牌型6,三条:三张相同+两张单。
说明:
(1)五张牌里不会出现牌大小和花色完全相同的牌。
(2)编号小的牌型较大,如同花顺比四条大,依次类推。
(3)包含A的合法的顺子只有10 J Q K A和A 2 3 4 5;类似K A 2 3 4的序列不认为是顺子。
输入描述
输入由5
行组成,每行为一张牌大小和花色,牌大小为2~10、J、Q、K、A,花色分别用字符H、S、C、D表示红桃、黑桃、梅花、方块。
输出描述
输出牌型序号,5
张牌符合多种牌型时,取最大的牌型序号输出。
示例一
输入
4 H
5 S
6 C
7 D
8 D
输出
5
说明
4 5 6 7 8`构成顺子,输出`5
示例二
输入
9 S
5 S
6 S
7 S
8 S
输出
1
说明
既是顺子又是同花,输出1
,同花顺
解题思路
一道纯模拟题,思路简单但是代码极其冗长,非常考验基础语法。
题目整体分析
有几个点需要注意的:
- 输入有且仅有
5
行,不用像【哈希表】2024E-斗地主之顺子那样,考虑可能出现更长顺子的情况 - 虽然在实际的德州扑克游戏中还有两对、一对、高牌等牌型,但本题只给出了
6
种牌型,我们就认为题目的输入一定会是这六种牌型之一,否则其他牌型不知道应该输出什么内容。 - 题目说明了包含
A
的合法的顺子只有10 J Q K A
和A 2 3 4 5
,所以需要额外判断顺子的合法性。 - 题目输出的要求是:当
5
张牌符合多种牌型时,取最大的牌型序号输出。所以我们在判断牌型的时候,必须严格按照上述6
种牌型的顺序从上往下考虑,一旦成立为某种牌型,则提前退出。
做完上述分析后,我们对这道题就有一些比较讨巧的做法了。
数字和花色,我们分别可以用哈希表cnt_num
和cnt_color
来储存。
我们可以查看这五张牌一共存在几种数字。当
- 数字种类是
5
种,那么必然是顺子或同花(以及同花顺) - 数字种类是
3
种,那么必然是三张 - 数字种类是
2
种,那么必然是四条或葫芦
显然不可能出现数字种类是4
种或者是1
种的情况。
接下来我们逐个对6
种不同的牌型进行判断。
同花的分析
关于同花的分析非常简单,我们只需要看这5
张牌是否只有一种花色即可。
可以通过构建函数isFlush()
来判断
def isFlush(cnt_color):
return len(cnt_color) == 1
cnt_color
的构建为
cnt_color = Counter(colors)
顺子的分析
首先我们可以对输入的卡牌进行从字符串到数字的映射,构建如下get_card()
函数
def get_card(card):
if card == "J":
return 11
if card == "Q":
return 12
if card == "K":
return 13
if card == "A":
return 14
return int(card)
所以cnt_num
的构建为
cnt_num = Counter(get_card(card) for card in nums)
当我们的数字种类为5
时,如果这5
张牌的最大值减去最小值的结果为4
,则5
张牌为顺子。
但这种写法,判断无法判断"A2345"
的顺子情况。所以需要额外做一个判断
def isStraight(cnt_nums):
if len(cnt_nums) == 5:
if max(cnt_nums) - min(cnt_nums) == 4:
return True
if sorted(cnt_nums) == [2,3,4,5,14]:
return True
return False
或者更简洁的写在同一行
def isStraight(cnt_nums):
return len(cnt_nums) == 5 and (max(cnt_nums) - min(cnt_nums) == 4 or sorted(cnt_nums) == [2,3,4,5,14])
四条、葫芦、三条的分析
这几种分析是类似的,我们都需要根据cnt_num
中键值对的情况来做判断。构建出以下三个函数
def isFourOfAKind(cnt_num):
return len(cnt_num) == 2 and max(cnt_num.values()) == 4
def isHulu(cnt_num):
return len(cnt_num) == 2 and max(cnt_num.values()) == 3
def isThreeOfAKind(cnt_num):
return len(cnt_num) == 3 and max(cnt_num.values()) == 3
整体代码的构建
上述所有函数都讨论完之后,我们就可以编写多个判断语句来构建整体的代码了。
我们为了方便直接退出得到答案,我们构建solve()
函数
def solve(nums, colors):
cnt_num = Counter(get_card(card) for card in nums)
cnt_color = Counter(colors)
if isStraight(cnt_num) and isFlush(cnt_color):
return 1
if isFourOfAKind(cnt_num):
return 2
if isHulu(cnt_num):
return 3
if isFlush(cnt_color):
return 4
if isStraight(cnt_num):
return 5
if isThreeOfAKind(cnt_num):
return 6
return 0
至此题目基本完成。
代码
Python
# 题目:【模拟】德州扑克
# 分值:100
# 作者:许老师-闭着眼睛学数理化
# 算法:模拟/哈希表
# 代码看不懂的地方,请直接在群上提问
from collections import Counter
# 根据输入的卡牌字符串获得对应数字的函数
def get_card(card):
if card == "J":
return 11
if card == "Q":
return 12
if card == "K":
return 13
if card == "A":
return 14
return int(card)
# 判断是否为同花的函数
def isFlush(cnt_color):
return len(cnt_color) == 1
# 判断是否为顺子的函数
def isStraight(cnt_nums):
return len(cnt_nums) == 5 and (max(cnt_nums) - min(cnt_nums) == 4 or sorted(cnt_nums) == [2,3,4,5,14])
# 判断是否为四条的函数
def isFourOfAKind(cnt_num):
return len(cnt_num) == 2 and max(cnt_num.values()) == 4
# 判断是否为葫芦的函数
def isHulu(cnt_num):
return len(cnt_num) == 2 and max(cnt_num.values()) == 3
# 判断是否为三条的函数
def isThreeOfAKind(cnt_num):
return len(cnt_num) == 3 and max(cnt_num.values()) == 3
# 解决整个问题的主函数
def solve(nums, colors):
# 分别获得数字哈希表和颜色哈希表
cnt_num = Counter(get_card(card) for card in nums)
cnt_color = Counter(colors)
# 如果同时为顺子和同花,则属于同花顺,输出1
if isStraight(cnt_num) and isFlush(cnt_color):
return 1
if isFourOfAKind(cnt_num):
return 2
if isHulu(cnt_num):
return 3
if isFlush(cnt_color):
return 4
if isStraight(cnt_num):
return 5
if isThreeOfAKind(cnt_num):
return 6
# 题目没有说明,非上述6种牌型应该返回什么,这里返回0
# 如果实际考试出现问题,可以自行进行尝试和调整(譬如返回-1)
return 0
# 初始化两个空列表,用于储存这5张牌的数字和花色字符串
nums, colors = list(), list()
# 循环5次,进行输入
for _ in range(5):
num, color = input().split()
nums.append(num)
colors.append(color)
# 调用上述的solve()函数,输出结果
print(solve(nums, colors))
Java
import java.util.*;
public class Main {
// 根据输入的卡牌字符串获得对应数字的函数
public static int getCard(String card) {
switch (card) {
case "J": return 11;
case "Q": return 12;
case "K": return 13;
case "A": return 14;
default: return Integer.parseInt(card);
}
}
// 判断是否为同花的函数
public static boolean isFlush(Map<String, Integer> cntColor) {
return cntColor.size() == 1;
}
// 判断是否为顺子的函数
public static boolean isStraight(Set<Integer> cntNums) {
if (cntNums.size() != 5) return false;
List<Integer> numsList = new ArrayList<>(cntNums);
Collections.sort(numsList);
return (numsList.get(4) - numsList.get(0) == 4) ||
numsList.equals(Arrays.asList(2, 3, 4, 5, 14));
}
// 判断是否为四条的函数
public static boolean isFourOfAKind(Map<Integer, Integer> cntNum) {
return cntNum.size() == 2 && cntNum.containsValue(4);
}
// 判断是否为葫芦的函数
public static boolean isHulu(Map<Integer, Integer> cntNum) {
return cntNum.size() == 2 && cntNum.containsValue(3);
}
// 判断是否为三条的函数
public static boolean isThreeOfAKind(Map<Integer, Integer> cntNum) {
return cntNum.size() == 3 && cntNum.containsValue(3);
}
// 解决整个问题的主函数
public static int solve(List<String> nums, List<String> colors) {
Map<Integer, Integer> cntNum = new HashMap<>();
Map<String, Integer> cntColor = new HashMap<>();
// 统计数字和花色
for (String num : nums) {
int cardValue = getCard(num);
cntNum.put(cardValue, cntNum.getOrDefault(cardValue, 0) + 1);
}
for (String color : colors) {
cntColor.put(color, cntColor.getOrDefault(color, 0) + 1);
}
Set<Integer> cntNums = cntNum.keySet();
if (isStraight(cntNums) && isFlush(cntColor)) return 1;
if (isFourOfAKind(cntNum)) return 2;
if (isHulu(cntNum)) return 3;
if (isFlush(cntColor)) return 4;
if (isStraight(cntNums)) return 5;
if (isThreeOfAKind(cntNum)) return 6;
return 0;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
List<String> nums = new ArrayList<>();
List<String> colors = new ArrayList<>();
// 输入5张牌
for (int i = 0; i < 5; i++) {
String num = scanner.next();
String color = scanner.next();
nums.add(num);
colors.add(color);
}
// 调用solve函数并输出结果
System.out.println(solve(nums, colors));
}
}
C++
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <string>
using namespace std;
// 根据输入的卡牌字符串获得对应数字的函数
int getCard(string card) {
if (card == "J") return 11; // J 表示 11
if (card == "Q") return 12; // Q 表示 12
if (card == "K") return 13; // K 表示 13
if (card == "A") return 14; // A 表示 14
return stoi(card); // 其余直接转换为整数
}
// 判断是否为同花的函数
bool isFlush(map<string, int>& cntColor) {
// 如果所有卡牌只有一种花色,说明是同花
return cntColor.size() == 1;
}
// 判断是否为顺子的函数
bool isStraight(map<int, int>& cntNums) {
vector<int> nums;
for (auto& pair : cntNums) {
nums.push_back(pair.first); // 提取所有数字
}
sort(nums.begin(), nums.end()); // 将数字排序
// 满足条件1:长度为5,最大值和最小值差为4
// 满足条件2:特例 [2,3,4,5,14],即 A2345 顺子
return nums.size() == 5 &&
(nums.back() - nums.front() == 4 || nums == vector<int>{2, 3, 4, 5, 14});
}
// 判断是否为四条的函数
bool isFourOfAKind(map<int, int>& cntNum) {
for (auto& pair : cntNum) {
if (pair.second == 4) return true; // 如果某张牌出现4次,则为四条
}
return false;
}
// 判断是否为葫芦的函数
bool isHulu(map<int, int>& cntNum) {
bool hasThree = false, hasTwo = false;
for (auto& pair : cntNum) {
if (pair.second == 3) hasThree = true; // 如果有三条
if (pair.second == 2) hasTwo = true; // 如果有一对
}
return hasThree && hasTwo; // 同时满足三条和一对
}
// 判断是否为三条的函数
bool isThreeOfAKind(map<int, int>& cntNum) {
int threeCount = 0;
for (auto& pair : cntNum) {
if (pair.second == 3) threeCount++; // 如果有三条
}
// 如果有三条,且总共只有三种不同牌,则为三条
return threeCount == 1 && cntNum.size() == 3;
}
// 主函数:解决整个问题
int solve(vector<string>& nums, vector<string>& colors) {
map<int, int> cntNum; // 数字计数
map<string, int> cntColor; // 花色计数
// 统计数字的出现次数
for (auto& card : nums) {
int value = getCard(card); // 将卡牌转为对应数字
cntNum[value]++;
}
// 统计花色的出现次数
for (auto& color : colors) {
cntColor[color]++;
}
// 判断牌型
if (isStraight(cntNum) && isFlush(cntColor)) return 1; // 同花顺
if (isFourOfAKind(cntNum)) return 2; // 四条
if (isHulu(cntNum)) return 3; // 葫芦
if (isFlush(cntColor)) return 4; // 同花
if (isStraight(cntNum)) return 5; // 顺子
if (isThreeOfAKind(cntNum)) return 6; // 三条
return 0; // 其余情况返回 0
}
int main() {
vector<string> nums(5), colors(5); // 分别存储5张牌的数字和花色
// 输入处理
for (int i = 0; i < 5; i++) {
cin >> nums[i] >> colors[i]; // 输入每张牌的数字和花色
}
// 调用解决函数并输出结果
cout << solve(nums, colors) << endl;
return 0;
}
C
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NUM_CARDS 5
// 根据输入的卡牌字符串获得对应数字的函数
int get_card(char* card) {
if (strcmp(card, "J") == 0) return 11;
if (strcmp(card, "Q") == 0) return 12;
if (strcmp(card, "K") == 0) return 13;
if (strcmp(card, "A") == 0) return 14;
return atoi(card); // 如果是数字,直接转为整数返回
}
// 判断是否为同花的函数
int isFlush(int cnt_color[]) {
for (int i = 0; i < 4; i++) {
if (cnt_color[i] == NUM_CARDS) return 1;
}
return 0;
}
// 判断是否为顺子的函数
int isStraight(int cnt_nums[]) {
int first = -1, count = 0;
for (int i = 2; i <= 14; i++) {
if (cnt_nums[i]) {
count++;
if (first == -1) first = i;
if (count == NUM_CARDS && (i - first == 4)) return 1;
} else {
count = 0;
}
}
// 特殊情况:A, 2, 3, 4, 5
return cnt_nums[2] && cnt_nums[3] && cnt_nums[4] && cnt_nums[5] && cnt_nums[14];
}
// 判断是否为四条的函数
int isFourOfAKind(int cnt_nums[]) {
for (int i = 2; i <= 14; i++) {
if (cnt_nums[i] == 4) return 1;
}
return 0;
}
// 判断是否为葫芦的函数
int isHulu(int cnt_nums[]) {
int three_count = 0, two_count = 0;
for (int i = 2; i <= 14; i++) {
if (cnt_nums[i] == 3) three_count++;
if (cnt_nums[i] == 2) two_count++;
}
return three_count == 1 && two_count == 1;
}
// 判断是否为三条的函数
int isThreeOfAKind(int cnt_nums[]) {
for (int i = 2; i <= 14; i++) {
if (cnt_nums[i] == 3) return 1;
}
return 0;
}
// 主逻辑解决问题的函数
int solve(char nums[][3], char colors[][2]) {
int cnt_nums[15] = {0}; // 统计数字的频率
int cnt_color[4] = {0}; // 统计花色的频率
// 分别统计数字和花色
for (int i = 0; i < NUM_CARDS; i++) {
int card_value = get_card(nums[i]);
cnt_nums[card_value]++;
if (strcmp(colors[i], "C") == 0) cnt_color[0]++;
if (strcmp(colors[i], "D") == 0) cnt_color[1]++;
if (strcmp(colors[i], "H") == 0) cnt_color[2]++;
if (strcmp(colors[i], "S") == 0) cnt_color[3]++;
}
// 判断牌型并返回对应分值
if (isStraight(cnt_nums) && isFlush(cnt_color)) return 1; // 同花顺
if (isFourOfAKind(cnt_nums)) return 2; // 四条
if (isHulu(cnt_nums)) return 3; // 葫芦
if (isFlush(cnt_color)) return 4; // 同花
if (isStraight(cnt_nums)) return 5; // 顺子
if (isThreeOfAKind(cnt_nums)) return 6; // 三条
return 0; // 其他
}
int main() {
char nums[NUM_CARDS][3];
char colors[NUM_CARDS][2];
// 输入卡牌的数字和花色
for (int i = 0; i < NUM_CARDS; i++) {
scanf("%s %s", nums[i], colors[i]);
}
// 调用solve函数并输出结果
printf("%d\n", solve(nums, colors));
return 0;
}
Node JavaScript
const readline = require("readline");
// 根据输入的卡牌字符串获得对应数字的函数
function getCard(card) {
if (card === "J") return 11; // J 表示 11
if (card === "Q") return 12; // Q 表示 12
if (card === "K") return 13; // K 表示 13
if (card === "A") return 14; // A 表示 14
return parseInt(card); // 其余直接转换为整数
}
// 判断是否为同花的函数
function isFlush(cntColor) {
// 如果所有卡牌只有一种花色,说明是同花
return Object.keys(cntColor).length === 1;
}
// 判断是否为顺子的函数
function isStraight(cntNums) {
const nums = Object.keys(cntNums).map(Number).sort((a, b) => a - b); // 将所有数字排序
// 满足条件1:长度为5,最大值和最小值差为4
// 满足条件2:特例 [2,3,4,5,14],即 A2345 顺子
return nums.length === 5 && (nums[4] - nums[0] === 4 || nums.join(",") === "2,3,4,5,14");
}
// 判断是否为四条的函数
function isFourOfAKind(cntNum) {
return Object.values(cntNum).includes(4); // 如果某张牌出现4次,则为四条
}
// 判断是否为葫芦的函数
function isHulu(cntNum) {
const values = Object.values(cntNum);
// 同时存在三条和一对,则为葫芦
return values.includes(3) && values.includes(2);
}
// 判断是否为三条的函数
function isThreeOfAKind(cntNum) {
const values = Object.values(cntNum);
// 如果有三条,且总共只有三种不同牌,则为三条
return values.includes(3) && values.length === 3;
}
// 主函数:解决整个问题
function solve(nums, colors) {
const cntNum = {}; // 数字计数
const cntColor = {}; // 花色计数
// 统计数字的出现次数
nums.forEach(card => {
const value = getCard(card); // 将卡牌转为对应数字
cntNum[value] = (cntNum[value] || 0) + 1; // 累加次数
});
// 统计花色的出现次数
colors.forEach(color => {
cntColor[color] = (cntColor[color] || 0) + 1; // 累加次数
});
// 判断牌型
if (isStraight(cntNum) && isFlush(cntColor)) return 1; // 同花顺
if (isFourOfAKind(cntNum)) return 2; // 四条
if (isHulu(cntNum)) return 3; // 葫芦
if (isFlush(cntColor)) return 4; // 同花
if (isStraight(cntNum)) return 5; // 顺子
if (isThreeOfAKind(cntNum)) return 6; // 三条
return 0; // 其余情况返回 0
}
// 输入处理
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const nums = [];
const colors = [];
let count = 0;
// 读取输入
rl.on("line", (line) => {
const [num, color] = line.split(" "); // 解析每行输入
nums.push(num); // 添加卡牌数字
colors.push(color); // 添加卡牌花色
count++;
if (count === 5) { // 读取完 5 张牌后,输出结果并退出
console.log(solve(nums, colors));
rl.close();
}
});
Go
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
)
// 根据输入的卡牌字符串获得对应数字的函数
func getCard(card string) int {
switch card {
case "J":
return 11 // J 表示 11
case "Q":
return 12 // Q 表示 12
case "K":
return 13 // K 表示 13
case "A":
return 14 // A 表示 14
default:
value, _ := strconv.Atoi(card) // 其余直接转换为整数
return value
}
}
// 判断是否为同花的函数
func isFlush(cntColor map[string]int) bool {
// 如果所有卡牌只有一种花色,说明是同花
return len(cntColor) == 1
}
// 判断是否为顺子的函数
func isStraight(cntNums map[int]int) bool {
nums := []int{}
for num := range cntNums {
nums = append(nums, num) // 提取所有数字
}
sort.Ints(nums) // 将数字排序
// 满足条件1:长度为5,最大值和最小值差为4
// 满足条件2:特例 [2,3,4,5,14],即 A2345 顺子
return len(nums) == 5 && (nums[4]-nums[0] == 4 || fmt.Sprint(nums) == "[2 3 4 5 14]")
}
// 判断是否为四条的函数
func isFourOfAKind(cntNum map[int]int) bool {
for _, count := range cntNum {
if count == 4 { // 如果某张牌出现4次,则为四条
return true
}
}
return false
}
// 判断是否为葫芦的函数
func isHulu(cntNum map[int]int) bool {
hasThree, hasTwo := false, false
for _, count := range cntNum {
if count == 3 { // 如果有三条
hasThree = true
}
if count == 2 { // 如果有一对
hasTwo = true
}
}
return hasThree && hasTwo // 同时满足三条和一对
}
// 判断是否为三条的函数
func isThreeOfAKind(cntNum map[int]int) bool {
threeCount := 0
for _, count := range cntNum {
if count == 3 { // 如果有三条
threeCount++
}
}
// 如果有三条,且总共只有三种不同牌,则为三条
return threeCount == 1 && len(cntNum) == 3
}
// 主函数:解决整个问题
func solve(nums []string, colors []string) int {
cntNum := make(map[int]int) // 数字计数
cntColor := make(map[string]int) // 花色计数
// 统计数字的出现次数
for _, card := range nums {
value := getCard(card) // 将卡牌转为对应数字
cntNum[value]++
}
// 统计花色的出现次数
for _, color := range colors {
cntColor[color]++
}
// 判断牌型
if isStraight(cntNum) && isFlush(cntColor) {
return 1 // 同花顺
}
if isFourOfAKind(cntNum) {
return 2 // 四条
}
if isHulu(cntNum) {
return 3 // 葫芦
}
if isFlush(cntColor) {
return 4 // 同花
}
if isStraight(cntNum) {
return 5 // 顺子
}
if isThreeOfAKind(cntNum) {
return 6 // 三条
}
return 0 // 其余情况返回 0
}
func main() {
reader := bufio.NewReader(os.Stdin)
nums := []string{}
colors := []string{}
// 读取输入
for i := 0; i < 5; i++ {
line, _ := reader.ReadString('\n')
var num, color string
fmt.Sscanf(line, "%s %s", &num, &color) // 解析输入
nums = append(nums, num)
colors = append(colors, color)
}
fmt.Println(solve(nums, colors)) // 输出结果
}
时空复杂度
时间复杂度:O(1)
。
空间复杂度:O(1)
。