#include <string>
#include <vector>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
string solution(string s, string skip, int index) {
string answer = "";
//sort 정렬 : 시간복잡도 N , 우선순위 큐 : 시간복잡도 : log n
//이기에 우선순위큐를 이용하여 minheap으로 정렬함
priority_queue<char,vector<char>,greater<char>> pq_skip;
for(char ch : skip) { pq_skip.push(ch);}
char pivot = 'a'; //알파벳 배열에 넣을 시작 문자
vector<char> alpabet;
while(pivot <= 'z')
{
//해당 알파벳이 skip문자열에 있는경우 알파벳 배열에 넣지 않는다.
if( pq_skip.top() != pivot) { alpabet.push_back(pivot); }
else { pq_skip.pop(); }
pivot++;
}
//문자열 s 길이만큼 순회하며 문자를 변환한다.
int result;
for(int i = 0; s.size() >i;i++)
{
//문자열 s의 단일문자가 알파벳배열의 어디에 위치하는지 찾는다.
int alpabet_index = find(alpabet.begin(),alpabet.end(),s[i]) - alpabet.begin();
//찾은 단일문자위치에 인자값index를 더하여 최종 위치를 찾는다.
result = alpabet_index + index;
//만약 알파벳 사이즈를 초과한 경우 , 알파벳 사이즈만큼 뺀다.
while(result> alpabet.size()-1 )
{
result -= alpabet.size();
}
answer += alpabet[result];
}
return answer;
}
해당 문제를 이렇게 풀었지만 , 다른 사람 풀이를 보니 더욱 가독성 좋고 쉽게 풀이한게 있어 코드를 첨부한다.
다른사람 풀이
#include <string>
#include <vector>
using namespace std;
string solution(string s, string skip, int index) {
string answer = "";
for(auto v : s)
{
int t = 0; //다음 문자로 넘어간 숫자를 카운트하는 변수
int c = v - 'a'; //문자v에서 'a'를 뺌으로 알파벳의 위치순서를 파악
while(t < index)
{
c++;//다음 알파벳위치(알파벳 변환)
v = (c % 26) + 'a';//해당 알파벳 위치를 문자열로 변환(v는 char 자료형)
//c%26은 알파벳이 총 26개 이기 때문에 큰 숫자가 들어와도 알파벳의 한 형태로 변환 가능하기떄문
//skip문자열에 해당 문자(v)가 있는지 체크
if(skip.find(v) == string::npos)
{
//없으면 카운트를 증가시킴
t++;
}
}
answer += v;
}
return answer;
}
#include <string>
#include <iostream>
#include <vector>
#include <map>
using namespace std;
//체육복 빌려주는 조건 : 자기 앞,뒷 1번호 만큼 가능
//체육수업을 들을수있는 학생의 최댓값 return
//여벌 체육복을 가져온 학생도 도난 가능,체육복을 빌려줄수 없음 하지만 자기것는 있음
bool reserve_check(int index, map<int,int>& students)
{
//iter = 자신의 앞 또는 뒤 학생의 iter
map<int,int>::iterator iter_before = students.find(index);
//해당 학생이 없을수도 있기에(제일 앞학생 또는 제일 뒷학생 고려)
//여벌 체육복이 있으면 해당 학생의 체육복을 -1 시킴
if(iter_before != students.end() && iter_before->second == 2)
{
iter_before->second--;
return true;
}
return false;
}
int solution(int n, vector<int> lost, vector<int> reserve) {
int answer = 0;
map<int,int> students; //학생번호,체육복 수
//여벌 체육복이 있는 학생을 맵에 추가
for(int num : reserve) { students.insert({num,2}); }
//체육복을 잃어버린 학생을 맵에 추가
for(int num : lost)
{
//해당 맵에 여벌 체육복을 갖고 있지만 잃어버린 학생이 있을수 있기에 find 실행
map<int,int>::iterator iter = students.find(num);
//find결과가 없으면 해당 체육복이 없는 학생으로 추가
if(iter == students.end()) { students.insert({num,0}); }
//find 결과가 있으면 해당 학생의 체육복 수를 1감소
else { iter->second--;}
}
for(int i=1; i <= n ; i++)
{
map<int,int>::iterator iter = students.find(i);
//해당 학생이 map에 없으면 수업을 들을 수 있기에 answer후 순회
if(iter == students.end()) { answer++; continue;}
//해당 학생이 체육복이 없는경우
if(iter->second == 0)
{
//자기 앞,뒷 번호의 여벌 체육복을 체크후 true인경우 해당 학생의 체육복을 1증가
if(reserve_check(i-1,students) || reserve_check(i+1,students)) iter->second++;
}
//해당 학생이 체육복이 1개 이상인경우 answer증가
if(iter->second >=1) { answer++; }
}
return answer;
}
#include <string>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
int solution(vector<int> topping) {
int answer = 0;
//A라는 사람의 topping map
map<int,int> map_A;
//A의 빵에 모든 토핑을 올려놓기
for (int elements : topping)
{
map<int, int> ::iterator iter = map_A.find(elements);
if (iter != map_A.end()) iter->second++;
else map_A.insert({ elements,1 });
}
//A라는 사람의 topping map
map<int, int> map_B;
//토핑의 갯수만큼 순회를 하며 A의 토핑을 B한테 하나씩 주며 체크하기
for (int i = 0; i < topping.size(); i++)
{
int pivot = topping[i];
//map_A의 원소 제거 (B한테 하나 주기)
map<int, int> ::iterator iter = map_A.find(pivot);
if (iter != map_A.end())
{
iter->second--;
if (iter->second <= 0) map_A.erase(iter);
}
//map_B의 원소 추가 (A한테서 받은 토핑 추가하기)
iter = map_B.find(pivot);
if (iter != map_B.end()) iter->second++;
else map_B.insert({ pivot,1 });
//각 토핑의 map갯수가 같은지 체크
if (map_A.size() == map_B.size()) answer++;
}
return answer;
}
문제에서 원하는건 [ 최소 연산 횟수를 return ]이기 때문에 bfs로 접근하였고, bfs를 사용하기에 queue 자료구조를 사용하였다, 인자값이 작아서 경우의수가 적을경우에는 원하는 값이 나왓지만 인자값이 엄청 큰 경우 재귀함수 반복횟수 초과 및 시간 복잡도 문제가 발생했다.
비트 연산 문제로 파악하여 비트를 쉽게 파악하기 위해 bitset을 사용 2개 이하 다른 비트를 찾기 위해 XOR 비트연산을 이용하여 [1의 갯수 < 2]의 조건을 찾아 정답을 찾음 하지만 인자값으로 큰 숫자가 들어올 경우 XOR을 해야되는 연산 수가 늘어나며 시간 복잡도에서 문제가 발생
두번째 접근 방법 : 각 비트의 규칙을 찾음
2진 숫자(numbers)
2진 변환(answer)
처음보는 0 뒤의 1숫자 index
numbers에 더해주는 값
010(2)
011(3)
0(처음 보는 0 뒤에 1이 없음)
+2^0
011(3)
101(5)
1(0의 index는 두번째,2-1 =1)
+2^1
100(4)
101(5)
0(처음 보는 0 뒤에 1이 없음)
+2^0
101(5)
110(6)
0(0의 index는 첫번째, 1-1=0)
+2^0
110(6)
111(7)
0(처음 보는 0 뒤에 1이 없음)
+2^0
111(7)
1011(11)
2(0의 index는 세번째 3-1=2)
+2^2
규칙을 보면 numbers의 비트들에서 오른쪽에서 처음으로 보는 0 뒤의 1숫자의 index를 2제곱해준 값을 numbers에 더해주면된다. 그렇게하면 비트는 무조건 2이하로 바뀌게 되는 조건을 만족한다.
ex)1 010(2)의 [처음보는 0뒤에 1 index]는 0이고, 그 값을 2^n에 대입하면
010 + 2^0 = 011(3) 값이 나온다.
ex2)011(3)의 [처음보는 0뒤에 1 index]는 1이고, 그 값을 2^n에 대입하면
#include <string>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
vector<long long> solution(vector<long long> numbers) {
vector<long long> answer;
for(long long i =0; i < numbers.size() ; i++)
{
long long number = numbers[i];
long long index_0 = 0; //처음보는 0의 index
//number를 이진법으로 변환하며 처음 만나는 0 찾기
while(number > 0)
{
if(number % 2 == 0) { break; } // 해당 이진법의 자리가 0인경우 탈출
index_0++; //0이 아닌경우 index가 증가함
number *= 0.5;
}
//0뒤의 숫자1의 index를 알아야하기 때문에 -1을 계산
if(index_0 > 0) { index_0 -= 1;}
//number에 더해줄 2^count 를 구함
long long sum = 1;
for(long long i =0; i< index_0; i++) { sum *= 2;}
answer.push_back(numbers[i] + sum);
}
return answer;
}
//Frustum.h
#pragma once
#include "Components/DXCamera.h"
class Frustum
{
private:
DXCamera* camera; //view,Projection 행렬을 가져오기 위한 변수
float planeEpsilon; // 평면테스트에서 사용되는 부동 소수점 오차범위를 나타냄
XMVECTOR frustumPlane[6]; //Plane : 평면 -> 절두체 평면
public:
bool Initialize(DXCamera* camera, float planeEpsilon = FLT_EPSILON);
void ConstructFrustum();
//IsInFrustumBounds~ 함수 설명
//물체의 기준으로 히트박스(Bounds뒤에 들어가는 도형,도형은 자유) 만들어서 그 안에있으면 있따고 처리 체크
bool IsInFrustum(XMFLOAT3 v); //Frustum 안에 물체 확인 bool 변수
bool IsInFrustum(XMVECTOR v); //Frustum 안에 물체 확인 bool 변수
bool IsInFrustumExceptUpDown(XMFLOAT3 v); //Frustum의 윗면과 아랫면은 무조건 있는걸로 취급하고 좌우앞뒤만 체크하는 변수
bool IsInFrustumBoundsSphere(XMFLOAT3 v,float radius); //Frustum 안에 물체의 기준으로 구 모형 안에 있는지 물체 확인 bool 변수
bool IsInFrustumBoundsSphere(XMVECTOR v, float radius); //Frustum 안에 물체의 기준으로 구 모형 안에 있는지 물체 확인 bool 변수
bool IsInFrustumBoundsSphereExceptUpDown(XMFLOAT3 v, float radius); //Frustum안에 물체의 기준으로 구 모혀 위와 아래는무조건 있는걸로 취급하고 좌우앞뒤만 체크하는 변수
bool IsInFrustumBoundsCube(XMFLOAT3 v, float radius); //Frustum 안에 육면체 모양 안에 물체 확인 bool 변수
bool IsInFrustumBoundsCube(XMVECTOR v, float radius); //Frustum 안에 육면체 모양 안에 물체 확인 bool 변수
//Rectangle : AABB/OBB -> 2D게임 만들때거나 3D게임의 UI 만들때 사용
bool IsInFrustumBoundsRectangle(XMFLOAT3 v, XMFLOAT3 r); //Frustum 안에 2D사각형 안에 물체 확인 bool 변수
bool IsInFrustumBoundsRectangle(XMVECTOR v, XMVECTOR r); //Frustum 안에 2D사각형 안에 물체 확인 bool 변수
private:
bool IsInBoundsCube(XMVECTOR p, XMFLOAT3 v, XMFLOAT3 r);
};
//====================================================================
#include "Frustum.h"
bool Frustum::Initialize(DXCamera* camera, float planeEpsilon)
{
if (!camera) return false; // 매개변수 camera 가 존재하지 않으면 false 반환
//Frustum data 초기화
this->camera = camera;
this->planeEpsilon = planeEpsilon;
for (int i = 0; 6 > i; i++) frustumPlane[i] = XMVectorSet(0, 0, 0, 0);
return true;
}
void Frustum::ConstructFrustum()
{
//최종 vertex는 (-1,-1,0) ~ (1,1,1) 사이의 값으로 바뀐다.
//최종Vertex = Vworld * ViewMatrix * ProjectMatirx해서 나온 Frusutm 영역의 8개 vertex data임.
XMFLOAT3 frustumVertices[8] =
{
{-1.0f, -1.0f, 0.0f }, // near left bottom v0
{1.0f, -1.0f, 0.0f }, // near right bottom v1
{1.0f, -1.0f, 1.0f }, // far right bottom v2
{-1.0f, -1.0f, 1.0f }, // far left bottom v3
{-1.0f, 1.0f, 0.0f }, // near left top v4
{1.0f, 1.0f, 0.0f }, // near right top v5
{1.0f, 1.0f, 1.0f }, // far right top v6
{-1.0f, 1.0f, 1.0f }, // far left top v7
};
//view * proj의 역행렬 계산.
XMMATRIX invViewProjectionMatrix = XMMatrixInverse(NULL, camera->GetViewMatrix() * camera->GetProjectionMatrix());
XMVECTOR frustumVectors[8]; //최종vertex * (viewprojectionMatrix)T를 계산한 vertex들의 vector
for (int i = 0; 8 > i; i++)
{
// XMVector3TransformCoord : 주어진 행렬을 사용하여 벡터로 변형하는 함수
//최종vertex * (viewprojectionMatrix)T 계산,
frustumVectors[i] = XMVector3TransformCoord(XMVectorSet(frustumVertices[i].x, frustumVertices[i].y, frustumVertices[i].z, 1.0f),
invViewProjectionMatrix);
}
//계산된 Vworld(월드상의 vertex)들을 3개씩 사용하여 절두체의 평면을 구성
//인덱스 순서에 따라 절두체면(폴리곤)의 윗면이 결정되어 윗면이 법선벡터의 방향이다.(중요)
frustumPlane[0] = XMPlaneFromPoints(frustumVectors[4], frustumVectors[7], frustumVectors[6]); // 상 평면(top)
frustumPlane[1] = XMPlaneFromPoints(frustumVectors[0], frustumVectors[1], frustumVectors[2]); // 하 평면(bottom)
frustumPlane[2] = XMPlaneFromPoints(frustumVectors[0], frustumVectors[4], frustumVectors[5]); // 근 평면(near)
frustumPlane[3] = XMPlaneFromPoints(frustumVectors[2], frustumVectors[6], frustumVectors[7]); // 원 평면(far)
frustumPlane[4] = XMPlaneFromPoints(frustumVectors[0], frustumVectors[3], frustumVectors[7]); // 좌 평면(left)
frustumPlane[5] = XMPlaneFromPoints(frustumVectors[1], frustumVectors[5], frustumVectors[6]); // 우 평면(right)
}
bool Frustum::IsInFrustum(XMFLOAT3 v)
{
return IsInFrustum(XMVectorSet(v.x, v.y, v.z, 1.0f));
}
bool Frustum::IsInFrustum(XMVECTOR v)
{
return IsInFrustumBoundsSphere(v, 0.0f);
}
bool Frustum::IsInFrustumExceptUpDown(XMFLOAT3 v)
{
return IsInFrustumBoundsSphereExceptUpDown(v, 0.0f);
}
bool Frustum::IsInFrustumBoundsSphere(XMFLOAT3 v, float radius)
{
return IsInFrustumBoundsSphere(XMVectorSet(v.x, v.y, v.z, 1.f), radius);
}
//절두체 안에 경계구가 있는지 체크하는 함수( 작동방식 밑에 서술)
bool Frustum::IsInFrustumBoundsSphere(XMVECTOR v, float radius)
{
for (int i = 0; 6 > i; i++) // 왜 6인가? 절두체의 면이 6개임
{
//XMPlaneDotCoord의 함수 반환값은 XMVECTOR인데, XMVECTOR의 모든값을 내적크기로 반환한다.
//VectorGetX,Y,Z 함수 아무거나 사용해도 괜찮다. 전부 내적의 값이기 떄문이다.
if (XMVectorGetX(XMPlaneDotCoord(frustumPlane[i], v)) > (radius + planeEpsilon))
{
// 벡터의 내적(물체와 절두체 면의 수직상의 거리) > 물체의 경계(히트박스)거리 가 참인경우
return false; //frustum 영역 외부에 있음
}
}
return true;//frustum 영역 내부에 있음
}
bool Frustum:: IsInFrustumBoundsSphereExceptUpDown(XMFLOAT3 v, float radius)
{
XMVECTOR vector = XMVectorSet(v.x, v.y, v.z, 1.0f);
for (int i = 2; 6 > i ; i++) // 0,1 인덱스의 vertex는 윗면과 ,아랫면의 vertex이므로 제외
{
//XMPlaneDotCoord (평면(plane) , 벡터(vertex) ) : 평면과 벡터사이의 내적값을 반환.
if (XMVectorGetX(XMPlaneDotCoord(frustumPlane[i], vector)) > (radius + planeEpsilon)) return false;
}
return true;
}
//절두체를 경계박스로 컬링하는 함수
bool Frustum::IsInFrustumBoundsCube(XMFLOAT3 v, float radius)
{
for (int i = 0; 6 > i; i++)
{
if (IsInBoundsCube(frustumPlane[i], v, XMFLOAT3(radius, radius, radius))) continue;
return false;
}
return true;
}
bool Frustum::IsInFrustumBoundsCube(XMVECTOR v, float radius)
{
XMFLOAT3 dest;
XMStoreFloat3(&dest, v);
return IsInFrustumBoundsCube(dest, radius);
}
//절두체를 2D사각형으로 컬링하는 함수(2D게임에서 사용하게됨)
bool Frustum::IsInFrustumBoundsRectangle(XMFLOAT3 v, XMFLOAT3 r)
{
for (int i = 0; 6 > i; i++)
{
//
if (IsInBoundsCube(frustumPlane[i], v, r)) continue;
return false;
}
return true;
}
bool Frustum::IsInFrustumBoundsRectangle(XMVECTOR v, XMVECTOR r)
{
XMFLOAT3 destV, destR;
XMStoreFloat3(&destV, v);
XMStoreFloat3(&destR, r);
return IsInFrustumBoundsRectangle(destV, destR);
}
//경계박스 ,및 경계사각형(2D)의 함수 작동방식
bool Frustum::IsInBoundsCube(XMVECTOR p, XMFLOAT3 v, XMFLOAT3 r)
{
return
(
//절두체 면(p) ,벡터v,r연산
//모든 벡터의 내적이 오차범위 보다 작으면 있면 frustum 안에 존재
(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x - r.x, v.y - r.y, v.z - r.z, 1.0f))) <= planeEpsilon) ||
(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x - r.x, v.y - r.y, v.z + r.z, 1.0f))) <= planeEpsilon) ||
(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x - r.x, v.y + r.y, v.z - r.z, 1.0f))) <= planeEpsilon) ||
(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x - r.x, v.y + r.y, v.z + r.z, 1.0f))) <= planeEpsilon) ||
(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x + r.x, v.y - r.y, v.z - r.z, 1.0f))) <= planeEpsilon) ||
(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x + r.x, v.y - r.y, v.z + r.z, 1.0f))) <= planeEpsilon) ||
(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x + r.x, v.y + r.y, v.z - r.z, 1.0f))) <= planeEpsilon) ||
(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x + r.x, v.y + r.y, v.z + r.z, 1.0f))) <= planeEpsilon)
);
}
최종 Vertex의 값 범위가 (-1,-1,0) ~ ( 1,1,1) 사이의 값으로 바뀐다 라는 주석이 있는데
이 값은 D3D에서는 절대 고정이며 view 변환시 카메라 시점을 원점으로 바꾸고 투영행렬을 계산하여 이미지화 (3D->2D)화 시키기 때문에 그런것이다.
//최종 vertex는 (-1,-1,0) ~ (1,1,1) 사이의 값으로 바뀐다.
//최종Vertex = Vworld * ViewMatrix * ProjectMatirx해서 나온 Frusutm 영역의 8개 vertex data임.
XMFLOAT3 frustumVertices[8] =
{
{-1.0f, -1.0f, 0.0f }, // near left bottom v0
{1.0f, -1.0f, 0.0f }, // near right bottom v1
{1.0f, -1.0f, 1.0f }, // far right bottom v2
{-1.0f, -1.0f, 1.0f }, // far left bottom v3
{-1.0f, 1.0f, 0.0f }, // near left top v4
{1.0f, 1.0f, 0.0f }, // near right top v5
{1.0f, 1.0f, 1.0f }, // far right top v6
{-1.0f, 1.0f, 1.0f }, // far left top v7
};
절두체 각 면의 법선벡터 방향을 정하는 것은
그래픽스 파이프라인 폴리곤을 그리는 방식에 따라 정해진다.
이 블로그의 그래픽스 파이프라인 - 기본용어 정리 -Vertex를 이용하여 삼각형을 그릴때 필요한 개념 내용을 참고하면 좋다.
// DirectX11Graphics.h
#include "Frustum.h"
class DirectX11Graphics final : public Graphics
{
Private:
// Mesh의 이미지 관련 함수들을 담당하고 있음
std::unique_ptr <PrimitiveModel> primitive;
// 컴포넌트 구조의 Mesh Obj : 컴포턴트 추가 및 Update관련(Mesh의 크기,회전,이동 함수등)
DXGameObject primitiveObj;
Frustum frustum; //절두체컬링을 위한 절두체 객체 추가
};
//========================================================
// DirectX11Graphics.cpp
void DirectX11Graphics::RenderFrame() //그래픽스 객체의 Draw 함수(프레임마다 렌더링함수)
{
//렌더링 할때마다 , 카메라시점 기준(뷰행렬,투영행렬)으로 절두체 영역 생성
frustum.ConstructFrustum();
// GameObject Primitive Mesh Draw
//절두체 컬링 - 경계구 판정을 사용하여 Mesh의 위치vector와 크기Vector를 인자값으로 가져감
//크기vector는 경계구의 반지름으로 사용, 해당 객체가 카메라영역에서 사라지고 경계구까지 영역에서 벗어나게되면 절두체 컬링진행
//(이 자리에 경계 구의 반지름 값을 조정하여 넣을 수있음 )
if (frustum.IsInFrustumBoundsSphere(primitiveObj.GetTransform()->GetPosition(), XMVectorGetX(primitiveObj.GetTransform()->GetScale())/*bounds*/))
{
//절두체 6면, 영역 내에 Mesh(객체)가 존재 함으로 Draw를 한다.
primitiveObj.Draw(cameraObj.GetComponent<DXCamera>()->GetViewProjectionMatrix());
}
}
bool DirectX11Graphics::InitializeScene() // 그래픽스 객체의 Scene(장면) Init
{
//상자 mesh
Primitive = std::make_unique<PrimitiveCube>(); // 상자 모양 mesh
Primitive->Initialize(device.Get(), deviceContext.Get(), constantMatricesBuffer);
Primitive->MakePrimitive("Textures\\box.jpg"); // mesh에 텍스쳐 이미지 추가
PrimitiveObj.AddComponent<DXMeshRenderer>()->SetModel(Primitive.get());
//절두체 객체 Init , 카메라의 뷰행렬,투영행렬이 필요하기 때문 -> 역행렬을 구해야함
frustum.Initialize(cameraObj.GetComponent<DXCamera>());
}