해결한 알고리즘 문제 정리를 위한 레포지토리입니다.
minsoo0715 / problemsolving Goto Github PK
View Code? Open in Web Editor NEW해결한 알고리즘 문제 정리를 위한 레포지토리입니다.
해결한 알고리즘 문제 정리를 위한 레포지토리입니다.
해결한 알고리즘 문제 정리를 위한 레포지토리입니다.
단순한 스택 괄호 검사 문제다
string a;
stack<char> s = stack<char>();
while(true) {
bool unmatched = false;
getline(cin, a);
if (a != ".") {
return 0;
}
for (char c: a) {
if (c == '(' || c == '[') {
s.push(c);
} else if (c == ']' || c == ')') {
if (s.empty()) {
unmatched = true;
break;
}
if (c == ')' && s.top() == '(')
s.pop();
else if (c ==']' && s.top() == '[')
s.pop();
else {
unmatched = true;
break;
}
}
}
if (s.empty() && !unmatched) {
cout << "yes" << endl;
} else {
cout << "no" << endl;
}
while (!s.empty()) s.pop(); // 스택 비우기
}
이동하기 전에 현재 주유소를 포함해, 이전 주유소 중 리터당 가격이 싼 주유소에서 충전해서 그때 그때 비용을 계산할 때, 최소의 비용이 된다.
비용 계산이 차가 이동하는 시점이 동일하지 않지만, 이미 그곳에서 충전해서 온 것으로 가정한다.
for(int i = 0; i<N-1; ++i) {
cin >> price[i];
min = std::min(min, price[i]);
sum += d[i] * std::min(min, price[i]);
}
cin >> price[N-1];
cout << sum;
회의실 배정을 할 때 많은 회의실을 배정할 수 있는 경우는 바로 끝나는 시간이 빠른 경우를 고를 때라고 생각할 수 있다. 또한 끝나는 시간이 동일하면, 더 일찍 시작할수록 유리하다.
따라서 끝나는 시간 기준, 내림차순으로 정렬하고, 끝나는 시간이 동일한 경우 시작 시간 기준으로 내림차순으로 정렬하고, 고르면 된다.
sort()
에 comparator 람다 표현식을 넘겨서 정렬 후, 시간이 겹치지 않는다면 그 회의를 카운팅한다.
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
#define endl '\n';
vector<pair<int, int>> c = vector<pair<int, int>>(100001);
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N, cnt = 1, end;
cin >> N;
for(int i = 0; i<N; ++i) {
cin >> c[i].first >> c[i].second;
}
sort(c.begin(), c.begin()+N, [](auto p1, auto p2) {
if(p1.second == p2.second) {
return p1.first < p2.first;
}
return p1.second < p2.second;
});
end = c[0].second;
for(int i = 1; i<N; ++i) {
if(end <= c[i].first) {
++cnt;
end = c[i].second;
}
}
cout << cnt;
return 0;
}
따라서
또한
재귀를 이용해 top-down 방식으로 구현, 편의를 위해서
int solve(int k = 0) {
if(k + t[k] > n+1) { // 범위를 넘어가면 0
return 0;
}
if(dp[k] != -1) {
return dp[k];
}
int &ref = dp[k] = 0;
for(int i = k + t[k]; i<=n; ++i) { // k + t[k] <= i <= n 내에서의 최대값
ref = std::max(solve(i), ref);
}
return ref += p[k];
}
std::cout << solve(0);
메모이제이션 문제로 보인다.
대신 수열의 형태가 아래와 같으므로
따라서 Key-Value로 저장하는 자료구조, map을 사용하면 될 것으로 보인다.
std::vector
대신 std::map
을 통해 메모이제이션, 재귀 함수를 사용해 top-down 방식으로 구현
#include <iostream>
#include <map>
using ll = long long int;
std::map<ll, ll> dp;
ll p, q;
ll solve(ll n) {
if(n == 0) {
return 1;
}
if(dp[n] != 0) {
return dp[n];
}
return dp[n] = solve(n/p) + solve(n/q);
}
Greedy의 일종인 prim 알고리즘을 통해 최소 스패닝 트리를 찾는다.
connected[1] = true; // 1번 노드부터 시작
while(true) {
int min = 10001;
int k = -1, l = -1;
for(int i = 0; i<edges.size(); ++i) {
pair<int, int> e = edges[i];
if(!connected[e.first] && !connected[e.second]) continue; // 두 노드 중 연결된 노드가 하나도 없는 경우
if(connected[e.first] && connected[e.second]) continue; // 두 노드가 이미 최소 스패닝 트리에 포함되어 있는 경우 (루프 발생 가능)
if(costs[i] < min) { // 최소값 업데이트
min = costs[i];
k = e.first;
l = e.second;
}
}
if(min == 10001) break; // 아무 노드도 선택되지 않은 경우 = 모든 노드가 연결된 경우
ans += min;
connected[k] = true;
connected[l] = true;
}
마이너스를 만나면 다음 마이너스를 만날 때까지 괄호를 씌워 덧셈 값을 뺄셈 값으로 바꾸게 함
ex. 40-30+40-20+40 -> 40-(30+40)-(20+40) -> 최소
편의상 뺄셈은 양수와 음수의 합으로 보고, 다음 음수를 만날 때까지 양수를 음수로 바꿈
예시: 40-30+40 -> 40+(-30) + (-40)
string str;
int chk = 0; // 이전 부호의 위치를 저장하고 있음.
int sum = 0;
bool ck = false; // 음수를 만났는지를 체크
for(int i = 0; i<str.length(); ++i) {
if(str[i] == '-' || str[i] == '+') { // 부호를 만나면 이전 번호 추출
int num = stoi(str.substr(chk, i));
if(num < 0) { // 음수를 만나면 ck = true로 set
ck = true;
}else if(ck) { // 음수를 만났으면 양수를 음수로 만듦.
num = -num;
}
sum += num; // 가산
chk = i; // 부호 위치 갱신
}
}
이 문제는 5kg, 3kg 봉지만 존재하므로, 남김없이 가져가는 경우 중에서, 5kg 봉지가 최대가 될 때 봉지의 개수는 최솟값을 가짐.
따라서 설탕을 최대로 가져갈 수 있는 5kg 봉지의 개수 구해서 이 값을 하나씩 줄여나가면서, 3kg 봉지로 남김없이 가져갈 수 있을 때, 봉지의 개수가 정답임.
단순히 인덱스i
를 N/5
를 초기값으로 설정하고(최대한의 5kg 봉지 갯수)
N - i * 5
가 3으로 나누어떨어질 때 까지 i
를 줄여나가면서 설탕봉지의 최소값을 구할 수 있음.
만약 이 값을 찾지 못하면 -1 반환.
for(int i = N/5; i>=0; --i) {
if((N - i*5) % 3 == 0) {
cout << (N - i * 5) / 3 + i;
return 0;
}
}
cout << -1;
0,0 ~ i, j 까지의 누적합은 다음과 같다.
s[i][j] = a[i][j] + s[i-1][j] + s[i][j-1] - s[i-1][j-1]
이때 (x1, y1) ~ (x2, y2) 사이의 구간 합은 다음과 같다.
s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]
입력과 동시에 값을 누적.
for(int i = 1; i<=N; ++i) {
for(int j = 1; j<=N; ++j) {
cin >> s[i][j];
s[i][j] += s[i-1][j] + s[i][j-1] - s[i-1][j-1];
}
}
for(int m = 0; m<M; ++m) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
cout << s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1] << endl;
}
색종이의 개수를 일단
분할 정복을 통해서
vector<int> cnt = vector<int>(2); // cnt[0] : 흰색 종이의 개수, cnt[1] : 파란색 종이의 개수
int cut(int N, int x = 0, int y = 0) {
int sum = 0; // 파란색 종이의 개수 4-sum은 흰색 종이의 개수
bool chk = true;
if(N == 1) {
++cnt[box[x][y]];
return box[x][y];
}
for(int _x = x; _x <= N/2+x; _x += N/2) {
for(int _y = y; _y <= N/2+y; _y += N/2) {
int tmp = cut(N/2, _x, _y);
if(tmp >= 0) sum += tmp; // 하나의 색종이가 될 수 없는 경우
else chk = false;
}
}
if(chk && (sum == 0 || sum == 4)) {
cnt[sum / 4] -= 3; 흰색 종이 또는 파란색 종이의 개수를 3개 줄인다. (4개 -> 1개)
return sum / 4;
}else {
return -1;
}
}
채널을 한 채널씩 증가, 감소 및 특정 채널로 이동이라는 세 가지 액션이 가능
가능한 가까운 채널로 이동 -> +- 버튼을 이용해 목적 채널로 이동할 수 있음.
이때 세 경우에 대해서는 무조건 최솟값을 가지므로 O(1)
으로 구할 수 있음.
void find(int k, int v, int e = 1)
k
: 자릿수 (일의 자리의 경우 0, 십의 자리의 경우 1, ...)
v
: 현재 까지의 값
e
: 현재 자리 번호에 곱해지는 값 (일의 자리의 경우 1, 십의 자리의 경우 10,
int input; // 입력 받은 숫자
int len; // 숫자의 길이
int max = -1; // 최소값 전역 변수로 관리
void find(int k, int v, int e = 1) { // 입력 받은 숫자와 가장 가까운 수를 구함
if(len == k) {
// 최소값 갱신
return;
}
if(vec[0]) { // 0버튼이 사용가능하지 않은 경우, 위의 모든 자릿수를 0으로 채운 경우는 가능함.
find(len, v);
}
// 다음 자리를 골라서 올림
for(int i = 0; i<10; ++i) {
if(vec[i]) continue;
find(k+1, v + i * e * 10, e * 10);
}
}
int main() {
if(input == 100) { // 이미 그 채널인 경우
// 0 출력
return 0;
}
if(N == 10) { // 숫자 버튼이 모두 없는 경우
// abs(100-input) 출력
return 0;
}
// size_t length() 및 std::string to_string(int)를 통해 길이 구하기
int len = std::to_string(input).length();
if(N == 0) { // 모든 숫자를 다 쓸 수 있는 경우
// abs(input-100), len중 최소값 출력
return 0;
}
// 초기 호출, 1의 자리를 고름.
for(int i = 0; i<10; ++i) {
if(vec[i]) continue;
find(0, i);
}
return 0;
}
1을 먼저 고르는 경우, 2를 먼저 고르는 경우, 3을 먼저 고르는 경우를 생각해보면
세 가지 경우를 생각해볼 수 있다.
일단 1, 2, 3에 대해서 생각해보자
1은 1의 한 가지 경우
2는 1+1, 2의 두 가지 경우
3은 1+1+1, 1+2, 2+1, 3의 세가지 경우가 존재한다.
이제 4를 생각해보자
따라서 점화식은 다음과 같음을 알 수 있다.
입력을 여러 개 받기 때문에 재귀로 top-down 방식으로 구현 했다.
int solve(int k) {
if(dp[k] != 0) // 값을 한번 구한 경우
return dp[k];
return dp[k] = solve(k-1) + solve(k-2) + solve(k-3);
}
하위 노드들이 루트 노드 밑에 어떻게 생길지 모르기 때문에, 루트 노드로부터 노드 탐색이 필요하다.
각 노드들의 연결된 노드를 배열에 기록해놓고, 방문한 노드를 제외하고 탐색을 한다.
이때 방문하기전에 현재 노드를 정답으로 기록한다.
연결된 노드가 기록된 배열을 순회하고, 방문하지 않은 노드라면 하위 노드에 대한 부모 노드를 저장 후, 재귀 호출을 통해 모든 노드를 방문한다.
void solve(int num = 1) {
visited[num] = true;
for(int i : v[num]) {
if(!visited[i]) {
ans[i] = num;
solve(i);
}
}
}
단어의 개수는 공백의 개수로 처리 가능
공백의 개수를 구하고, 앞 뒤 공백 여부에 따라서 답 계산
int cnt = 0, len = s.length();
for(int i = 0; i<len; ++i) {
if(s[i] == ' ') ++cnt;
}
if(s[0] == ' ' && s[len-1] == ' ') {
cnt += -1;
}else if(s[0] != ' ' && s[len-1] != ' ') {
cnt += 1;
}
cout << cnt;
시간:분을 분 단위로 변경해서 45분을 빼준다. 만약 이때 음수가 된다면 전날이 되는 것이므로 24*60을 더해줌.
m = h*60 + m - 45;
if(m < 0) {
m += 24*60;
}
cout << m / 60 << ' ' << m % 60;
26개의 문자에 대해서 누적합 배열을 만들고 구간의 합을 구한다 (특정 문자가 등장하면 +1)
문자 i에 대해서 구간 [x, y]의 구간합은 다음과 같다.
s[i][y] - s[i][x-1]
단, x == 0
일 때 s[i][x-1] == 0
이다
문자가 아스키코드로 표현됨을 활용하여 (문자의 아스키코드) - 'a'
로 알파벳을 0~25로 매칭한다
for(int i = 0; i<str.length(); ++i) {
s[str[i] - 'a'][i] += 1;
if(i > 0) {
for(int j = 'a'; j<='z'; ++j) {
s[j - 'a'][i] += s[j - 'a'][i-1];
}
}
}
for(int i = 0; i<N; ++i) {
cin >> alphabet >> m >> n;
if(m == 0) {
cout << s[alphabet-'a'][n] << endl;
continue;
}
cout << s[alphabet-'a'][n] - s[alphabet-'a'][m-1] << endl;
}
if문 사용
삼항연산자, if문을 통해 조건에 맞게 구현
#include <iostream>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int a, b, c;
cin >> a >> b >> c;
if(a == b && b == c) {
cout << 10000 + a * 1000;
}else if(a == b || b == c || a == c) {
cout << 1000 + (a == b ? a : b == c ? b : a == c ? c : 0) * 100;
}else {
cout << max(max(a,b), c) * 100;
}
return 0;
}
한 자리 이친수의 개수는 1, 두 자리 이친수의 개수는 1이다.
n 자리 이친수를 만들 때, n-1자리 이친수 뒤에 0이나 1을 뒤에 붙이는 방법을 생각해보자
n-1 자리 이친수에 0을 붙이는 경우, 무조건 이친수가 나오게 된다.
n-1 자리 이친수에 1을 붙이는 경우, 앞이 0으로 끝나는 경우에만 이친수가 된다.
이때 n-2 자리 이친수의 개수를 생각해보자,
이 n-2 자리 이친수의 개수는 0으로 끝나는 n-1자리 이친수의 개수와 같다.(n-2자리 이친수에 0을 붙이면 무조건 n-1자리 이친수이므로)
따라서 n자리 이친수의 개수는 다음과 같다.
위 점화식을 down-to-top 방식으로 구현
dp[1] = 1;
dp[2] = 1;
for(int i = 3; i<=n; ++i) {
dp[i] = dp[i-1] + dp[i-2];
}
dp[i][j]
를 다음과 같이 정의한다.
str1의 i번째 까지의 부분 문자열과 str2의 i번째 까지의 부분 문자열의 LCS(길이)
두 문자열 ACAYKP
, CAPCAK
에 대해서 살펴보자
ACAY
와 CAPCA
ACAY
와 CAPCA
의 LCS는 ACA
이고,
ACAYK
와 CAPC
의 LCS는 AC
이다.
이때 A
, K
로 다른 문자이므로, 두 경우 중 더 길이가 긴 것이 ACAYK
CAPCA
의 LCS라고 할 수 있다.
ACAYK
와 CAPCAK
ACAY
와 CAPCA
의 LCS에 K를 추가하는 경우 이므로
ACAY
, CAPCA
의 LCS에 K가 추가 된 문자열이 ACAYK
와 CAPCAK
의 LCS이다.
따라서 LCS의 길이는 다음과 같이 정리할 수 있다. ( string은 0-based, dp 배열은 1-based)
str1[i-1] == str2[j-1]
일때dp[i][j] = dp[i-1][j-1] + 1
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
* | A | C | A | Y | K | P |
---|---|---|---|---|---|---|
C | 0 | 0 | 0 | 0 | 0 | 0 |
A | 0 | 0 | 0 | 0 | 0 | 0 |
P | 0 | 0 | 0 | 0 | 0 | 0 |
C | 0 | 0 | 0 | 0 | 0 | 0 |
A | 0 | 0 | 0 | 0 | 0 | 0 |
K | 0 | 0 | 0 | 0 | 0 | 0 |
i = 1 일 때 (string idx = 0)
* | A | C | A | Y | K | P |
---|---|---|---|---|---|---|
C | 0 | 1 | 1 | 1 | 1 | 1 |
A | 0 | 0 | 0 | 0 | 0 | 0 |
P | 0 | 0 | 0 | 0 | 0 | 0 |
C | 0 | 0 | 0 | 0 | 0 | 0 |
A | 0 | 0 | 0 | 0 | 0 | 0 |
K | 0 | 0 | 0 | 0 | 0 | 0 |
반복
* | A | C | A | Y | K | P |
---|---|---|---|---|---|---|
C | 0 | 1 | 1 | 1 | 1 | 1 |
A | 1 | 1 | 2 | 2 | 2 | 2 |
P | 1 | 1 | 2 | 2 | 2 | 3 |
C | 1 | 2 | 2 | 2 | 2 | 3 |
A | 2 | 2 | 3 | 3 | 3 | 3 |
K | 2 | 2 | 3 | 3 | 4 | 4 |
for(int i = 1; i<=l1; ++i) {
for(int j = 1; j<=l2; ++j) {
if(str1[i-1] == str2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
}else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
누적합 배열에서 특정 구간 [i, j]의 합은 다음과 같다.
// 누적합 배열 생성
for(int i = 1; i<=N; ++i) {
cin >> v[i];
if(i > 1) {
v[i] += v[i-1];
}
}
// 구간 합 출력
for(int i = 1; i<=M; ++i) {
int m, n;
cin >> m >> n;
cout << v[n] - v[m-1] << endl;
}
동전 개수는 높은 액수의 동전을 가능한 많이 가져올 때 최소가 된다.
-> 그리디
for(int i = N-1; i>=0; --i) {
if(K < coin[i]) continue;
if(K == 0) break;
cnt += K / coin[i];
K %= coin[i];
}
cout << cnt;
설탕이 N
kg일 때 최소한의 봉지의 양을
처음에 5kg 봉지로 설탕을 넣는 경우와, 3kg 봉지로 설탕을 넣는 경우로 나눌 수 있다.
5kg 봉지로 설탕을 넣기 시작할 때의 봉지의 최솟값은 결국
즉, 이 두 경우 중에 가능한 경우에서 최솟값을 가지는 게
따라서 다음과 같이 식을 세울 수 있다.
둘다 불가능한 경우
둘 중 하나만 불가능한 경우
위 점화식을 반복문을 통해 down-top 방식으로 구현.
vector<int> dp = vector<int>(5001, -1); // 불가능한 경우의 값인 -1을 초깃값으로 지정
dp[3] = dp[5] = 1; // 초기 값
for(int i = 6; i<=N; ++i) {
s5 = dp[i-5]; s3 = dp[i-3];
if(s5 > 0 && s3 > 0) { // 두 경우 모두 가능한 경우
dp[i] = min(s3, s5) + 1;
}else if(s5 < 0 && s3 < 0) { // 두 경우 모두 불가능한 경우
dp[i] = -1;
} else { // 두 경우 중 하나만 가능한 경우
dp[i] = (s3 > 0 ? s3 : s5) + 1; // 가능한 경우의 수 + 1
}
}
cout << dp[N];
return 0;
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.