코딩 테스트

[코딩테스트 18일차] BAEKJOON 2908번, 2798번

sunlight-dby 2025. 5. 14. 01:40

[BAEKJOON 2908번 : 상수]

[문제]


[고찰]

아래의 코드는 제가 이 문제를 풀었을 때의 답안입니다.

저는 문자열 스트림을 통해 풀었습니다만, stoi 함수를 사용하는 방안 또한 알게되어 정리하고자 합니다.

#include <iostream>
#include <algorithm>
#include <sstream>

using namespace std;

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    string x, y;
    int rx, ry;
    
    cin >> x >> y;
    
    reverse(x.begin(), x.end());
    reverse(y.begin(), y.end());
    
    stringstream ss_x(x);
    ss_x >> rx;
    
    stringstream ss_y(y);
    ss_y >> ry;
    
    if (rx > ry)
        cout << rx; 
    else 
        cout << ry; 
    
    return 0;
}

[개념]

[stoi 함수]

  • 역할
    • std::stoi 함수는 std::string 타입의 문자열을 int 타입의 정수 값으로 변환하는 C+11 표준부터 추가된 함수입니다.
    • 문자열의 내용이 정수 형태여야 하며, 변환 과정에서 앞, 뒤의 공백 문자를 자동으로 건너뛰는 기능도 있습니다.
  • 헤더파일
    • <string>
  • 함수 오버로드
    • int stoi (const std::string& str, std::size_t* idx = nullptr, int base = 10);
    • int stoi (const std::wstring& str, std:size_t* idx = nullptr, int base = 10);  // wstring 버전
      • const std::string& str
        • 변환할 대상이 되는 입력 문자열입니다. const&로 전달되어 원본 문자열을 변경하지 않고 접근합니다.
      • std::size_t* idx = nullptr
        • 선택적 매개변수입니다. 만약 nullptr이 아닌 size_t 타입 변수의 주소를 전달하면, 함수는 문자열에서 숫자가 아닌 첫 번째 문자의 인덱스를 이 변수에 저장합니다. 이를 통해 문자열의 어느 부분까지 숫자로 변환되었는지 확인할 수 있습니다.
      • int base = 10
        • 선택적 매개변수입니다. 변환할 숫자가 어떤 진법으로 표현되었는지 지정합니다.
    • 반환값
      • 반환에 성공하면, 문자열 내용에 해당하는 정수(int) 값을 반환합니다.
    • 오류 처리
      • std::stoi는 변환 과정에서 문제가 발생하면 예외를 발생시킵니다.
      • 주요 예외
        • std::invalid_argument : 문자열에 유효한 숫자가 포함되어 있지 않은 경우 발생합니다.
        • std::out_of_range : 문자열 내용이 유효한 숫자 형태지만,
                                           int 타입으로 표현할 수 있는 범위를 벗어나는 경우 발생합니다.

 

[reverse 함수]

추가적으로 코드에서 활용된 reverse 함수를 헤더파일을 사용하지 않아 한 번 틀렸었습니다.

기존 '코딩테스트 9일차' 글에도 정리되어 있지만, 정확히 알고자 다시 한번 자세히 정리해둡니다.

  • 역할
    • std::reverse 함수는 주어진 반복자 범위 안의 요소들의 순서를 반대로 뒤집습니다.
  • 헤더파일
    • <alogorithm>
  • 함수 원형
    • template <class BidirecionalIterator>
      void reverse (BidirectionalIterator first, BidirectionalIterator last);
      • BidirectionalIterator
        • 반복자의 카테고리 중 하나입니다. std::reverse는 순방향과 역방향으로 모두 이동할 수 있는 양방향 반복자를 요구합니다.
        • std::vector, std::list, std::deque와 같은 대부분의 표준 컨테이너가 제공하는 반복자는 양방향 반복자 이상의 기능을 제공하므로 std::reverse에 사용할 수 있습니다. 배열의 포인터도 양방향 반복자로 동작합니다.
      • first : 뒤집기 시작할 범위의 시작을 가리키는 반복자입니다. 이 위치에 있는 요소부터 뒤집기 대상에 포함됩니다.
      • last : 뒤집기 끝의 바로 다음 위치를 가리키는 반복자입니다. 이 위치에 있는 요소는 뒤집기 대상에 포함되지 않습니다.
  • 반환값
    • std::reverse 함수는 void 타입으로, 아무것도 반환하지 않습니다. 범위 내의 요소들의 순서를 직접 변경하는 역할을 수행합니다.
  • 작동 방식
    • std::reverse는 기본적으로 first와 last - 1에 있는 요소를 교환하고, ++first와 --last를 반복하여 중간으로 이동하면서 요소들을 쌍으로 교환합니다. first가 last를 만나거나 지나칠 때까지 이 과정을 반복합니다.
// 코드로 표현한 std::reverse 함수의 대략적 구현
while (first != last && first != --last)
{
    std::swap(*first, *last);
    ++first;
}
  • 특징
    • 별도의 추가 메모리 공간을 사용하지 않고 주어진 범위 내에서 요소들의 위치만 변경하여 순서들을 뒤집습니다.
    • 요소들의 순서를 뒤집기 위해 내부적으로 std::swap 연산을 사용합니다. 따라서 범위 내의 요소들은 서로 교환될 수 있어야 합니다. 대부분의 기본 제공 타입과 복사 생성자 및 대입 연산자가 잘 정의된 사용자 정의 타입은 기본적으로 교환 가능합니다.

[정리]

기존에 정리해두었던 것도 제대로 복습하지 않았다면 소용이 없다는 것을 제대로 체험한 것 같습니다.

더욱 복습하는 시간을 늘려야겠습니다.


[Solution]

#include <iostream>
#include <algorithm>
#include <string>

using namespace std;

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    string x, y;
    int rx, ry;
    
    cin >> x >> y
    
    reverse(x.begin(), x.end());
    reverse(y.begin(), y.end());
    
    rx = stoi(x);
    ry = stoi(y);
    
    cout << (rx > ry ? x : y);
    
    return 0;
}

[BAEKJOON 2798번 : 블랙잭]

[문제]


[고찰]

아래의 코드는 제가 해당 문제를 푼 답변 코드입니다만, 우연찮게 정답에 요구하는 입력값에 대해 올바른 출력이 되어 정답 처리가 된 코드인 것 같습니다.

 

문제를 풀면서 적용한 아이디어는, 카드들을 정렬시킨 뒤 가장 큰 값을 기준으로, 작은 값들을 차례대로 비교하는 것이었습니다.

다만, 다시 리뷰해보면서 해당 코드로는 가장 작은 세 값끼리는 합할 수 없는 치명적인 논리 오류가 있었습니다.

 

예제 입력을 과신하고, 나름대로 입력 값을 만들면서도 작은 값들끼리만 합해도 될 상황을 생각하지 못한 것입니다.

또한 굳이 벡터를 정렬하여서 시간 복잡도를 늘어나게 했다는 것도 문제였습니다.

 

나름대로 최대한 효율적인 루트를 짜본 것이었는데, 접근 방법이 잘못되었습니다.

그래서 이에 다시 올바르게 문제를 푼 내용에 대해 정리하고자 합니다.

#include <iostream>
#include <algorithm>   
#include <vector>     

using namespace std;

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);

	int cardNum, baseValue;
	int maxValue = 0;
	vector<int> cards;

	cin >> cardNum >> baseValue;

	while (cardNum--)
	{
		int x;
		cin >> x;
		cards.push_back(x);
	}

	sort(cards.begin(), cards.end());

	for (int i = cards.size() - 1; i >= 0; i--)
	{
		for (int j = 0; j < i - 1; j++)
		{
			for (int k = j + 1; k <= i - 1; k++)
			{
				int value = cards[i] + cards[j] + cards[k];
				if (value <= baseValue && value > maxValue)
				{
					maxValue = value;
				}
			}
		}
	}

	cout << maxValue;

	return 0;
}

[개념]

이번에는 벡터를 정렬하지 않고, 모든 카드를 탐색하게끔 구현을 해 보았습니다.

 

첫 번째 카드를 선택하는 루프는 0부터 cardNum - 2까지 반복합니다.

두 번째 카드를 선택하는 루프는 첫 번째 루프의 시작 카드의 다음부터 cardNum -1까지 반복합니다.

세 번째 카드를 선택하는 루프 역시, 두 번째 루프의 시작 카드의 다음부터 cardNum까지 반복합니다.

 

for (int i = 0; i < cardNum - 2; i++)
{
    for (int j = i + 1; j < cardNum - 1; j++)
    {
        for (int k = j + 1; k < cardNum; k++)
        {
            int currentValue = cards[i] + cards[j] + cards[k];
            // ...
        }
    }
}

 

이렇게 중첩 루프문을 구성했을 경우, cardNum이 5이고 벡터가 1부터 5까지의 값을 가진다고 했을 때, 다음의 상황들을 확인할 수 있습니다.

card [ i ] card [ j ] card [ k ]
1 2 3
4
5
3 4
5
4 5
2 3 4
5
4 5
3 4 5

[정리]

예제 입력만을 과신하는 일이 없어야 하는데, 여러번 실수를 하게 되는 것 같습니다. 더욱 더 예제 입력만을 과신하지 말고, 다양한 상황까지 커버할 수 있게끔 상황을 가정하고 문제를 풀어야겠습니다.

뿐만 아니라, 풀이를 할 때 정립한 논리를 한 번더 확인하는 습관을 들여야겠습니다.


[Solution]

#include <iostream>
#include <algorithm>
#include <vector>    

using namespace std;

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int cardNum, baseValue;
    int maxValue = 0;
    
    cin >> cardNum >> baseValue;
    
    vector<int> cards(cardNum);
    
    for (int i = 0; i < cardNum; i++)
    {
        cin >> cards[i];
    }
    
    for (int i = 0; i < cardNum - 2; i++)
    {
        for (int j = i + 1; j < cardNum - 1; j++)
        {
            for (int k = j + 1; k < cardNum; k++)
            {
                int currentValue = cards[i] + cards[j] + cards[k];
                
                if (currentValue <= baseValue)
                    maxValue = max(maxValue, currentValue)
            }
        }
    }
    
    cout << maxValue;
    
    return 0;
}