문자열 섞기

문제 설명

길이가 같은 두 문자열 str1과 str2가 주어집니다.

두 문자열의 각 문자가 앞에서부터 서로 번갈아가면서 한 번씩 등장하는 문자열을 만들어 return 하는 solution 함수를 완성해 주세요.

제한사항
  • 1 ≤ str1의 길이 = str2의 길이 ≤ 10
    • str1과 str2는 알파벳 소문자로 이루어진 문자열입니다.

입출력 예

str1 str2 result
"aaaaa" "bbbbb" "ababababab"

작성한 코드

function solution(str1, str2) {
    var answer = '';
    
    for(let i = 0; i < str1.length; i++){
        answer += str1[i] + str2[i];
    }
    
    return answer;
}

문자 리스트를 문자열로 변환하기

문제 설명

문자들이 담겨있는 배열 arr가 주어집니다. arr의 원소들을 순서대로 이어 붙인 문자열을 return 하는 solution함수를 작성해 주세요.

제한사항
  • 1 ≤ arr의 길이 ≤ 200
    • arr의 원소는 전부 알파벳 소문자로 이루어진 길이가 1인 문자열입니다.

입출력 예

arr result
["a","b","c"] "abc"

작성한 코드

function solution(arr) {
    var answer = '';
    answer = arr.join('');
    return answer;
}

해설

join()은 배열 요소 사이에 지정한 구분자를 넣어 연결하지만 공백 없이 붙일 때는 join('')처럼 빈 문자열을 구분자로 사용 가능


문자열 곱하기

문제 설명

문자열 my_string과 정수 k가 주어질 때, my_string을 k번 반복한 문자열을 return 하는 solution 함수를 작성해 주세요.

제한사항
  • 1 ≤ my_string의 길이 ≤ 100
  • my_string은 영소문자로만 이루어져 있습니다.
  • 1 ≤ k ≤ 100

입출력 예

my_string k result
"string" 3 "stringstringstring"
"love" 10 "lovelovelovelovelovelovelovelovelovelove"
입출력 예 설명

입출력 예 #1

  • 예제 1번의 my_string은 "string"이고 이를 3번 반복한 문자열은 "stringstringstring"이므로 이를 return 합니다.

입출력 예 #2

  • 예제 2번의 my_string은 "love"이고 이를 10번 반복한 문자열은 "lovelovelovelovelovelovelovelovelovelove"이므로 이를 return 합니다.

작성한 코드

function solution(my_string, k) {
    return my_string.repeat(k);
}

더 크게 합치기

문제 설명

연산 ⊕는 두 정수에 대한 연산으로 두 정수를 붙여서 쓴 값을 반환합니다. 예를 들면 다음과 같습니다.

  • 12 ⊕ 3 = 123
  • 3 ⊕ 12 = 312

양의 정수 a와 b가 주어졌을 때, a ⊕ b와 b ⊕ a 중 더 큰 값을 return 하는 solution 함수를 완성해 주세요.

단, a ⊕ b와 b ⊕ a가 같다면 a ⊕ b를 return 합니다.

제한사항
  • 1 ≤ a, b < 10,000

입출력 예

a b result
9 91 991
89 8 898
입출력 예 설명

입출력 예 #1

  • a ⊕ b = 991 이고, b ⊕ a = 919 입니다. 둘 중 더 큰 값은 991 이므로 991을 return 합니다.

입출력 예 #2

  • a ⊕ b = 898 이고, b ⊕ a = 889 입니다. 둘 중 더 큰 값은 898 이므로 898을 return 합니다.

작성한 코드

function solution(a, b) {
    const ab = Number(String(a) + String(b));
    const ba = Number(String(b) + String(a));
    return ab > ba ? ab : ba;
}

다른 사람의 코드

function solution(a, b) {
    return Math.max(Number(`${a}${b}`), Number(`${b}${a}`))
}

해설

숫자를 문자열로 바꾸고 + 연산으로 이어붙인 뒤, Number()로 다시 숫자로 변환.
그 둘 중 더 큰 값을 삼항 연산자로 비교해 반환

Math 함수를 사용해서 값을 비교하는 것도 가능


두 수의 연산값 비교하기

문제 설명

연산 ⊕는 두 정수에 대한 연산으로 두 정수를 붙여서 쓴 값을 반환합니다. 예를 들면 다음과 같습니다.

  • 12 ⊕ 3 = 123
  • 3 ⊕ 12 = 312

양의 정수 a와 b가 주어졌을 때, a ⊕ b와 2 * a * b 중 더 큰 값을 return하는 solution 함수를 완성해 주세요.

단, a ⊕ b와 2 * a * b가 같으면 a ⊕ b를 return 합니다.

제한사항
  • 1 ≤ a, b < 10,000

입출력 예

a b result
2 91 364
91 2 912
입출력 예 설명

입출력 예 #1

  • a ⊕ b = 291 이고, 2 * a * b = 364 입니다. 둘 중 더 큰 값은 364 이므로 364를 return 합니다.

입출력 예 #2

  • a ⊕ b = 912 이고, 2 * a * b = 364 입니다. 둘 중 더 큰 값은 912 이므로 912를 return 합니다.

작성한 코드

function solution(a, b) {
    const ab = Number(String(a) + String(b));
    const ab2 = Number(Number(a) * Number(b) * 2);
    return ab >= ab2 ? ab : ab2;
}

'코딩 테스트 준비 > JavaScript' 카테고리의 다른 글

[Programmers] JavaScript - Day2  (0) 2025.04.17
[Programmers] JavaScript - Day1  (1) 2025.04.16

덧셈식 출력하기

문제 설명

두 정수 a, b가 주어질 때 다음과 같은 형태의 계산식을 출력하는 코드를 작성해 보세요.

a + b = c

제한사항
  • 1 ≤ a, b ≤ 100

입출력 예

입력 #1

4 5

출력 #1

4 + 5 = 9​

작성한 코드

const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

let input = [];

rl.on('line', function (line) {
    input = line.split(' ');
}).on('close', function () {
    const a = Number(input[0]);
    const b = Number(input[1]);
    const sum = a + b;
    
    console.log(a + " + " + b + " = " + sum);
});

개념 정리

const [a, b] = line.split(' ').map(Number) 식으로 split과 동시에 Number 형태를 부여하는 것도 가능


문자 리스트를 문자열로 변환하기

문제 설명

두 개의 문자열 str1, str2가 공백으로 구분되어 입력으로 주어집니다.
입출력 예와 같이 str1과 str2을 이어서 출력하는 코드를 작성해 보세요.

제한사항
  • 1 ≤ str1, str2의 길이 ≤ 10
입출력 예

입력 #1

apple pen

출력 #1

applepen

입력 #2

Hello World!

출력 #2

HelloWorld!

작성한 코드

const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

let input = [];

rl.on('line', function (line) {
    input = line.split(' ');
}).on('close', function () {
    str1 = input[0];
    str2 = input[1];
    
    console.log(str1 + str2);
});

 


문자열 돌리기

문제 설명

문자열 str이 주어집니다.
문자열을 시계방향으로 90도 돌려서 아래 입출력 예와 같이 출력하는 코드를 작성해 보세요.

제한사항

1 ≤ str의 길이 ≤ 10

입출력 예

입력 #1

abcde

출력 #1

a
b
c
d
e

작성한 코드

const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

let input = [];

rl.on('line', function (line) {
    input = [line];
}).on('close',function(){
    str = input[0];
    
    if (input.length === 1) {
        for (let char of str) {
            console.log(char);
        }
    } else {
        console.log(input.reverse().join(''));
    }
});

개념 정리

가로에서 세로로 돌리기와 세로에서 가로로 돌릴때의 경우의 수를 생각해서 코드를 구현

input값이 1개 일 때에는 반복문을 이용해 원소 하나씩 출력하고 원소가 세로로 하나씩 주어질 때에는 reverse를 통해 순서를 뒤집고 join으로 합침.


문자열 겹쳐쓰기

 

문제 설명

문자열 my_string, overwrite_string과 정수 s가 주어집니다. 문자열 my_string의 인덱스 s부터 overwrite_string의 길이만큼을 문자열 overwrite_string으로 바꾼 문자열을 return 하는 solution 함수를 작성해 주세요.

제한사항
  • my_string와 overwrite_string은 숫자와 알파벳으로 이루어져 있습니다.
  • 1 ≤ overwrite_string의 길이 ≤ my_string의 길이 ≤ 1,000
  • 0 ≤ s ≤ my_string의 길이 - overwrite_string의 길이
입출력 예
my_string overwrite_string s result
"He11oWor1d" "lloWorl" 2 "HelloWorld"
"Program29b8UYP" "merS123" 7 "ProgrammerS123"

입출력 예 설명

입출력 예 #1

  • 예제 1번의 my_string에서 인덱스 2부터 overwrite_string의 길이만큼에 해당하는 부분은 "11oWor1"이고 이를 "lloWorl"로 바꾼 "HelloWorld"를 return 합니다.

입출력 예 #2

  • 예제 2번의 my_string에서 인덱스 7부터 overwrite_string의 길이만큼에 해당하는 부분은 "29b8UYP"이고 이를 "merS123"로 바꾼 "ProgrammerS123"를 return 합니다.

작성한 코드

function solution(my_string, overwrite_string, s) {
    var answer = '';
    
    answer += my_string.slice(0, s);
    answer += overwrite_string;
    answer += my_string.slice(s + overwrite_string.length);
    
    return answer;
}

 

'코딩 테스트 준비 > JavaScript' 카테고리의 다른 글

[Programmers] JavaScript - Day3  (0) 2025.04.17
[Programmers] JavaScript - Day1  (1) 2025.04.16

문자열 출력

문제 설명

문자열 str이 주어질 때, str을 출력하는 코드를 작성해 보세요.

제한사항
  • 1 ≤ str의 길이 ≤ 1,000,000
  • str에는 공백이 없으며, 첫째 줄에 한 줄로만 주어집니다.
입출력 예

입력 #1

HelloWorld!

출력 #1

HelloWorld!​

작성한 코드

const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

let input = [];

rl.on('line', function (line) {
    input = [line];
}).on('close',function(){
    str = input[0];
    console.log(str);
});

회고

realine 은 Node.js에서 표준 입력(키보드 입력)을 받을 때 사용하는 기본 도구(모듈)임.

rl.on('line', callback) 은 line 이라는 변수로 입력값을 받는 것, 그걸 console.log로 출력


a와 b 출력하기

문제 설명

정수 a와 b가 주어집니다. 각 수를 입력받아 입출력 예와 같은 형식으로 출력하는 코드를 작성해 보세요.

제한사항
  • -100,000 ≤ a, b ≤ 100,000
입출력 예

입력 #1

4 5

출력 #1

a = 4
b = 5

작성한 코드

const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

let input = [];

rl.on('line', function (line) {
    input = line.split(' ');
}).on('close', function () {
    const a = Number(input[0]);
    const b = Number(input[1]);

    console.log(`a = ${a}`);
    console.log(`b = ${b}`);});

개념 정리

 

split(' ') 은 문자열을 공백 기준으로 나눠 배열로 만들어주는 역할을 함.

Number()은 문자열을 숫자로 바꿔줌.

${} 템플릿 문자열 `` 안에서 사용 가능하고 안에 넣은 변수의 값을 출력 가능함.


a + b 출력하기

문제 설명

문자열 str과 정수 n이 주어집니다.
str이 n번 반복된 문자열을 만들어 출력하는 코드를 작성해 보세요.

제한사항
  • 1 ≤ str의 길이 ≤ 10
  • 1 ≤ n ≤ 5
입출력 예

입력 #1

string 5

출력 #1

stringstringstringstringstring
const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

let input = [];

rl.on('line', function (line) {
    input = line.split(' ');
}).on('close', function () {
    str = input[0];
    n = Number(input[1]);
    
    console.log(str.repeat(n));
});

개념 정리

 

repeat() 은 문자열을 반복해서 출력이 가능함. 단, repeat은 문자열에만 사용이 가능하다.

for 문을 사용하는 것도 가능하지만 2개의 방식이 미묘한 차이점이 있음.

repeat() for문
내장 함수라서 속도가 빠름 조건 추가, 중간 로직 삽입이 가능함
간단한 반복이라면 가장 간결하고 가독성이 좋음 인덱스 별로 다른 처리를 하는 등 상황에 따라 더 유연함

대소문자 바꿔서 출력하기

문제 설명

영어 알파벳으로 이루어진 문자열 str이 주어집니다. 각 알파벳을 대문자는 소문자로 소문자는 대문자로 변환해서 출력하는 코드를 작성해 보세요. 

제한사항
  • 1 ≤ str의 길이 ≤ 20
    • str은 알파벳으로 이루어진 문자열입니다.
입출력 예

입력 #1

aBcDeFg

출력 #1

AbCdEfG​
const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

let input = [];

rl.on('line', function (line) {
    input = [line];
}).on('close',function(){
    str = input[0];
    let answer = "";
    
    for(let char of str){
        if(char == char.toUpperCase()){
            answer += char.toLowerCase();
        } else{
            answer += char.toUpperCase();
        }
    }
    
    console.log(answer);
});

개념 정리

 

== 뒤에 toUpperCase 혹은 toLowerCase를 사용하면 대소문자인지 판별하는 코드가 되고

+= 뒤에 붙여 사용하면 해당 문자를 대소문자 변환 하여 추가하는 코드가 됨.

 const result = str
        .split('')
        .map(char => {
            return char === char.toUpperCase()
                ? char.toLowerCase()
                : char.toUpperCase();
        })
        .join('');

이런 식으로 map과 ? 삼항 연산자를 이용하여 변환하고 join으로 합치는 방식도 가능함.


특수문자 출력하기

문제 설명

다음과 같이 출력하도록 코드를 작성해 주세요.

출력 예시

!@#$%^&*(\'"<>?:;​
const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

rl.on('close', function () {
    console.log("!@#$%^&*(\\'\"<>?:;");
});

개념 정리

 

다른 특수문자들은 평범하게 문자열 방식으로 출력 가능하고

', " 같은 문자열 안의 따옴표는 \ 를 붙여 처리해야 출력이 가능함.

'코딩 테스트 준비 > JavaScript' 카테고리의 다른 글

[Programmers] JavaScript - Day3  (0) 2025.04.17
[Programmers] JavaScript - Day2  (0) 2025.04.17

IP 단편화 피하기 - 경로 MTU 발견

IP 단편화(IP Fragmentation)

  • 패킷을 전송할 때 MTU(Maximum Transmission Unit)을 초과하면 한번에 전송할 수 없어 패킷을 MTU 이하의 조각으로 분할하는 것을 단편화(Fragmentation), 분할된 조각을 단편(Fragment)이라고 한다.
  • 거치는 라우터 마다 전송에 적합한 데이터 링크 계층 프레임으로 변환이 필요하다.
  • 재조립(Reassembly)은 항상 최종 수신지에서만 가능하다.

IPv4 단편화

  • IPv4에서는 발신지 뿐만 아니라 중간 라우터에서도 IP 단편화가 가능하다.
  • 각 조각들은 최종 목적지 시스템에 전달되기 전에는 재조립되지 않고, 최종 목적지에 전달되면 목적지 시스템의 IP 소프트웨어가 원래의 데이터그램으로 재조립 됨

IPv6 단편화

  • IPv6에서는 IP 단편화가 발신지에서만 가능하다.
  • IPv6에서는 라우팅 처리 효율을 높이기 위해, 가급적 IP 단편화를 필요없도록 한다.
  • Ipv4와 달리, 기본 헤더 상에 단편화 제어 관련 필드를 두지 않고, 단편화 확장 헤더를 통해 단편화한다.

MTU(Maximum Transmission Unit)

MTU는 최대 전송 단위로서 TCP/IP Network 등과 같이 패킷 또는 프레임 기반의 네트워크에서 전송될 수 있는 최대 크기의 패킷 또는 프레임을 가리키며 대개 옥텟을 단위로 사용한다.

  • MTU가 너무 크면 큰 크기의 패킷을 처리할 수 없는 라우터를 만나면 재전송을 해야하는 경우가 생길 수 있다.
  • MTU가 너무 작으면 상대적으로 헤더 및 송수신 확인에 따르는 오버헤드가 커진다

단편화 피하기

IP의 본래 목적은 주소 지정과 단편화지만 오늘날의 네트워크 환경에서는 잘 발생하지 않는데, 이는 네트워크 성능의 발전 때문도 있지만 단편화가 잦으면 불필요한 트래픽 증가와 대역폭 낭비를 초래할 수 있기 때문이다. 혹은 단편화된 패킷을 재조립하는 과정에서 발생하는 부하도 성능 저하로 이어질 수 있다.

단편화 피하는 방법

  • IP 패킷을 주고받는 경로에 존재하는 모든 호스트의 처리 가능한 MTU 크기를 고려해서 IP 단편화 없이 주고 받을 수 있는 크기만큼만 전송해야 함
  •  IP 단편화 없이 주고 받을 수 있는 크기 = 경로 MTU(Path MTU)
  • 주고받을 수 있는 경로 MTU를 구해서 해당 크기만큼만 송수신하여 IP 단편화를 회피하는 기술을 경로 MTU 발견(Path MTU discovery) 라고 함
  • 오늘 날의 네트워크는 경로 MTU 발견을 지원하고 처리 가능한 최대 MTU 크기도 균일하여 IP 단편화가 자주 발생하지 않게 됨.

IP 주소

IP 주소는 어느 네트워크의 어느 호스트라는 것을 식별하는 주소입니다. IP 주소는 호스트가 속한 네트워크 주소/브로드캐스트 주소인 네트워크 부(Network Part 또는 Network ID)와 호스트의 주소인 호스트 부 (Host Part 또는 Host ID)로 구성됩니다.

네트워크 부는 어떤 네트워크 인지를 나타내 다른 네트워크와 구분하는 역할을 하고, 호스트 부는 해당 네트워크의 어느 호스트인지를 나타내 다른 호스트와 구분하는 역할을 합니다. 여기서 호스트는 컴퓨터뿐만이 아니라 IP 주소가 할당되는 라우터를 포함합니다.

네트워크 부는 인터넷에 접속되어 있는 모든 네트워크 중에서, 호스트 부는 그 호스트가 속한 네트워크 내에서 유일한 번호를 할당하여 인터넷 전체에서 동일한 IP 주소를 갖는 호스트는 1대밖에 없도록 설정합니다.

따라서 같은 네트워크 안에 있는 컴퓨터, 즉 라우터 없이도 데이터 전송이 가능한 컴퓨터는 네트워크 부가 동일하고 호스트 부만 다릅니다. 달리 말하면 네트워크 부가 다르다는 것은 서로 다른 네트워크라는 의미이고, 라우터를 통하지 않고는 통신이 불가능하다는 뜻입니다. 서로 다른 네트워크가 라우터를 통해 통신이 가능한 것은 라우터가 IP 주소의 네트워크 부를 보고 라우팅을 하여 데이터를 전송하기 때문입니다.

인터넷에 접속 가능한 네트워크를 만들기 위해 IP 주소 할당 기관(NIC)에 IP 주소 할당을 신청하면 할당 기관에서는 네트워크 부까지만 할당합니다. 네트워크 부를 할당받으면 네트워크를 만드는 사람(네트워크 관리자)이 호스트 부를 결정하여 네트워크 부와 호스트 부를 합친 IP 주소를 개별 호스트에 설정하는 것입니다.

IPv4 도입 초기에는 클래스(class)를 기준으로 네트워크부와 호스트를 나누는 방식을 사용했지만, 클래스 방식의 비효율성으로 인해 현재는 클래스에 구애받지 않고 서브넷 마스크(subnet mask) 방식을 사용하고 있습니다.

IP 주소의 클래스

클래스 기준은 IP 주소를 앞에서 8비트씩 나눈 그룹을 조합하여 네트워크 부와 호스트 부를 정한 것입니다. 즉, 클래스에 따라 어디까지가 네트워크 부이고, 어디까지가 호스트 부인지가 결정됩니다.

클래스 A

클래스 A는 IP 주소 32비트 중 앞 8비트를 네트워크 부로, 다음 24비트를 호스트 부로 나눈 것입니다. 네트워크 부의 첫 비트는 클래스 A 식별 비트인 '0'이 할당되기 때문에 00000000 ~ 01111111의 번호가 네트워크 부로 사용됩니다. 이를 십진수로 표기하면 클래스 A의 네트워크 부는 0 ~ 127의 번호가 할당됩니다.

다음 24비트는 호스트 부로 사용되고, 한 네트워크 안에서 할당할 수 있는 호스트 번호는 0.0.0 ~ 255.255.255까지 약 16,777,214개입니다. 말하자면 IP 주소 관리기관이 IP 주소 신청자에게 클래스 A의 네트워크 부 1개를 할당하면, 신청자는 약 1,677만 개의 호스트 부를 마음대로 정할 수 있게 되는 것입니다. 1개의 네트워크에 약 1,677만 개의 호스트를 연결할 수 있기 때문에 클래스 A는 주로 대규모의 네트워크를 구축하는 기관에 할당됩니다.

호스트 부의 모든 비트가 0과 1인 번호는 특수 목적(네트워크 주소와 브로드캐스트 주소)으로 사용하기 때문에 IP 주소의 호스트 부를 할당하는 경우에는 이 두 번호를 제외합니다. 따라서 클래스 A의 호스트 부에서는 2의 24승인 16,777,216에서 2를 뺀 16,777,214개의 번호를 호스트 부에 할당할 수 있습니다.

네트워크 부와 호스트 부를 조합해 클래스 A에서 할당 가능한 IP 주소의 범위는 0.0.0.0 ~ 127.255.255.255이고, 이는 2,147,483,648(2의 31승) 개로 전체 IP 주소의 개수 중 약 50%에 해당합니다.

② 클래스 B

클래스 B는 IP 주소 32비트 중 앞 16비트를 네트워크 부로, 다음 16비트를 호스트 부로 나눈 것입니다. 네트워크 부의 맨 앞 2비트는 클래스 B의 식별 비트인 '10'으로 할당되기 때문에 10000000 ~ 10111111의 번호가 네트워크 부의 첫 8비트로 사용됩니다. 네트워크 부 16비트를 십진수로 표기하면 클래스 B의 네트워크 부는 128.0 ~ 191.255 번호가 할당됩니다.

다음 16비트는 호스트 부로 할당되고, 한 네트워크 안에서 할당할 수 있는 호스트 주소는 65,534(2의 16승 - 2) 개입니다.

네트워크 부와 호스트 부를 조합해 클래스 B에서 할당 가능한 IP 주소의 범위는 128.0.0.0 ~ 191.255.255.255이고, 이는 1,073,741,824(2의 30승) 개로 전체 IP 주소의 개수 중 약 25%에 해당합니다.

③ 클래스 C

클래스 C는 IP 주소 32비트 중 앞 24비트를 네트워크 부로, 다음 8비트를 호스트 부로 나눈 것입니다. 네트워크 부의 맨 앞 3비트는 클래스 C의 식별 비트인 '110'으로 할당되기 때문에 11000000 ~ 11011111의 번호가 네트워크 부의 첫 8비트로 사용됩니다. 네트워크 부 24비트를 십진수로 표기하면 클래스 C의 네트워크 부는 192.0.0 ~ 255.255.255 번호가 할당됩니다.

다음 8비트는 호스트 부로 할당되고, 한 네트워크 안에서 할당할 수 있는 호스트 주소는 254(2의 8승 -2) 개입니다.

네트워크 부와 호스트 부를 조합해 클래스 C에서 할당 가능한 IP 주소는 192.0.0.0 ~ 223.255.255.255입니다.

네트워크 주소와 브로드캐스트 주소

네트워크 1의 호스트나 라우터는 203.179.33.0이나 203.179.33.255의 IP 주소를 사용할 수 없습니다. 마찬가지로 네트워크 2의 호스트나 라우터도 192.168.24.0이나 192.168.24.255의 IP 주소를 사용할 수 없습니다. 다시 말해 클래스 C인 경우 호스트 부 8비트가 모두 0, 즉 십진수로 0인 번호와 호스트 부 8비트가 모두 1, 즉 10진수로 255인 번호는 컴퓨터나 라우터가 자신의 IP 주소로 사용할 수 없습니다.

클래스를 불문하고 IP 주소 중 호스트 부의 모든 비트가 0인 번호는 네트워크 주소로, 모든 비트가 1인 번호는 브로드캐스트 주소라는 특수 목적으로 사용하기 때문에 호스트와 라우터에는 할당하지 않습니다.

네트워크 주소는 전체 네트워크에서 작은 네트워크를 식별할 때 사용되고, 호스트 부가 십진수로 0이면 그 네트워크를 대표하는 주소가 됩니다.

브로드캐스트 주소는 하나의 네트워크에 있는 모든 호스트에 동시에 데이터를 보낼 때 사용되는 전용 IP 주소를 의미합니다.

즉, 전체 네트워크에 데이터를 전송할 때는 호스트 부에 255를 설정하면 됩니다. 만약 IP 주소 203.179.33.13인 호스트가 IP 주소 192.168.24.255로 데이터 를 전송하면 192.168.24.0의 네트워크에 있는 모든 호스트가 데이터를 수신합니다.

서브넷팅과 서브넷

클래스 기반 주소 지정 방식에서는 클래스가 정해지면 네트워크 부와 호스트 부의 길이 및 하나의 네트워크 당 사용 가능한 IP 주소가 정해집니다.

클래스 A를 사용할 경우 한 개의 네트워크 당 약 1,677만 대의 호스트를 연결할 수 있고 클래스 B를 사용할 경우 한 개의 네트워크 당 약 6만 5천대의 호스트를 연결할 수 있습니다. 하지만 실제로 이렇게 많은 호스트를 하나의 네트워크에 연결하는 경우는 거의 없기 때문에 전체 IP 주소의 75%를 차지하는 클래스 A와 클래스 B에서 많은 수의 IP 주소가 사용되지 않고 낭비됩니다.

IP 주소를 효율적으로 활용하기 위해서 클래스 A와 B 같은 대규모 네트워크를 좀 더 작은 네트워크로 분할하는 것을 서브넷팅(Subnetting)이라 하고, 분할된 네트워크를 서브네트워크(Subnetwork) 혹은 줄여서, 서브넷(Subnet)이라고 합니다.


네트워크 부가 0인 클래스 A의 네트워크 1개를 서브넷팅 하여 256개의 작은 네트워크로 분할한 것입니다. 호스트 부의 비트를 서브넷 부로 변경하여 서브넷으로 만듭니다. 서브넷팅을 하면 네트워크 부와 호스트 부로 구성되었던 클래스가 네트워크 부, 서브넷 부, 호스트 부로 변경됩니다.

약 1,667만 대의 컴퓨터를 연결할 수 있던 하나의 네트워크를 256개의 서브넷으로 분할하여 하나의 서브넷에 약 6만 5천 대의 컴퓨터를 연결할 수 있게 만든 것입니다.

서브넷팅이라는 논리적인 방법으로 분할된 네트워크는 라우터에 의해 물리적으로 구별됩니다.

서브넷팅 전에는 라우터 없이 약 1,667만 대의 컴퓨터 간에 통신이 가능했지만, 서브넷팅 후에는 서브넷이 서로 통신을 하기 위해선 라우터가 필요합니다.

이처럼 서브넷팅을 통해 호스트 부가 서브넷 부로 변경되면서 네트워크 부가 확장됩니다. 따라서 클래스 A의 IP 주소를 서브 넷팅하면 네트워크 부가 변경되는데 IP 주소만으로는 변경된 네트워크 부가 어디까지인지 알 수가 없습니다. 아래 <그림 8>과 같이 서브넷팅을 한다고 해서 IP 주소가 변경되지 않기 때문입니다.

IP 주소만으로 네트워크 부와 호스트 부의 경계를 알 수 있도록 만든 클래스가 서브넷팅으로 인해 그 의미를 잃게 된 것입니다.

따라서 IP 주소를 서브넷팅하는 경우 IP 주소와 별도로 어디까지가 네트워크 부이고 어디까지가 호스트 부인지 구별할 수 있는 식별자가 필요한데, 이 식별자를 서브넷 마스크라고 합니다.

서브넷 마스크

서브넷 마스크는 IP 주소의 네트워크 부와 호스트 부의 경계를 식별하기 위해 만든 숫자입니다. 서브넷 마스크는 IP 주소의 32비트에 대응한 32비트로 구성되어 있습니다. 즉, 서브넷 마스크는 IP 주소처럼 32개의 0 또는 1로 구성된 값입니다.

IP 주소의 비트가 네트워크 부이면 이 비트에 대응하는 서브넷 마스크의 비트는 1이 되고, IP 주소의 비트가 호스트 부이면 서브넷 마스크의 비트는 0이 됩니다. 이러한 방법으로 클래스에 구애받지 않고 IP 주소의 네트워크부를 식별할 수 있습니다.


서브넷 마스크 표기법

① 십진수 표기법

32비트로 표현된 서브넷 마스크도 IP 주소와 마찬가지로 보기 쉽게 전체 32비트를 8비트씩 4그룹으로 나누어, 각 그룹을 십진수로 변환하고, 그룹의 경계에 '.'을 넣은 방법으로 표기하고 있습니다.


② 프리픽스 표기법

서브넷 마스크를 슬래시(/)와 네트워크부 비트수로 나타내는 프리픽스(prefix) 표기법을 사용할 수 있습니다. 프리픽스로 표기한 서브넷 마스크는 IP 주소와 함께 묶어 표현합니다.

네트워크 부가 8비트인 클래스 A에서 8비트를 서브넷팅한 IP 주소를 서브넷 마스크로 정의

서브넷팅 후 IP 주소는 동일하지만 달라진 서브넷 마스크로 서브넷팅 후에는 앞에서 16비트까지가 네트워크 부인 것을 식별할 수 있게 됩니다.

서브넷 마스크의 유용성

서브넷 마스크를 사용하면 8비트 단위가 아닌 1비트 단위로 네트워크 부를 구성할 수 있기 때문에 더 세분화된 네트워크를 만들 수 있습니다.

예를 들어, 약 60개의 호스트를 연결하는 네트워크를 구축하는 경우 클래스 기준에서는 가장 적은 수의 호스트를 연결할 수 있는 클래스 C의 네트워크 부를 사용할 수밖에 없습니다. 다시 말해 60개의 호스트를 연결하는 하나의 네트워크를 만들기 위해 IP 주소 관리 기관에 IP 주소 할당 신청을 하면 주소 관리 기관에서는 C클래스의 네트워크 부 주소 하나를 할당하게 되고 신청자는 254(2의 8승 - 2) 개의 IP 주소를 사용할 수 있게 됩니다. 60개가 필요한 데 254개가 할당되니 나머지 IP 주소는 누구도 사용하지 못하는 주소가 됩니다.

만약 호스트 부에서 2비트를 빌려 서브넷팅 하면 클래스 C의 1개의 네트워크를 4개로 분할하고 각 네트워크 당 62(2의 6승 -2)의 IP 주소를 사용할 수 있게 됩니다. 따라서 60개의 IP 주소가 필요한 사람에게 서브넷 마스크가 255.255.255.192인 IP 주소의 네트워크 부 주소 하나를 할당하고, 나머지 3개의 네트워크 부 주소를 다른 사람에게 할당하는 방법으로 IP 주소를 낭비하지 않을 수 있습니다.

현재는 클래스와 상관없이 한 네트워크에 연결하고 싶은 호스트들의 규모에 맞게 네트워크 부와 호스트 부의 길이를 비트 단위로 유연하게 변경할 수 있는 서브넷 마스크를 사용하여 IP 주소를 할당합니다.

클래스 방식에서 네트워크 부를 8비트 단위로 IP 주소 32비트의 맨 앞에서부터 선택한 것처럼 서브넷 마스크 방식에서도 네트워크 부를 1비트 단위로 IP 주소 32비트의 맨 앞에서부터 차례로 선택합니다. 따라서 서브넷 마스크는 반드시 연속한 '1' 과 연속한 '0' 으로 구성됩니다. '1'과 '0'이 교대로 나타나는 서브넷 마스크는 없습니다. 이로 인해 서브넷 마스크의 십진수 표기법이 가질 수 있는 값은 다음과 같습니다.

NAT와 NAPT

인터넷이 보편화되면서 IP 주소의 부족 문제를 해결하고, 네트워크 보안을 강화하기 위해 다양한 기술이 개발되었습니다. 그 중에서 NAT(Network Address Translation)NAPT(Network Address Port Translation)는 네트워크에서 자주 사용되는 기술입니다.

개념

NAT와 NAPT는 현대 네트워크 환경에서 필수적인 기술입니다.

NAT는 내부 네트워크의 사설 IP 주소를 공인 IP 주소로 변환하여 인터넷과 통신할 수 있게 하는 기술로 IP 주소의 절약과 보안 향상에 기여합니다.

NAPT는 NAT의 확장 개념으로, 단순히 IP 주소를 변환하는 것에 더해 포트 번호까지 함께 변환합니다. 이를 통해 하나의 공인 IP 주소를 사용하여 다수의 내부 네트워크 장비들이 동시에 인터넷에 접속할 수 있습니다. 다수의 장치가 동시에 인터넷에 접속할 수 있게 합니다

NAT

특징

  • 사설 IP 주소를 외부에서 감출 수 있음.
  • IP 주소의 재사용이 가능해, 공인 IP 주소의 사용을 줄일 수 있음.
  • 네트워크 보안을 강화할 수 있음.

장점

  • 공인 IP 주소의 절약.
  • 내부 네트워크 구조를 외부에 노출시키지 않음으로써 보안 강화.

단점

  • 네트워크 연결에 있어 일부 프로토콜과의 호환성 문제가 발생할 수 있음.
  • 특정 애플리케이션에서 문제가 발생할 수 있음.

사용 사례

  • 소규모 가정용 네트워크나 중소기업에서 내부 네트워크 보호 및 IP 주소 절약을 위해 널리 사용됩니다.

NAPT

 

개념

  • NAPT는 NAT의 확장 개념으로, 단순히 IP 주소를 변환하는 것에 더해 포트 번호까지 함께 변환합니다. 이를 통해 하나의 공인 IP 주소를 사용하여 다수의 내부 네트워크 장비들이 동시에 인터넷에 접속할 수 있습니다.

특징

  • IP 주소와 포트 번호를 모두 변환함으로써, 여러 장치가 동시에 하나의 공인 IP 주소를 공유할 수 있음.
  • "포트 포워딩" 설정을 통해 외부에서 내부 네트워크의 특정 장치로 직접 접근할 수 있음.

장점

  • 단일 공인 IP 주소로 여러 장치의 인터넷 접속을 가능하게 함.
  • 내부 네트워크의 세밀한 트래픽 제어가 가능.

단점

  • 포트 번호가 한정되어 있어, 매우 많은 연결 시 포트 고갈 문제가 발생할 수 있음.
  • NAT와 마찬가지로, 특정 애플리케이션과의 호환성 문제 발생 가능.

사용 사례

  • 대규모 네트워크 환경에서 공인 IP 주소 절약과 내부 장치 보호를 위해 주로 사용됩니다.
  • 가정용 라우터에서도 NAPT는 일반적으로 기본 설정으로 제공됩니다.

 


참고자료

출처: https://better-together.tistory.com/118 [변계사 Sam의 테크 스타트업!:티스토리]

 

네트워크 통신이란

어원은 그물을 뜻하는 net과 work의 합성어이며 그물을 짜는 행위를 가리키는 명사에서 임의의 연결망을 지칭하는 용어로 그 범위가 확장된 단어이다. 또한 통신 장치 간에 데이터 및 정보를 교환하는 것을 의미한다. 정보를 교환하기 위해서는 프로토콜(Protocol)이라는 통신 규약에 따라 정보를 전송하고 공유한다.

네트워크 통신의 발달로 전세계 소식을 실시간으로 확인하고, 정보를 교류할 수 있게 되었으며, 사물 인터넷(IoT)기기의 발달과 보급으로 다양한 전자 기기들을 제어할 수 있게 되었다.

네트워크 통신의 역사

  • 1800년경
    • 유선 통신의 시작
    • 볼타가 최초로 전지를 발명하여 전선을 통해 신호를 보내는 방법을 연구하기 시작
    • 사무엘 모르스가 알파벳 문자에 대해 점과 대시를 사용해 신호를 보냈는데 이게 모스부호
  • 1864년
    • 무선 통신의 시작
    • 제임스 클럭 맥스웰이 전자기파가 대기 중에서 전파된다고 예측함
  •  1876년
    • 알렉산더 그레이엄 벨이 음성을 전달할 수 있는 전화기를 개발
  • 1888년
    • 하인리히 루돌프 헤르츠가 실험을 통해 라디오파를 주고받음으로써 전자기파가 대기 중에 있다고 입증
  • 1894년
    • 마르코니가 지상의 금속판에 연결된 수직 안테나를 사용해 신호 전달의 범위를 증가시킬 수 있음을 증명
    • 해당 실험으로 121km 까지 소식 교환이 가능하게 됨
  • 1958년
    • 컴퓨터 통신의 시작
    • 벨 텔레폰 연구소의 조지 스티비츠가 전화 교환회로를 산술기기로 발전시킨 모델-K 기기를 개발
    • 모델-K가 CNC (Complex Number Calculator) 로 발전해 더 정교한 산술 계산이 가능하게 됨.
    • CNC는 입력기가 본체와 따로 떨어져 전화선으로 데이터를 주고받을 수 있었는데 이런 원격 데이터 통신 방식은 이후 모뎀, 시분할 시스템, 컴퓨터 네트워크 기술로 발전
  • 1964년 
    • 군사 작전 수행을 위한 고성능 컴퓨터 개발을 목적으로 ARPA (Agency for Advanced Research Project Agency) 에 커맨드 컨트롤 리서치라는 부서를 설립
    •  J.C.R 리클라이더에 의해 커맨드컨트롤 리서치는 순수 컴퓨터 과학 연구를 위한 IPTO(Information Processing Techniques Office)로 재탄생
  • 1965년
    • 세계 최초의 컴퓨터 네트워크 개발에 착수, 이 네트워크가 ARPANET (Advanced Research Project Agency Network) 탄생으로 이루어진다.
    • 이후 최초의 장거리 컴퓨터 통신이 이루어짐. MIT 링컨 연구소의 TX-2가 캘리포니아 산타 모니카 SDC(System Development Corporation)의 Q-32 컴퓨터와 전화선으로 직접 통신함.
    • ARPA가 처음 시도한 장거리 컴퓨터 통신망이었다. 기존의 방대한 전화망을 이용해 컴퓨터 네트워크를 구축하려고 한 것이다.  그러나 너무 느리고 비싸고 비효율적이었다.
    • TX-2와 SDC Q-32 네트워크 구축을 제안한 심리학자 톰 마릴이 컴퓨터 간에 메시지를 전달하는 과정에서  메시지가 제대로 도착했는지 확인하는 방법을 가리켜 '기술적 은어' 라는 뜻으로 프로토콜이라고 부름
  • 1967년
    • ARPA가 ACM 모임에서 각 호스트를 IMP라는 특정 컴퓨터에 연결하고, IMP들을 서로 연결하는 ARPANET이라는 아이디어를 제안
    • IMP는 현재의 라우터 개념과 유사함.
  • 1969년
    • 4개의 노드를 네트워크로 구성하고 NCP라는 프로토콜을 호스트 간 통신에 사용
  • 1971년
    • 레이 톰린슨이 전자 메일 프로그램을 개발
    • 톰린슨이 전자 메일에 @기호를 넣었고, 지금도 이 방식에 따라 아이디 뒤에 @기호를 사용
  • 1972년
    • Vinton Cerf Bob Kanhn이 네트워크를 통해 패킷을 전송하는 중계 하드웨어 역할을 하는 게이트웨이를 개발
  • 1973년
    • 빈트 서프와 로버트 칸이 TCP/IP프로토콜과 인터넷 구조를 설계
    • 컴퓨터와 터미널로 구성되는 네트워크로 IBM의 SNA망이 최초
  • 1974년
    • 제록스가 이더넷을 개발
    • 이더넷은 네트워크 구조를 호스트-터미널 구조에서 오늘날과 같은 클라이언트-서버 구조로 전환하는데 큰 역할을 함
  • 1979년
    • 유즈넷 탄생
  • 1981년
    • TCP를 두 개의 프로토콜인 TCP와 IP로 나누고 이를 표준화
    • 유닉스 운영체제에 TCP/IP가 배포
    • TCP/IP는 ARPANET의 공식 프로토콜이 됨
  • 1983년
    • ARPANET에서 MILNET (군사용 네트워크)이 분리
    • 존 포스텔이 도메인 이름 시스템을 개발
  • 1984년
    • DNS가 구성되어 네트워크가 폭발적으로 확장되었다.
    • OSI(Open Systems Interconnection)모델, 컴퓨터 및 네트워크 모든 시스템을 상호 연동하여 사용할 수 있도록 표준화한 모델을 발표
  • 1989년
    • 버너스-리 라는 CERN(유럽핵물리연구소)의 물리학자가 웹 개념을 제안
  • 1990년
    • ARPANET이 해체되고 NSFNET이 만들어짐
    • 버너스-리라가 동 료인 로버트 카이유와 서로 다른 컴퓨터끼리 정보를 공유하고 서로 링크되어 찾기 쉽도록 하이퍼텍스트형태의 서비스를 도입
    • 웹 브라우저, WWW(World Wide Web)가 등장
  • 2007년
    • 휴대전화와 카메라 그리고 인터넷 기능이 하나로 통합된 iPhone(스마트폰)이 등장

OSI 4계층 / 7계층

OSI 7계층

국제 표준화 기구(ISO, International Organization Standardization)

  • 물리 계층(Physical Layer)
    • 가장 최하위 계층으로 비트 신호를 주고받는 계층
    • 물리적인 유무선 통신 매체(LAN, 케이블 등)을 통해 비트 스트림(BitStream)을 전송
  • 데이터 링크 계층(Data Link Layer)
    • 물리 계층에서는 단순히 데이터를 전달만 하기에 데이터 상에 문제가 발생하여도 알 수가 없기 때문에 같은 LAN에 속한 호스트끼리 올바르게 정보를 주고받기 위한 계층
    • 데이터 시작과 끝 부분에 제어 정보를 추가하여 에러를 확인하고 제어
    • 호스트를 식별할 수 있는 주소(MAC 주소) 등을 할당하여 네트워크 장비들을 식별
  • 네트워크 계층(Network Layer)
    • 네트워크 간 통신을 가능하게 하는 계층
    • 데이터를 전송하는 스위칭(Switching)기능과 데이터를 전송을 위한 최적의 경로를 결정하는 라우팅(Routing)기능을 제공
    • 네트워크 간 통신 과정에서 호스트를 식별할 수 있는 주소(IP 주소)를 이용해 다른 네트워크와 통신을 주고받기 위해 필요한 계층
  • 전송 계층(Transfer Layer)
    • 수신지와 목적지를 감독하면서 전체 데이터가 오류 없이 순서대로 도착하는 것을 보장하며 목적지까지 에러 제어, 흐름 제어 등을 수행하며 신뢰성 있는 데이터 전송을 담당
    • 대표적인 프로토콜로 TCP(Transport Protocol)와 UDP(User Datagram Protocol)가 있다
  • 세션 계층(Session Layer)
    • 응용 프로그램 간의 연결 상태를 의미하는 세션(Session)을 유지하거나 새롭게 생성하고 필요하다면 연결을 끊는 역할을 하며 관리하기 위한 계층
    • 파일을 전송하던 중에 전송이 중단되어 이어서 전송 해야 하는 경우 데이터를 동기화하고 통신 세션을 설정하고 유지하는 역할을 함.
  • 표현 계층(Presentation Layer)
    • 데이터를 변환, 인코딩, 압축, 암호화, 복호화 등 번역을 수행하여 상위 계층인 응용 계층이 이해할 수 있는 데이터로 가공하는 계층
    • 대표적인 예시로 JPEG, AVI 등이 있음
  • 응용 계층(Application Layer)
    • 사용자와 가장 밀접하게 맞닿아 있어 통신할 수 있는 여러 응용 네트워크 서비스를 제공한다.
    • 대표적인 예시로 웹 브라우저, FTP등이 있다

TCP/IP 모델 (OSI 4계층)

  • 네트워크 엑세스 계층(Network Access Layer)
    • 물리적인 연결 매체에 연결되어 패킷을 주고받는 작업을 담당
    • OSI 모델의 물리, 데이터 링크 계층과 유사
  • 인터넷 계층(Internet Layer)
    • IP(Internet Protocol)를 사용하여 목적지까지 데이터를 전달하는 기능을 담당
    • 인터넷 주소를 부여(Addressing)기능과 최적의 경로를 탐색하는 라우팅(Routing)기능을 제공
    • 인터넷 계층의 프로토콜로 IP, ARP, ICMP등이 있음
    • OSI 모델의 네트워크 계층과 유사
  • 전송 계층(Transport Layer)
    • 수신지와 목적지를 감독하면서 데이터가 오류 없이 도착하는 것을 담당
    • 전송 계층의 프로토콜로 연결형 서비스인 TCP, 비연결형 서비스 UDP가 있음
    • OSI 모델의 전송 계층과 유사
  • 응용 계층(Application Layer)
    • 사용자와 통신할 수 있는 응용 서비스를 제공
    •  웹 브라우저, FTP등이 있음
    • OSI 모델의 세션, 표현, 응용 계층을 합친 것과 유사

https://softeer.ai/practice/6275

 

Softeer - 현대자동차그룹 SW인재확보플랫폼

 

softeer.ai

풀이

로봇을 정해진 방향으로 이동시키고 회전시키는 구현 문제이다. 

신경 써야할 점은 로봇이 A 명령을 받았을 때 2칸을 움직인다는 것과 처음 시작을 어디에서 해야하는지 이다.

이동을 2칸씩 하기 때문에 로봇이 길에서 한 칸을 건너뛰어서 이동하는 경우가 생길 수 있어 도착하는 지점과 중간에 있는 지점을 확인해줘야합니다.

처음 시작은 로봇 경로의 도착 지점 혹은 시작 지점을 찾아야 하는데 해당 지점은 근처 #의 개수를 통해 찾아 주었습니다.

코드

#include <iostream>
#include <vector>
#define MAX 26
using namespace std;

int a, b, startX, startY, dir;
char board[MAX][MAX];
int visited[MAX][MAX] = {};
int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0, -1, 0};
char dirs[] = {'>', 'v', '<', '^'};
vector<char> commands;

void init(){
  ios_base::sync_with_stdio(false); 
  cin.tie(NULL); cout.tie(NULL);
  cin >> a >> b;

  for(int i = 0; i < a; i++){
    for(int j = 0; j < b; j++){
      cin >> board[i][j];
    }
  }
}

bool isValid(int x, int y) {    
    return x >= 0 && x < a && y >= 0 && y < b && board[x][y] == '#' && !visited[x][y];
}

void start(){
  for(int i = 0; i < a; i++){
    for(int j = 0; j < b; j++){
      int cnt = 0;
      for (int k = 0; k < 4; k++) {
        int nx = i + dx[k];
        int ny = j + dy[k];
          if (board[i][j] == '#' && isValid(nx, ny)){
            cnt++;
            dir = k;

            if(cnt > 1)
              break;
        }
      }
      if(cnt == 1){
        startX = i;
        startY = j;
        return;
      }
    }
  }
}

void move(int x, int y, int dir){
  visited[x][y] = true;

  for (int i = 0; i < 4; i++) {
    int new_dir = (dir + i) % 4;
    int nx = x + dx[new_dir];
    int ny = y + dy[new_dir];

    if (isValid(nx, ny)) {
      visited[nx][ny] = true;

      if (new_dir != dir) {
        int turn = new_dir - dir;
        if (turn == 1 || turn == -3)
          commands.push_back('R');
        else if (turn == -1 || turn == 3)
          commands.push_back('L');
      }
      commands.push_back('A');
        nx += dx[new_dir];
        ny += dy[new_dir];
      move(nx, ny, new_dir);
    }
  }
}

int main() {
  init();
  start();

  move(startX, startY, dir);
  cout << startX + 1 << " " << startY + 1 << "\n";
  cout << dirs[dir] << '\n';
  
  for (char command : commands) {
    cout << command;
  }
  
  return 0;
}

성능

https://softeer.ai/practice/6247

 

Softeer - 현대자동차그룹 SW인재확보플랫폼

 

softeer.ai

풀이

처음엔 간단하게 중간값부터 포인터 두 개를 사용해서 조합을 만들려고 했다. 정렬해서 중간 값 좌우를 보면 간단히 조합의 개수를 셀 수 있는데다가 모든 조합을 구하는 것이 아니라 조합의 개수를 찾는 것이기 때문에 중간 값의 좌우 개수를 곱해주면 되기 때문이다.

다만 간과한 것이 중간 값이 다양하게 나올 때마다 중간에 있는 중간 값을 찾아줘야 하는데 이 경우 find 함수를 쓰게 되면 시간복잡도가 O(n) 이다. 거기에 중간 값이 q 번만큼 나오기 때문에 최종 시간 복잡도는
정렬 + (find * 중간값 input) = O(n log n + n * q) 가 된다. n의 최대는 5만, q의 최대는 20만이기 때문에 곱하면 100억이 된다...

그래서 시간 복잡도를 줄이기 위해 값들과 함께 인덱스를 미리 unordered_map에 저장하는 방식을 사용했습니다. 해당 방식을 사용하면 find와 다르게 주어지는 중간 값으로 바로 인덱스를 찾을 수 있어 시간복잡도가 O(1)이 됩니다.

코드

오답 코드(투 포인터)

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

int n, q;
long long temp, m;
vector<int> fuel;
int cnt;

int init(){
  ios::sync_with_stdio(false);
  cin.tie(0); cout.tie(0);

  cin >> n >> q;
  fuel.resize(n);
  for(int i = 0; i < n; i++){
    cin >> fuel[i];
  }

  sort(fuel.begin(), fuel.end());
}

void question(int m){
  int idx = find(fuel.begin(), fuel.end(), m) - fuel.begin();

  if (idx >= n || fuel[idx] != m){
    cout << 0 << "\n";
    return;
  }

  cout << idx * (n - idx - 1) << "\n";
}

int main(){
  init();
  for(int i = 0; i < q; i++){
    cin >> m;
    question(m);
  }

  return 0;
}

정답 코드

#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>

using namespace std;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  
  int n, q;
  cin >> n >> q;
  
  vector<int> data(n);
  for (int i = 0; i < n; ++i) {
    cin >> data[i];
  }
  
  sort(data.begin(), data.end());

  unordered_map<int, int> index;
  for (int i = 0; i < n; ++i) {
    index[data[i]] = i;
  }

  while (q--) {
    int m;
    cin >> m;

    if (index.find(m) == index.end()) {
      cout << 0 << "\n";
    } else {
      int median_idx = index[m];
      if (m == data[0] || m == data[n - 1]) {
        cout << 0 << "\n";
      } else {
        int left_cnt = median_idx;
        int right_cnt = n - median_idx - 1;
        cout << left_cnt * right_cnt << "\n";
      }
    }
  }

  return 0;
}

 

성능

정적 배열 & 동적 배열

정적 배열(Static Array)

연관된 데이터를 메모리상에 연속적이며 순차적으로 미리 할당된 크기만큼 저장하는 자료구조입니다. 미리 할당하기 때문에 프로그램 실행 전에 크기가 고정된다는 특징이 있습니다. 

정적 배열은 시작 메모리 주소만을 저장하고 그 외의 주소는 따로 저장 하지 않으며 그것들에 접근 하기 위해 Offset이라는 개념을 사용하여 그것들에 접근합니다. Offset은 첫번째 원소로 부터 얼마나 떨어진지를 말하며, 그를 비교하기 위해서 index가 0부터 시작 합니다.

장점

  • 인덱스만 알고 있으면 조회를 빠르게 할 수 있어서 조회를 자주해야 되는 작업에서 많이 사용합니다.
  • 인덱스만 알고 있다면 빠르게 조회가 가능합니다. (시간복잡도: (O(1))

단점

  • 프로그램 시작 전 크기가 고정되므로 사용할 메모리보다 크기를 너무 크게 선언했을 경우 메모리 낭비나 추가적인 overhead가 발생할 수 있는 것이 단점입니다.
  • 하나의 같은 자료형으로만 선언이 가능하고 하나의 배열 내에 int, char등의 여러 자료형이 나올 수 없습니다.
  • 배열의 중간에 데이터를 삽입하거나 삭제하기 어려우며 이로 인해 데이터 이동이 필요할 수 있습니다.
  • 특정 조건을 충족하는 값을 찾는 탐색은 상대적으로 느립니다. (시간복잡도: (O(n))

동적 배열(Dynamic Array / Linked List)

저장 공간이 가득 차는 경우 자동적으로 사이즈를 늘려 데이터를 추가 및 저장 할 수 있는 유동적인 자료구조입니다. 정적 배열의 한계를 극복하고자 고안되었으며 예상한 것보다 더 많은 수의 데이터를 저장하느라 선언된 배열의 크기를 넘어선 경우 기존의 크기보다 더 큰 배열을 선언하여 데이터를 옮긴 뒤 기존의 배열은 메모리에서 삭제합니다.

데이터 삽입

  1. 새로운 노드를 생성하고 그 노드에 삽입하고자 하는 데이터를 넣는다.
  2. n - 1번째 노드에 접근하여 그것이 들고 있는 다음 노드의 주소 값을 생성한 노드의 다음 노드 참조로 한다.
  3. n - 1번째 노드의 다음 노드 참조를 생성한 노드의 주소로 한다.

데이터 삭제

  1. n - 1번째 노드의 다음 노드 참조를 n번째 노드의 다음 노드 참조로 변경한다.
  2. n번째 노드의 메모리를 릴리즈 한다.

resizable array, arraylist, liked list, dynamic array 모두 동적 배열을 의미 합니다.

C++에서는 vector, Java에서는 LinkedList로 구현되어있습니다.

장점

  • 배열의 중간에 데이터를 삽입하거나 삭제하는 것이 가능하여 데이터의 접근과 할당이 빨라 메모리를 효율적으로 관리 할 수 있습니다. 
  • 배열의 크기가 고정되어 있지 않고 유동적이어서 프로그램의 실행 중에도 늘어나거나 줄어들 수 있습니다.

단점

  • n번째 노드를 접근하기 위해서는 n번째 노드 전의 모든 노드들에 접근 해야할 수 있기 때문에 느립니다.
  • 필요한 것 이상의 메모리 공간을 할당받으므로 낭비되는 메모리 공간이 발생할 수 있습니다.
  • 각각의 노드들이 다음, 이전 노드의 주소를 가지고 있어야 하기에 공간 복잡도를 많이 활용하여 비효율적입니다.

resize vs reverse

동적배열을 더 심화해서 알기 위해서 resize와 reverse의 함수 차이에 대해 알고 있으면 좋습니다. 이것들은 c++ 에서 vector에 사용할 수 있는 함수입니다.

resize와 reverse를 비교하기 위해 size와 capacity를 알아야합니다. size()가 실제 벡터에 들어있는 원소의 크기를 리턴해 준다면 capacity는 얼마만큼의 원소가 들어갈 수 있는지를 나타내 줍니다.

reverse는 실제로 값을 넣는것이 아닌 앞으로 값을 넣을 자리를 만드는 함수라고 생각하면 됩니다. 해당 함수를 사용하는 이유는 불필요한 재할당을 막아주기 위해서입니다. 동적 배열은 프로그램 실행 중에 사이즈를 늘이거나 줄일 수 있는 만큼 재할당시에 필요한 것 이상의 메모리 공간을 할당해 줍니다.

다음은 배열에 할당을 하지 않았을 때와 100으로 할당했을 때의 reverse를 100으로 해줬을 때의 차이입니다.

재할당 시기 X 100
1 2 100
2 4 200
3 8 400
4 16 800
5 32 1600
6 64 3200
7 128 6400
8 256 12800
9 512  
10 1024  
11 ...  

10000 이상의 값을 할당하기 위해 reverse를 사용해서 할당을 해주면 8번의 재할당을 하지만 자동으로 재할당을 해줄 시에는 10번을 재할당해도 10000개의 공간이 생기지 않는 것을 볼 수 있습니다. 만약 10000개 이상의 값을 넣어야한다면 그때마다 재할당을 해줘야하기 때문에 필요없는 메모리가 사용될 것입니다.

resize를 사용하면 만약 resize 크기가 원래 사이즈보다 작다면 뒤쪽 원소들이 삭제(소멸자 실행)되고, 크다면 원소들이 뒤쪽에 삽입(생성자 실행)된다.
만약 공간이 필요하다면 공간을 재할당 받고 부족한 크기만큼 새 원소를 생성합니다. 이후에 새 배열에 기존 원소들 모두 복사한 뒤에 기존 원소들을 모두 삭제합니다.

벡터의 크기를 미리 지정하는 경우, 미리 할당된 메모리 크기만큼 공간을 확보할 수 있기 때문에 성능상 이점이 있을 수 있습니다. 여러 번 push_back()을 호출하는 것보다 한 번에 resize()를 사용하여 메모리를 미리 확보하는 것이 더 효율적일 수 있습니다.

 

+ Recent posts