TIL
05.22 TIL (ch.2 팀프로젝트 day2)
2026. 5. 22. 19:52

*오늘의 코드카타*

주어진 수가 1이 될 때까지 입력된 수가 짝수라면 2로 나누고, 입력된 수가 홀수라면 3을 곱하고 1을 더한 후 결과로 나온 수에 같은 작업을 1이 될 때까지 반복한다면, 이 작업을 몇 번이나 반복해야 하는지 반환하는 함수 solution을 완성해주세요. 단, 주어진 수가 1인 경우에는 0을, 작업을 500번 반복할 때까지 1이 되지 않는다면 -1을 반환해 주세요.

입력된 수, num은 1 이상 8,000,000 미만인 정수입니다.

 

#include <string>
#include <vector>

using namespace std;

int solution(int num) {
    int answer = 0;
    
    long long n = num;
    
    if (n == 1) {
        return 0;
    }
    
    while (n != 1 && answer < 500) {
        if (n % 2 == 0) {
            n /= 2;
        } else {
            n = (n*3) + 1;
        }
        answer++;
    }
    if (n == 1) {
        return answer;
    } else {
        return -1;
    }
}

콜라즈 추측에 관한 문제이다.

어렵게 생각하지 말고, 어떤 조건을 만족할 때까지 반복해야 하는 경우 while을 쓰는 게 편하다.

또한 num을 입력받더라도 중간 계산 과정에서 int 값을 초과할 수 있어서 long long n = num; 을 사용했다.


*그 외에 오늘 한 것*

[프로그래머스]

수열과 구간 쿼리 4

먼저 const auto& range for 문을 사용해서 배열을 순회하면서 i를 k로 나누어 나머지가 0일 때 arr[i]에 1을 더하는 식으로 구성했다.

#include <string>
#include <vector>

using namespace std;

vector<int> solution(vector<int> arr, vector<vector<int>> queries) {
    for (const auto& query : queries) {
        int s = query[0];
        int e = query[1];
        int k = query[2];
        
        for (int i = s; i <= e; i++) {
            if (i % k == 0) {
                arr[i]++;
            }
        }
    }
    return arr;
}

다르게 구성해보자면 다음과 같은 방법이 있다. 처음에 s, e, k 를 쓰지 말고 q[0], q[1]. q[2] 를 직접 사용하는 것이다.

#include <string>
#include <vector>

using namespace std;

vector<int> solution(vector<int> arr, vector<vector<int>> queries) {
    vector<int> answer;
    for(auto q:queries){
        for(int i = q[0]; i<=q[1]; i++){
            if(i%q[2] == 0){
                arr[i]++;
            }
        }
    }
    return arr;
}

 

배열 만들기 2

내 아이디어 : 벡터 원소를 문자열로 변환해서 find 함수 사용해서, 0과 5를 찾고 없으면 -1이 담긴 배열을 return.

(1) to_string을 사용해서 문자 0, 5만 있는지 확인 후 있으면 배열에 i 값 추가하기

#include <string>
#include <vector>

using namespace std;

vector<int> solution(int l, int r) {
    vector<int> answer;
    
    for (int i = l; i <= r; i++) {
        string strNum = to_string(i);
        bool isZeroFive = true;
        
        for (char c : strNum) {
            if (c != '0' && c != '5') {
                isZeroFive = false;
                break;
            }
        }
        if (isZeroFive) {
            answer.push_back(i);
        }
    }
    if (answer.empty()) {
        answer.push_back(-1);
    }
    return answer;
}

(2) 10으로 나누면서 나머지 값으로 숫자 찾아내기

이 경우에는 10으로 나누면서 나머지가 0또는 5가 아닌 순간 그냥 바로 반복문을 탈출하도록 만들어야한다.

#include <vector>

using namespace std;

vector<int> solution(int l, int r) {
    vector<int> answer;

    for (int i = l; i <= r; i++) {
        int temp = i;
        bool isOnlyZeroAndFive = true;
        

        while (temp > 0) {
            int digit = temp % 10; 
            if (digit != 0 && digit != 5) {
                isOnlyZeroAndFive = false;
                break; 
   			}
            temp /= 10; 
            
        if (isOnlyZeroAndFive) {
            answer.push_back(i);
        }
    }

    if (answer.empty()) {
        answer.push_back(-1);
    }
    
    return answer;
}

(3) **접근 방식 바꾸기(5와 0으로 이루어진 숫자를 먼저 찾고 조건에 맞는지 체크하고 넣기)**

즉, queue를 활용한 BFS 방식을 적용해보자.

0과 5를 하나씩 넣어보면서 l~r 까지의 값에 해당하면 그 값을 반환하는 방식이다.

초기값을 0과 5로만 이루어진 자연수 중 가장 작은 수인 5를 큐에 넣고 시작하고,

범위를 초과하는 것을 막기 위해서 가지치기를 실시한다. -> if(n>r) continue;

그리고 이미 가지치기를 해놨으므로 if(n >= l ) 조건만 만족하면 정답 배열에 추가한다.

이후, 가지치기를 해놓은 상태에서 q.push(n*10);, q.push(n*10 + 5); 이렇게 현재 숫자 n의 뒤에 0과 5를 붙인 새로운 숫자 2개를 만들어서 큐의 대기열에 추가한다. -> 조건문을 만족하는 것만 통과시켜서 정답 배열에 추가한다.

 

 

카운트 업

#include <string>
#include <vector>

using namespace std;

vector<int> solution(int start_num, int end_num) {
    vector<int> answer;
    
    for (int i = start_num; i <= end_num; i++) {
        answer.push_back(i);
    }
    return answer;
}

단순한 문제여서 그냥 풀었는데, 다른 분들의 풀이를 보니까 신기(?)한게 있어서 좀 더 알아보았다.

#include <bits/stdc++.h>
using namespace std;

vector<int> solution(int start, int end) {
    vector<int> answer(end - start + 1);
    iota(answer.begin(), answer.end(), start);
    return answer;
}

1. #include <bits/stdc++.h> : C++의 거의 모든 표준 라이브러리(vector, string, queue, numeric 등등...)를 한번에 불러오는 만능 헤더 파일. 보통 알고리즘 문제를 풀면 이것 저것 추가할 헤더들이 많을 때 관행적으로 사용한다.

2. vector<int> answer(end - start + 1); : 벡터의 메모리 재할당 과정을 아예 생략해서 속도를 더 빠르게 할 수 있도록 메모리를 재할당 하는 과정이다.

3. iota 함수 : iota(이오타)는 라이브러리에 포함된 함수로, 지정된 구간을 특정 시작점부터 1씩 증가하는 값으로 쫙 채워주는 역할을 한다. push_back을 써서 하나씩 내가 채우는게 아니라, 그냥 처음값, 끝값 줄테니까 엑셀마냥 + 마우스 눌러서 쭉 드래그 한다고 생각하면 된다.

 

콜라츠 수열 만들기

#include <string>
#include <vector>

using namespace std;

vector<int> solution(int n) {
    vector<int> answer;
    
    answer.push_back(n);
    
    while (n != 1) {
        if (n % 2 == 0) {
            n /= 2;
        }
        else {
            n = 3 * n + 1;
        }
        answer.push_back(n);
    }
    return answer;
}

 

배열 만들기 4

문제 읽는 연습용으로 좋은 거 같다. 처음에 읽는데 뭔말인지를 몰랐다. 이런 경우에는 입출력 예를 보고 직접 넣어보면서 시뮬레이션 해보는 게 좋은 것 같다.

배웠던 개념인 stack을 사용해서 풀 수 있다.

#include <string>
#include <vector>

using namespace std;

vector<int> solution(vector<int> arr) {
    vector<int> stk;
    int i = 0;
    
    while (i < arr.size()) {
        if (stk.empty()) {
            stk.push_back(arr[i]);
            i++;
        }
        else {
            if (stk.back() < arr[i]) {
                stk.push_back(arr[i]);
                i++;
            }
            else {
                stk.pop_back();
            }
        }
    }
    
    return stk;
}

이 코드는 알고리즘에서 자주 쓰이는 단조 증가 스택을 만드는 과정이다.

stk.empty() : 스택이 비어있는 경우 무조건 현재 원소인 arr[i]를 넣고, 다음 원소를 확인하기 위해 i를 1 증가시킨다.

stk.back() < arr[i]: 스택의 가장 마지막 원소가 새로 들어올 값보다 작다면 오름차순이 유지되므로, 값을 그대로 스택에 넣는다.

stk.back() >= arr[i] : 스택의 마지막 원소가 새 원소보다 크거나 같으면 오름차순을 유지하기 위해 기존 원소를 빼버린다. 그리고 다음번 while 루프에서 같은 arr[i]를 가지고(i가 증가되지 않았음) 스택의 다음 마지막 원소와 다시 크기를 비교한다.

 

간단한 논리연산

#include <string>
#include <vector>

using namespace std;

bool solution(bool x1, bool x2, bool x3, bool x4) {
    bool answer = true;
    answer = (x1 | x2) & (x3 | x4);
    return answer;
}

논리 연산자를 사용하면 그 결과는 true or false라는 점을 이용. 문제에서 주어진 연산자를 그대로 옮겨 적은 것과 같다.

 

주사위 게임 3

문제를 보고서는 우선 주사위를 4번 굴려서 같은 수가 몇개 나왔는지 세어보고 그 케이스별로 분류하여 계산 공식을 작성하기로 했는데, 코드 작성이 너무 길어지고 알아보기도 어려워서 다른 방향으로 수정했다. 컨테이너와 정렬 방식을 활용하는 편이 더 좋아보였다. 그리고, 절대값이 나오니까 애초에 배열안에 값을 집어넣을 때 오름차순으로 정렬되도록 sort()를 사용했다.

(정렬 방식)

#include <string>
#include <vector>
#include <cmath>
#include <algorithm>

using namespace std;

int solution(int a, int b, int c, int d) {
    vector<int> v = {a, b, c, d};
    sort(v.begin(), v.end());
    
    if (v[0] == v[3]) {
        return 1111*v[0];
    }
    else if (v[0] == v[2]) {
        int p = v[0];
        int q = v[3];
        return (10*p + q) * (10*p + q);
    }
    else if (v[1] == v[3]) {
        int p = v[3];
        int q = v[0];
        return (10*p + q) * (10*p + q);
    }
    else if (v[0] == v[1] && v[2] == v[3]) {
        int p = v[0];
        int q = v[2];
        return (p+q) * abs(p-q);
    }
    else if (v[0] == v[1]) {
        return v[2] * v[3];
    }
    else if (v[1] == v[2]) { 
        return v[0] * v[3];
    }
    else if (v[2] == v[3]) { 
        return v[0] * v[1];
    }    

    else {
        return v[0]; 
    }
}

근데 조금 더 고민해보니까 정렬하면서 중복된 값을 제거하는 성질을 이용하면 좀 더 쉽게 구할 수도 있지 않을까? 생각도 해보았다.

 

 

그런데 알고보니, 반복되어 나온 주사위의 숫자를 count 하고, 해당 count 를 이용해서 문제를 풀어나가는 굉장히 좋은 방법이 있었다. count를 기준으로 주사위 숫자를 그룹화하는 table 배열을 사용하는 것이다.

#include <string>
#include <vector>
#include <cmath>
#include <algorithm>

using namespace std;

int solution(int a, int b, int c, int d) {
    int answer = 0;

    vector<int> table(6);
    table[a-1]++;    table[b-1]++;    table[c-1]++;    table[d-1]++;
    vector<vector<int>> nums(5);    
    nums[table[0]].emplace_back(1);
    nums[table[1]].emplace_back(2);
    nums[table[2]].emplace_back(3);
    nums[table[3]].emplace_back(4);
    nums[table[4]].emplace_back(5);
    nums[table[5]].emplace_back(6);

    if(!nums[4].empty())    
    {
        return 1111*nums[4][0];
    }
    else if(!nums[3].empty())    
    {
        return pow(10*nums[3][0]+nums[1][0],2);
    }
    else if(nums[2].size() == 2)    
    {
        return (nums[2][0]+nums[2][1])*abs(nums[2][0]-nums[2][1]);
    }
    else if(nums[2].size() == 1)
    {
        return nums[1][0]*nums[1][1];
    }
    else
    {
        return *min_element(nums[1].begin(),nums[1].end());
    }

    return answer;
}

1. 주사위 숫자가 각각 몇 번 나왔는지 table에 기록한다. table[a-1]++; table[b-1]++,....

2. vector<vector<int>> nums(5); // nums[table[i]].emplace_back(1~6); : 어떻게 이런 생각을 할 수 있었을까. 하는 포인트였다. 

nums의 인덱스 번호는 주사위가 나온 횟수(0~4)를 의미하게 되고, 각각의 nums에는 해당 횟수만큼 나온 주사위 숫자들이 들어가게 된다.

이렇게 분류를 미리 해버린니까 뒤에서 반복문을 쓸 필요가 없어져 버린다.

3. nums 배열에서 각각의 경우에 수에 맞는 값만 꺼내어서 answer를 반환한다. nums[4] 부터 출발해서 비어있으면 nums[3]으로 가고, ... 결과적으로 아무것도 해당되지 않으면 가장 작은 값을 찾는 방식으로 진행한다.

4. 여기서 push_back을 사용해야겠다고 생각하기 쉬운데, 코드에서 값을 벡터에 넣을 때 push_back 대신 emplace_back을 사용하는 이유가 있다. int 자료형같은 단순한 자료형에서는 성능이 거의 동일하지만 점차 복잡한 자료들을 다룰 수록 emplace_back으로 객체를 다루는 것이 유리하다. 어려운 말은 일단 제쳐두고 복사/이동 연산을 생략할 수 있어서 성능상 push_back 보다 유리하다고 이해했다.

 

 

글자 이어 붙여 문자열 만들기

#include <string>
#include <vector>

using namespace std;

string solution(string my_string, vector<int> index_list) {
    string answer = "";
    for (int idx : index_list) {
        answer += my_string[idx];
    }
    return answer;
}

단순한 문제였다. 이제 반복문 사용해서 하는 거는 어느정도 익숙해진 것 같다.

 

9로 나눈 나머지

#include <string>
#include <vector>

using namespace std;

int solution(string number) {
    int answer = 0;
    int sum = 0;
    
    for (char c : number) {
        sum += c - '0';
    }
    answer = sum % 9;    

    return answer;
}

요새 자주 보는 녀석. 문자에서 '0'을 빼면 고유의 숫자 값이 나온다.

 

문자열 여러 번 뒤집기

처음에는 while문으로 두 숫자 사이를 좁혀가면서 두 숫자의 위치를 바꿔가며 구현하려고 했다.

(1) while문을 이용한 구식 방법

#include <string>
#include <vector>

using namespace std;

string solution(string my_string, vector<vector<int>> queries) {
    string answer = "";
    
    for (const auto& query : queries) {
        int s = query[0];
        int e = query[1];
        
        while (s < e) {
            char temp = my_string[s];
            my_string[s] = my_string[e];
            my_string[e] = temp;
            
            s++;
            e--;
        }
        
    }
    answer = my_string;
    return answer;
}

(2) C++의 swap 이용하기

#include <string>
#include <vector>

using namespace std;

string solution(string my_string, vector<vector<int>> queries) {
    string answer = "";
    
    for (const auto& query : queries) {
        int s = query[0];
        int e = query[1];
        
        while (s < e) {
            swap(my_string[s], my_string[e]);
            
            s++;
            e--;
        }
        
    }
    answer = my_string;
    return answer;
}

(3) C++의 reverse 이용하기

#include <string>
#include <vector>
#include <algorithm>

using namespace std;

string solution(string my_string, vector<vector<int>> queries) {
    string answer = "";
    
    for (const auto& query : queries) {
        int s = query[0];
        int e = query[1];
        
        reverse(my_string.begin() + s, my_string.begin()+e+1);
    }    
        
    answer = my_string;
    return answer;
}

 


 

[atanny]

UPROPERTY 매크로의 주요 역할 : 가비지 컬렉션 대상으로 등록, 에디터에서 편집 가능, 시리얼라이제이션 지원

UPROPERTY, UFUNCTION, USTRUCT, UENUM 매크로

BlueprintCallable 지정자

UPROPERTY 없이 선언된 UObject 포인터 : 가비지 컬렉션 시스템에 등록되지 않아 메모리 누수 발생 위험 있음.

BlueprintImplementableEvent, BlueprintNativeEvent


[C++ 프로젝트]

Text RPG 팀프로젝트 -> Github Destktop 사용(branch, pull request, sln 에러 잡기) Item.h, Item.cpp 작성(Item 클래스), 포션 사용 함수 use(), int count; 추가 후 count--; 사용으로 포션 차감 추가, 포인터 사용하고 Character.cpp에 use(), rand()(+<cstdlib> 함수 사용해서 랜덤으로 두 포션 중 하나를 소모하도록 구성, inventory.erase(inventory.begin() + randomidx); 를 이용해서 포션 남은 개수가 0이면 벡터형 인벤토리에서 제거. 

this는 C++에서 "지금 이 코드를 실행하고 있는 객체 자기 자신의 주소"를 의미하는 포인터.

 

<개발자들의 약속 : 코드 표기법>

ItemManager 헤더 : 파스칼 [클래스 이름, 타입 이름] 

 1. 모든 단어의 첫 글자를 대문자로 시작하기.

 2. 단어 사이에는 공백이나 특수 문자가 없고, 단어들을 연결하여 작성하기.

itemManager 지역 변수 : 카멜 [변수 이름, 메서드 이름, 클래스 이름, 파일 이름]

 1. 단어의 첫 글자는 소문자로 시작하기.

 2. 두 번째 단어부터는 첫 글자를 대문자로 표기하기

 3, 단어 사이에는 공백이나 특수 문자가 없고, 단어들을 연결하여 작성하기

total_score 변수 :  스네이크 [변수 이름, 함수 이름, 데이터베이스 테이블, 열의 이름]

 1. 단어와 단어 사이를 밑줄로 구분하고 각 단어는 소문자로 작성하기

 2. 주로 데이터베이스에서 사용된다. 데이터베이스와 어플리케이션 코드 간의 일관성을 유지하기 위해 사용!

 

 

 

데일리 스크럼(오전 : 전날 뭐했는지, 마무리 시간 : 오늘 뭐할건지 짧게 공유하고 + 기록 하였음)

오후 (7시~) 팀원들과 작성한 코드 보면서 리뷰 및 피드백 -> 깃허브에서 main branch로 푸시 하기 전에 다함께 코드 확인.

트러블 슈팅 : 조건식의 오류를 발견, 필수 과제 완성 이후 추가 기능 부재

해결 :  팀원과 문제 공유 후 더 나은 개선 방안으로 수정하였음. 추가 기능 아이디어 제안(상점, 인벤토리, 확률 및 강화 로직) + 개인적으로는 사용자 경험 개선을 위한 필터 기능 구현을 추가.

 

칸반보드 -> 트렐로에서 소규모 프로젝트는 무료. 주문표 쳐내는 거 시각화 한것(할일 목록을 최대한 잘게 쪼개기)

클래스 다이어그램, 시퀀스 다이어그램

다 같이 스크럼하면서 서로의 오류 크로스 체크. 다같이 집중해서 밀도있게 검사할 수록 더 좋은 아이디어도 나오고 오류도 금방 수정이되고, 개선안도 창의적으로 나올 수 있을 것 같다.

 

(26.05.22) 팀원들과의 커뮤니케이션, 노션을 이용한 팀프로젝트 관리, Github Desktop + Github 팀 프로젝트 경험을 할 수 있었다.


[C++ 공부(?)]

 

팀프로젝트 진행 과정 중 복습 : 배열 함수, 조건문 작성, 연산자, 조건문, 배열과 함수, 포인터와 레퍼런스, 클래스 개념, 객체지향 프로그래밍, 객체지향적 설계

 

visual studio 폴더 정리 방법(#include "../"Entity.h" 상위 폴더 지정), 폴더 변경 시 sln 에러 해결

github push 할 때 한글 깨지는 문제 --> 작성 중인 소스 파일의 인코딩을 UTF-8로 변경한 뒤 다시 커밋하고 푸시 (visual studio가 한글 윈도우에서 기본 인코딩이 다르게 설정되어있는 경우가 많기 때문.

 

C+++ 코딩 표준(언리얼 엔진)

https://dev.epicgames.com/documentation/unreal-engine/epic-cplusplus-coding-standard-for-unreal-engine

클래스 체계, 저작권 고지 방법

명명 규칙 : 대문자, 접두사, 해당 타입에 적합한 접두사, 매크로는 모두 대문자로 구성되고 단어를 언더스코어로 분리 + 접두사 UE

변수, 메서드, 클래스 이름은 명확하고 확실하고 내용을 파악할 수 있어야 함. 과도한 약어는 피할 것.

포용적인 단어를 선택하고 전문적인 언어를 사용하기 위해 노력할 것.

표준 라이브러리의 사용 : <atomic>, <type_traits>, <initializer_list>, <regex>, <limits>, <cmath>, <cstring> : memcpy(), memset()

const의 정확한 사용 방법

 

'TIL' 카테고리의 다른 글

05.29 TIL (ch.2 팀프로젝트 day6)  (0) 2026.05.29
05.28 TIL (ch.2 팀프로젝트 day5)  (0) 2026.05.28
05.27 TIL (ch.2 팀프로젝트 day4)  (0) 2026.05.27
05.26 TIL (ch.2 팀프로젝트 day3)  (0) 2026.05.26
05.21 TIL (ch.2 팀프로젝트 day1)  (0) 2026.05.21