Facebook Pixel

822. Card Flipping Game

MediumArrayHash Table
Leetcode Link

Problem Description

You have n cards, each with two sides - a front and a back. The i-th card has a positive integer fronts[i] on the front side and backs[i] on the back side.

Initially, all cards are placed on a table with their front sides facing up (visible) and back sides facing down (hidden). You can flip any number of cards (including zero cards) to swap which side is facing up.

After flipping some cards, an integer is considered good if:

  • It appears on at least one card's face-down side (hidden side)
  • It does NOT appear on any card's face-up side (visible side)

Your task is to find the minimum possible good integer after optimally flipping the cards. If no good integer exists, return 0.

Example walkthrough:

  • If a card has the same number on both front and back (like fronts[i] = backs[i] = 5), then 5 can never be a good integer. No matter how you flip this card, 5 will always be visible on one side.
  • For cards with different numbers on front and back, you can choose which number to hide by flipping the card appropriately.

The solution identifies all numbers that appear on both sides of the same card (these can never be good), then finds the minimum number from all card values that isn't in this "impossible" set.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Let's think about when a number can be "good". For a number to be good, we need to be able to hide it from all face-up positions while keeping it on at least one face-down position.

The key insight comes from considering individual cards:

  1. Cards with different numbers on front and back: These cards give us flexibility. If we have a card with fronts[i] = 3 and backs[i] = 5, we can choose to show either 3 or 5 by flipping the card. This means we can potentially hide one of these numbers while showing the other.

  2. Cards with the same number on both sides: This is the critical case. If fronts[i] = backs[i] = 7, then no matter how we flip this card, the number 7 will always be visible. There's no way to hide it from the face-up side. Therefore, 7 can never be a good number.

This observation leads us to the solution strategy:

  • First, identify all "impossible" numbers - those that appear on both sides of at least one card. These numbers will always be visible no matter how we arrange the cards.
  • Then, from all the numbers that appear on any card (front or back), find the smallest one that isn't "impossible".

Why does this work? Because for any number that isn't in the "impossible" set, we know it only appears on cards where the front and back are different. For each such card, we can flip it to hide this number on the face-down side while showing a different number on the face-up side.

The elegance of this solution is that we don't need to track which specific cards to flip - we just need to know which numbers are possible candidates for being "good", and then pick the minimum among them.

Solution Approach

The implementation follows directly from our intuition using a hash table approach:

Step 1: Identify impossible numbers

We create a hash set s to store all numbers that can never be "good". We iterate through all positions and check if fronts[i] == backs[i]. If they're equal, we add that number to our set:

s = {a for a, b in zip(fronts, backs) if a == b}

This set comprehension zips the fronts and backs arrays together and adds any number a where the front and back values are identical.

Step 2: Find the minimum valid number

We need to check all numbers that appear on any card (both fronts and backs). Using chain(fronts, backs), we create an iterator that goes through all values from both arrays.

For each number x in this combined list:

  • If x is not in set s, it's a candidate for being "good"
  • We want the minimum among all such candidates
return min((x for x in chain(fronts, backs) if x not in s), default=0)

The generator expression (x for x in chain(fronts, backs) if x not in s) produces all numbers that aren't in the "impossible" set. The min() function finds the smallest such number.

Edge case handling:

If no valid number exists (all numbers appear on both sides of at least one card), the generator will be empty. The default=0 parameter handles this case by returning 0 when there are no good integers.

Time Complexity: O(n) where n is the number of cards, as we iterate through the arrays a constant number of times.

Space Complexity: O(n) in the worst case for the hash set if all cards have the same number on both sides.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with 3 cards:

  • Card 1: front = 1, back = 2
  • Card 2: front = 3, back = 3
  • Card 3: front = 2, back = 4

Step 1: Identify impossible numbers

We check each card to find numbers that appear on both sides:

  • Card 1: front (1) ≠ back (2) → No impossible numbers
  • Card 2: front (3) = back (3) → 3 is impossible (can never be hidden)
  • Card 3: front (2) ≠ back (4) → No impossible numbers

Impossible set = {3}

Step 2: Find minimum valid number

All numbers appearing on cards: [1, 2, 3, 3, 2, 4] = {1, 2, 3, 4}

Check each number:

  • 1: Not in impossible set → Valid candidate ✓
  • 2: Not in impossible set → Valid candidate ✓
  • 3: In impossible set → Cannot be good ✗
  • 4: Not in impossible set → Valid candidate ✓

Minimum valid number = 1

Verification: We can achieve this by:

  • Keep Card 1 as is (showing front = 1, hiding back = 2)
  • Keep Card 2 as is (doesn't matter since 3 is always visible)
  • Flip Card 3 (showing back = 4, hiding front = 2)

Result: Face-up shows [1, 3, 4], Face-down has [2, 3, 2]

  • Number 2 is hidden on face-down sides but not visible on any face-up side → 2 is good
  • Number 1 is visible on face-up side → 1 is not good
  • Actually, we need to flip Card 1 to hide 1!

Let me reconsider:

  • Flip Card 1 (showing back = 2, hiding front = 1)
  • Keep Card 2 (showing 3)
  • Keep Card 3 (showing front = 2, hiding back = 4)

Result: Face-up shows [2, 3, 2], Face-down has [1, 3, 4]

  • Number 1 appears on face-down side only → 1 is good ✓
  • Number 4 appears on face-down side only → 4 is good ✓

The minimum good integer is 1.

Solution Implementation

1from typing import List
2from itertools import chain
3
4class Solution:
5    def flipgame(self, fronts: List[int], backs: List[int]) -> int:
6        # Find all numbers that appear on both sides of the same card
7        # These numbers cannot be the answer since flipping won't help
8        same_on_both_sides = {front for front, back in zip(fronts, backs) if front == back}
9      
10        # Find the minimum number from all cards (both fronts and backs)
11        # that is not in the set of numbers appearing on both sides
12        # If no valid number exists, return 0 as default
13        return min((number for number in chain(fronts, backs) 
14                   if number not in same_on_both_sides), default=0)
15
1class Solution {
2    public int flipgame(int[] fronts, int[] backs) {
3        // Set to store numbers that appear on both sides of the same card
4        // These numbers are "bad" because no matter how we flip, they'll always be visible
5        Set<Integer> sameOnBothSides = new HashSet<>();
6      
7        // Get the number of cards
8        int numberOfCards = fronts.length;
9      
10        // Find all numbers that appear on both front and back of the same card
11        // These cannot be our answer since they'll always be face-up
12        for (int i = 0; i < numberOfCards; i++) {
13            if (fronts[i] == backs[i]) {
14                sameOnBothSides.add(fronts[i]);
15            }
16        }
17      
18        // Initialize minimum value to a large number (max possible value based on constraints)
19        int minGoodNumber = 9999;
20      
21        // Check all numbers on the front of cards
22        // If a number doesn't appear on both sides of any card, it's a candidate
23        for (int value : fronts) {
24            if (!sameOnBothSides.contains(value)) {
25                minGoodNumber = Math.min(minGoodNumber, value);
26            }
27        }
28      
29        // Check all numbers on the back of cards
30        // If a number doesn't appear on both sides of any card, it's a candidate
31        for (int value : backs) {
32            if (!sameOnBothSides.contains(value)) {
33                minGoodNumber = Math.min(minGoodNumber, value);
34            }
35        }
36      
37        // Return the minimum good number found, or 0 if no good number exists
38        // (when minGoodNumber remains 9999, modulo operation returns 0)
39        return minGoodNumber % 9999;
40    }
41}
42
1class Solution {
2public:
3    int flipgame(vector<int>& fronts, vector<int>& backs) {
4        // Store numbers that appear on both sides of the same card
5        // These numbers cannot be the answer since they'll always be visible
6        unordered_set<int> sameNumbers;
7        int n = fronts.size();
8      
9        // Find all cards where front and back have the same number
10        for (int i = 0; i < n; ++i) {
11            if (fronts[i] == backs[i]) {
12                sameNumbers.insert(fronts[i]);
13            }
14        }
15      
16        // Initialize answer with a large value (representing infinity)
17        int minValue = 9999;
18      
19        // Check all front values for potential minimum
20        for (const int& value : fronts) {
21            if (!sameNumbers.count(value)) {
22                minValue = min(minValue, value);
23            }
24        }
25      
26        // Check all back values for potential minimum
27        for (const int& value : backs) {
28            if (!sameNumbers.count(value)) {
29                minValue = min(minValue, value);
30            }
31        }
32      
33        // Return the minimum value found, or 0 if no valid number exists
34        // (when minValue remains 9999, modulo operation returns 0)
35        return minValue % 9999;
36    }
37};
38
1/**
2 * Finds the minimum number that can be shown after flipping cards.
3 * Cards with the same number on front and back cannot be used as the answer.
4 * 
5 * @param fronts - Array of numbers on the front of each card
6 * @param backs - Array of numbers on the back of each card
7 * @returns The minimum valid number, or 0 if no valid number exists
8 */
9function flipgame(fronts: number[], backs: number[]): number {
10    // Set to store numbers that appear on both sides of the same card
11    // These numbers are invalid as they'll always be visible
12    const invalidNumbers: Set<number> = new Set<number>();
13  
14    const cardCount: number = fronts.length;
15  
16    // Find all numbers that appear on both sides of the same card
17    for (let i = 0; i < cardCount; i++) {
18        if (fronts[i] === backs[i]) {
19            invalidNumbers.add(fronts[i]);
20        }
21    }
22  
23    // Initialize minimum answer with a large value
24    let minAnswer: number = 9999;
25  
26    // Check all front numbers for the minimum valid number
27    for (const frontNumber of fronts) {
28        if (!invalidNumbers.has(frontNumber)) {
29            minAnswer = Math.min(minAnswer, frontNumber);
30        }
31    }
32  
33    // Check all back numbers for the minimum valid number
34    for (const backNumber of backs) {
35        if (!invalidNumbers.has(backNumber)) {
36            minAnswer = Math.min(minAnswer, backNumber);
37        }
38    }
39  
40    // Return 0 if no valid number found (minAnswer still 9999), otherwise return minAnswer
41    return minAnswer % 9999;
42}
43

Time and Space Complexity

Time Complexity: O(n)

The algorithm performs the following operations:

  • Creating set s by iterating through fronts and backs with zip(): O(n) time
  • The set comprehension checks condition a == b for each pair: O(1) per element
  • chain(fronts, backs) creates an iterator over 2n elements
  • The generator expression iterates through at most 2n elements
  • For each element, checking membership in set s takes O(1) average time
  • Finding the minimum of the filtered elements takes O(n) in total

Overall time complexity: O(n) + O(2n) = O(n)

Space Complexity: O(n)

The space usage includes:

  • Set s stores at most n unique values (worst case when all pairs have matching front and back): O(n) space
  • The generator expression and chain() use O(1) additional space as they create iterators
  • The min() function processes elements one at a time without storing them all

Overall space complexity: O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Misunderstanding What Makes a Number "Good"

The Mistake: Many people initially think that a number is good if it appears on ANY back side and doesn't appear on ANY front side. This leads to incorrect solutions like:

# WRONG approach
def flipgame(self, fronts, backs):
    front_set = set(fronts)
    back_set = set(backs)
    # Find numbers only in backs but not in fronts
    good_numbers = back_set - front_set
    return min(good_numbers) if good_numbers else 0

Why It's Wrong: The problem allows you to flip cards! After flipping, what was on the back becomes visible (face-up), and what was on the front becomes hidden (face-down). A number can be good even if it initially appears on the front, as long as you can flip cards to hide all occurrences of it.

The Fix: Understand that you have control over which side is up for each card, except when both sides have the same number.

Pitfall 2: Forgetting About Cards with Identical Sides

The Mistake: Trying to track which specific cards to flip without recognizing the fundamental constraint:

# WRONG approach - overly complex and misses the key insight
def flipgame(self, fronts, backs):
    min_good = float('inf')
    for target in set(fronts + backs):
        can_hide_all = True
        for i in range(len(fronts)):
            if fronts[i] == target and backs[i] == target:
                can_hide_all = False
                break
            # Trying to figure out complex flipping logic...
        if can_hide_all:
            min_good = min(min_good, target)
    return min_good if min_good != float('inf') else 0

Why It's Wrong: This approach overcomplicates the problem. The key insight is that if a number appears on both sides of the same card, it's impossible to make it good. For all other numbers, you can always arrange the cards to make them good.

The Fix: Simply identify numbers that appear on both sides of the same card - these are the only numbers that cannot be good. All other numbers that appear on any card are valid candidates.

Pitfall 3: Not Considering All Numbers on Cards

The Mistake: Only checking numbers from the fronts array or only from the backs array:

# WRONG approach
def flipgame(self, fronts, backs):
    same = {a for a, b in zip(fronts, backs) if a == b}
    # Only checking fronts array - missing potential answers from backs
    return min((x for x in fronts if x not in same), default=0)

Why It's Wrong: A good number could be any number that appears on any card (either front or back), as long as it doesn't appear on both sides of the same card.

The Fix: Check all numbers from both fronts and backs arrays when finding the minimum valid number, which is why the correct solution uses chain(fronts, backs).

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More