❓  문제

다리를 지나는 트럭

 

🛎️  아이디어

  1. bridge 길이 만큼의 deque를 생성한다.

  2. 모든 트럭이 다리에 올라갈 때까지 while문을 돈다.

  3. bridge에서 popleft를 통해 한 칸 씩 왼쪽으로 옮겨간다. (rotate와 다름. rotate는 1->2->3->1 처럼 돌기 때문)

  4. 새롭게 들어갈 트럭의 무게와 현재 bridge의 무게를 더해서 견딜 수 있는지 확인한다.
    • 견딜 수 있다면 새롭게 들어갈 트럭의 무게를 더하고
    • 견딜 수 없다면 0을 더한다.

 

💻  풀이

# 최대 bridge_length대 올라갈 수 있음. (다리의 길이)
# weight만큼 버틸 수 있음.
# 다리에 완전히 오르지 않았다면 무게를 무시한다.

from collections import deque

def solution(bridge_length, weight, truck_weights):
    time = 0

    # 현재 올라간 트럭의 무게 합
    weight_sum = 0

    # bridge 크기만큼의 데큐 생성
    bridge = deque([0 for i in range(bridge_length)])
    while truck_weights:
        time += 1
        weight_sum -= bridge.popleft()

        # 이번에 들어갈 트럭 무게 계산
        truck = truck_weights[0]
        if weight_sum + truck <= weight:
            weight_sum += truck
            bridge.append(truck_weights.pop(0))
        else:
            bridge.append(0)

    # 마지막 트럭이 들어간 후 완전히 나올 때 까지의 시간 더해주기 (bridge_length)
    return time + bridge_length

 

🧩  포인트

- deque를 사용해서 popleft, append로 실제 다리를 건너는 것처럼 한다.

- 처음에 현재 bridge의 무게를 구할 때 sum() 함수를 사용했는데, 이럴 경우 시간 초과가 나기 때문에 sum을 담을 변수를 생성한 후 계산을 해줘야 한다.

❓  문제

행렬 테두리 회전하기

 

🛎️  아이디어

1. 회전해야하는 숫자들을 top, right, bottom, left순으로 리스트에 담는다.
    a. 단, bottom과 left는 역순으로 담는다.

2. deque의 rotate 함수를 활용해서 1칸씩 오른쪽으로 이동해준다.

3. 다시 top, right, bottom, left순으로 넣어준다.
    a. 단, bottom과 left는 역순으로 넣어준다.

 

💻  풀이

from collections import deque

def solution(rows, columns, queries):
  items = [[y + x * columns for y in range(1, columns + 1)] for x in range(rows)]
  minimum_list = []

  for x1, y1, x2, y2 in queries:
    x1 -= 1
    y1 -= 1
    x2 -= 1
    y2 -= 1

    # 위치 변경할 숫자들
    change_list = []

    # top
    change_list.extend(items[x1][y1: y2])

    # right
    for x in range(x1, x2):
      change_list.append(items[x][y2])

    # bottom
    change_list.extend(reversed(items[x2][y1 + 1: y2 + 1]))

    # left
    for x in range(x2, x1, -1):
      change_list.append(items[x][y1])

    # 최솟값 구하기
    minimum_list.append(min(change_list))

    # deque를 이용한 위치 변경
    dq = deque(change_list)
    dq.rotate(1)

    # top, right, bottom, left 순으로 넣어주기
    # top
    for y in range(y1, y2):
      items[x1][y] = dq.popleft()

    # right
    for x in range(x1, x2):
      items[x][y2] = dq.popleft()

    # bottom
    for y in range(y2, y1, -1):
      items[x2][y] = dq.popleft()

    # left
    for x in range(x2, x1, -1):
      items[x][y1] = dq.popleft()

  return minimum_list

 

❓  문제

2019 KAKAO BLIND RECRUITMENT - 후보키

 

🛎️  아이디어

1. 후보키가 될 수 있는 집합을 만든다.
    a. relation의 컬럼(column) 숫자를 가지고 만든다.
    b. 해당 결과는 (0), (1), ... (0, 1), (0, 2), ... (0, 1, 2, 3) 으로 나온다.

2. 만든 집합을 돌면서 해당 인덱스에 맞는 값들을 가지고 실제 집합을 만들어 준다.

3. 실제 집합 상태에서 중복 제거를 해준다.
      a. 유일성을 만족하는지 확인하기 위함이다.

4. 유일성을 만족한다면 (중복 제거 후에도 relation의 row길이와 같다면) 
      a. answer 배열에 해당 인덱스 집합의 부분집합이 있는지 확인한다.
      b. 예를 들어, (0, 1)은 (0)을 포함하기 때문에 최소성을 만족하지 않는다.
      c. 부분집합에 해당하지 않으면 answer 배열에 해당 인덱스 집합을 더해준다.

 

💻  풀이

from itertools import combinations

def solution(relation):
    
    # 가능한 인덱스 조합
    index_combination = []
    for r in range(1, len(relation[0]) + 1):
        index_combination.extend(combinations(range(len(relation[0])), r))
        
    answer = []
    for comb in index_combination:
        
        # 해당 인덱스를 사용한 실제 조합
        tmp1 = []
        for r in relation:
            tmp2 = []
            for c in comb:
                tmp2.append(r[c])
            
            # 이후에 2차원 중복제거를 위해 tuple로 변환 후 append
            tmp1.append(tuple(tmp2))
        
        # 유일성 확인
        if len(set(tmp1)) == len(relation):
            # 최소성 확인
            check = True
            for a in answer:
                if set(a).issubset(comb):
                    check = False
                    break
        
            if check: answer.append(comb)
    return len(answer)

 

🧩  포인트

- 2차원 리스트를 중복제거 하기 위해서는 tuple을 사용해 1차원 리스트로 만들어 주고 set을 사용한다.
- tuple은 중복 제거가 되지 않는다.

❓  문제

2018 KAKAO BLIND RECRUITMENT - [3차] 방금그곡

 

🛎️  아이디어

1. musicinfos 배열에서 가져온 데이터를 "," 기준으로 분리한다.

2. sheet(악보정보)를 list를 사용해 문자열을 분리하고 만약 분리된 문자열이 #이면 바로 앞 문자에 붙여준다.
      a. "AB#C" -> ["A", "B", "#", "C"] -> ["A", "B#", "#", "C"]

3. sheet 배열에서 '#'를 삭제해준다.
      a. ["A", "B#", "#", "C"] -> ["A", "B#", "C"]

4. 시작 시간과 종료 시간은 ":" 기준으로 시/분으로 분리한다.

5. 시와 분끼리 계산해주고 실제 재생된 Melody를 만들어준다.

6. 해당 멜로디에 네오가 기억하는 부분이 있는지 확인한다.
      a. 샾이 붙은 데이터는 미리 제거한다.
            ㄱ. ABC#D에는 ABC와 ABC# 모두 포함될 수 있다.
            ㄴ. 네오가 기억하는 부분에 #를 더한 ABC#과 ABC##을 찾아서 미리 제거한다.

7. 네오가 기억하는 부분이 포함되면 match_list에 append해준다.

8. 재생시간 기준으로 역순 정렬한다. 만약 match_list가 비어있다면 (None)을 반환한다.

 

💻  풀이

# 음악이 시작한 시각, 끝난 시각, 음악 제목, 악보 정보
def solution(m, musicinfos):
    match_list = []

    for musicinfo in musicinfos:
        start_time, end_time, title, sheet = musicinfo.split(",")

        # sheet 변경
        #1. 모든 문자열 분리 및 #붙이기
        sheet = list(sheet)
        for idx, item in enumerate(sheet):
            if item == "#":
                sheet[idx - 1] += "#"

        # 샾 제거
        sheet = [i for i in sheet if i != "#"]

        # 분, 초 분리
        start_hour, start_minute = start_time.split(":")
        end_hour, end_minute = end_time.split(":")

        # 재생 시간 계산
        sub_hour = int(end_hour) - int(start_hour)
        sub_minute = int(end_minute) - int(start_minute)
        play_time = 60 * sub_hour + sub_minute

        melody = sheet * (play_time // len(sheet)) + sheet[:play_time % len(sheet)]

        # 샾이 붙은 데이터 제거
        # ABC#D에는 ABC와 ABC# 모두 포함될 수 있음. 따라서 ABC#과 ABC##을 찾아서 미리 없앰.
        check_melody = ''.join(melody).replace(m + "#", '')
        if m in check_melody:
            match_list.append({"playTime": play_time,
                               "title": title})

    match_list = sorted(match_list, key= lambda x: x["playTime"], reverse= True)
    if match_list == []:
        return "(None)"
    else:
        return match_list[0]["title"]

❓  문제

Summer/Winter Coding(~2018) - 스킬트리

 

 

🛎️  아이디어

1. skill_trees 배열에서 하나씩 꺼내온다.

2. 해당 배열에서 find를 사용하여 skill의 각각의 원소에 해당하는 인덱스를 가져온다.

3. 만약 find의 리턴 값이 -1이라면 (해당 배열에 값이 없는 경우) skills의 길이를 넣어준다.
    a. -1이 앞에 있는 경우 미통과이기 때문에 순서를 뒤바꿀 수 있는 큰 값을 넣어주는 것이다.

4. 정렬했을 때 순서가 바뀌지 않는다면 통과!

 

💻  풀이

def solution(skill, skill_trees):
    answer = 0

    for skills in skill_trees:
        token_index_list = list(map(lambda x: skills.find(x), skill))
        token_index_list = list(map(lambda x: len(skills) if x == -1 else x, token_index_list))
        if token_index_list == sorted(token_index_list):
            answer += 1

    return answer

    print(solution("CBD", ["BACDE", "CBADF", "AECB", "BDA"]))

"""
token_index_list 결과가 다음과 같으면,

[2, 0, 3] -> 미통과
[0, 1, 3] -> 통과
[2, 3, -1] -> 통과
[-1, 0, 1] -> 미통과

-1이 있는 두 가지를 구별할 방법은 -1인 경우 큰 값을 넣어주기!
"""

 📌 순환알고리즘 하노이 타워 문제

원반의 개수를 3에서 10까지 변경하면서 그 결과를 확인해 본다. 또한 디스크 이동 횟수도 확인해 볼 것.

 

** 화면 출력 예 (위치 지점을 A, B, C라고 한 경우의 예) :

#1. 디스크 1개를 A지점에서 C로 옮긴다.

#2. 디스크 1개를 B지점에서 B로 옮긴다.

.......

(*출력 줄의 수는 디스크 이동 횟수를 의미)

 

 📌 풀이

출처:&nbsp;https://ledgku.tistory.com/39

가장 큰(맨 밑) 원판과 나머지로 나누어서 생각해보면 가장 큰 원판을 뺀 나머지를 B로 옮긴 후 가장 큰 원판을 C로 옮기고 다시 나머지를 B에서 C로 옮긴다.

 

즉, 단계마다 가장 큰 원판을 제외한 나머지는 모두 B를 거쳐서 C를 간다는 것이다. (출발지에서 경유지를 들려서 목적지에 도착함.)

 

그러면 재귀 함수를 사용해서 예를 들어 원판이 5개가 있고 1, 2, 3, 4, 5를 크기순이라고 치면

1. 1~4를 B로 옮기고 5를 C로 옮긴다.

2. 1~3을 A로 옮기고 4를 C로 옮긴다.

3. 1~2를 B로 옮기고 3을 C로 옮긴다.

4. 1을 A로 옮기고 2를 C로 옮긴다.

5. 1을 C로 옮긴다.

이렇게 볼 수 있다.

 

 📌 코드1 (주석 참고)

#include <iostream> 
using namespace std;

int cnt = 0; // 이동 횟수에 이용.

void Hanoi(int n, char from, char temp, char to)
// n : 원반개수, from : 원래 위치, temp : 임시 장소, to :목적지
{
	// cnt = pow(2, n) - 1;
	// 가장 밑에 있는 원반을 제외한 모든 원반은 from -> temp -> to로 옮겨진다.

	if (n == 1) {
		cout << "#" << ++cnt << ".디스크 1개를 " << from << "지점에서 " << to << "로 옮긴다." << endl;
	}
	else {
		Hanoi(n - 1, from, to, temp); // 맨 밑 원반을 제외한 모든 원반을 from에서 temp로 옮긴다.
		cout << "#" << ++cnt << ".디스크 1개를 " << from << "지점에서 " << to << "로 옮긴다." << endl; // 맨 밑 원반을 from에서 to로 옮긴다.
		Hanoi(n - 1, temp, from, to); // 맨 밑 원반을 제외한 모든 원반을 temp에서 to로 옮긴다.
	}
}


void main()
{
	int n; //원반의 수

	cout << "원반의 갯수를 입력하세요 : ";
	cin >> n;

	Hanoi(n, 'A', 'B', 'C');    // n개의 원반을 'A'에서 'C'로 이동

	cout << "전체 원반 이동 수(원반수 : " << n << ") = " << cnt << endl;
 }

1. 코드

from selenium import webdriver

driver = webdriver.Chrome(r"C:\Users\SAMSUNG\Desktop\chromedriver_win32\chromedriver")
driver.get(r"http://zzzscore.com/1to50/?ts=1591020560516")

numbtn = driver.find_elements_by_xpath(r'//*[@id="grid"]/div[*]')

for i in range(1, 51):
    for n in numbtn:
        if n.text == str(i):
            n.click()
            break

 

2. 결과

 

3. 영상

1. 주제

네이버 웹툰 크롤링

 

2. 상세 내용

-네이버 웹툰 하나 선정해서 웹툰 제목으로 폴더를 만든다.

-그 안에 최근 10회차를 회자 별로 폴더를 만든다.

-그 안에 이미지를 저장한다.

 

3. 코드

from bs4 import BeautifulSoup
import requests
import os
import shutil #rmtree 사용하기 위한 모듈

headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36'}
#크롤링 우회하기 위한 헤더 수정

url = "https://comic.naver.com/webtoon/list.nhn?titleId=654774" #소녀의 세계 웹툰 이름, 회차 목록 확인
html1 = requests.get(url, headers = headers)
result1 = BeautifulSoup(html1.content, "html.parser")


webtoonName = result1.find("span", {"class", "wrt_nm"}).parent.get_text().strip().split('\n')
#class가 wrt_nm인 span태그의 부모 태그의 text가져와서 앞뒤 공백 제거 후 개행문자로 나눠서 배열에 저장
os.chdir(r"C:\Users\SAMSUNG\Desktop")

if os.path.isdir(r"C:\Users\SAMSUNG\Desktop\\" +  webtoonName[0]): #이미 디렉토리가 있을 경우
    shutil.rmtree(r"C:\Users\SAMSUNG\Desktop\\" +  webtoonName[0]) #지정된 디렉토리와 하위 디렉토리까지 모두 삭제
    
os.mkdir(webtoonName[0]) #웹툰 이름으로 디렉토리 만들기, webtoonName 배열의 인덱스 0이 웹툰 이름
print(webtoonName[0] + "   folder created successfully!")
os.chdir(r"C:\Users\SAMSUNG\Desktop\\" +  webtoonName[0]) #웹툰 제목 디렉토리로 이동


title = result1.findAll("td", {"class", "title"}) #class가 title인 td태그 모두 찾음

for t in title:
    os.mkdir((t.text).strip()) #웹툰 디렉토리 안에 회차별로 디렉토리 만들기, text가져와서 앞뒤 공백 제거
    os.chdir(os.getcwd() + "\\" + (t.text).strip()) #회차별 디렉토리로 이동

    url ="https://comic.naver.com" + t.a['href'] #회차 링크
    html2 = requests.get(url, headers = headers) #헤더 우회해서 링크 가져옴
    result2 = BeautifulSoup(html2.content, "html.parser") #html로 읽겠다

    webtoonImg = result2.find("div", {"class", "wt_viewer"}).findAll("img") #class가 wt_viewer인 div태그 찾고 거기서 img태그 모두 찾음
    num = 1 #이미지 저장 명

    for i in webtoonImg:
        saveName = os.getcwd() + "\\" + str(num) + ".jpg" #저장될 위치 + 저장 명 + 확장자
        with open(saveName, "wb") as file: #파일 열기, 이미지 저장할 것임
            src = requests.get(i['src'], headers = headers) #헤더 우회해서 이미지 src가져옴
            file.write(src.content) #이미지 저장
        num += 1 #저장 명 + 1

    os.chdir("..") #전 디렉토리로 이동

    print((t.text).strip() + "   saved completely!") #한 회차 이미지들 저장 완료

네이버 웹툰 이미지 크롤링은 웹 사이트에서 웹 크롤링 봇을 감지하고 차단하는 기능이 있었기 때문에 크롤링 하기 위해서는 헤더를 우회하는 과정이 필요했다.

*우회 하지 않으면 403 forbidden에러가 난다.

 

4. 실행 화면

webtoon코드를 실행한 파이썬 쉘 화면이다.

 

소녀의 세계라는 이름으로 된 폴더가 생성되었다.

 

소녀의 세계라는 폴더 안에 최신 10회차 제목의 파일이 생겼다.

 

2부 1화 폴더의 내부이다.

 

213화 다시 만날 때까지 폴더의 내부이다.

 

모든 폴더에 이미지가 잘 들어온 것을 확인할 수 있다.

+ Recent posts