제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

선행 작업이 끝나야 뒤에 작업을 배포할 수 있습니다. 따라서 이전 기능 개발이 끝나는 날을 기록하여 해당 기간까지 다음 기능개발을 끝내는 수 있는지 확인하여 분기처리를 진행합니다.

소스코드

import java.util.*;

class Solution {
    
    public int[] solution(int[] progresses, int[] speeds) {
        List<Integer> production = new ArrayList<>();
        
        int len = progresses.length;
        int curDay = 0;
        int features = 0;
        
        for (int i = 0; i < len; i++) {
            // 남은 작업일 계산
            int remainDay = (100 - progresses[i]) / speeds[i];
            if ((100 - progresses[i]) % speeds[i] != 0) {
                remainDay++;
            }
            
            // 현재 기능이 배포 가능일보다 오래 걸리면 새로운 배포
            if (curDay < remainDay) {
                if (features > 0) {
                    production.add(features); // 이전에 쌓인 기능 수를 추가
                }
                curDay = remainDay; // 현재 배포 일자 갱신
                features = 1; // 새 기능 초기화
            } else {
                // 현재 기능이 이전 배포 일자 내에 완성되면 함께 배포
                features++;
            }
        }
        
        // 마지막에 남은 기능 수 추가
        if (features > 0) {
            production.add(features);
        }
        
        // 결과 배열로 변환
        return production.stream().mapToInt(Integer::intValue).toArray();
    }
}

코드 설명

  • remainDay = 현재 기능을 완료하기까지 걸리는 일 수.
    curDay = 지난 일자
    features = 배포 기능 개수
  • int remainDay = (100 - progresses[i]) / speeds[i];
    • 남은 작업 일을 계산합니다. 나누어 떨어지지 않는다면 하루가 더 필요합니다.
// 현재 기능이 배포 가능일보다 오래 걸리면 새로운 배포
if (curDay < remainDay) {
    if (features > 0) {
        production.add(features); // 이전에 쌓인 기능 수를 추가
    }
    curDay = remainDay; // 현재 배포 일자 갱신
    features = 1; // 새 기능 초기화
} else {
    // 현재 기능이 이전 배포 일자 내에 완성되면 함께 배포
    features++;
}

// 마지막에 남은 기능 수 추가
if (features > 0) {
    production.add(features);
}
  • 지금까지 걸린 기간에서 남은 일보다 작으면 현재 기능을 배포할 수 없습니다. 
    따라서 현재 기간까지 배포 가능한 기능 수(features)를 add해주고
    현재 배포 일자와 새 기능 수를 갱신합니다.
  • 반대로 걸린 기간이 남은 일자보다 더 크면 배포 기능 수를 추가해줍니다.
  • 이후 마지막에 남은 기능 수를 추가해줍니다.
  • return production.stream().mapToInt(Integer::intValue).toArray();
    • 결과를 int[] 배열로 반환합니다.
제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

연속적으로 나타나는 숫자를 파악하기 위해 list나 배열에 index를 통해 확인할 수 있지만
LIFO구조의 Stack이라는 자료구조를 통해 더 효율적으로 문제를 풀 수 있다.

소스코드

import java.util.*;

public class Solution {
    
    public int[] solution(int[] arr) {
        Stack<Integer> ans = new Stack<>();
        
        for (int num: arr) {
            if (ans.isEmpty() || ans.peek() != num) {
                ans.add(num);
            } 
        }
        
        return ans.stream().mapToInt(Integer::intValue).toArray();
        
    }
}

코드 설명

  • Stack<Integer> ans = new Stack<>();
    • Integer 타입을 저장하는 스택을 선언한다.
  • if (ans.isEmpty() || ans.peek() != num)
    • 스택이 비어있거나 마지막으로 넣은 값과 연속적이지 않으면 스택에 값을 넣는다.
  • return ans.stream().mapToInt(Integer::intValue).toArray();
    • int[] 배열로 반환한다.
제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

두번의 정렬을 이용합니다. 장르 별로 정렬을 이용하고, 장르 내에 앨범 별로 정렬을 이용합니다. 

소스코드

import java.util.*;

class Solution {
    static class Album implements Comparable<Album>{
        int idx, playCnt;

        public Album(int idx, int playCnt) {
            this.idx = idx;
            this.playCnt = playCnt;
        }

        @Override
        public int compareTo(Album album) {
            if(this.playCnt == album.playCnt) {
                return this.idx - album.idx;
            } else {
                return album.playCnt - this.playCnt;
            }
        }
    }

    public int[] solution(String[] genres, int[] plays) {
        int len = genres.length;
        Map<String, Integer> genreRank = new HashMap<>();
        Map<String, List<Album>> albumRank = new HashMap<>();

        // 데이터 초기화
        for (int i = 0; i < len; i++) {
            genreRank.put(genres[i], genreRank.getOrDefault(genres[i], 0) + plays[i]);
            albumRank.putIfAbsent(genres[i], new ArrayList<>());
            albumRank.get(genres[i]).add(new Album(i, plays[i]));
        }

        // 장르별 총 재생 횟수에 따라 장르 정렬
        List<String> sortedGenres = new ArrayList<>(genreRank.keySet());
        sortedGenres.sort((a, b) -> genreRank.get(b) - genreRank.get(a));

        List<Integer> ans = new ArrayList<>();

        // 각 장르 내에서 앨범 정렬 및 상위 2곡 선택
        for (String genre : sortedGenres) {
            List<Album> genreAlbum = albumRank.get(genre);
            Collections.sort(genreAlbum);
            for (int i = 0; i < Math.min(2, genreAlbum.size()); i++) {
                ans.add(genreAlbum.get(i).idx);
            }
        }

        // 결과를 배열로 반환
        return ans.stream().mapToInt(Integer::intValue).toArray();
    }
}

코드 설명

static class Album implements Comparable<Album> {
    int idx, playCnt;

    public Album(int idx, int playCnt) {
        this.idx = idx;
        this.playCnt = playCnt;
    }

    @Override
    public int compareTo(Album album) {
        if(this.playCnt == album.playCnt) {
            return this.idx - album.idx;
        } else {
            return album.playCnt - this.playCnt;
        }
    }
}

 

  • Album 클래스:
    • 각 앨범(노래)의 인덱스(idx)와 재생 횟수(playCnt)를 나타냅니다.
    • compareTo 메서드는 재생 횟수를 기준으로 내림차순 정렬하며, 재생 횟수가 같으면 인덱스 기준으로 오름차순 정렬합니다.
  • solution 메서드:
    • genreRank 해시맵: 각 장르별 총 재생 횟수를 저장합니다.
    • albumRank 해시맵: 각 장르별로 Album 객체 리스트를 저장합니다.
    • for 루프를 사용해 genreRank와 albumRank를 초기화합니다.
      • 각 노래를 해당 장르의 총 재생 횟수에 추가하고, Album 객체를 장르 리스트에 추가합니다.
    • genreRank의 키(장르)를 재생 횟수에 따라 내림차순으로 정렬하여 sortedGenres 리스트를 만듭니다.
      sortedGenres.sort((a, b) -> genreRank.get(b) - genreRank.get(a));
// 각 장르 내에서 앨범 정렬 및 상위 2곡 선택
for (String genre : sortedGenres) {
    List<Album> genreAlbum = albumRank.get(genre);
    Collections.sort(genreAlbum);
    for (int i = 0; i < Math.min(2, genreAlbum.size()); i++) {
        ans.add(genreAlbum.get(i).idx);
    }
}
  • 장르 내 노래 정렬 및 선택:
    • 각 장르의 노래 리스트(genreAlbum)를 Collections.sort를 통해 정렬합니다.
    • 상위 2개의 노래 인덱스를 결과 리스트 ans에 추가합니다.
  • int[] 타입으로 반환해야하니 ans 리스트를 배열로 변환해 반환합니다.
    return ans.stream().mapToInt(Integer::intValue).toArray();

 

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

확률에서 사용했던 조합을 이용하였습니다. 각 종류별로 nC1을 하고 모든 의상을 안입는 경우를 제외하여 경우의 수를 구하였습니다.

의상 소스코드

import java.util.*;

class Solution {
    
    public int solution(String[][] clothes) {
        int len = clothes.length;
        Map<String, Integer> matches = new HashMap<>();
        
        for(int i = 0; i < len; i++) {
            String item = clothes[i][0];
            String kind = clothes[i][1];
            matches.put(kind, matches.getOrDefault(kind, 0) + 1);
        }
        int answer = 1;
        
        for(String key: matches.keySet()) {
            answer *= matches.get(key) + 1;
        }

        return answer - 1;
        
    }
}

코드 설명

 

  • matches 해시맵: 각 옷의 종류별 개수를 저장합니다.
    • clothes[i][0]은 옷의 이름, clothes[i][1]은 옷의 종류입니다.
    • 해시맵에 옷의 종류를 키로, 해당 종류의 옷의 수를 값으로 저장합니다.
  • 모든 옷의 조합 계산:
    • answer는 초기값으로 1로 설정됩니다.
    • 각 옷의 종류마다 (해당 종류의 옷 수 + 1)를 answer에 곱해줍니다.
    • +1은 해당 종류의 옷을 입지 않는 경우를 포함하기 위해 사용됩니다.
  • 결과 반환:
    • answer - 1은 아무 옷도 입지 않는 경우를 제외한 옷의 조합 수를 반환합니다.

 

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

트라이 노드를 사용해서 문제를 풀었습니다. String에서 한자를 노드의 value로 생각하셔 문자가 같으면 자식 노드로 진입하면서 접두어가 같은지 확인합니다. 같지 않으면 접두어가 같지 않습니다.

 

이전에 문제를 풀었을 때 O(N^2)의 코드로 풀어서 좋은 방법이 없을까 하다가 트라이 노드로 문제를 풀었습니다. 하지만 코딩 테스트를 진행할 때에는 적합한 풀이는 아니라고 생각합니다.

폰켓몬 소스코드

import java.util.*;

class Solution {
    
    static class PhoneBook {
        // 트라이 노드 클래스 정의
        static class TrieNode {
            Map<Character, TrieNode> children = new HashMap<>();
            boolean isEndOfNumber;
        }

        // 루트 노드
        private TrieNode root = new TrieNode();

        // 번호를 트라이에 삽입
        private boolean insert(String number) {
            TrieNode current = root;
            for (char c : number.toCharArray()) {
                current.children.putIfAbsent(c, new TrieNode());
                current = current.children.get(c);

                // 번호의 중간에서 이미 끝인 경우 접두어 조건 위반
                if (current.isEndOfNumber) {
                    return false;
                }
            }

            // 번호가 완전히 끝난 경우를 표시
            current.isEndOfNumber = true;

            // 현재 노드에 자식이 있는 경우 접두어 조건 위반
            return current.children.isEmpty();
        }

        public boolean isConsistent(String[] phoneBook) {
            for (String number : phoneBook) {
                if (!insert(number)) {
                    return false;
                }
            }
            return true;
        }
    }
    
    public boolean solution(String[] phone_book) {
        PhoneBook phoneBookChecker = new PhoneBook();
        return phoneBookChecker.isConsistent(phone_book);
    }
}

코드 설명

 

  • TrieNode 클래스: 각 문자를 자식으로 연결하는 트라이 노드입니다.
  • insert 메서드: 전화번호를 트라이에 삽입하고 접두어 조건을 위반하는지 확인합니다. 중간에 이미 끝인 번호가 있거나 새로 추가된 번호의 마지막 노드에 자식이 있으면 false를 반환하여 접두어 조건 위반을 나타냅니다.
  • isConsistent 메서드: 전화번호 목록의 각 번호를 insert 메서드로 확인하여 접두어 관계가 있는 경우 false를 반환합니다.

 

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

participant와 completion에서 언급된 선수가 짝수이면 완주한 선수이고 홀수이면 완주하지 못한 선수입니다.

완주하지 못한 선수 소스코드

import java.util.*;

class Solution {
    public String solution(String[] participant, String[] completion) {
        Set<String> runners = new HashSet<>();
        
        for (String runner: participant) {
            if (!runners.contains(runner)) {
                runners.add(runner);
            } else {
                runners.remove(runner);
            }
        }
        
        for (String runner: completion) {
            if (runners.contains(runner)) {
                runners.remove(runner);
            } else {
                runners.add(runner);
            }
        }
        
        for (String ans: runners) {
            return ans;
        }
        return null;
    }
}

코드 설명

 

  • Set<String> runners = new HashSet<>();
    참가자와 완주자를 저장할 집합 runners를 생성합니다.
  • for (String runner: participant)
    참가자 배열을 순회하며:
    • 이미 집합에 있는 참가자이면 집합에서 제거합니다 (완주 처리).
    • 그렇지 않으면 집합에 추가합니다.
  • for (String runner: completion)
    완주자 배열을 순회하며:
    • 집합에 있는 참가자이면 제거합니다. (완주)
    • 집합에 없는 참가자이면 집합에 추가합니다. (완주하지 못한 참가자)
  • for (String ans: runners) { return ans; }
    남은 하나의 이름이 완주하지 못한 참가자이므로 반환합니다.

 

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

Map을 사용하여 푸는 문제였습니다. 폰켓몬의 종류와 폰켓몬의 갯수를 확인하는 것이 포인트였다고 생각합니다.

N/2 마리를 선택할 때 폰켓몬의 종류보다 N/2가 더 크면 폰켓몬의 종류를 리턴하고 반대면 N/2를 리턴하면 됩니다.

폰켓몬 소스코드

import java.util.*;

class Solution {
    
    public int solution(int[] nums) {
        Map<Integer, Integer> ponkets = new HashMap<>();
        for (int num: nums) {
            ponkets.put(num, ponkets.getOrDefault(num, 0) + 1);
        }
        
        int halfN = nums.length / 2;
        if (halfN <= ponkets.size()) {
            return halfN;
        } else {
            return ponkets.size();
        }
    }
}

코드 설명

 

  • Map<Integer, Integer> ponkets = new HashMap<>();
    정수형 num을 키로 하고, 각 숫자가 몇 번 등장하는지를 값으로 하는 해시맵 ponkets를 생성합니다.
  • for (int num: nums) { ponkets.put(num, ponkets.getOrDefault(num, 0) + 1); }
    nums 배열을 순회하면서 각 숫자가 ponkets 맵에 이미 존재하는지 확인합니다.
    • 존재하면 해당 숫자의 값에 +1을 합니다.
    • 존재하지 않으면 기본값 0에서 +1을 하여 새로운 키-값 쌍으로 추가합니다.
  • int halfN = nums.length / 2;
    nums 배열의 길이를 반으로 나눈 halfN을 계산하여 가질 수 있는 폰켓몬 수의 최대치를 나타냅니다.
  • if (halfN <= ponkets.size()) { return halfN; } else { return ponkets.size(); }
    고유한 폰켓몬의 종류 수(ponkets.size())와 halfN을 비교합니다.
    • halfN이 고유한 폰켓몬의 종류 수보다 작거나 같으면 halfN을 반환합니다 (최대 가질 수 있는 폰켓몬 수).
    • 그렇지 않으면 고유한 폰켓몬의 종류 수를 반환합니다.

 

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
접은 글을 통해 먼저 답변을 해보시고 제가 정리한 답을 확인해보시기 바라겠습니다!!

면접 리스트

소켓이란 무엇인가요?

더보기

응용 프로그램에서 TCP/IP 기반으로 생성하는 것으로 응용프로그램과 transport layer를 연결해주는 역할로, 두 머신이 네트워크를 통해 서로 통신할 수 있도록 양쪽에 생성되어 서로 다른 프로세스가 양방향, 실시간 통신을 할 수 있게 해주는 interface입니다.

소켓이 구현됨으로써, 네트워크 및 전송 계층의 캡슐화가 가능해집니다.

쿠키와 세션의 차이를 설명해주세요

더보기

HTTP 프로토콜은 connectionless를 지향하고 stateless protocol로써 리소스의 낭비를 줄여주지만 매 통신마다 클라이언트가 인증이 필요합니다.

 

쿠키와 세션은 이 단점을 보완하는 기술인데 두 기술의 가장 큰 차이는 저장위치로, 쿠키는 클라이언트세션은 서버에 저장됩니다. 그래서 쿠키는 빠르지만 보안에 취약하고, 세션은 느리지만 상대적으로 보안성이 좋습니다.

 

또 쿠키는 브라우저가 종료되어도 남아있지만 세션은 삭제된다는 차이가 있습니다. 따라서 보안성이 중요할때에는 세션을, 종료시에 유지되기 위해서는 쿠키를 사용해야합니다. 하지만 세션의 경우 서버의 자원을 사용하는 것이므로 사용자가 많아지면 자원 관리면에서 문제가 발생할 수 있습니다.

세션과 JWT Token을 비교해서 설명해주세요

더보기

세션은 서버에서 사용자의 id와 pw를 비교하여 세션 저장소에서 세션 id를 넘겨주고 사용자의 정보를 저장합니다. 클라이언트가 서버에게 정보를 보낼때 쿠키에 세션 id를 포함해서 같이 보내 서버는 세션 저장소에서 사용자임을 알 수 있고, 이전에 사용자가 했던 통신을 이어서 할 수 있게 합니다.

 

JWT는 서버에서 발급하는 것으로, 따로 저장소 없이 사용자의 고유 id 값을 보유하고 서버는 토큰을 검증 이후 조작 여부와 유효기간을 확인합니다. 검증이 완료되면 payload를 디코딩하여 사용자의 id에 맞는 데이터를 가져옵니다.

 

가장 큰 차이는 Session 저장소에 유저의 정보를 넣는 반면 JWT는 토큰 안에 유저의 정보를 넣습니다.

TCP가 어떻게 신뢰성을 보장하나요?

더보기
  1. 연결 설정: TCP 3방향 핸드셰이크는 송신자와 수신자 사이의 연결을 설정하는 데 사용됩니다. 여기에는 송신자의 SYN(동기화) 패킷, 수신자의 SYN-ACK(동기화-확인) 패킷 및 송신자의 ACK(확인) 패킷이 포함됩니다.
  2. 시퀀스 번호: TCP는 전송된 각 데이터 바이트에 고유한 시퀀스 번호를 할당합니다. 이렇게 하면 수신기가 올바른 순서로 데이터를 재구성할 수 있습니다.
  3. ACK: 수신기는 ACK 패킷을 전송하여 데이터의 성공적인 수신을 확인합니다. 보낸 사람이 지정된 시간 초과 기간 내에 ACK를 수신하지 않으면 승인되지 않은 데이터를 재전송합니다.
  4. Selective ack 및 Go-Back-N: TCP는 선택적 반복 또는 Go-Back-N 메커니즘을 사용하여 손실되거나 순서가 잘못된 패킷을 처리할 수 있습니다. Selective ack 을 통해 수신기는 특정 손실 패킷을 승인하고 재전송할 수 있는 반면 Go-Back-N손실된 패킷 이후 모든 후속 패킷을 재전송해야 합니다.

TCP가 어떻게 흐름제어를 구현하나요? 그리고 윈도우 사이즈는 무엇인가요?

더보기

TCP는 Sliding window를 이용하여 흐름제어를 구현합니다. 수신 측에서 설정한 윈도우 크기만큼 송신측에서 ACK 없이 세그먼트를 전송할 수 있게 데이터를 동적으로 조절하는 기법입니다. 수신측은 윈도우 사이즈가 바뀔때마다 송신측에게 윈도우 사이즈를 보낼 수 있고, 송신측은 그에 따른 데이터 바이트 크기만큼 확인 응답없이 계속해서 보낼 수 있습니다. 만약 ACK가 왔다면 그만큼 윈도우를 이동하면 됩니다.

 

윈도우 사이즈는 수신측에서 받을 수 있는 바이트 수를 의미하는데 호스트들은 데이터를 보내기 전에 윈도우 사이즈 크기를 수신 호스트의 윈도우 사이즈 크기에 맞춥니다.

 

따라서 TCP는 window size에 따라 sliding window로 흐름 제어를 구현합니다.

TCP 재전송 매커니즘을 설명해보세요

더보기

먼저 타임아웃이 있습니다. 송신측에서 설정한 RTO보다 RTT가 더 크면 패킷이 타임아웃되어 손실되었다고 생각하여 패킷을 다시 보냅니다. 해당 방법은 동작이 느리므로 네트워크의 지연을 초래할 수 있습니다.

 

그 대안으로 Fast Retransmission이 있습니다. Fast Retransmission은 중복된 ACK가 세개가 되었을 시에 송신측에서 패킷이 유실되었다고 하여 패킷을 다시 보내는 것을 말합니다. 또 패킷을 보낼때에는 Go-back-N 방식이 아닌 Selective Retransmission으로 네트워크 리소스를 절약하고 효율성을 향상 시킬 수 있습니다.

TCP 혼잡제어를 어떻게 관리하나요? 네트워크 혼잡을 예방하기 위한 메커니즘은 무엇인가요?

더보기

TCP는 혼잡제어를 AIMD와 slow start가 있습니다.

 

AIMD는 처음 패킷을 하나씩 보내고 문제없이 도착하면 window size를 1씩 증가시켜 전송하는 방식입니다. 만약 패킷 전송에 실패하면 그때의 패킷을 절반으로 줄이고 다시 window 사이즈를 1씩 증가합니다. 하지만 이 방법은 초기 네트워크에 많은 리소스를 사용하지 못하므로 많은 시간이 걸립니다.

 

slow start는 전송 속도를 늦게 올리는 AIMD와 다르게 window size를 지수형태로 증가합니다. 만약 혼잡현상이 발생하면 window 사이즈를 1로 떨어뜨리고 다음 1씩 증가합니다. fast recovery도 있는데 혼잡현상이 발생했을 때 window 사이즈를 1로 낮추는 것이 아닌, window 사이즈를 반으로 낮추어 그 window size에서 1씩 증가하는 방법입니다.

'기술면접 > 네트워크' 카테고리의 다른 글

[기술면접] 네트워크 5  (3) 2024.11.14
[기술면접] 네트워크 4  (3) 2024.11.10
[기술면접] 네트워크 3  (0) 2024.11.10
[기술면접] 네트워크 1  (2) 2024.11.06

+ Recent posts