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

3-oesnuj #11

Merged
merged 1 commit into from
Apr 10, 2024
Merged

3-oesnuj #11

merged 1 commit into from
Apr 10, 2024

Conversation

oesnuj
Copy link
Member

@oesnuj oesnuj commented Apr 2, 2024

🔗 문제 링크

백준 | 카드 놓기

✔️ 소요된 시간

40분

✨ 수도 코드

이 문제는 카드 N장을 입력 받은 규칙에 맞게 내려놓았더니 위에서 부터 순서대로 1, 2, 3, ..., N이 적혀있는 카드 배치가 되었다. 이때 원래 카드 상태를 구하는 문제이다.

? ? ? ? ? ---규칙 적용---> (아래)5 4 3 2 1(위)
? ? ? ? ?는 어떻게 구성되어있었는 지를 찾아내야한다.

규칙은 카드를 내려놓을 때 사용한 순서대로 입력 받는다.

원래 카드 상태로 복원 하기 위해서는 마지막에 내려놓은 규칙부터 처음내려놓은 규칙순으로 되돌아가야한다.
카드 복원 시에 규칙에 따라 자료구조 제일 앞, 뒤로 원소를 삽입해야하기에 Deque 자료구조를 사용하여 복원한 값을 채운다.

덱(deque) 자료구조에 대해 모르시는 분이 있을 것 같아 간단설명합니다.
덱은 스택과 큐의 연산을 모두 지원하는 자료구조로 양쪽에서 삽입, 삭제가 가능한 구조입니다.
카드 규칙이 앞뒤로 원소를 빼내기 때문에 덱 자료구조를 사용하기로 했습니다.

  • C++을 모르시는 분들을 위해 : C++ vector는 자동으로 메모리가 할당되는 배열이라고 생각하시면 됩니다.

Note

카드복원하기

(위)1, 2, 3, ..., N(아래) 결과 배치의 제일 위에 있는 1카드부터 N카드 순서로, 마지막에 쓰인 규칙부터 활용해 복원

규칙 1. 제일 위의 카드를 내려놓으므로 복원 시에는 이 카드를 덱 제일 위에 넣는다.

규칙 2. 위에서 두번째 카드를 내려놓으므로 복원시에는 이 카드를 덱 제일 위에서 두번째에 넣는다.

규칙 3. 제일 밑에 있는 카드를 내려놓으므로 복원시에는 이 카드를 덱 제일 밑에 넣는다.

위 복원 로직을 활용해 1부터 N까지 덱에 넣으면서 복원시킨다.
이렇게 하면 원래 카드 배치를 만들 수 있다.

📃코드 설명

c++ iterator에 대한 사용감을 키울려고 최대한 많이 적극적으로 사용해봤다.

  1. 먼저 덱과 벡터 선언한다.
  2. 입력받은 규칙을 순서대로 벡터에 넣는다.
  3. 역방향 iterator를 활용해 벡터의 마지막요소에서부터 첫번째요소로 반복문을 수행한다.
    벡터의 마지막요소가 마지막에 쓰인 규칙이기때문이다
  4. 위에서 설명한 복원로직에 맞게 카드(1부터 규칙이 바뀔때 마다 +1씩한다.)를 덱에 넣는다.
    덱에 넣는 방법은 주석으로 달아놨습니다!
  5. 덱에 모든 규칙에 맞게 복원한 카드를 넣었으면 reverse(d.begin(), d.end());를 사용해 덱의 요소를 뒤집는다.
    현재 덱의 요소에 일반적으로 앞에서 부터 접근할 수 있는데 앞의 카드는 아래에 있는 카드를 의미함
    가장 위에 있는 요소부터 출력하라고 해서 뒤집는다.
  6. 덱에 있는 요소를 앞에서 부터 출력한다. 처음 카드 배치에서 위에 있더 카드부터 출력된다.
#include <iostream>
#include <algorithm>
#include <deque>
#include <vector>
using namespace std;


int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	deque <int> d;
	vector <int > v;
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {       //규칙 저장하기 : 사용한 순서대로 vector에 넣음
		int num;
		cin >> num;
		v.push_back(num);
	}
	vector<int>::reverse_iterator rit = v.rbegin(); //복원시 가장 마지막에 사용한 규칙부터 사용해야하기에 역반복자 사용

	int card = 1; //다 내려놓은 이후 현재 제일 위의 카드는 1
	for (rit; rit != v.rend(); rit++) {
		//규칙 1은 제일 위 카드 1장 바닥에 내려놓기이므로 복원시에는 덱의 가장 위로 옮긴다.
		if (*rit == 1) 
			d.push_back(card);


		// 규칙 2는 위에 두번째 카드 바닥에 내려놓기이므로 복원시에는 덱의 위에서 두번째로 옮긴다.
		else if (*rit == 2)
		{
			int temp = d.back(); //가장 위의 숫자는 잠깐 빼고
			d.pop_back();
			d.push_back(card); //넣어야할 카드 넣고
			d.push_back(temp); //빼두었던 가장 위의 숫자 다시 넣기
		}

		//규칙 3은 제일 밑의 카드를 바닥으로 내려놓기이므로 복원시에는 덱의 제일 밑으로 옮긴다.
		else if (*rit == 3)
			d.push_front(card);

		card++; //다음카드를 복원시켜야함(다음카드는 하나 큰 숫자임)
	}

	reverse(d.begin(), d.end()); //덱의 요소를 뒤(위)에서 부터 출력해야해 뒤집는다.
	for (auto &k : d) {      //출력
		cout << k << " ";
	}
	return 0;
}

📚 새롭게 알게된 내용

vector::reverse_iterator rit; 역방향 반복자를 처음 사용해보았는데 rit로 v.rbegin -> v.rend방향으로 벡터를 순회할때 반대로 순회하기에 rit--라고 생각했는데 rit++를 써야한다는 사실을 알게되었다.
for (auto &k : d) 마지막 덱 요소 출력시 범위 기반 루프를 사용했는데 이때 &(참조)를 사용해야 덱 요소에 직접 접근해서 출력한다.
for (auto k : d) 도 같은 결과가 나오지만 덱 요소를 복사 한 후에 복사한 값을 출력하기때문에 참조를 사용한 출력이 더 효율적이라는 것을 알게되었다.

추가로 이 문제풀면 귀여운 펭귄 solved.ac 프로필 배경을 얻을 수 있으니 관심있으면 풀어보길 바랍니다..😊

Copy link

@9kyo-hwang 9kyo-hwang left a comment

Choose a reason for hiding this comment

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

덱 문제 한 번 풀어봤으니... 스택 문제도 한 번?

Comment on lines +13 to +20
vector <int > v;
int n;
cin >> n;
for (int i = 0; i < n; i++) { //규칙 저장하기 : 사용한 순서대로 vector에 넣음
int num;
cin >> num;
v.push_back(num);
}

Choose a reason for hiding this comment

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

벡터를 선언할 때 미리 크기를 지정해줄 수 있습니다.

vector<int> v(n);  // 4byte(int 크기) * n 만큼 메모리 공간 할당
vector<int> v(n, 0);  // 뒤에 특정 값을 넣어주면 해당 값으로 초기화

이렇게 하면 아래에서 반복문으로 수를 입력받을 때 ranged-loop를 이용하여 간편하게 처리할 수 있습니다.

for(int& i : v) {  // 수를 입력받기 위해선 참조(&) 필요
    cin >> i;
}

Copy link
Member Author

Choose a reason for hiding this comment

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

오 참조를 활용해서 벡터에 값을 바로 넣을 수가 있군요!!
훨씬 깔끔한 코드가 되어버렸네요

Comment on lines +21 to +44
vector<int>::reverse_iterator rit = v.rbegin(); //복원시 가장 마지막에 사용한 규칙부터 사용해야하기에 역반복자 사용

int card = 1; //다 내려놓은 이후 현재 제일 위의 카드는 1
for (rit; rit != v.rend(); rit++) {
//규칙 1은 제일 위 카드 1장 바닥에 내려놓기이므로 복원시에는 덱의 가장 위로 옮긴다.
if (*rit == 1)
d.push_back(card);


// 규칙 2는 위에 두번째 카드 바닥에 내려놓기이므로 복원시에는 덱의 위에서 두번째로 옮긴다.
else if (*rit == 2)
{
int temp = d.back(); //가장 위의 숫자는 잠깐 빼고
d.pop_back();
d.push_back(card); //넣어야할 카드 넣고
d.push_back(temp); //빼두었던 가장 위의 숫자 다시 넣기
}

//규칙 3은 제일 밑의 카드를 바닥으로 내려놓기이므로 복원시에는 덱의 제일 밑으로 옮긴다.
else if (*rit == 3)
d.push_front(card);

card++; //다음카드를 복원시켜야함(다음카드는 하나 큰 숫자임)
}

Choose a reason for hiding this comment

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

현재 이터레이터가 for문 안에서만 사용되기 때문에, 선언을 반복문 안으로 넣어주는 것이 좀 더 안전할 것 같습니다.
추가로, 사소한 부분이긴 하지만 이터레이터는 보통 후위증감(it++)보단 전위증감(++it)을 사용합니다. 한 번 이 글을 읽어보시는 것도 좋을 것 같습니다.

for(auto it = v.rbegin(); it != v.rend(); ++it) {  // 이터레이터는 타입이 너무 길어서 auto로 대체하기도 합니다.
    ...
}

Comment on lines +47 to +49
for (auto &k : d) { //출력
cout << k << " ";
}

Choose a reason for hiding this comment

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

단순히 auto로 할 경우 복사가 발생하기 때문에 약간의 오버헤드가 있죠. 이 부분을 캐치해서 &를 넣은 건 좋습니다.
다만 단순히 &만 붙일 경우 해당 원소값이 변경될 수 있기 때문에, 출력처럼 값을 확인하는 용도로만 사용하는 경우 추가로 "const" 지정을 해주는 것이 안전합니다.

for(const auto& k : d) {
    cout << k << " ";
}

Copy link
Member Author

@oesnuj oesnuj Apr 3, 2024

Choose a reason for hiding this comment

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

const auto &로 하면 접근은 하지만 값은 수정할 수 없어 확인만 진행할 때더 안전한 코드가 되겠네요.
이터레이터 타입이 너무 길어 불편했는데 이렇게 편하게 할 수가 있네요.
참조해주신 글도 잘 읽어보겠습니다.

감사합니다😊 오늘도 새로운 거 많이 배워가요!!
다음부터는 더 나은 코드를 짤 수 있을 것 같아요👍👍

Copy link
Collaborator

@pu2rile pu2rile left a comment

Choose a reason for hiding this comment

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

헉... 저는 자료구조를 c언어로만 공부해 봐서 c++로 구현한 덱은 처음 보는데 굉장히.. 신기하면서도?.. 어렵네욧.. 분명 올해 초까지 자료구조를 공부했었는데 문제가 어렵게 느껴지는 것 보니 저는 또 까먹었나 봐요... 준서님 덕분에 다시 자료구조 복습하러 갑니다...!! pr 수고하셨어요!!..

Copy link
Collaborator

@suhyun113 suhyun113 left a comment

Choose a reason for hiding this comment

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

덱이라는 자료구조에 대해 이름만 들어봤지 개념을 제대로 몰라 문제 자체가 이해하기 어려웠는데, 덱 자료구조를 설명해주셔서 너무 좋았습니다. 입력값에 들어가는 5가지 값이 어떤 것을 의미하는지 잘 이해하지 못 해 문제를 이해하는데 시간이 많이 걸렸습니다. 문제에 주어진 3가지 기술 종류에 대해 어떤 기술을 어떤 순서에 사용할 지 주어져있다는 것을 알게 되었습니다. c++언어를 배우고는 이지만, iterator에 대한 개념이 부족해 걱정되었지만, 는 현재 많이 사용하고 있어 반가웠습니다. 최종 코드를 봤는데, 도 선언하는 것이 vector와 같다는 것을 알게되었습니다. 코드 하나 하나가 이해하는데 저에겐 좀 버거웠지만, 귀여운 펭귄 프로필 배경을 얻을 수 있다니 꼭 풀어봐야 겠어요!

@Ghdrn1399
Copy link
Contributor

Pr작성하시느라 우선 수고 많으셨어요!! 자료구조를 공부안한지 꽤 지나서 덱을 까먹고 있었는데 준서님 덕분에 다시 새롭게 기억이 나네요!! C++이라는 언어가 사실 저에게는 되게 낯선 언어인데 작성해주신 수도코드를 통해 이해가 빨리됐고, 더욱 공부해야 겠다는 생각도 드네요!! 감사합니다

@oesnuj oesnuj merged commit 88eee6e into main Apr 10, 2024
1 check passed
@oesnuj oesnuj deleted the 3-oesnuj branch April 10, 2024 04:05
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.

5 participants