Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

37-pknujsp #149

Merged
merged 1 commit into from
Mar 22, 2024
Merged

37-pknujsp #149

merged 1 commit into from
Mar 22, 2024

Conversation

pknujsp
Copy link
Collaborator

@pknujsp pknujsp commented Mar 4, 2024

🔗 문제 링크

n + 1 카드게임

✔️ 소요된 시간

1시간 15분

✨ 수도 코드

n개의 카드 목록에서 순서대로 왼쪽부터 카드 n / 3개를 먼저 뽑은 상태에서 1 라운드를 시작으로, 규칙에 따라 카드를 두 개씩 뽑아가는 게임

1. 소유 카드 집합뽑은 카드 집합을 생성

  • ownCards : 가지고 있는 카드 목록, 초기값으로 n / 3개의 카드를 담는다
  • pickedCards : 뽑은 카드 목록

2. n / 3번째 위치의 카드부터 두 개씩 뽑기를 반복

매 라운드 마다 카드 교환 여부를 확인해야 할 필요가 없고
뽑은 카드들을 따로 모아두고 필요할 때마다 남은 코인을 보고 합이 n + 1이 되는 카드를 교환하면 된다

  • pickedCards에 뽑은 카드 두개를 담는다
  • ownCards에서 합이 n + 1인 카드 쌍을 찾는다
    • 쌍이 있다면 카드 쌍을 제거하고
    • 다음 라운드로 넘어가서 새로운 카드 쌍을 뽑는다
  • ownCards에 해당하는 카드 쌍이 없으면, ownCardspickedCards 각각에서 카드를 하나씩 확인하면서 n + 1이 되는 쌍이 있는지 확인한다
    • 쌍이 있다면 두 집합에서 카드 쌍을 제거하고
    • 다음 라운드로 넘어가서 새로운 카드 쌍을 뽑는다
    • 코인으로 교환해야 하는 카드를 냈으므로
    • 코인을 하나 차감한다
  • 위 방법으로도 못찾으면 pickedCards에서 합이 n + 1인 카드 쌍을 찾는다
    • 쌍이 있다면 카드 쌍을 제거하고
    • 다음 라운드로 넘어가서 새로운 카드 쌍을 뽑는다
    • 찾은 카드는 모두 코인으로 교환해야 하는 것이므로
    • 코인을 두 개 차감한다
  • 두 집합에서 n + 1이 되는 쌍이 없다면, 반복을 종료한다
  • 이 라운드가 최종 라운드가 된다

예시 [3, 6, 2, 7, 1, 10, 5, 9, 8, 12, 11, 4]

라운드 새로 뽑은 카드 쌍 소유한 카드 목록 뽑은 카드 목록 코인 교환한 카드 낸 카드
0, 시작 전 3, 6, 7, 2 4
1 1, 10 3, 6, 7, 2 1, 10 4 6, 7
2 5, 9 3, 2 1, 10, 5, 9 3 10 3, 10
3 8, 12 2 1, 10, 5, 9, 8, 12 1 1, 12 1, 12
4 11, 4 2 10, 5, 9, 11, 4 0 11 2, 11

4 라운드를 마지막으로 더 이상 뽑을 수 있는 카드가 없으므로 종료

📚 새롭게 알게된 내용

Copy link
Member

@tgyuuAn tgyuuAn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def solution(coin, cards):
    n = len(cards)
    now = set(cards[:n//3])
    now_can_make = set()
    keep = set()

    for element in now:
        if (n-element+1) in now and (n-element+1,element) not in now_can_make:
            now_can_make.add((element,n-element+1))

    for idx, start_idx in enumerate(range(n//3,n,2)):
        first = cards[start_idx]
        second = cards[start_idx+1]

        # 먼저 두 카드에 대한 구매 여부 결정
        if ((n-first+1) in now) and ((n-first+1, first) not in now_can_make) and (coin > 0):
            now_can_make.add((first,n-first+1))
            now.add(first)
            coin -= 1

        else: keep.add(first)

        if ((n-second+1) in now) and ((n-second+1, second) not in now_can_make) and (coin > 0):
            now_can_make.add((second,n-second+1))
            now.add(second)
            coin -= 1

        else: keep.add(second)


        # 다음 라운드 수명 연장을 위해서 카드 제출
        if now_can_make:
            for pair_1, pair_2 in now_can_make:
                now.discard(pair_1)
                now.discard(pair_2)
                
                now_can_make.discard((pair_1, pair_2))
                break

        # 제출할 카드가 없을 경우 keep 목록에서 탐색
        elif coin >= 2:
            for element in keep:
                if n-element+1 in keep:
                    keep.discard(element)
                    keep.discard(n-element+1)
                    coin -= 2
                    break
                    
            else: return idx+1

        # 만약 제출할 게 없으면 게임 종료
        else: return idx+1

    else: return idx+2

준성님이랑 완전 똑같은 로직으로 풀었네요.

저는 이거 그리디인줄 알았는데 구현일까요..?

당장 라운드를 통과하기 위해서 최적의 해만 찾으면 되니까...

"그리디 + 구현" 인 것 같기도 하고..




이 문제는 딱 이걸로만 풀릴 것 같아요. 정해가 정해져 있는?!

1시간 고민하다가 직접 시뮬레이션 돌려보니 바로 슥슥 풀리네요 아자뵤~

Copy link
Collaborator

@H0ngJu H0ngJu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수도 코드랑 주석을 깔끔하게 적어두셔서 쉽게 이해할 수 있었습니다!
그 때문인지 문제가 재밌게 느껴지던데 얼른 이 유형의 알고리즘 문제들도 풀어보고 싶네요 🔥🔥!!

@@ -0,0 +1,53 @@
def solution(coin, cards):
a = set(cards[:len(cards) // 3])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

준성님 수도 코드보면서 코드 작성하다가 궁금한게 생겨서 코멘트 남깁니당
set이 아니라 list로 구현해도 통과는 되던데 집합으로 구현하신 이유가 시간 복잡도 때문일까요?

for ~ in에서 list의 경우에는 시간복잡도가 O(n), set의 경우에는 O(1)이여서 집합으로 구현하신건지 궁금합니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네네 말씀하신 그대로 입니다

카드 목록에서 n + 1 이 되는 조합을 확인해볼때

리스트로 한다면 모든 카드를 두 쌍으로 만들어서 일일히 다 비교해야 하는데

집합이라면 쌍으로 비교할 필요없이 카드 하나씩만 확인해보면서 n + 1 이 가능한 카드의 존재여부를 한번에 확인가능하니까요.

removed = False
# 현재 가지고 있는 카드 목록 중 n + 1이 가능한 경우 확인
for x in list(a):
if t - x in a:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

t - x를 따로 변수로 담을 필요 없이 구현이 가능하군요 .. 이렇게 변수 하나를 또 줄일 수 있군요.. 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 윗 코멘트의 내용과 같은 부분으로 보시면 됩니다

@Munbin-Lee
Copy link
Member

#include <string>
#include <vector>
#include <unordered_map>

using namespace std;

int solution(int coin, vector<int> cards) {
    int n = cards.size();
    
    int index = 0;
    int pair = 0;
    unordered_map<int, bool> hands, needed;
    
    for (; index < n / 3; index++) {
        int card = cards[index];
        
        if (needed[card]) {
            needed.erase(card);
            hands.erase(n + 1 - card);
            pair++;
            continue;
        }
        
        hands[card] = true;
        needed[n + 1 - card] = true;
    }
    
    int sparePair = 0;
    int answer = 1;
    unordered_map<int, bool> spares, spareNeeded;
    
    for (; index < n; index += 2) {
        for (int card : {cards[index], cards[index + 1]}) {
            if (coin && needed[card]) {
                coin--;
                needed.erase(card);
                hands.erase(n + 1 - card);
                pair++;
                continue;
            }
            
            if (spareNeeded[card]) {
                spareNeeded.erase(card);
                spares.erase(n + 1 - card);
                sparePair++;
                continue;
            }
            
            spares[card] = true;
            spareNeeded[n + 1 - card] = true;
        }
        
        if (pair == 0 && coin >= 2 && sparePair) {
            coin -= 2;
            sparePair--;
            pair++;
        }
        
        if (pair == 0) {
            break;
        }
        
        pair--;
        answer++;
    }
    
    return answer;
}

이 문제의 포인트는

  1. 각 카드마다 n+1 쌍을 이루는 페어는 하나뿐이다.
  2. 카드를 뽑을 때 바로 가지지 않고 미루었다가 가질 수 있다.

인 것 같아요.

저는 초기에 가진 카드 중 페어가 있는지,
초기에 가진 카드와 새로 뽑을 카드의 페어가 있는지,
새로 뽑을 카드끼리 페어가 있는지 (이 경우는 미루었다가 한번에 코인 2개를 소모해야함)

순서로 확인했어요.

@Munbin-Lee
Copy link
Member

딕셔너리 for 순회하실 때 리스트로 변환하지 않고 바로 순회할 수 있어요.

a,b 변수명은 조금 컹스하네요.

@tgyuuAn
Copy link
Member

tgyuuAn commented Mar 22, 2024

딕셔너리 for 순회하실 때 리스트로 변환하지 않고 바로 순회할 수 있어요.

아니 당신 C++ 로 하면서 왜 파이썬 더 잘 알아







a,b 변수명은 조금 컹스하네요.

ㅋㅌㅋㅌㅋㅌㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅌㅋㅋ

@pknujsp
Copy link
Collaborator Author

pknujsp commented Mar 22, 2024

딕셔너리 for 순회하실 때 리스트로 변환하지 않고 바로 순회할 수 있어요.

a,b 변수명은 조금 컹스하네요.

딕셔너리를 아니구 세트인데 직접 이 세트를 순회하는 중에는 원소를 이 세트에서
지우는게 불가능해서 리스트로 따로 만들어서 지우게 했답니다 허헛

변수명은 이때 코테 시간 때문에 최대한 짧게 짓고 넘기던 습관을 만들고 있었던지라
이런 상태군요 ㅋㅋㅋㅋㅋ pr올릴때는 다 수정해서 올리는데 이땐 깜빡스했네요 ㅋㅋ

@pknujsp pknujsp merged commit 193f85c into main Mar 22, 2024
1 check passed
@pknujsp pknujsp deleted the 37-pknujsp branch March 22, 2024 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants