Best Poker Hand
EasyPrompt
You are given an integer array ranks and a character array suits. You have 5 cards where the ith 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:
"Flush": Five cards of the same suit."Three of a Kind": Three cards of the same rank."Pair": Two cards of the same rank."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.
Example 1:
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".Example 2:
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.
Also note that other cards could be used to make the "Three of a Kind" hand.Example 3:
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".
Constraints:
ranks.length == suits.length == 51 <= ranks[i] <= 13'a' <= suits[i] <= 'd'- No two cards have the same rank and suit.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach directly translates the rules into code using standard Java collections. It first checks for a "Flush" by seeing if all suits are the same. A HashSet is a natural choice for this; if all 5 suits are added to a set and the final size is 1, it's a flush. If it's not a flush, it then checks for rank-based hands ("Three of a Kind", "Pair"). A HashMap is used to count the occurrences of each rank. By iterating through the counts in the map, we can determine if there's a group of 3 or 2. The checks are performed in order of hand strength to ensure the best hand is always returned.
Algorithm
- Check for Flush:
- Create a
HashSet<Character>. - Iterate through the
suitsarray and add each suit to the set. - If the size of the set is 1, it's a flush. Return
"Flush".
- Create a
- Count Ranks:
- If it's not a flush, create a
HashMap<Integer, Integer>to store the frequency of each card rank. - Iterate through the
ranksarray, and for each rank, update its count in the map.
- If it's not a flush, create a
- Check for Three of a Kind or Pair:
- Initialize a boolean flag,
hasPair, tofalse. - Iterate through the values (counts) of the
HashMap.- If any count is 3 or greater, we have found a "Three of a Kind". Return
"Three of a Kind". - If any count is 2, we have found a pair. Set
hasPair = true.
- If any count is 3 or greater, we have found a "Three of a Kind". Return
- Initialize a boolean flag,
- Determine Final Hand:
- After checking all rank counts, if the
hasPairflag istrue, return"Pair".
- After checking all rank counts, if the
- Default to High Card:
- If the function has not returned yet, return
"High Card".
- If the function has not returned yet, return
Walkthrough
The logic is broken down into sequential checks based on the hierarchy of poker hands.
- Flush Check: We use a
HashSetto efficiently determine if all suits are identical. Adding all five suits to a set will result in a set of size 1 if and only if they are all the same. - Rank Counting: If the hand is not a flush, we need to analyze the ranks. A
HashMapis used to count the frequency of each rank. We iterate through theranksarray and store the counts in the map. - Hand Evaluation: We then iterate through the frequency counts stored in the map's values. Since we must return the best hand, we check for "Three of a Kind" first. If any rank appears 3 or more times, we immediately return. If not, we check for a "Pair". A boolean flag
hasPairtracks if we've seen any rank appear twice. - Final Result: After checking all rank frequencies, if we found a pair (and not a three of a kind), we return "Pair". If neither of these conditions is met, the hand is a "High Card".
import java.util.HashMap;import java.util.HashSet;import java.util.Map;import java.util.Set; class Solution { public String bestHand(int[] ranks, char[] suits) { // 1. Check for Flush Set<Character> suitSet = new HashSet<>(); for (char suit : suits) { suitSet.add(suit); } if (suitSet.size() == 1) { return "Flush"; } // 2. Count Ranks Map<Integer, Integer> rankCounts = new HashMap<>(); for (int rank : ranks) { rankCounts.put(rank, rankCounts.getOrDefault(rank, 0) + 1); } // 3. Check for Three of a Kind or Pair boolean hasPair = false; for (int count : rankCounts.values()) { if (count >= 3) { return "Three of a Kind"; } if (count == 2) { hasPair = true; } } // 4. Determine Final Hand if (hasPair) { return "Pair"; } // 5. Default to High Card return "High Card"; }}Complexity
Time
O(1) - The number of cards is fixed at 5. All loops run a constant number of times.
Space
O(1) - The `HashSet` and `HashMap` will store at most 5 elements, as the input size is fixed.
Trade-offs
Pros
The code is highly readable and directly maps to the problem's logic.
It's a general approach that would work even if the range of ranks was large or non-contiguous.
Cons
HashMapandHashSethave some performance overhead compared to using arrays, especially for a small, fixed range of integer keys.This approach may involve multiple passes over the data or data structures, making it slightly less efficient than a single-pass solution.
This approach is an optimization of the first one. Since the card ranks are small integers (1-13), we can replace the HashMap with a simple integer array to count rank frequencies. An array of size 14 can act as a direct-access table (or a frequency map), where the index corresponds to the rank. This is generally faster than using a HashMap because it avoids the computational overhead of hashing. The check for a flush is also simplified to a direct iteration without needing a HashSet. The overall logic of checking for hands in descending order of value remains the same.
Algorithm
- Check for Flush:
- Iterate from the second card (
i=1) and compare its suit with the first card's suit (suits[0]). - If any suit is different, it's not a flush. If the loop completes, it is a flush. Return
"Flush".
- Iterate from the second card (
- Count Ranks with an Array:
- If not a flush, create an integer array,
rankCounts, of size 14, initialized to zeros. - Iterate through the
ranksarray. For eachrank, increment the count atrankCounts[rank].
- If not a flush, create an integer array,
- Check for Three of a Kind or Pair:
- Initialize a boolean flag,
hasPair, tofalse. - Iterate through the
rankCountsarray (from index 1 to 13).- If
rankCounts[i] >= 3, return"Three of a Kind". - If
rankCounts[i] == 2, sethasPair = true.
- If
- Initialize a boolean flag,
- Determine Final Hand:
- After the loop, if
hasPairistrue, return"Pair".
- After the loop, if
- Default to High Card:
- If no other hand was found, return
"High Card".
- If no other hand was found, return
Walkthrough
This method improves performance by using data structures tailored to the problem's constraints.
- Optimized Flush Check: Instead of a
HashSet, we can perform a simple loop. We assume the hand is a flush and iterate from the second card, comparing each suit to the first. If we find any mismatch, we know it's not a flush and can break the check. - Array for Rank Counting: Given that ranks are
1 <= ranks[i] <= 13, aHashMapis overkill. An integer arrayrankCountsof size 14 provides a more efficient way to store frequencies. The rank itself serves as the index into the array. - Hand Evaluation: The evaluation process is similar to the first approach but operates on the
rankCountsarray. We iterate through the array to find if any rank occurs 3 or more times (for "Three of a Kind") or exactly 2 times (for "Pair").
class Solution { public String bestHand(int[] ranks, char[] suits) { // 1. Check for Flush boolean isFlush = true; for (int i = 1; i < suits.length; i++) { if (suits[i] != suits[0]) { isFlush = false; break; } } if (isFlush) { return "Flush"; } // 2. Count Ranks with an array int[] rankCounts = new int[14]; for (int rank : ranks) { rankCounts[rank]++; } // 3. Check for Three of a Kind or Pair boolean hasPair = false; for (int i = 1; i < rankCounts.length; i++) { if (rankCounts[i] >= 3) { return "Three of a Kind"; } if (rankCounts[i] == 2) { hasPair = true; } } // 4. Determine Final Hand if (hasPair) { return "Pair"; } // 5. Default to High Card return "High Card"; }}Complexity
Time
O(1) - The loops for checking suits, counting ranks, and checking counts all run a small, constant number of times.
Space
O(1) - We use a `rankCounts` array of fixed size 14.
Trade-offs
Pros
More efficient than using a
HashMapdue to direct array access for frequency counting.Avoids the overhead of
HashSetfor the flush check.Maintains good readability while being more performant.
Cons
The logic is still spread across a few distinct steps or loops, which could be slightly condensed.
This is the most streamlined approach. It prioritizes the highest-ranking hand, "Flush," with a quick check for an early exit. If the hand is not a flush, it proceeds to analyze the ranks in a highly efficient manner. We can count the rank frequencies and find the maximum frequency in a single pass over the ranks array. Based on this maximum frequency (e.g., 3 for "Three of a Kind", 2 for "Pair"), we can immediately determine the hand type. This avoids redundant checks and combines data gathering and analysis.
Algorithm
- Flush Check (Early Exit):
- Use a
HashSetto check if all suits are the same. If its size is 1 after adding all suits, return"Flush".
- Use a
- Rank Analysis in a Single Pass:
- If not a flush, create a
rankCountsarray of size 14. - Initialize
maxFreq = 0. - Iterate through the
ranksarray once:- For each rank
r, incrementrankCounts[r]. - Update
maxFreq = Math.max(maxFreq, rankCounts[r]).
- For each rank
- If not a flush, create a
- Determine Hand from Max Frequency:
- After the loop, check the value of
maxFreq:- If
maxFreq >= 3, return"Three of a Kind". - If
maxFreq == 2, return"Pair". - Otherwise, return
"High Card".
- If
- After the loop, check the value of
Walkthrough
This approach refines the logic to be as concise and efficient as possible.
- Prioritize Flush Check: The check for "Flush" is performed first. As it's the highest-value hand, if it exists, no other checks are necessary. A
HashSetprovides a clean way to do this. - Combined Rank Analysis: If the hand isn't a flush, we analyze the ranks. Instead of counting first and then iterating again to find the hand type, we can determine the maximum frequency of any rank while we are counting. A variable
maxFreqis maintained and updated inside the same loop that populates therankCountsarray. - Final Decision: After a single loop through the ranks, the
maxFreqvariable holds all the information we need. IfmaxFreqis 3 (or more, though not possible with 5 cards without a flush), we have a "Three of a Kind". If it's 2, we have a "Pair". If it's 1, it's a "High Card". This simple check onmaxFreqreplaces the need for flags or further loops.
import java.util.HashSet;import java.util.Set; class Solution { public String bestHand(int[] ranks, char[] suits) { // 1. Flush Check Set<Character> suitSet = new HashSet<>(); for (char s : suits) { suitSet.add(s); } if (suitSet.size() == 1) { return "Flush"; } // 2. Rank Analysis int[] rankCounts = new int[14]; int maxFreq = 0; for (int r : ranks) { rankCounts[r]++; maxFreq = Math.max(maxFreq, rankCounts[r]); } // 3. Determine Hand from Max Frequency if (maxFreq >= 3) { return "Three of a Kind"; } if (maxFreq == 2) { return "Pair"; } return "High Card"; }}Complexity
Time
O(1) - The input size is constant. We iterate through the suits once and the ranks once.
Space
O(1) - Uses a `HashSet` (max 4 elements) and a fixed-size array (14 elements).
Trade-offs
Pros
Most concise and efficient implementation for this problem.
Combines rank counting and finding the maximum frequency in a single loop, reducing operations.
Clear separation of concerns: flush check is handled first, followed by a single phase for rank analysis.
Cons
While efficient, the logic of tracking max frequency during the counting loop might be slightly less direct for a beginner compared to a separate analysis loop.
Solutions
Solution
class Solution {public String bestHand(int[] ranks, char[] suits) { boolean flush = true; for (int i = 1; i < 5 && flush; ++i) { flush = suits[i] == suits[i - 1]; } if (flush) { return "Flush"; } int[] cnt = new int[14]; boolean pair = false; for (int x : ranks) { if (++cnt[x] == 3) { return "Three of a Kind"; } pair = pair || cnt[x] == 2; } return pair ? "Pair" : "High Card"; }}class Solution {public: string bestHand(vector<int> &ranks, vector<char> &suits) { bool flush = true; for (int i = 1; i < 5 && flush; ++i) { flush = suits[i] == suits[i - 1]; } if (flush) { return "Flush"; } int cnt[14]{}; bool pair = false; for (int &x : ranks) { if (++cnt[x] == 3) { return "Three of a Kind"; } pair |= cnt[x] == 2; } return pair ? "Pair" : "High Card"; }};class Solution: # if len(set(suits)) == 1: if all ( a == b for a , b in pairwise ( suits )): return 'Flush' cnt = Counter ( ranks ) if any ( v >= 3 for v in cnt . values ()): return 'Three of a Kind' if any ( v == 2 for v in cnt . values ()): return 'Pair' return 'High Card' def bestHand(self, ranks: List[int], suits: List[str]) -> str:Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.