https://www.acmicpc.net/problem/17299

 

17299번: 오등큰수

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다. 둘째에 수열 A의 원소 A1, A2, ..., AN (1 ≤ Ai ≤ 1,000,000)이 주어진다.

www.acmicpc.net

 

설명

문제에서 말하는 오등큰수는 이 수가 나타난 수보다 더 많이 나타난 수 중 가장 가까운 오른쪽 수를 의미합니다. 특정 수의 오등큰수가 없을 수가 있습니다. 이런 경우 그 수의 오등큰수는 -1로 정해줍니다. 문제의 예제는 이렇습니다.

A = [1, 1, 2, 3, 4, 2, 1]

우선 F(Ai)는 빈도수라고 하여 차례대로 빈도수를 구하면 이렇게 되겠네요. 

F(1) = 3, F(2) = 2, F(3) = 1, F(4) = 1

이렇게 빈도수가 구해지면 NGF라는 오등큰수를 구할 수 있습니다. NGF(1)을 구하는데, 1보다 빈도수가 큰 수는 없으므로 NGF(1) = -1이 됩니다. NGF(2) = 1이됩니다. 왜냐면 2보다 빈번하게 나타난 수는 1이고 이 수가 가장 가까운 오른쪽에 있는 수가 되기 때문이죠. 예제의 답은 그래서 아래와 같습니다.

-1 -1 1 2 2 1 -1

 

풀이

이 문제는 스택으로 풀 수 있습니다. 우선 F(Ai)를 구해야하는데요. 이건 간단합니다. 비어있는 배열 F에 입력받은 수를 index삼아서 하나씩 증가시키면 되기 때문입니다. 이제 F를 구했으면 스택을 이용해서 본격적으로 문제를 풀어봅시다. 

아래의 블록은 F를 나타냅니다. 높을 수록 F가 높다는뜻입니다.

왼쪽의 빨간 블록의 오등큰수를 구하려면 오른쪽에서 찾아야합니다. 그런데 자신보다 큰 오등큰수가 두개가 있습니다. 4와 3이 있네요. 이때 빨간색의 오등큰수는 4가 됩니다. 이때 3은 이 다음 왼쪽 수의 오등큰수가 될 수 있을까요? 이미 4한테 막혀있고 4가 더 큰값이기 때문에 가능성이 없습니다. 그래서 3은 지워버립니다. 대신 4가 왼쪽의 오등큰수가 될 수 있는것을 알 수 있네요.

따라서 현재 자신보다 큰 값의 오등큰수가 존재하면 스택에 저장하고 작거나 같으면 스택에서 지워버리는 식으로 문제를 풀 수 있습니다. 

 

코드

문제 해답에 대한 전체코드는 아래와 같습니다.

#include <cstdio>
#include <stack>
using namespace std;

int n;
int F[1000001], ans[1000001], nums[1000001];
stack<int> st;
int main() {
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%d", &nums[i]);
		F[nums[i]]++;
	}

	for (int i = n - 1; i >= 0; i--) {
		int number = nums[i];
		int height = F[number];
		while (!st.empty()) {
			int topNum = nums[st.top()];
			int topHeight = F[topNum];
			if (height >= topHeight) st.pop();
			else break;
		}
		ans[i] = -1;
		if (!st.empty()) ans[i] = nums[st.top()];
		st.push(i);
	}
	for (int i = 0; i < n; i++)
		printf("%d ", ans[i]);
	printf("\n");

}

반응형
블로그 이미지

REAKWON

와나진짜

,

https://www.acmicpc.net/problem/3986

 

3986번: 좋은 단어

이번 계절학기에 심리학 개론을 수강 중인 평석이는 오늘 자정까지 보고서를 제출해야 한다. 보고서 작성이 너무 지루했던 평석이는 노트북에 엎드려서 꾸벅꾸벅 졸다가 제출 마감 1시간 전에

www.acmicpc.net

설명

같은 글자를 짝지어 주는 문제입니다. 아치형으로 서로 짝을 지어주고 모든 짝을 지어줄 수 있다면 그 단어는 좋은 단어라고 합니다. 단, 아치형이 서로 겹치지 않게 짝을 이뤄줘야합니다. 아래의 그림을 보면 바로 이해가 가실겁니다.

다음의 경우에는 좋은 단어입니다. 아치형이 겹치지 않죠

 

좋은단어

 

아래의 경우는 아치가 겹치므로 좋은단어가 아닙니다.

안좋은 단어

 

풀이

이 문제의 힌트는 바로 스택을 활용한 괄호 짝 맞추기 문제입니다. 가장 최근에 나온 짝이 자신의 짝인지 확인하는 문제와 컨셉이 같은데요. 여기서 괄호( '(', ')' )만 살짝 'A','B'로 바꾼것일 뿐입니다.

ABBA를 예로 들겠습니다.

1. A는 스택이 이미 비어있으니 스택에 집어넣습니다. (현재 스택 - A)

2. B는 스택 꼭대기 글자가 A이므로 B는 스택이 저장합니다. (현재 스택 - A B)

3. B는 스택 top의 글자가 B이므로 이전의 B를 스택에서 pop합니다. (현재 스택 - A)

4. A는 현재 스택 top의 글자와 같으므로 A를 스택에서 pop합니다. (현재 스택 - ' ' )

 

코드

아래는 전체 문제 풀이 코드입니다.

#include <cstdio>
#include <stack>
#include <string.h>
using namespace std;

char str[100001];
int n;
int main() {

	int ans = 0;
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%s", str);
		stack<char> st;
		int m = strlen(str);
		for (int j = 0; j < m; j++) {
			if (!st.empty()&&st.top()==str[j]) st.pop();
			else st.push(str[j]);
		}
		if (st.empty()) ans++;
	}
	printf("%d\n", ans);
}

 

어렵지 않게 코드가 이해가실거라 생각합니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

BOJ 17298 오큰수

https://www.acmicpc.net/problem/17298

 

17298번: 오큰수

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다. 둘째 줄에 수열 A의 원소 A1, A2, ..., AN (1 ≤ Ai ≤ 1,000,000)이 주어진다.

www.acmicpc.net

 

문제 설명

어떤 수열이 있는데요. 이 수열의 한 원소를 기준으로 오른쪽에 있는 자신보다 큰 원소 중 가장 왼쪽에 있는 수가 오큰수라고 합니다. 이때 수열에 대해 모든 원소의 오큰수를 구하는 것이 문제입니다. 

이때 오큰수가 없는 경우도 있습니다. 가장 큰 원소이거나 가장 오른쪽에 있는 원소는 오큰수가 없을 수가 있죠. 이때는 -1로 오큰수를 정해줍니다.

예를 들어 3 5 2 7 6 8이라는 수열이 있다면 5 7 7 8 8 -1이 해답이 됩니다. 5의 오른쪽에 있는 큰 수들은 7 6 8이 있는데, 이때 가장 왼쪽에 있는 수가 7이므로 5의 오큰수는 7이 됩니다. 여기까지 이해를 했으면 이제 문제를 풀어보겠습니다.

 

풀이

이 문제에 대해서 왼쪽부터 시작하지 말고 오른쪽부터 시작하면 접근이 쉽습니다. 가장 오른쪽에 있는 오큰수는 없으니까 -1로 시작하게 됩니다.

 

가장 최근에 큰 수(A)을 기억하고 있다가 그것보다 작은 수(B)가 나오게 되면 B의 오큰수는 A가 됩니다. 이때 B는 그냥 버리면 안됩니다. 왜냐면 B가 왼쪽 어떤 C의 오큰수 일 수 있으니까요. 그래서 B를 기억하고 있어야합니다. 아래와 같은 상황에서 B는 C의 오큰수가 됩니다. 이때 C도 역시 기억하고 있어야됩니다. C도 어떤 수의 오큰수가 될 수 있게 되니까요. 아래의 그림은 이해하기 쉽게 도형의 크기로 수의 크기를 표현하였습니다.

 

하지만 C가 B보다 큰 경우 B는 오큰수가 아니게 되겠죠? C 이전의 수들은 B가 필요없게 됩니다. 왜냐면 C가 B보다 크면서 더 왼쪽에 있으니까요. 그래서 B는 이제 기억에서 사라지고 C를 기억하고 있어야됩니다.

 

자, 이제 뭔가 좀 감이 잡힐 수 있습니다. 가장 최근에 것을 상황에 따라 보관하고 있다가 비교하거나 버리고 하는 것을 알 수 있게 됩니다. 그래서 스택이라는 자료구조가 이 문제에서 사용됩니다.

Stack에는 가장 오른쪽부터 시작해서 오큰수들이 들어가게 됩니다. 이때 value는 현재 수열의 비교할 값을 말한다고 합시다. 스택의 가장 위(top)는 어떠한 오큰수가 들어가게 되는데, value보다 작다면 이 오큰수는 쓸모가 없게 되므로 스택에서 pop하면 됩니다. stack의 top이 value보다 더 클때까지 계속 pop하게 되면 결국 stack이 비게 되거나, value보다 큰 오큰수가 존재하게 됩니다. 

이때 스택이 비어있다면 value의 오큰수가 없다는 것, 비어있지 않다면 스택에 있는 top의 값이 오큰수가 되게 됩니다.

위 설명을 따른 예제에 대한 그림 풀이가 아래에 있습니다. 참고하시기 바랍니다.

 

전체코드

전체 코드는 아래와 같습니다. 주석처리하여 추가설명하였습니다.

#include <cstdio>
#include <stack>

using namespace std;
int n, numbers[1000001], ans[1000001];

int main() {

	stack<int> st = stack<int>();

	scanf("%d", &n);

	for (int i = 0; i < n; i++) 
		scanf("%d", &numbers[i]);
	
	st.push(numbers[n - 1]);	//가장 오른쪽에 있는 수는 stack에 push
	ans[n - 1] = -1;			//가장 오른쪽의 있는 수는 오큰수가 없으므로 -1
	for (int i = n - 2; i >= 0; i--) {	//거꾸로 for문
		int value = numbers[i];			//stack의 top과 비교할 숫자
		while (!st.empty() && value >= st.top()) {	//스택이 비어있지 않고, 스택의 top이 value보다 작거나 같으면
			st.pop();								//그 수는 버린다.
		}
		//이렇게 되면 두가지 상황이 발생하는데,
		//스택이 비어있으면 value 오른쪽에 오큰수가 없다는 것으로 value의 오큰수는 -1
		//숫자가 남아있다면 value보다 큰 수가 있는 것으로 오큰수는 st.top
		ans[i] = st.empty() ? -1 : st.top();
		
		//그리고 value를 stack에 push
		st.push(value);
	}

	for (int i = 0; i < n; i++) printf("%d\n", ans[i]);
	printf("\n");
}

 

이상으로 포스팅을 마치겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

스택( 연결 리스트 형)


스택은 링크드리스트로도 구현할 수 있습니다.  링크드리스트의 장점을 그대로 물려받게 되면서요.


어찌 구현하면 될까요? 링크드리스트의 형태를 우선 돌이켜보면 어떻게 구현하는 지 감이 올거에요.




우리는 옆으로 나열된 노드를 아래로 나열된 리스트 형태로 한번 떠올려볼까요? 그리고 맨 처음의 Root노드를 Top으로 정의하게 된다면 연결형태의 Stack을 구현할 감이 오게 됩니다.




구현

node와 stack 구조체를 정의할 겁니다. node는 링크드리스트의 노드와 같습니다. 그리고 stack이라는 구조체는 현재 node형의 top이라는 변수를 갖고 있습니다. top은 역시 stack의 top을 말하지요. 


노드는 이렇게 생겨먹었습니다. 익숙하군요.

struct node {
	int data;
	struct node *next;
};



그리고 스택은 지금 이렇게 생겨먹었죠. 근데 top과 len(길이)밖에 없으니 무언가 공허합니다.

struct stack {
	node *top;
        int len;
        stack(){
                top=NULL;
                len=0;
        }
};


초기값 top은 NULL을 가리키고 스택 크기는 0입니다. 우리는 이 stack이라는 구조체에 연산을 삽입할 겁니다.


스택의 연산에는 isEmpty, push, peek, pop 정도가 있었지요? 추가로 스택의 size를 구하는 연산도 넣겠습니다. 이제 링크드리스트 형태로 구현해볼 것입니다.




1.size

스택의 크기가 얼만큼 되는 가에 대해 질의하는 연산입니다. 

int size() {
		return len;
}


그냥 스택의 길이만 반환하면 됩니다.


2. isEmpty

스택에 노드가 없느냐 물어보는 질의형태의 연산입니다. 만약 스택이 비어있다면 스택이 모두 비워져있는 상태이므로 top은 NULL을 가리키게 됩니다. 또는 스택의 길이를 이용하는 겁니다. 길이가 0이면 비워진 스택이 됩니다. 코드는 간단합니다.

int isEmpty() {
		//return top == NULL;
                return len == 0;
}

이 코드를 따로 구현한 이유는 while 조건문을 간단하게 구현하기 위함입니다.


3. push

스택에 노드를 추가하는 연산입니다. 어떤 노드를 추가하고 그 노드의 next는 이미 기존의 top을 가리킵니다. 그리고 top을 새로운 노드로 가리키게 만들어주면 됩니다.


그림으로 더 쉽게 보도록 하시죠. 






새로운 노드가 스택에 추가되려고 기다리고 있습니다. 새로들어오는 노드의 next를 기존의 top이 가리키고 있는 노드를 가리키도록 바꾸어줍니다. 그리고 top을 다시 새로운 노드로 바꾸어주는 것이죠. 그러면 그림은 아래처럼 바뀌게 됩니다.



스택에 하나 넣었으니 스택의 크기를 하나 증가시킵니다(len++).


이것을 코드로 구현한 것이 아래의 코드입니다.

void push(int data) {
		
		node *newNode = (node*)malloc(sizeof(node));
		newNode->data = data;
		newNode->next = top;
		top = newNode;
                len++;
}


4. pop

처음 top에 있었던 데이터를 복사하고 난 후 기존 top의 next를 다시 top이 가리키도록 바꾸면 됩니다.

그리고 나서는 기존의 top 노드를 삭제해야하는 데 그것은 free함수만 사용하면 됩니다.




스택에서 하나 꺼내니까 사이즈가 줄겠죠?(len--);

pop의 연산을 아래의 코드로 구현했습니다. 

int pop() {
		
		int ret;
		if (isEmpty()) return INF;
		node *here = top;
		ret = here->data;
		top = here->next;
		free(here);
                len--;
		return ret;
	}


우선 스택이 비어있는지 확인하는 작업이 필요합니다. 스택이 비어있는데, 거기서 node를 꺼낼 수 없거든요. 새롭게 확인하는 코드를 쓰기 보다 그전에 isEmpty연산 이용하도록 합시다. 스택이 비어있다면 INF라는 무한정 큰값을 반환합니다. 




그리고 어떤 케이스에 따라서는 node 자체를 반환해야할 때가 있습니다. 그럴때는 위의 코드처럼 free함수를 하면 안됩니다. 메모리가 해제된 상태로 node 포인터를 반환하게 되어버리기 때문입니다.


5. peek

peek 연산은 pop에서 별반 다를것이 없습니다. 그냥 노드를 삭제하지 않고 데이터만 뽑아내면 되기 때문이죠.


전체코드

전체코드(C++)는 아래와 같습니다. 

#include <iostream>
#define INF 987654321

using namespace std;

struct node {
	int data;
	struct node *next;
};

struct stack {
	node *top;
        int len;
        stack(){
                top = NULL;
                len = 0;
        }
        int size() {
		return len;
	}
	int isEmpty() {
		//return top == NULL;
                return len==0;
	}

	void push(int data) {
		
		node *newNode = (node*)malloc(sizeof(node));
		newNode->data = data;
		newNode->next = top;
		top = newNode;
                len++;
	}

	int pop() {
		
		int ret;
		if (isEmpty()) return INF;
		node *here = top;
		ret = here->data;
		top = here->next;
		free(here);
                len--;
		return ret;
	}

	int peek() {
		if (isEmpty()) return INF;
		node *here = top;
		return here->data;
	}
};

stack st = { NULL };


int main() {
	
	cout << st.peek() << endl;
	for (int i = 1; i <= 3; i++) 
		st.push(i);
	

	cout << st.pop() << endl;
	cout << st.pop() << endl;
	for (int i = 4; i <= 6; i++)
		st.push(i);

	while (!st.isEmpty())
		cout << st.pop() << endl;

}
main함수에서의 결과를 예측해보세요.  배열로 구현한 스택과 동일한 연산을 합니다.

지금까지 링크드리스트 형태의 스택이 어떻게 동작하고 구현되는 지 알아보았습니다. 딱히 어려운 것은 없었지요?


반응형
블로그 이미지

REAKWON

와나진짜

,

스택(Stack) 개념


자료구조에서 스택은 어떤 자료구조일까요? 스택은 영어 단어 자체에서 힌트를 얻을 수 있습니다.


stack

1. (보통 깔끔하게 정돈된) 무더기   2. 많음, 다량   3. (특히 공장의 높은) 굴뚝
1. 쌓다


stack은 쌓다, 무더기 이런 의미로 쓰이죠.


이 의미가 자료구조에 그대로 반영이 됩니다. 

우리는 어떤 물건을 쌓는다면 가장 먼저 쌓은 물건은 아래에 깔리고 가장 최근 쌓은 물건은 위에 쌓이게 되지요.


꺼낼때는 어떨까요?

나중에 쌓은 물건을 먼저 꺼내겠죠?


이처럼 나중에 쌓은 것이 먼저 나오고 먼저 쌓은 물건은 더 나중에 나온다 라는 개념이 LIFO(Last In First Out)이라고 합니다. 그와 반대, 먼저 들어온 것이 먼저 나간다의 개념은 FIFO(First In First Out)라고 합니다.




종류

스택은 두 가지 정도의 종류가 존재합니다.


● 배열형태의 스택

● 연결리스트 형태의 스택


두 형태는 스택을 구현하는 기능은 같더라도 효율성에서 다릅니다. 


배열형 스택은 우선 접근 속도가 빠릅니다. 하지만 다시 크기를 확장하거나 줄일때 효율성이 떨어지게 되죠. 생각해보세요. 스택 크기가 10인 스택이 데이터를 더 저장하기 위해 스택의 길이를 늘리게 되면 우선 배열의 크기를 다시 할당하고 난 후에 값을 복사해야합니다. 길이가 10인 스택이라 그렇지 크기가 크면 클 수록 성능이 저하되는 것을 알 수 있습니다.


그리고 사용자가 얼마만큼의 스택 크기가 필요한지 예측을 할 수도 없습니다.


반면 연결리스트형 스택은 가변적으로 스택의 크기를 줄였다 늘였다 할 수 있습니다. 반면 값에 접근하려면 그 효율성이 떨어지게 됩니다. 연결리스트 형 스택을 구현하려면 linked list 자료구조를 우선 알아야합니다.


스택의 사용처

스택은 함수의 호출 뿐만 아니라 수식에서도 사용되며 사용처는 많습니다. 우리가 즐겨쓰는 인터넷의 뒤로가기 역시 스택 자료구조가 쓰이게 됩니다. 심지어 미로를 찾는데에도 스택을 쓸 수 있습니다.


구현

기본적으로 스택은 여러 언어에서 표준 라이브러리로 제공이 되긴 합니다만 어떻게 구현이 되어있는지 정도는 이해하고 있어야합니다.


아래의 코드는 배열형 스택을 c++ 코드로 구현한 것입니다. 



#include <iostream>
#define stack_size 100
using namespace std;

struct stack {
	int top = -1;
	int arr[stack_size];
	void push(int data) {
		if (top == stack_size-1) {
			printf("stack is full\n");
			return;
		}
		arr[++top] = data;
	}
	int pop() {
		if (empty()) {
			printf("stack is empty\n");
			return -1;
		}
		return arr[top--];
	}
	int peek() {
		if (empty()) {
			printf("stack is empty\n");
			return -1;
		}
		return arr[top];
	}
	bool empty() {
		return top <= -1;
	}
};
int main() {
	stack st;
	//현재 스택은 비워져있는 상태
	cout << "is stack empty? "<<st.empty() << endl;
	cout << st.pop() << endl;
	cout << st.peek() << endl;

        //for 루프가 돌면 스택은 1개만 저장 가능한 상태
	for (int i = 0; i < stack_size-1; i++)
		st.push(i + 1);

        cout << endl;
        cout << "after for loop"<<endl;
	st.push(15);
        //스택은 전부 채워져 있는 상태
	st.push(120);
        //스택에서 한 개가 비워짐, 1개 채울 수 있는 상태
	st.pop();

	st.push(120);

	cout<<endl;
        //스택이 비워지면 while루프 종료
	while(!st.empty())
		cout << st.pop() << endl;
	
}




stack은 다음과 같은 연산을 하게 됩니다.

push : 데이터를 쌓습니다.
pop : 데이터를 하나 꺼내옵니다.
peek : 데이터를 하나 봅니다. 꺼내오지는 않습니다.
empty : 스택이 비어있는 지 확인합니다.

위의 코드를 이해하기 쉽게 그림으로 표현해보겠습니다. 


1. 초기 스택의 상태는 이런 그림입니다. top은 맨 아래, -1이라는 배열의 인덱스로 쓸 수 없는 위치에 있지요.

 

그래서 stack.empty()는 true(1)입니다. stack에서 peek이나 push 연산을 해도 데이터가 없기 때문에 stack is empty라는 메시지를 보게 되고 -1이 반환됩니다.

2. 그 이후 for 루프로 스택의 최상위 위치를 빼고 전부 push연산으로 채웁니다.

top은 아래의 위치에 있습니다.



3. 이제 15을 push하게 되면 스택은 전부 채워졌고, 다음 120은 모두 채워져있기 때문에 스택에 쌓이지 못합니다.





4. 이후 스택에서 pop연산을 했으니 공간이 하나 남고, 다시 120을 push하면 스택에 들어가게 됩니다.


5. 이제 while루프를 돌며 pop연산을 하게 됩니다. 스택에 데이터가 남아있는 동안 출력하게 됩니다. 최종적으로 스택은 초기상태가 됩니다.




코드를 잘 따라가 보면 이해하실 겁니다.


배열 스택을 조금 이해하셨나요?

반응형
블로그 이미지

REAKWON

와나진짜

,