728x90

https://school.programmers.co.kr/learn/courses/30/lessons/42627#

 

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

문제 설명보다는 기존 다른 코드와 다른 점을 설명하고자 한다.
https://school.programmers.co.kr/learn/courses/14743/lessons/118891

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 
위 링크에서 문제에서 조금 더 고려해야 하는 부분을 알아두는 것이 좋다.
내 소감은 이렇게 풀면 안 되지만 Heap을 사용하는 문제라, Heap을 어떤 용도로 사용할까? 란 것에 초점을 맞췄다.
하지만 Heap 무조건 넣으면 안 되고, 각 요소마다 값이 2개씩이 있고 어느 쪽으로 정렬해서 넣어야 하는지 애매한 부분이 많았다.

#include <vector>
#include <iostream>
#include <queue>
#include <algorithm>
 
using namespace std;
 
struct cmp {
    bool operator()(vector<int> a, vector<int> b) {
        return a.at(1) > b.at(1);
    }
};
 
int solution(vector<vector<int>> jobs) {
    int answer = 0, j = 0, time = 0;
    sort(jobs.begin(), jobs.end());
    priority_queue<vector<int>, vector<vector<int>>, cmp> pq;
    while (j < jobs.size() || !pq.empty()) {
        if (jobs.size() > j && time >= jobs[j][0]) {
            pq.push(jobs[j++]);
            continue;
        }
        if (!pq.empty()) {
            time += pq.top()[1];
            answer += time - pq.top()[0];
            pq.pop();
        }
        else
            time = jobs[j][0];
    }
    return answer / jobs.size();
}

다른 분들의 코드를 보면 jobs을 한번 정렬해서 시간 순으로 만든다. 그리고 난 뒤 job을 훑어가면서 내 시간에 따라 heap에 넣고, heap에서 시간 계산을 해나가며 내 상태 값을 변경해 나간다.
그리고 heap에 값이 없는 경우에는 시간을 변경한다.
나는 Job을 정렬하는 것이 마음에 들지 않았다.

결국 Job을 정렬한다는 의미는 Heap을 사용해도 된다는 의미로 생각이 들었다.
그리고 무엇보다도 while문이 두 가지 행위를 한번에 하고 있었다. 
1. job을 돌기
2.Heap 체킹
어쩔 수 없이 heap에서 값을 빼내가며 상태값을 바꿔야 한다. 그래도 분리하고 싶어서 최대한 분리해봤다.

#include <vector>
#include <iostream>
#include <queue>
#include <algorithm>
#include <iostream>
using namespace std;
 
struct pairFirstASC {
    bool operator()(const pair<int, int> &a, const pair<int, int> &b) {
        return a.first > b.first;
    }
};

struct pairSecondASC {
    bool operator()(const pair<int, int> &a, const pair<int, int> &b) {
        return a.second > b.second;
    }
};

int solution(vector<vector<int>> jobs) {
    int answer = 0;

    priority_queue<pair<int, int>, vector<pair<int, int>>, pairFirstASC> pq;
    priority_queue<pair<int, int>, vector<pair<int, int>>, pairSecondASC> pq2;
    
    for(vector<int> job: jobs) {
        pq.push({job[0], job[1]});
    } 
   
    int curTime = 0;
    
    while(!pq.empty()) {
        while(!pq.empty() && pq.top().first <= curTime) {
            pq2.push(pq.top());
            pq.pop();
        }
        
        if(pq2.empty()) {
            curTime = pq.top().first;
        } else if(!pq2.empty()) {
            curTime += pq2.top().second;
            answer += curTime - pq2.top().first;
            pq2.pop();
        }
    }
    
    while(!pq2.empty()) {
        curTime += pq2.top().second;
        answer += curTime - pq2.top().first;
        pq2.pop();
    }
    
    return answer / jobs.size();
}

코드가 더 늘어나긴 했지만, 재밌는 도전이었던 것 같다.

 

반응형
728x90

새로 알게 된 것
set<string> num(gems.begin(), gems.end());

중요하다고 생각한 것

1. for문 한번만 돌아야 된다.
2. 도는 것을 굳이 나눠서 첫 번째 초기값을 따로 구하지 않아도 된다.

더 생각해볼 것은 하나의 케이스에 메몰되지 않고, 더 다양한 케이스에 대해서 고민해볼 것

#include <string>
#include <vector>
#include <unordered_map>
#include <set>
#include <iostream>
using namespace std;

vector<int> solution(vector<string> gems) {
    vector<int> answer(2);
    unordered_map<string, int> m;
    set<string> num(gems.begin(), gems.end());
    
    int start = 0;
    bool isFirst = true;
    for(int i=0; i<gems.size(); i++) {
        if(m.find( gems[i] ) != m.end()) {
            m[gems[i]]++;
        } else {
            m[gems[i]] = 1; 
        }
        // 앞 요소 제거
        while(m[ gems[start] ] > 1) {
            m[ gems[start] ]--;
            start++;
        }

        if(m.size() == num.size()) {
            if(isFirst || answer[1] - answer[0] > i - start) {
                answer[0] = start;
                answer[1] = i;  
                isFirst = false;
            }            
        }
    }
    
    return {
        answer[0] + 1,
        answer[1] + 1
    };
}

https://school.programmers.co.kr/learn/courses/30/lessons/67258

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

 

반응형
728x90

https://school.programmers.co.kr/learn/courses/30/lessons/172927

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

문제의 조건을 확인

곡갱이 종류별로 5개, 미네랄 50개

 

생각할만한 방법

제일 완벽하고 만능인 방법이 뭘까, 모든 방법을 탐색하고, 최적의 값을 가져오면 된다.

 

주먹구구식으로 값 찾기

모든 방법을 탐색하는 데 탐색을 몇 번 해야하나? 곡갱이가 총 15개이기에, 곡갱이를 총 곡갱이를 모든 조합을 구한다면 15!이 되고, 이는 1e9를 넘어간다.

 

문제의 조건 확인하기

문제의 조건에서 미네랄이 총 50개이고, 곡갱이가 5번씩 사용 가니, 총 미네랄을 10개의 묶음을 만들 수 있다.

10개의 묶음 그러니까 곡갱이를 15개 중 최대 10개 밖에 못 쓴다.

이는 결국 15C10이다. 이를 계산하면 다음과 같다. 3,003이다.

 

충분히 완전 탐색으로 구할 수 있는 수이다.

그래서 다른 사람들의 코드를 확인한다면, dfs로 탐색하는 것이다.

 

다른 방법은?

조합을 생각하지 못하고, 다른 방법을 생각하더라도 해볼 수 있는 수는 하나 더 있다.

그리디다. 광물을 5개씩 묶음으로 만들고 이를 광물의 가치에 따라 내림차순으로 정렬하면 된다.

그리고 가치가 높은 다이아 곡갱이부터 광물을 캐면 된다.

 

이 방법이 되는 이유는 곡갱이의 순서가 고정이 되고, 광물의 순서를 변경해서 최소를 구할 수 있기 때문이다.

이를 증명하려면 그리디 정당성을 증명해야 하는데 솔직히 말해서 잘 모른다.

 

고려해야 부분이 하나 더 있는데, 광물의 가치를 어떻게 판단하느냐이다.

가치를 하나의 값으로 관리를 하거나, 직접적으로 광물의 수를 기록하는 방법이 있다.

이때 돌 5개가 철 1개보다 가치가 낮음을 알아야 한다.

그렇기 때문에 정렬 시 무조건 가치가 돌이 하나라도 더 많으면 앞으로 와야한다.

이를 광물의 수로 정렬 시 정렬 함수를 광물의 보유 수로 하나씩 비교해서 해야 한다.

하지만 광물의 수를 기록하지 말고 하나의 가치로 합산하는 방법으로도 가능하다.

돌이 1, 총 5개의 값은 5임으로 철은 6이면 된다.

이런 방식으로 돌 1, 철 6, 다이아 31로 가치를 합산해서 하나의 값으로 내림차순 정렬을 해도 된다.

 

나중에 그리디 정당성 증명을 잘 알게 된다면 추가적인 글을 작성하지 않을까 싶다.

반응형
728x90

https://school.programmers.co.kr/learn/courses/30/lessons/77885

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

난이도도 낮은 문제에, 월간 문제(코테)에 2번 문제로 나온 문제이다. 

문제를 읽어보면, 이진수 비트 연산에 관한 문제로 파악할 수 있다.

하지만 문제를 바로 알 수 있을까? 다른 방법으로는 생각할 수는 있지 않을까?

나는 2가지 접근 방법이 있었을 것 같다고 생각한다.

1. 찾는 방법
2. 만드는 방법

무식하게 찾는 방법은 꽤나 오래 걸린다. 그렇기 때문에 결국 이진수 비트 연산 방법으로 풀어야 한다.

해설은 공식 해설을 추천하니 다음 글을 보는 것을 추천한다.

프로그래머스 해설 - 

https://prgms.tistory.com/57 

반응형

'코딩 관련 > c++' 카테고리의 다른 글

[카카오 인턴] 보석 쇼핑 - C++  (0) 2024.03.02
광물 캐기 C++ 분석  (0) 2023.11.08
연속된 부분 수열의 합  (0) 2023.09.02
2018 KAKAO BLIND RECRUITMENT [3차] 압축, C++  (0) 2023.08.26
공원 산책 프로그래머스  (0) 2023.06.21
728x90

https://school.programmers.co.kr/learn/courses/30/lessons/178870

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

먼저 문제를 읽었을 때, 각 구간의 값을 구하면 되겠다는 생각을 하게 되었다.

그래서 아래처럼 구하려고 했다. 구간 값에 현재 값을 더해서 구하는 방식

[ 1 2 3 4 5 ], k = 7

1  2  3  4  5
   3  5  9  12
       6  10 15
               ...

시간 초과가 발생했다. 어디가 느릴까?
작성한 예시에서 보면 삼각형을 형태를 이루어지는 것을 볼 수 있다. for문 2개로 O(n2)의 시간 복잡도를 가짐을 볼 수 있다.

문제에서 주어진 입력을 한번 볼까?
   5 ≤ sequence의 길이 ≤ 1,000,000
길이가 10^6으로 O(n2)으로 구하면 시간 초과가 발생할 수 있다.

그러면 시간 복잡도를 낮춰야할 건데 어떻게 낮출까?

two pointer란 방법으로 하면 된다. O(n)으로 구할 수 있다.

이 문제에서 최소 길이의 구간을 구하는 것이니 for문으로 돌면서 최소 길이 구간을 만날 때마다 구간을 업데이트하면 된다.

#include <string>
#include <vector>
#include <iostream>
using namespace std;

vector<int> solution(vector<int> sequence, int k) {
    vector<int> ans;
    
    int i = 0, j = 0;
    int sum = sequence[i];
    while(i < sequence.size() ) {
        if(sum == k) {
            if(ans.size() == 0) {
                ans = {i , j};   
            } else {
                int temp = ans[1] - ans[0];
                if(j - i < temp) {
                    ans = { i, j };
                }
            }
            sum -= sequence[i++];
        } else if(sum < k && j + 1< sequence.size()) {
            sum += sequence[++j];
        // } else if(sum > k) {
        } else {
            sum -= sequence[i++];
        }
    }
    
    return ans;
}

 

 

반응형
728x90

문제는 간단하다. 아래 지문을 그대로 구현만 하면 된다.

LZW 압축은 다음 과정을 거친다.

  1. 길이가 1인 모든 단어를 포함하도록 사전을 초기화한다.
  2. 사전에서 현재 입력과 일치하는 가장 긴 문자열 w를 찾는다.
  3. w에 해당하는 사전의 색인 번호를 출력하고, 입력에서 w를 제거한다.
  4. 입력에서 처리되지 않은 다음 글자가 남아있다면(c), w+c에 해당하는 단어를 사전에 등록한다.
  5. 단계 2로 돌아간다.

단순히 쉽지만, C++ String을 쓰면서 깨달았던 점을 정리하기 위해 글을 작성한다.

1. Char to String

String으로 변환할 때 단순하게 암묵적 변환을 쓰면 해당 글자에 해당하는 아스키 코드의  숫자가 들어간다. 그리고 명시적으로 변환한다고 한들 제대로 되지 않으니, String 변수를 하나 선언해야 한다.

string s = ""; s += c; 와 같이 해도 되나 길어서 별로니 아래처럼 하면 된다.
string(1, c); 

2. 문자열을 찾을 때에는 문자열을 잘라 사용하기

찾는 위치를 정해주지 않기 때문에 편리하다.

 

#include <string>
#include <vector>
#include <map>
#include <iostream>
using namespace std;

vector<int> solution(string msg) {
    vector<int> answer;    
    map<string, int> m;
    
    for(int i=0; i<26; i++) {
        m.insert({std::string(1, 'A' + i), i+1});
    }
    
    bool check = true;
    int w = 0;
        
    while(msg.size() > 0) {
        int index = 0;
        string temp = msg.substr(w, 1);
        int i = msg.size();
        for(; i>0; i--) {
            temp = msg.substr(w, i);
            if(m.find(temp) != m.end()) {
                index = m[temp];
                break;
            }
        }
        temp = msg.substr(w, i+1);
        m.insert({temp, m.size()+1});
        answer.push_back(index);
        msg = msg.substr(i);
    }  
        
    return answer;
}

 

반응형
728x90

헷갈렸던 부분

1. 배열의 size 고려

  • 주어진 방향으로 이동할 때 공원을 벗어나는지 확인합니다.
    해당 조건을 고려하기 위해서는 배열의 크기를 제대로 인지하고 있어야 하는데 매번 문제 풀 때마다 이 부분이 악취가 나는 부분이었습니다.

size()는 배열이 3칸이면 2를 반환하는 것이 아닌 3을 반환한다.
즉 index는 0부터 시작함으로 우리가 조건을 확인할 때 -1을 하거나 =으로 확인해줘야 한다.

Q. 인지의 오류를 줄일 수 있는 방법은 없을까?

애초에 w와 h에 -1을 하고 =을 사용하면 조금 더 인지적 오류 없이 사용하기 좋을 것 같다는 생각이 드네요.

int h = park.size() - 1;
int w = park[0].size() - 1;

for(int i=0; i<= h; i++) {
        for(int j=0; j<= w; j++) {
            if(park[i][j] == 'S') {
                point = { i, j };
                goto start;
            }
        }
    }
for문은 배열의 내부를 돌기 때문에 =을 사용하고

if(point.first < 0 || point.first > h ||
               point.second < 0 || point.second > w) {
                point = temp;
                break;
 } 

범위를 나갔는지 확인할 때에는 >으로 확인하면 되니 인지적 오류가 줄어들 것 같습니다.

2. char to int, string to int 
매번 헷갈리나 char는 간단하게  - '0'을, string의 경우 stoi를 쓰면 된다.

중요하게 생각했던 점

1. 내 위치를 저장하는 구조
pair를 사용하여 한 묶음으로 가지고 다녔다.
배열을 사용해도 되나 인지적으로 0번이 세로고, 1이 가로임을 생각하는 것이 불편했다.
first와 second로 생각하면 현재 다루고 있는 배열과 다른 생각 묶음으로 가져갈 수있어서 구분이 쉽다고 생각했다.

2. 예외 조건 생각하기
동서남북으로 point를 변경하여 조건을 매번 확인해줘야 하는데 
동 서 남 북으로 반복문을 작성하고 확인하면 코드가 늘어나서 이동은 switch 문을 사용해서 코드의 양을 줄이도록 해봤다.

3. 레이블을 이용한 goto문
악마의 goto문이지만 이중 for문을 나갈 때 유용해서 애용해보려고 써봤습니다. 좋네요.
기존에는 bool check사용해서 한번 더 if문을 확인했는데 이중 for문에 한해서는 사용하는 것이 코드도 깔끔하고 좋네요.

#include <string>
#include <vector>
#include <iostream>
using namespace std;

// 로봇 강아지에 미리 입력된 명령에 따라 진행하며, 명령은 다음과 같은 형식으로 주어집니다.
// ["방향 거리", "방향 거리" … ]
// "E 5"는 로봇 강아지가 현재 위치에서 동쪽으로 5칸 이동했다는 의미입니다.
// 명령 수행하기 전에 확인
// 👾 주어진 방향으로 이동할 때 공원을 벗어나는지 확인합니다.
// 👾 주어진 방향으로 이동 중 장애물을 만나는지 확인합니다.
// 위 두 가지중 어느 하나라도 해당된다면, 로봇 강아지는 해당 명령을 무시하고 다음 명령을 수행합니다.
vector<int> solution(vector<string> park, vector<string> routes) {
    vector<int> answer;
    // 배열로 관리하는 방법도 있을 것 같음
    pair<int, int> point = { 0, 0 };
    
    int h = park.size();
    int w = park[0].length();
    // 배열의 크기가 3칸임으로 3칸이 나온다. 2칸이 아니라, index는 0부터 시작임으로 =으로 해야함.
    for(int i=0; i<h; i++) {
        for(int j=0; j<w; j++) {
            if(park[i][j] == 'S') {
                point = { i, j };
                goto start;
            }
        }
    }
    
start:
    for(string route: routes) {
        pair<int, int> temp = point;
        const char op = route[0];
        // char를 int로 변경하는 방법으로 - '0'이 있고, (int) <- 앤 잘 안 됨
        const int n = route[2] - '0';
        // string to i => stoi
        // const int n = stoi(route[2]);
        
        for(int i=0; i<n; i++){
            switch(op) {
                case 'N': point.first -= 1; break;
                case 'S': point.first += 1; break;
                case 'W': point.second -= 1; break;
                case 'E': point.second += 1; break;
            }
            
            // 범위가 w-1, h-1이라 =을 해줬는데 이게 의식적으로 잘 안 그려짐
            if(point.first < 0 || point.first >= h ||
               point.second < 0 || point.second >= w) {
                point = temp;
                break;
            } else if(park[point.first][point.second] == 'X') {
                point = temp;
                break;
            }
        }
    }
    answer.push_back(point.first);
    answer.push_back(point.second);
    
    return answer;
}

https://school.programmers.co.kr/learn/courses/30/lessons/172928

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

다른 사람 코드를 보고 느낀 점

int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};

switch를 사용하지 않고 이동을 하는 방법
-> 그래프 문제 사용할 때 많이 쓰던 방법인데 여기서도 이렇게 사용할 수 있구나 싶기도 하고 오히려 복잡한 것 같기도 한 것 같다는 생각이 드네요.

    int cx, cy;
                tie(cx, cy) = {i, j};

변수로 선언한 것들을 tie로 묶어서 받는 모습, 굳이란 생각도 들지만 저런 방법이 있다는 게 신기하네요.

반응형
728x90

https://school.programmers.co.kr/learn/courses/30/lessons/131127

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

10일동안 원하는 제품을 모두 할인해서 구매해야한다.

unoredred_map의 사용법이 신기했던 문제다.

#include <string>
#include <vector>
#include <iostream>
#include <unordered_map>
#include <deque>
using namespace std;

int solution(vector<string> want, vector<int> number, vector<string> discount) {
    int answer = 0;
    
    deque<string> dq;
    unordered_map<string, int> m;
    unordered_map<string, int> origin;    
     
    for(int i=0; i<want.size(); i++) {
        origin.insert({want[i], number[i]});
    }
    
    for(int i=0; i<10; i++) {
        string temp = discount[i];
        dq.push_back(temp);
        m[temp]++;
    }
        
    if(m == origin) {
        answer++;
    }

    for(int i=10; i<discount.size(); i++) {
        m[dq.front()]--;
        dq.pop_front();
        m[discount[i]]++;
        dq.push_back(discount[i]);
        
        bool check = true;
        for(int j=0; j<want.size(); j++) {
            
            if(m[want[j]] < origin[want[j]]) {
                check = false;
                break;
            }
        }
        if(check) {
            answer++;
        }
    }
        
    return answer;
}

맵에 있는 값을 --처리하여 0이 되어도, map에서 사라지지 않는다.

그렇기 때문에 비교하는 for문이 하나 더 들어가게 된다.

erase를 써서 지워버린다면, for문이 사라지고 == 처리로 비교가 한번에 가능해진다.

하지만 내가 사고 싶은 물건보다 더 많이 할인하는 경우를 체크하지 못하지 않을까 싶지만 사실 상관없다.
=>  애매한 부분이라 정확한 설명은 못하지만, ==처리로 같은지 확인할 수있다는 점을 알게 되었다는 점에서 신기하다.

#include <string>
#include <vector>
#include <iostream>
#include <unordered_map>
#include <deque>
using namespace std;

int solution(vector<string> want, vector<int> number, vector<string> discount) {
    int answer = 0;
    
    deque<string> dq;
    unordered_map<string, int> m;
    unordered_map<string, int> origin;    
     
    for(int i=0; i<want.size(); i++) {
        origin.insert({want[i], number[i]});
    }
    
    for(int i=0; i<10; i++) {
        string temp = discount[i];
        dq.push_back(temp);
        m[temp]++;
    }
        
    if(m == origin) {
        answer++;
    }

    for(int i=10; i<discount.size(); i++) {
        m[dq.front()]--;
        if(m[dq.front()] == 0) {
            m.erase(dq.front());
        }
        dq.pop_front();
        m[discount[i]]++;
        dq.push_back(discount[i]);
        
        if(m == origin) {
            answer++;
        }
    }
        
    return answer;
}
반응형

+ Recent posts