실버를 목표로 약 5일간 꾸준히 알고리즘 문제들을 푼 결과 실버를 달성하게 되었다 !
목표는 3일이었지만 수준이 높아질수록 간단해보이는 문제도 예외처리나 반복문 내의 구조적 오류로 나를 힘들게 했다.
백준을 풀다보니 내가 이 분야에 재능이 없다는 사실을 느낄 수 있었다. 하지만 소수의 사람을 제외하고는 모두 이런 시련을 겪으며 성장했을 것이라고 생각한다. 더욱 겸손한 자세로 꾸준히 노력해야겠다.
이제 자료구조와 알고리즘을 다시 공부하면서 배우는 족족 백준 문제를 풀며 복습차원에서 직접 구현해보면서 손에 익힐것이다.
자료구조와 알고리즘의 이론을 학습하고 나면 C++로 전향하여 골드 문제들을 수월하게 풀 수 있는 수준이 되는것을 목표로 백준에만 몰두하려고한다.
이 과정에서 영어 공부와 경제 공부도 틈틈히 하고 운동도 꾸준히 해 아쉬움 없는 군생활을 할 것이다.
복학해서는 쌓은 기반으로 학점을 수월하게 따고, 전공 공부를 하며 군대에서 만든 토익 930점 이상의 성적과 준수한 코딩 테스트 실력을 만들 생각이다.
전역 후에는 python과 같이 효율은 떨어지더라도 더욱 실용적인 언어로 전향하여 몇 년간 쌓아온 알고리즘 실력을 바탕으로 웹이나 앱 등을 다루는 개인 프로젝트도하고, 각종 서비스도 출시해보며 내 실력을 수익 창출에 사용해보고 싶다.
또한 여행이나 알바를 통한 경험을 쌓아 사업에 대한 꿈도 동시에 키우고 싶다.
이 모든것을 이루기 위해서는 더 열심히 해야겠다.
나의 velog 작성 목적은 기록과 복습이기에 배운점들을 정리해보려고 한다.
입력 받는대로 계속 더해주는 프로그램을 짤때 무작정 while(1)을 사용하면 안된다.
while(scanf("%d %d", &a, &b) != -1)
while(scanf("%d %d", &a, &b) != EOF)
받은 문장이 없을 때 종료되도록 -1 혹은 EOF(End Of File)
개념을 사용해야한다.
EOF
란 주어진 입력 파일만 갖고 입력을 받을 때 더이상 읽을 수 있는 데이터가 없는 경우. 파일의 끝을 의미한다.
i를 사용해야하는 경우 아닐때는
for(int i=0; i<m; i++) 대신
while(m--)로 간단히 표현 가능
서로 다른 요소가 몇 개 인지 출력하는 예제에서 count++
를 이용해 구현하는 점은 좋았으나 break
문의 흐름을 잘 이해하지 못하여 실패했다.
break
가 되어 안쪽의 for
문이 해제되더라도 뒷 부분은 실행이 되기에 break
문보다 실제로 필요했던건 b배열에 a의 요소를 넣을지 말지를 결정해줄 조건이었다.
실패
#include <stdio.h>
int main(void){
int a[10], b[10], count = 0;
for(int i=0; i<10; i++){
scanf("%d", &a[i]);
a[i] %= 42;
}
for(int i=0; i<10; i++){
for(int j=0; j<i; j++){
if(a[i] == a[j]) break;
}
b[count++] = a[i];
}
printf("%d", count);
return 0;
}
는 성공의 어머니
#include <stdio.h>
int main(void){
int a[10], b[10], count = 0;
for(int i=0; i<10; i++){
scanf("%d", &a[i]);
a[i] %= 42;
}
for(int i=0; i<10; i++){
int isUnique = 1;
for(int j=0; j<i; j++){
if(a[i] == a[j]){
isUnique = 0;
break;
}
}
if(isUnique){
b[count++] = a[i];
}
}
printf("%d", count);
return 0;
}
이때 첫 번째 for문에서 int isUnique = 1 를 선언해 flag로 사용하는 방법을 배웠고 이후의 문제에서도 이 방법을 정말 유용하게 써먹었다.
#include <stdio.h>
int main(void){
int n, m, start, end, a[100], b[100] = {0, };
scanf("%d", &n);
scanf("%d", &m);
for(int i=0; i<n; i++){
a[i] = i+1;
}
for(int i=0; i<m; i++){
int j = 0;
scanf("%d", &start);
scanf("%d", &end);
start--;
end--;
for(int i = end; i>=start; i--){
b[j++] = a[i];
}
j = 0;
for(int i = start; i<=end; i++){
a[i] = b[j++];
}
}
for(int i = 0; i<n; i++) printf("%d ", a[i]);
return 0;
}
for
문 내에서 0에서 특정 수까지 점차 올라가며 변수를 사용하고 싶다면, 그 변수를 초기화 하는 내용을 반복문 내에서 사용하면 안된다.
for(int i = end; i>=start; i--){
int j = 0;
b[j++] = a[i];
}
이런 식으로 사용하였다가 b 배열에 요소가 잘 들어가지 않아 애썼다.
지금 돌아보니 정말 멍청한 실수이다.
#include <string.h>
string.h
를 참조하면 strlen(변수)
를 통해 char*
형 함수의 글자 수를 구할 수 있다.
이 기능도 java의 length
처럼 정말 유용하게 사용하게 되었다.
sum+=word[i] - '0';
char형을 int형과 연산하기 위한 형 변환에서
아스키 코드의 특성을 활용하면 편하게 변환할 수 있다.
문자열 0이 48이고 1이 49이기에
-'0'을 할 시에 정수값으로 연산이 되기 때문이다.
이런 문자열의 연산도 이때 처음 알게됐는데 이후로 유용하게 사용했다.
알파벳 소문자로만 이루어진 단어 S가 주어진다. 각각의 알파벳에 대해서, 단어에 포함되어 있는 경우에는 처음 등장하는 위치를, 포함되어 있지 않은 경우에는 -1을 출력하는 프로그램을 작성하시오.
#include <stdio.h>
#include <string.h>
int main(void){
int a;
char word[100];
scanf("%s", word);
a = strlen(word);
for(int i='a'; i<='z'; i++){
int flag = 0;
int index = 0;
for(int j=0; j<a; j++){
if(word[j] == i){
flag = 1;
index = j;
break;
}else index = -1;
}
if(flag == 0) printf("%d ", index);
else printf("%d ", index);
}
return 0;
}
남들이 사용한 예제를 보고 for문 말고 while문도 적재적소에 잘 활용해봐야겠다고 느낀 예제이다.
#include <stdio.h>
#include <string.h>
int main(void){
int count = 0;
char word[1000001];
scanf("%[^\n]s", word);
if(word[0] != ' ') count++;
for(int i=1; i<strlen(word); i++)
if(word[i-1] == ' ' && word[i] != ' ') count++;
printf("%d", count);
return 0;
}
%[^\n]s
: 공백까지 받아들이는 방법
맨 앞에 빈칸이 있는 경우 등 예외처리를 잘 해야한다.
char 형에서 종단 문자 \0이 있으므로 조건이 최대 10이면 11로 여유롭게 배열을 선언해야한다.
논리 연산자 잘 활용하기
1.word[i] == ' '
2.word[i-1] == ' ' && word[i] != ' '
공백이면서 다음에는 글자가 나오는 위치를 조건으로 수정함으로써 예외가 엄청 줄어들었다.
상근이의 동생 상수는 수학을 정말 못한다. 상수는 숫자를 읽는데 문제가 있다. 이렇게 수학을 못하는 상수를 위해서 상근이는 수의 크기를 비교하는 문제를 내주었다. 상근이는 세 자리 수 두 개를 칠판에 써주었다. 그 다음에 크기가 큰 수를 말해보라고 했다.
상수는 수를 다른 사람과 다르게 거꾸로 읽는다. 예를 들어, 734와 893을 칠판에 적었다면, 상수는 이 수를 437과 398로 읽는다. 따라서, 상수는 두 수중 큰 수인 437을 큰 수라고 말할 것이다.
두 수가 주어졌을 때, 상수의 대답을 출력하는 프로그램을 작성하시오.
#include <stdio.h>
#include <math.h>
int main(void){
int a = 0, b = 0;
char input[3];
scanf("%s", input);
for(int i = 0; i<3; i++){
a += pow(10, i)*(input[i]-'0');
}
scanf("%s", input);
for(int i = 0; i<3; i++){
b += pow(10, i)*(input[i]-'0');
}
if(a>b) printf("%d", a);
else printf("%d", b);
return 0;
}
거듭제곱 사용방법
#include <math.h>
참조
pow(a, b);
//a의 b제곱
나의 코드
#include <stdio.h>
#include <string.h>
int main(void){
char a[15];
int sum = 0;
scanf("%s", a);
for(int i=0; i<strlen(a); i++){
if(a[i]>=65 && a[i]<=67) sum += 3;
else if(a[i]>=68 && a[i]<=70) sum += 4;
else if(a[i]>=71 && a[i]<=73) sum += 5;
else if(a[i]>=74 && a[i]<=76) sum += 6;
else if(a[i]>=77 && a[i]<=79) sum += 7;
else if(a[i]>=80 && a[i]<=83) sum += 8;
else if(a[i]>=84 && a[i]<=86) sum += 9;
else if(a[i]>=87 && a[i]<=90) sum += 10;
}
printf("%d", sum);
return 0;
}
남의 코드
#include <stdio.h>
int main()
{
int result = 0;
int dial[26] = {2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7,
8, 8, 8, 9, 9, 9, 9};
char input;
while(scanf("%c ", &input) != -1)
result += (dial[input - 'A'] + 1);
printf("%d", result);
}
배열을 이런식으로 사용할 수도 있다는 것을 깨달았다.
아래처럼 scanf
를 따로 사용하지않고 while
을 사용할때 조건식으로 사용할 수도 있고 이렇게 사용한다면 *char형 변수를 만들 필요조차 없다는걸 배웠다.
while(scanf("%c ", &input) != -1)
#include <stdio.h>
int main() {
char a[101];
while(scanf("%[^\n]", a) != EOF) {
printf("%s\n", a);
getchar();
}
return 0;
}
getchar()
없이 코드를 짜니 처음 숫자만 반복 출력되는 구조적 문제가 생겼다.
그래서 더 알아본 결과, 키보드를 통해 값을 입력하고 나서 엔터키를 칠 때 이 엔터를 \n
으로 받아들여 버퍼에 계속 남아있게되는 것에서 발생하는 문제라고 한다.
이 현상을 해결하기 위해서는 scanf("%d", &number);
와 scanf("%c", &character);
사이에서 버퍼를 비워주어야 한다. 이때 버퍼를 비우는 작업은 getchar()
함수로 해결할 수 있다. getchar()
함수는 변수=getchar();
의 형태로 활용해 입력값을 변수에 저장하는데, 입력값을 받기만 하고 아무 변수에도 저장하지 않는 형태로 만들면 \n
을 비우는 효과를 낼 수 있다.
쉬운 문제처럼 보였는데 생각보다 고려할게 많앗다. 버퍼와 EOF에 대해 알아볼 수 있는 문제였다.
#include <stdio.h>
int main() {
int a;
scanf("%d", &a);
for(int i=0; i<a; i++){
for(int j=0; j<a-1-i; j++) printf(" ");
for(int j=0; j<1+2*i; j++) printf("*");
printf("\n");
}
for(int i=0; i<a-1; i++){
for(int j=0; j<1+i; j++) printf(" ");
for(int j=0; j<(2*a-3)-2*i; j++) printf("*");
printf("\n");
}
return 0;
}
바로 n
으로 생각하면 너무 어려우니
먼저 5와 같은 피라미드를 만드는걸 목표로 두고
반복문안에 변수들을 넣어 별과 공백을 찍은 다음
한 줄이 넘어갈때마다 각각의 수가 어떻게 변하는지를 반복문의 변수인 i와 연관지어 풀고
n을 대입해주면 쉽게 풀 수 있다.
for문의 구동이 헷갈려 횟수가 안될때 j를 위에서부터 --하면서 for문으로 구현하려고 시도했었는데 결국에는 우리가 필요한건 공백과 별의 개수이기에 사실은 아무런 영향이 없었다는 점도 깨우쳤다.
그냥 헷갈리지 않게 무조건 0에서부터 i와 연관된 조건을 잘 정하고 ++해가면서 구하는게 가장 쉽다.
#include <stdio.h>
#include <string.h>
int main() {
char s[101];
int ans = 0, i = 0;
scanf("%s", s);
while (i < strlen(s)) {
if (s[i] == 'c') {
if (s[i + 1] == '=')
i++;
else if (s[i + 1] == '-')
i++;
}
else if (s[i] == 'd') {
if (s[i + 1] == '-')
i++;
else if (s[i + 1] == 'z' && s[i + 2] == '=')
i += 2;
}
else if (s[i] == 'l') {
if (s[i + 1] == 'j')
i++;
}
else if (s[i] == 'n') {
if (s[i + 1] == 'j')
i++;
}
else if (s[i] == 's') {
if (s[i + 1] == '=')
i++;
}
else if (s[i] == 'z') {
if (s[i + 1] == '=')
i++;
}
ans++;
i++;
}
printf("%d", ans);
}
처음에 고안했던 방법이지만 너무 조건이 많아져 더 효율적인 방법은 없을까 고민하게되었다.
하지만 다른 사람들이 구현한 알고리즘도 대부분 위와 같이 되어있었다.
조금 더 효율적인 방법도 있긴 했는데 strlen
즉 길이를 결과로 정해놓고
=이나 특정 값이 나올때마다 --해서 줄여가는 방법이었다.
#include <stdio.h>
#include <string.h>
int main() {
int len, max = 0, max_index = 0, flag = 0;
char a[1000001];
int b[26] = {0, };
scanf("%s", a);
len = strlen(a);
for(int i=0; i<len; i++){
if(a[i]>='a' && a[i] <= 'z') a[i] -= 'a' - 'A';
}
for(int i=0; i<len; i++){
for(int j='A'; j<='Z'; j++){
if(a[i]==j) b[j-'A']++;
}
}
for(int i=0; i<26; i++){
if(max < b[i]){
max = b[i];
max_index = i;
}
}
for(int i=0; i<26; i++){
if(max == b[i]) flag++;
}
if(flag<2) printf("%c", max_index+'A');
else printf("?");
return 0;
}
시간 초과로 틀렸는데, strlen()
함수 실행 시간이 코딩테스트에서는 꽤 긴가보다 변수에 대입하여 재사용하니 시간 초과 문제가 사라졌다.
변수에 담아 사용하는 습관을 길러야겠고, 아스키 코드를 이제 확실하게 사용할 수 있을 것 같다.
나의 코드
#include <stdio.h>
#include <string.h>
int main() {
int n, result = 0;
char a[101];
scanf("%d", &n);
for(int i=0; i<n; i++){
scanf("%s", a);
int count[26] = {0, }, flag = 1, len = strlen(a);
for(int j=0; j<len; j++){
count[a[j]-'a']++;
}
for(int j=0; j<len;){
if(flag==0) break;
char c = a[j];
for(int k=0; k<count[c-'a']; k++){
if(a[j]==c){
j++;
}else{
flag = 0;
break;
}
}
}
if(flag == 1) result++;
}
printf("%d", result);
return 0;
}
내 알고리즘은 각 단어가 나온 횟수를 조사한다.
그 후에 알파벳의 횟수만큼 글을 넘기다가 알파벳 횟수보다 적은 수의 알파벳만이 연속될때 flag를 0으로 만들고 이중 for문을 모두 break 한다.
이 코드는 알파벳을 모두 확인해야하는 사전의 과정이 필요하다.
또한 구현 과정에서 반복문이 너무 복잡해 구조적 오류가 많이 발생하였다.
반면에 chat gpt가 알려준 코드는 뭔가 재귀와 같은 느낌으로 간단하게 문제를 해결해 나에게 충격을 주었기에 이 내용을 기록하고 싶었다.
chat gpt의 갓 코드
#include <stdio.h>
#include <string.h>
int main() {
int n, result = 0;
char a[101];
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%s", a);
int visited[26] = {0}; // 각 알파벳의 등장 여부를 저장하는 배열
int len = strlen(a);
int flag = 1;
for (int j = 0; j < len; j++) {
if (visited[a[j] - 'a'] == 0) { // 처음 등장하는 알파벳일 경우
visited[a[j] - 'a'] = 1; // 등장한 것으로 표시
while (j + 1 < len && a[j] == a[j + 1]) {
j++; // 동일한 알파벳이 연속으로 나타날 경우 j를 증가시킴
}
} else {
flag = 0; // 이미 등장한 알파벳이 다시 등장하면 그룹 단어가 아님
break;
}
}
if (flag == 1) result++;
}
printf("%d\n", result);
return 0;
}
while
문에서 for
문의 조건을 저런식으로 사용할 수 있다는 걸 처음 알게 되었고 내부에서 j
를 증가시켜 예외를 처리함과 동시에 자연스럽게 for
문이 흘러가도록 설계되어있다.
뭔가 재귀를 보는 느낌이랄까.. 나도 이런 깔끔한 코드를 짜고싶다..