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:
  - slug: greedy
    is_optimal: true

function_signature: "def best_hand(ranks: list[int], suits: list[str]) -> str:"

test_cases:
  visible:
    - input: { ranks: [13, 2, 3, 1, 9], suits: ["a", "a", "a", "a", "a"] }
      expected: "Flush"
    - input: { ranks: [4, 4, 2, 4, 4], suits: ["d", "a", "a", "b", "c"] }
      expected: "Three of a Kind"
    - input: { ranks: [10, 10, 2, 12, 9], suits: ["a", "b", "c", "a", "d"] }
      expected: "Pair"
  hidden:
    - input: { ranks: [1, 2, 3, 4, 5], suits: ["a", "b", "c", "d", "a"] }
      expected: "High Card"
    - input: { ranks: [3, 3, 3, 3, 5], suits: ["a", "b", "c", "d", "a"] }
      expected: "Three of a Kind"
    - input: { ranks: [1, 1, 2, 2, 3], suits: ["a", "b", "c", "d", "a"] }
      expected: "Pair"
    - input: { ranks: [7, 7, 7, 7, 7], suits: ["a", "a", "a", "a", "a"] }
      expected: "Flush"
    - input: { ranks: [13, 13, 13, 1, 1], suits: ["b", "c", "d", "a", "b"] }
      expected: "Three of a Kind"
    - input: { ranks: [2, 4, 6, 8, 10], suits: ["d", "d", "d", "d", "d"] }
      expected: "Flush"

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.
