Programmers Lv3, 표 편집[C++, Java]

junto·2024년 6월 27일
0

programmers

목록 보기
37/66
post-thumbnail

문제

Programmers Lv3, 표 편집

핵심

  • 입력의 크기가 1,000,000이라 대략 O(NlogN)O(N * logN)이하 알고리즘을 사용한다.
  • 임의의 위치에서 삽입과 삭제가 반복된다. 직관적으로 list를 사용하는 문제임을 알 수 있다. 배열로 푸는 경우 임의의 위치에서 삭제하고 삽입하는 과정에서 배열 복사 비용이 들고, 효율성 케이스를 통과하지 못한다.

1. List STL

  • C++에서 list 자료구조를 이용할 수 있을까? 사용할 수 있다. 하지만, 원소의 위치를 파악하기가 어렵다. 7번째 위치에 원소를 삽입해야 한다면, 해당 위치까지 iterator를 이동시켜야 한다. 이 작업은 O(N)으로 N이 1,000,000이고 명령어가 200,000까지 가능하기 때문에 효율성 케이스를 통과하지 못한다. 즉, 특정 위치의 원소에 바로 접근할 수 있는 iterator를 미리 준비해 두어야 한다. 아래처럼 iterator를 특정 인덱스마다 기록해 두면 O(1)O(1)에 접근할 수 있다. (즉, 삽입과 삭제가 가능하다)
auto it = l.begin();
for(int i = 0; i < n; i++){
    l_it[i] = it;
    it++;        
}
  • 원소를 삭제할 때 삭제한 원소와 원소의 위치를 stack에 저장해두면 가장 최근에 삭제된 행을 O(1)O(1)에 찾을 수 있다. 삭제한 원소를 insert() 함수로 추가할 때 iterator 앞에 추가되는 것을 유의한다. 따라서 현재 커서에 있는 원소를 삭제할 때 커서 오른쪽 원소의 위치를 기억해 둔다. 그럼, 제일 마지막 원소를 삭제할 때 커서를 어디에 위치시켜야 할까? 그림에 나와 있듯 커서 왼쪽에 위치시킨다. 그림으로 나타내면 아래와 같다.
  • 커서의 위치가 작아지면 제일 마지막 원소를 삭제한 것이고, 커지면 마지막 원소가 아닌 것을 삭제한 것이다. 다시 추가한 원소의 위치로 바로 접근할 수 있도록 iterator 위치를 추가하는 작업을 한다. 여기서 iterator를 직접 수정하게 되면 추후 iterator 연산 과정에서 예기치 못한 문제가 발생할 수 있으니 주의하자.
if (cur < nxt) {
	l.insert(l_it[nxt], cur);
	auto tmp = l_it[nxt];
	tmp--;
	l_it[cur] = tmp;
}
else {
	l.insert(l.end(), cur);
	l_it[cur] = --l.end();
}

C++

#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <iterator>
#include <list>

using namespace std;

list<int> l;
list<int>::iterator l_it[1000004];
list<int>::iterator cursor; 
stack<pair<int,int>> s;
string solution(int n, int k, vector<string> cmd) {
    
    for(int i = 0; i < n; i++)
        l.push_back(i);
    auto it = l.begin();
    for(int i = 0; i < n; i++) {
        l_it[i] = it;
        it++;        
    }

    cursor = l_it[k];
    
    for (auto str : cmd) {
        if (str[0] == 'U') {
            int num = stoi(str.substr(2));
            while(num--) --cursor;
        }
        else if (str[0] == 'D') {
            int num = stoi(str.substr(2));
            while(num--) ++cursor;
        }
        else if (str[0] == 'C') {
            auto init = cursor;
            cursor = l.erase(cursor); 
            if (cursor == l.end()) {
                --cursor;
            }
            s.push({ *init, *cursor });
        }
        else {
            auto [cur, nxt] = s.top(); s.pop();
                        
            if (cur < nxt) {
                l.insert(l_it[nxt], cur);
                auto tmp = l_it[nxt];
                tmp--;
                l_it[cur] = tmp;
            }
            else {
                l.insert(l.end(), cur);
                l_it[cur] = --l.end();
            }
        }        
    }
    string str(n, 'X');
    for (auto e : l) {
        str[e] = 'O';
    }

    return str;
}

개선

  • 가장 끝에 더미 원소를 추가해 해당 분기 처리를 간단하게 할 수 있다.
string solution(int n, int k, vector<string> cmd) {
    
    for(int i = 0; i < n + 1; i++)
        l.push_back(i);
    auto it = l.begin();
    for(int i = 0; i < n + 1; i++){
        l_it[i] = it;
        it++;        
    }
    ...
    for (auto str : cmd) {
    	...
        else if (str[0] == 'C'){
            s.push({*cursor, *(next(cursor))});
            cursor = l.erase(cursor);
            if (*cursor == n) --cursor;
        }
        else {
            auto [cur, nxt] = s.top(); s.pop();
            l_it[cur] = l.insert(l_it[nxt], cur);
        }    
    }
    ...

Java에도 LinkedList()가 존재한다. 구현이 가능할까?

  • 이론상은 구현 가능하다. 하지만, 직접 해본 결과 iterator를 사용하는 데 있어서 제한이 많았고, 특히 동시 수정 문제가 엄격하다.

2. 직접 List 구현

1. C++

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

const int MX = 1'200'004;
int dat[MX];
int pre[MX];
int nxt[MX];
int nums[MX];
int unused = 1;

int insert(int addr, int num) {
    dat[unused] = num;
    pre[unused] = addr;
    nxt[unused] = nxt[addr];
    if (nxt[addr] != -1) pre[nxt[addr]] = unused;
    nxt[addr] = unused;

    return unused++;
}

int erase(int addr){
    nxt[pre[addr]] = nxt[addr];
    
    if (nxt[addr] != -1) {
        pre[nxt[addr]] = pre[addr];
        return nxt[addr];
    }
    
    return pre[addr];
}

string solution(int n, int k, vector<string> cmd) {
    
    dat[0] = nxt[0] = -1;
    for (int i = 0; i < n; ++i) 
        nums[i] = insert(i, i);
    
    int cursor = nums[k];
        
    stack<pair<int, int>> s;
        
    for (auto str : cmd) {
        if (str[0] == 'U'){
            int num = stoi(str.substr(2));
            while(num--) cursor = pre[cursor];
        }
        else if (str[0] == 'D'){
            int num = stoi(str.substr(2));
            while(num--) cursor = nxt[cursor];
        }
        else if (str[0] == 'C'){
            s.push({ dat[pre[cursor]], dat[cursor] });
            cursor = erase(cursor);
        }
        else {
            auto [cur, nxt] = s.top(); s.pop();
            
            int idx;
            if (cur == -1) idx = 0;
            else idx = nums[cur];
            
            nums[nxt] = insert(idx, nxt);
        }
    }
    string str(n, 'X');
    int cur = nxt[0];
    while (cur != -1) {
        str[dat[cur]] = 'O';
        cur = nxt[cur];
    }

    return str;
}

2. Java

import java.util.*;

public class Solution {
    final int MX = 1200004; 
    int[] dat = new int[MX];
    int[] pre = new int[MX];
    int[] nxt = new int[MX];
    int[] nums = new int[MX];
    int unused = 1;
    
    public int insert(int addr, int num) {
        dat[unused] = num;
        pre[unused] = addr;
        nxt[unused] = nxt[addr];
        if (nxt[addr] != -1) pre[nxt[addr]] = unused;
        nxt[addr] = unused;
        return unused++;
    }
    
    public int erase(int addr) {
        nxt[pre[addr]] = nxt[addr];
        if (nxt[addr] != -1) {
            pre[nxt[addr]] = pre[addr];
            return nxt[addr];
        }
        return pre[addr];
    }
    
    public String solution(int n, int k, String[] cmd) {
        dat[0] = nxt[0] = -1;
        for (int i = 0; i < n; i++) 
            nums[i] = insert(i, i);
        
        int cursor = nums[k];
        Stack<int[]> s = new Stack<>(); 
        
        for (String str : cmd) {
            if (str.charAt(0) == 'U') {
                int num = Integer.parseInt(str.substring(2));
                while (num-- > 0) cursor = pre[cursor];
            } else if (str.charAt(0) == 'D') {
                int num = Integer.parseInt(str.substring(2));
                while (num-- > 0) cursor = nxt[cursor];
            } else if (str.charAt(0) == 'C') {                
                s.push(new int[] { dat[pre[cursor]], dat[cursor] });
                cursor = erase(cursor);
            } else {
                int[] top = s.pop();
                int cur = top[0];
                int next = top[1];
                
                int idx;
                if (cur == -1) idx = 0;
                else idx = nums[cur];
                
                nums[next] = insert(idx, next);
            }
        }        
        StringBuilder str = new StringBuilder("X".repeat(n));
        int cur = nxt[0];
        while (cur != -1) {
            str.setCharAt(dat[cur], 'O');
            cur = nxt[cur];
        }
        return str.toString();
    }
}

profile
꾸준하게

0개의 댓글