Files
codetutor/backend/data/questions/best-poker-hand.yaml

180 lines
8.0 KiB
YAML

title: Best Poker Hand
slug: best-poker-hand
difficulty: easy
leetcode_id: 2347
leetcode_url: https://leetcode.com/problems/best-poker-hand/
categories:
- arrays
- hash-tables
patterns:
- greedy
description: |
You are given an integer array `ranks` and a character array `suits`. You have `5` cards where the i<sup>th</sup> card has a rank of `ranks[i]` and a suit of `suits[i]`.
The following are the types of **poker hands** you can make from best to worst:
1. `"Flush"`: Five cards of the same suit.
2. `"Three of a Kind"`: Three cards of the same rank.
3. `"Pair"`: Two cards of the same rank.
4. `"High Card"`: Any single card.
Return *a string representing the **best** type of **poker hand** you can make with the given cards.*
**Note** that the return values are **case-sensitive**.
constraints: |
- `ranks.length == suits.length == 5`
- `1 <= ranks[i] <= 13`
- `'a' <= suits[i] <= 'd'`
- No two cards have the same rank and suit.
examples:
- input: 'ranks = [13,2,3,1,9], suits = ["a","a","a","a","a"]'
output: '"Flush"'
explanation: "The hand with all the cards consists of 5 cards with the same suit, so we have a \"Flush\"."
- input: 'ranks = [4,4,2,4,4], suits = ["d","a","a","b","c"]'
output: '"Three of a Kind"'
explanation: "The hand with the first, second, and fourth card consists of 3 cards with the same rank, so we have a \"Three of a Kind\". Note that we could also make a \"Pair\" hand but \"Three of a Kind\" is a better hand."
- input: 'ranks = [10,10,2,12,9], suits = ["a","b","c","a","d"]'
output: '"Pair"'
explanation: "The hand with the first and second card consists of 2 cards with the same rank, so we have a \"Pair\". Note that we cannot make a \"Flush\" or a \"Three of a Kind\"."
explanation:
intuition: |
Think of this problem like a poker player evaluating their hand. You're dealt 5 cards and need to identify the **best possible hand** you can claim.
The key insight is that the hand types are ordered from best to worst, so we should check for them **in that order**. As soon as we find a match, we can return immediately — no need to check lesser hands.
For a Flush, we need all 5 cards to share the same suit. This is easy to check: if there's only one unique suit among all cards, it's a Flush.
For Three of a Kind or Pair, we need to count how many times each rank appears. The **maximum frequency** of any rank tells us our best option:
- If any rank appears 3+ times → "Three of a Kind"
- If any rank appears exactly 2 times → "Pair"
- Otherwise → "High Card"
The problem is simplified by the fixed hand size of 5 cards, which means we can use simple counting with hash tables.
approach: |
We solve this using a **Priority Check with Counting** approach:
**Step 1: Check for Flush**
- Count the unique suits in the `suits` array
- If there's only 1 unique suit, all 5 cards match → return `"Flush"`
- We can use a set to find unique elements efficiently
&nbsp;
**Step 2: Count rank frequencies**
- Use a hash map (Counter/dictionary) to count occurrences of each rank
- Find the maximum frequency among all ranks
&nbsp;
**Step 3: Determine hand based on max frequency**
- If `max_count >= 3` → return `"Three of a Kind"`
- If `max_count == 2` → return `"Pair"`
- Otherwise → return `"High Card"`
&nbsp;
This greedy approach works because we check hands in order of priority. The moment we find a qualifying hand, we return it — guaranteeing we report the best possible hand.
common_pitfalls:
- title: Checking in Wrong Order
description: |
A common mistake is checking for Pair before Three of a Kind, or checking ranks before suits.
The problem states the hand types from best to worst. If you have four cards of the same rank (like `[4,4,4,4,2]`), that qualifies as both "Three of a Kind" and "Pair". You must return "Three of a Kind" because it's ranked higher.
Always check in priority order: Flush → Three of a Kind → Pair → High Card.
wrong_approach: "Check for Pair before Three of a Kind"
correct_approach: "Check hands in order of priority (best to worst)"
- title: Overcomplicating the Flush Check
description: |
Some solutions iterate through the suits array comparing elements when a simpler approach exists.
Since we have exactly 5 cards and 5 suits, checking if all suits are the same is equivalent to checking if there's only 1 unique suit. Using `len(set(suits)) == 1` is cleaner and more Pythonic.
wrong_approach: "Loop comparing suits[i] == suits[0] for all i"
correct_approach: "Use len(set(suits)) == 1"
- title: Missing the Four of a Kind Case
description: |
The problem doesn't list "Four of a Kind" as a separate hand type. When you have 4 cards of the same rank, it still only counts as "Three of a Kind" per the problem definition.
Don't add extra logic for four-of-a-kind — the `max_count >= 3` check handles it correctly by returning "Three of a Kind".
key_takeaways:
- "**Priority-based evaluation**: When categorising into ranked tiers, check from best to worst and return on first match"
- "**Hash tables for counting**: `Counter` or dictionaries make frequency counting trivial — finding max frequency is O(n)"
- "**Sets for uniqueness**: Converting to a set instantly tells you how many unique elements exist"
- "**Fixed input size**: With only 5 cards, even 'inefficient' approaches are fast — but clean code still matters"
time_complexity: "O(1). We process exactly 5 cards, making this effectively constant time regardless of implementation details."
space_complexity: "O(1). The hash map stores at most 5 entries (one per card), and the set stores at most 4 suits — both bounded by constants."
solutions:
- approach_name: Priority Check with Counting
is_optimal: true
code: |
from collections import Counter
def best_hand(ranks: list[int], suits: list[str]) -> str:
# Check for Flush: all 5 cards same suit
if len(set(suits)) == 1:
return "Flush"
# Count frequency of each rank
rank_counts = Counter(ranks)
max_count = max(rank_counts.values())
# Check for Three of a Kind (3 or more of same rank)
if max_count >= 3:
return "Three of a Kind"
# Check for Pair (exactly 2 of same rank)
if max_count == 2:
return "Pair"
# Default: High Card
return "High Card"
explanation: |
**Time Complexity:** O(1) — We always process exactly 5 cards.
**Space Complexity:** O(1) — Counter and set are bounded by the fixed hand size.
We check for each hand type in order of priority. The Flush check uses a set for O(1) uniqueness counting. The Counter gives us rank frequencies, and we only need the maximum to determine Three of a Kind vs Pair vs High Card.
- approach_name: Manual Counting (No Imports)
is_optimal: false
code: |
def best_hand(ranks: list[int], suits: list[str]) -> str:
# Check for Flush: compare all suits to the first
is_flush = all(s == suits[0] for s in suits)
if is_flush:
return "Flush"
# Count rank frequencies using a dictionary
rank_counts = {}
for rank in ranks:
rank_counts[rank] = rank_counts.get(rank, 0) + 1
# Find max frequency
max_count = max(rank_counts.values())
if max_count >= 3:
return "Three of a Kind"
if max_count == 2:
return "Pair"
return "High Card"
explanation: |
**Time Complexity:** O(1) — Fixed 5-card input.
**Space Complexity:** O(1) — Dictionary bounded by hand size.
This version avoids importing `Counter` by manually building a frequency dictionary. The `all()` function provides a readable Flush check. Functionally equivalent to the optimal solution, just more verbose.