나는 현재 NEXT에서 알고리즘 스터디를 진행하고 있다.

빠른 바로 가기를 위해서 다음 링크를 추가해둔다. 오 그리고 옆에다가 간단히 내용도 정리해두면 좋겠다.

 

200 - 자료구조 1

201 - 자료구조 1 (연습)

203 - 자료구조 1 (참고)

300 - 수학 1

301 - 수학 1 (연습)

303 - 수학 1 (참고)

400 - 다이나믹 프로그래밍 1

401 - 다이나믹 프로그래밍 1 (연습)

402 - 다이나믹 프로그래밍 1 (도전)

[문제]

1.A진법으로 나타낸 숫자를 B진법으로 변환시켜주는 프로그램을 작성하기로 하였다.

2.N진법이란, 한 자리에서 숫자를 표현할 때 쓸 수 있는 가짓수가 N개라는 뜻이다.

3.예를들어, N=17일 때, 한 자릿수에서 사용할 수 있는 수는 0, 1, 2, ..., 17가지이다.

 

[입력]

1.첫 줄에는 A진법, B진법이 공백을 구분하여 주어진다. (2<=A,B<=30 이하의 자연수)

2.입력의 두 번째 줄에는 A진법으로 나타낸 숫자의 자리수의 개수 m(1<=m<=25)이 주어진다.

3.세 번째 줄에는 A진법을 이루고 있는 숫자 m개가 공백으로 구분되어 높은 자릿수부터 주어진다.

4.각 숫자는 0이상 A미만임이 보장되며, 0으로 시작되는 경우는 존재하지 않는다.

5.A진법 수를 10진법으로 변환하면, 양의 정수이며 2^20보다 작다.

 

[출력]

입력으로 주어진 A진법으로 나타낸 수를 B진법으로 변환하여 출력한다.

 

[예제 입력 1]

17 8

2

2 16

 

[예제 출력 1]

6 2

 

[문제 풀이]

1. a_nums를 10진수로 변환할 때, 각 자리를 변환하고 10진수로 합쳐야하는데, 다음과 같이 한 번에 처리하는 오류가 발생했다.

나는 아래 내 코드가 완벽하게, a_nums를 10진수로 바꿔준다고 생각했는데, 아닌가보다!

#a_nums에서 하나씩 꺼내서 x자리에 넣고, 그것은 A진수이니, 10진수로 바꿔주세요. 그리고 a_nums_10에 넣어주세요
a_nums_10=list(map(lambda x : int(x,A), a_nums))

 

문제에 주어져있었다. A진법을 이루고 있는 숫자 m개가 높은 자리수부터 주어진다는 것이므로 결국 하나의 숫자다.

 

위처럼 잘못 풀었던 첫 번째 코드

import sys
input=sys.stdin.readline

A, B = map(int,input().split())
m=int(input())
a_nums=input().split()
#1.10진수로 변환 시키는 과정 필요
###방법 : int(숫자,n진수)
#a_nums에서 하나씩 꺼내서 x자리에 넣고, 그것은 A진수이니, 10진수로 바꿔주세요. 그리고 a_nums_10에 넣어주세요
a_nums_10=list(map(lambda x : int(x,A), a_nums))

#2.10진수를 n진수로 변환시키는 함수 작성
def convert_to_base(num, base):
    if num==0:
        return 0
    digits=[]
    while num:
        digits.append(int(num%base))
        num=num//base
    return ''.join(str(x) for x in digits[::-1])

#3.a_nums_10 차례대로 넣어서 반환
a_nums_b=list(map(lambda a : convert_to_base(a, B), a_nums_10))
print(*a_nums_b)

 

 

[정답코드]

import sys
input=sys.stdin.readline

A, B = map(int,input().split())
m=int(input())
a_nums = list(map(int, input().strip().split()))
#1.10진수로 변환 시키는 과정 필요
###방법 : int(숫자,n진수)
#a_nums에서 하나씩 꺼내서 x자리에 넣고, 그것은 A진수이니, 10진수로 바꿔주세요. 그리고 a_nums_10에 넣어주세요
# a_nums_10=list(map(lambda x : int(x,A), a_nums))
decimal_value=0
for num in a_nums:
    decimal_value=decimal_value*A + num

#2.10진수를 n진수로 변환시키는 함수 작성
def convert_to_base(num, base):
    if num==0:
        return [0] #다른 return 값이 리스트이므로 [0]으로 통일
    digits=[]
    while num:
        digits.append(int(num%base))
        num=num//base
    return digits[::-1]
#3.a_nums_10 차례대로 넣어서 반환
b_nums=convert_to_base(decimal_value, B)
print(' '.join(map(str,b_nums)))

 

10진수로 변환시키는 저 부분을 이해해야할 것 같다.

1.sort() 정복하기

-리스트 안에 담긴 것들은 모두 기본으로 오름차순 정렬이 된다.(숫자, 문자, 문자열 전부 상관없이)

-아스키코드 기반으로 정렬하는 것이므로, 대문자가 소문자보다 먼저 오게 된다.

-내림차순을 하고 싶으면, reverse=True 를 속성으로 넣어주면 된다.

-2차원 배열부터는 어떤 것을 기준으로 배열할지 기준을 정해줘야한다. 이때 람다 함수를 사용한다.

c = sorted(a, key = lambda x: x[0]
#[(0, 1), (1, 2), (3, 0), (5, 1), (5, 2)]

d = sorted(a, key = lambda x : x[1])
# [(3, 0), (5, 1), (0, 1), (1, 2), (5, 2)]

#비교할 아이템이 요소가 복수 개일 경우, 튜플로 우선순위를 정해준다.
#게다가 -를 붙이면, 현재와 반대차순으로 정렬된다.

e = sorted(a, key= lambda x : (x[0], -x[1]))
#[(0, 1), (1, 2), (3, 0), (5, 2), (5, 1)]

f = sorted(a, key = lambda x : -x[0])
#[(5, 1), (5, 2), (3, 0), (1, 2), (0, 1)])

-sort(key=len) 이라고 하면, 문자열을 알파벳 순서대로 정렬해준다.

python 1181 단어정렬(실버5)
 #1181 단어 정렬

N = int(input())
words=[input() for _ in range(N)]
words=set(words)
word_tuples=[]
for word in words:
  word_tuples.append([word,len(word)])
word_tuples.sort(key= lambda x : (x[1], x[0]))

for word in word_tuples:
  print(word[0])
n = int(input())
lst = []

for i in range(n):
    lst.append(input())

lst.sort()	## 괄호 안에 아무 값도 넣지 않으면 알파벳 순서대로 정렬을 해 준다.
lst.sort(key = len)	## 문자열 길이 순으로 정렬.

for i in lst:
    print(i)

조합은 파이썬에서 라이브러리를 제공해주는 것으로 알고 있다.

 

import math

# nCr 계산
n = 5
r = 3
result = math.comb(n,r)

 

Q1.숫자를 list()로 감싸는 것은 에러가 나는가?

O. 에러가 난다. 주의할 것.

 

따라서 문제에 대한 답 코드는 다음과 같이 작성하였다.

import math, sys

def input():
    return sys.stdin.readline().rstrip()

n,m=map(int, input().split())

result = math.comb(n,m)
result_list=list(str(result))
cnt=0

for i in reversed(result_list):
    if i=='0':
        cnt+=1
    else:
        break
print(cnt)

 

하지만 리스트 연산이 많아서 그런지 시간 초과가 떴다.

import math, sys

def input():
    return sys.stdin.readline().rstrip()

n,m=map(int, input().split())

result = math.comb(n,m)
cnt=0

for i in str(result)[::-1]:
    if i=='0':
        cnt+=1
    else:
        break
print(cnt)

 

흠.. 이렇게 했는데도 시간 초과가 뜬다. 그러면 어떻게 하지?

 

일단 개선 방안을 생각해보았을 때, math.comb(n,m) 이 함수가 생각보다 시간이 오래걸리는 듯하다.

직접 만들어서 쓰는게 훨씬 효과적일 듯 하다.

 

일단 팩토리얼 함수 만들어보자.

def factorial(num):
    result=1
    for i in range(2,num+1):
        result *=i
    return result

조합 식은 다음과 같은데, 

우선 조합을 코드로 표현해보겠다.

def combination(n,k):
    #예외 처리
    if k > n:
        return 0
    if k == 0 or k==n:
        return 1
    
    #팩토리얼 계산
    def factorial(num):
        result=1
        for i in range(2,num+1):
            result *=i
        return result
    
    #이때 조합의 계산은 항상 정수이기 때문에, 정수 계산을 위해서 /가 아니라 //를 사용한다.
    result = factorial(n)//(factorial(n-k)*factorial(k))

    return result

 

이때 k!과 n-k!에서 중복되는 부분이 상당히 생긴다.

이 부분을 한 번 메모이제이션으로 해결해보자.

 

열심히 아래와 같이 코드를 제작하였다.

import sys

def input():
    return sys.stdin.readline().rstrip()

def comb(n,k):
    if k > n:
        return 0
    if k==0 or n==k:
        return 1
    
    #k가 n/2보다 큰 경우, nCk=nC(n-k)와 같음. 더 작은 수로 바꿔주는 과정
    if k > n/2 :
        k = n-k

    result=1
    for r in range(k): #r-1까지 돌 것임
        result = result * (n-r) // (r+1)

    return result

n, r = map(int, input().split())
print(comb(n, r))

 

하지만 여전히 에러가 났다 ^_^ 한 단계 더 가야하나보다.

파스칼의 삼각형을 이용하는 방법도 생각하였으나, 정답을 보니 0의 개수는 2와 5의 개수로 판단하는 것 같다.

 

팩토리얼에서 특정 소인수의 개수를 계산하는 방법

 

1.모든 2의 배수를 카운트한다.

: n을 2로 나눈 몫은 n 이하의 모든 수 중 2의 배수의 개수를 의미한다.

몫 = n이하의 2의 배수들의 개수

이게 잘 이해가 안됐었는데, 나열해놓고 세어보면 결국 그렇다. 그냥 외우자.


예를 들어, n=10이면 10//2=5로, 2 4 6 8 10 총 5개의 숫자가 하나 이상의 2를 갖고 있다.

 

이때 코드는 두 가지 관점에서 짤 수 있다. 결국 결과는 같다.

1) 나누는 수를 2, 4, 8 ...로 높인다.

2) 주어진 n을 2로 나눠가면서 반복 계산한다.

 

원리를 이해하기에는 1번이 더 쉽지만 코드 짜기에는 2번이 더 쉬운 듯 하다.

드디어 정답 코드

 

import sys

def input():
    return sys.stdin.readline().rstrip()

def cnt(s,n):
    if n < s:
        return 0
    cnt=0
    while n>=s:
        cnt +=n//s
        n=n//s
    return cnt

n,m = map(int,input().split())
two_cnt = cnt(2,n) - cnt(2,n-m) - cnt(2,m)
five_cnt = cnt(5,n) - cnt(5,n-m) - cnt(5,m)

print(min(two_cnt,five_cnt))

 

 

+) sys.stdin.readline()과 sys.stdin.read()의 차이

이때 sys.stdin.readline()과 sys.stdin.read()의 차이가 궁금해서 찾아봤다.

-read()

: 여러 줄 입력을 처리할 때 사용된다. 따라서 메모리 사용량이 많아질 수 있으나, 빠르게 많은 양의 데이터 읽기 가능하다.

 

-readline()

: 한 줄씩 입력을 처리한다. 따라서 메모리 사용량이 상대적으로 적으나, read()보다 느리다.

1. 시간 초과 코드

: people 이라는 리스트에 전부다 넣고, 길이가 2이면 새로운 find_people 리스트에 넣어서 하나씩 출력하는 형태의 코드를 작성했다. 당연히 시간 초과!

import sys
def input()->str:
    return sys.stdin.readline().rstrip()

n, m = map(int, input().split())
#n 듣도 못한 사람의 수
#m 보도 못한 사람의 수

people=[]

for i in range(n+m):
    people.append(input())
num=0
find_people=[]
while(len(people)!=0):
    tem_people=people[0]
    if people.count(tem_people)==2:
        find_people.append(tem_people)
        del people[0]
        num+=1
    else: #개수 하나
        del people[0]
print(num)
find_people.sort()
for j in find_people:
    print(j)

 

2. 두 리스트에서 공통인 것만 출력하는 기능을 활용한다. 교집합 개념 활용!! (성공)

import sys
def input()->str:
    return sys.stdin.readline().rstrip()

n, m = map(int, input().split())
#n 듣도 못한 사람의 수
#m 보도 못한 사람의 수

people_who_dont_hear=[]
people_who_dont_see=[]

for i in range(n):
    people_who_dont_hear.append(input())

for j in range(n):
    people_who_dont_see.append(input())

intersection = list(set(people_who_dont_hear) & set(people_who_dont_see))
intersection.sort()
print(len(intersection))
for i in intersection:
    print(i)

 

완성 코드. 브루트포스. 생각보다 아직 어려워서 다른 코드들을 참고했다.

문제 이해도 너무 오래 걸리고, 푸는 데도 너무 오래 걸려서 속상하다.

그래도 점점 늘겠지? 다양한 인사이트들을 얻었고, 가장 많이 참고한 정답 코드 블로거보다 내가 더 짧고 가독성 좋게 입력한 부분도 있었다.

그리고 브루트포스라는 힌트만 얻고나서, 그래도 나름 맞게 코드를 작성했었다. 오류들이 많아서 그렇지...

 

문제 꼼꼼하게 읽자..!

import sys
def input()->str:
    return sys.stdin.readline().rstrip()

n, m, b = map(int, input().split()) #세로 n, 가로 m, 인벤토리에 99개의 블록
heights=[list(map(int, input().split())) for _ in range(n)]

#블록을 추가하거나, 빼는 방법 2가지 경우 스킬 존재
#약간 체스판 자르기랑 비슷한 문제인 것 같다.

# 브루트포스로 모든 경우의 수를 구하고, 가장 시간이 적게 걸린 것 중,
# 땅의 높이가 높은 것 출력.

# 절대값 구하는 함수 abs()

ans = int(1e9) #답을 무한으로 걸어놓고, 조건을 만족시키면 바꿔준다.
glevel=-1
for h in range(0, 257):
    take_block=0
    use_block=0
    for i in range(n): #세로(row)
        for j in range(m):#가로(column)
            if heights[i][j]>=h: #추가
                take_block+=heights[i][j]-h
            else: #heights[i][j] <h:
                use_block+=h-heights[i][j]
    if use_block > take_block + b:
        pass
    else:
        t_sum=take_block*2+use_block

        if t_sum <= ans:
            ans = t_sum
            glevel=h #어차피 점점 높이가 높은 것으로 갱신 되니까
print(ans,glevel )

아래는 문제를 풀면서 얻은 내용들이다.

 

[.index() 특징]

.index()는 중복값이 있을 경우, 가장 큰 값을 리턴한다.

 

[높이 구하는 방법]

일단 초기값을 glevel = -1로 지정해 놓은 뒤, 어차피 높이가 올라가면서 가장 높은 것으로 자동으로 갱신되도록 했다.

 

[최댓값, 최솟값 리스트 쓰지 않고 계산하기]

매번 계산한 다음, 계산한 값들 중 최대값을 구할 때는 항상 list에다가 넣은 후, max(list)를 하는 방식으로 계산했었는데,

그게 아니라 초기값을 0으로 잡아둔 뒤(최소값 구할 때는 int(1e9)로 잡기) 하나의 값을 지정하고, 이 값보다 작을 경우 업데이트 하는 방식으로 가능.

 

드디어 Class 2 문제 다풀었다. 야호 !

박은 캠프 때 n개의 랜선을 만들어야한다.

오에게 도움을 청했고, 오는 k개의 랜선을 가지고 있는데, 길이가 제 각각이다.

박은 랜선을 모두 n개의 같은 길이 랜선으로 만들고 싶다.

이때 최대 랜선의 길이를 구하는 프로그램을 만들어라.

 

알고리즘을 활용해야하는 문제 같다. 이진탐색 알고리즘을 사용하는 것으로 판단되지만, 일단 인간이 접근하는 방법을 토대로 풀어보고 싶었다.

 

1. 첫 번째 풀이 : 내 마음대로 풀이(테스트케이스 통과O but 시간 초과 코드)

그래서 우선은 나에게 수학적 문제로 주어졌다면 어떻게 풀 수 있을까 생각해봤다.

 

만약 k=n을 요구했다면, 당연히 가장 길이가 짧은 랜선의 길이가 result가 될 것이다.

그리고 실제로 인간의 감각(훈련된 수학적 사고)로 어떻게 하는지 생각해봤는데,

내림차순으로 lines를 정렬하고,

만약 k+1<=n가 시작되면

반복문은, 큰 수의 1/2부터 넣어보며, 몇 개의 조각이 나올 수 있는지 계산해본다.

그리고 가장 작은 수까지 반복문을 돌았는데 원하는 n에 접근되지 못하면,

 

큰수의 1/4부터 넣어보며, 전체에서 총 몇 개의 조각이 나올 수 있는지 계산한다.

이렇게 했을 때, 미세한 조정은 조금 더 필요하겠으나 대략적인 접근과 예제 입력은 만족시킬 수 있게 된다.

import sys, math
input=sys.stdin.readline

k, n= map(int, input().rstrip().split())
#k개의 랜선
#를 n개로 모두 같은 길이로 만드세요
#n개를 만들 수 있는 랜선의 최대 길이 출력

lines=[int(input()) for _ in range(k)]

lines.sort(reverse=True) #오름차순으로 정리
    #lines는 내림차순 정렬 이후 건들이기 없기

cnt=0 #몇 번째 순회인지 확인
breaker1=False
breaker2=False
while(True):
    if breaker1 == True and breaker2 == True: #삼중반복문 탈출
        break
    #밖에서 Lines를 변형해준다.
    standard_lines = [math.floor(line*((0.5)**cnt)) for line in lines] #변경 가능한 조건
    for standard in standard_lines: #line은 나누는 기준 line
        #이중반복문 탈출
        if breaker1==True: 
            breaker2=True
            break

        q_sum=0 #만들어지는 그룹의 개수
        for line in lines:
            q=line //standard
            q_sum+=q
        if q_sum == n:
            print(standard)
            breaker1=True
            break
    cnt+=1

그래도 바로 틀렸어요!가 아니라 시간초과가 떠서 그나마 기분이 좋다.

왜냐하면 종이에다가 쓰고, 블로그에다가 글을 쓰면서 계산하니까

술술 풀린 문제이고, 스스로도 뿌듯했기 때문이다.

 

반복문이 너무 많은 게 문제인 것 같아서

standard_lines를 매번 반복문으로 돌리는 부분을 좀 어떻게 해볼까 싶다.

 

해결하기가 쉽지 않아서, 카카오톡 오픈 채팅방에다가 물어보기도 했는데

이렇게 맞닿은 상황에서는, 로직 자체가 잘못된 경우에는 빠르게 바꾸는 것이 좋다고 한다.

2. 이진 탐색을 이용한 풀이

먼저 아직 이진 탐색 풀이에 익숙하지 않기 때문에, 이전에 풀었던 문제부터 복습해보겠다.

 

1920 수찾기 문제에서 풀었던 이진탐색 코드는 다음과 같다.

def binary_search(a_list, x):# a는 탐색 대상 리스트, 찾아야 하는 수 x
    start=0
    end=len(a_list)-1

    while(start<=end):
        mid=(start+end)//2

        if a_list[mid] == x:
            return 1
        elif a_list[mid] < x:
            start= mid+1
        else: #a_list[mid] > x:
            end = mid -1
    return 0

 

최종적으로 작성한 코드는 다음과 같다.(정답 코드)

import sys
def input()->str:
    return sys.stdin.readline().rstrip()

#이진탐색
def binary_search(a_list, x):# a는 랜선 리스트, x는 입력한 n
    start=1 #가장 짧은 길이
    end=a_list[-1] #가장 긴 길이

    while(start<=end):
        print('현재 start=',start,'현재 end=', end)
        mid=(start+end)//2 #결국 최종적으로 mid가 문제에서 찾고자 하는 정답이 될 것임
        result=0
        print('현재 mid=',mid)
        for line in a_list:
            result+= line // mid
        print('현재 result=',result)
        if result >= x:
            start = mid+1
        elif result < x : #개수가 x보다 적으면,개수를 늘릴 수 있도록 해야하니까 정답인 mid를 더 작게 바꿔야한다.
            end = mid -1
        # else: #result > x: #개수가 x보다 많으면, 개수를 줄일 수 있도록 정답인 mid를 키운다.
        #     start= mid+1
    return end

k, n = map(int, input().split())
#k개의 랜선을 n개로 모두 같은 길이로 만드세요
#n개 만들 수 있는 랜선의 최대 길이 출력(answer)

lines=[int(input()) for _ in range(k)]

lines.sort() #오름차순으로 정리

print(binary_search(lines, n))

처음에는 이분탐색 함수 내에서,

if result == x:
	return mid
elif result > x:
	start = mid + 1
else: #result < x:
	end = mid -1

 

이렇게 조건문을 작성했다. 정답 코드에서는 end를 출력하는데, 내 오답에서는 mid를 출력했다.

약간은 이해가 덜 되긴 했는데, start가 아닌 mid인 이유는, 마지막에 start는 while문에서 조건식을 초과하기 때문에 end로 출력한 것이고, while문이 모두 끝나야 최선의 답을 찾을 수 있기 때문에, while문이 모두 끝난 뒤의 값을 출력하는 게 예외사항 없이 맞는 듯하다.

 

이진탐색(이분탐색)에 대해 확실히 공부할 수 있어서 좋은 문제였다.

 

 

+) 추가 공부 : 이중 반복문에서 break해서 나오는 방법

맨날 코드를 짜다가 이러한 상황에 마주해서 당황한 적이 많았는데, 한번 정리를 하고 가는게 맞는 것 같다.

1) 불린형 조건 활용

breaker = False로 기본값으로 주어주고, 첫 번째 반복문을 나올 때 breaker=True로 바꾸면서 break한다.

그리고 두 번째 반복문에서는 breaker가 True이면 정지하도록 걸어둔다.

 

2) 예외처리 try: except: 문 활용

try: 이중 반복문

except :
	#오류 발생시 실행할 문구 작성

와 같이 코드를 짜면, NotImplementedError(선언되지 않은 에러)를 발생시켜, 다중 for문을 바로 종료시킨다.

그런데 이 방법보다는 1번 방법이 더 선호되는 것으로 알고 있다. 너무 많이 쓰면 가독성이 떨어지고, 코드 실행속도가 느려질 수 있기 때문!

 

이외 방법은 아래에서 참고 가능하다.

try:
    # 예외 발생 가능한 코드
    result = some_function()

except ValueError:
    # ValueError 예외 처리
    print("ValueError occurred")

except IndexError:
    # IndexError 예외 처리
    print("IndexError occurred")

except Exception as e:
    # 그 외 모든 예외 처리
    print("An error occurred:", e)

finally:
    # 예외 발생 여부에 상관없이 실행되는 코드
    print("Execution complete")

위 코드 블록 내용 출처 : https://twoicefish-secu.tistory.com/520

 

1. 해시함수란?

임의의 길이의 입력을 받아서, 고정된 길이의 출력을 내보내는 함수.

보통 자료의 저장에도 사용 되지만, 주로 매우 빠른 데이터 탐색에도 사용된다.

 

2. 해시함수 알고리즘이란?

긴 길이의 데이터를 짧은 길이의 데이터로 변환하는 알고리즘이다.

단, 제 3자는 짧은 길이 데이터로부터 긴 길이의 데이터를 도출할 수 없어야한다.

그리고 동일한 출력을 갖는 데이터를 찾을 수 없어야한다.

 

공개키 암호 알고리즘 RSA가 해시함수의 일종이지 않을까 생각해봤다.

그러나 공개키 암호 알고리즘은, 긴 길이의 데이터를 짧은 길이의 데이터로 변환하는 것은 아니니, 완전히 같지는 않고

제 3자로부터 암호화 전의 비밀번호를 알지 못하게 하는 '보안'에서 사용된다는 공통점이 있겠다.

 

3. 문제 풀이

영어 알파벳은 26개 이므로, a~z에 1~26 숫자를 부여한다.

    따라서 문자열을 입력하면, 수열을 얻을 수 있다.

이렇게 얻은 수열을 하나의 정수로 치환하기 위해서, 적당히 큰 수 M으로 나눠주겠다.

M으로 나눴을 때의 나머지를 H(출력 값=해시 값)이라고 하겠다.

 

나누는 수가 생긴다는 것은, 나눴을 때 나오는 나머지의 범위로 범위가 정해진다.

    (비둘기집의 원리-27로 나누는데, 전체 모수가 28개 이상이 되면 비둘기집의 원리에 의해 겹치는 쌍이 1개 이상 발생한다.)

이렇게 동일한 해시 값을 가지는 것을 '해시 충돌'이라고 하고, 좋은 해시 함수는 해시 충돌의 수가 적다.

 

지금까지의 해시함수 암호화 방법은

1) a~z에 1~26 숫자 부여

2) 다 더한 뒤, 적당히 큰 수 M으로 나눈다.

이 방법이다. 그런데 이것은, abc와 acb와 같이 순서가 달라져도 같은 값을 가지므로 해시 충돌이 발생한다.

 

따라서 이 방법을 개선하기 위해서, 각 항마다 고유한 계수를 부여한다.

특정한 숫자 r을 i번째면 i만큼 곱해준다.(0번째 수는 r^0, 1번째 수는 r^2)

r과 M은 서로소로 정하는 것이 일반적이다.

이 문제에서는 r=31, M=1234567891로 가정한다.(둘 다 소수임)

 

4. 코드 작성 순서

1. a~z를 1~26으로 전환하는 코드 작성

2. 반복문으로 문자열의 위치에 따른 계수들을 곱해줌

3. 합을 구하고, M으로 나눈 H값 출력

 

1. a~z를 1~26으로 전환하는 코드 작성

우선 아스키 코드를 활용해보면 좋을 것 같다.

a=97이므로 아스키 코드 수로 바꾼 후, -96을 해줬다. 그리고 문자로 바꾼 뒤, new_string이라는 문자열에 더해주는 반복문을 작성했다.

이렇게 하면 수열로 바뀐 것을 출력할 수 있다.

import sys
input=sys.stdin.readline

l=int(input().rstrip()) #문자열의 길이
string=input().rstrip() #문자열

r=31 #예시 소수 : 
m=1234567891 #예시 소수 : 나누는 수

new_string=''
for chr in string:
    new_string+=str(ord(chr)-96)

 

2. 반복문으로 문자열의 위치에 따른 계수들을 곱해줌

수열을 출력하는 것이 아니라, 한번의 반복문으로 해결 시키기 위해서, for문 안에서 바로 r을 곱한 후의 sum 값을 가져오도록 코드를 짰다.

import sys
input=sys.stdin.readline

l=int(input().rstrip()) #문자열의 길이
string=input().rstrip() #문자열

r=31 #예시 소수 : 
m=1234567891 #예시 소수 : 나누는 수

sum=0
i=0
for chr in string:
    sum+=(ord(chr)-96)*(r**i)
    i+=1

3. 합을 구하고, M으로 나눈 H값 출력 🚀 정답 코드

자 이제 정답만 구하면 끝난다. 정답이다.

import sys
input=sys.stdin.readline

l=int(input().rstrip()) #문자열의 길이
string=input().rstrip() #문자열

r=31 #예시 소수 : 
m=1234567891 #예시 소수 : 나누는 수

sum=0
i=0
for chr in string:
    sum+=(ord(chr)-96)*(r**i)
    i+=1

result=sum % m
print(result)

 

1. 첫 번째 코드 (정답 코드)

#괄호 균형 판단 프로그램
#(), []

while(True):
    tf=0
    line = input()
    small=0#괄호가 열리면 +1, 닫히면 -1
    large=0
    #<괄호의 균형을 맞춰야하는 문제 해결 방법>
        #문이 열릴 때마다,색을 칠해준다.
        #닫을 때 칠해진 색과 닫을 색이 일치하지 않으면 break
        #일치하면 그 색을 뽑아버린다.
    color=[]
    if len(line)==1 and line[-1]=='.':#.이 들어오면 정지
        break

    else:
        for i in line:
            if i == '(':
                small+=1
                color.append('small')
            elif i==')':
                small-=1
                if small <0: #한 번이라도 이런 경우가 생기면 에러처리를 위해 tf=-1로 변경.
                    tf=-1
                    break
                if color[-1] !='small':
                    tf=-1
                    break
                else:
                    color.pop()
            elif i=='[':
                large+=1
                color.append('large')
            elif i==']':
                large-=1
                if large <0:
                    tf=-1
                    break
                if color[-1] !='large':
                    tf=-1
                    break
                else:
                    color.pop()

    if small == 0 and large == 0 and tf==0:
        print('yes')
    else:
        print('no')

#마지막에 .을 넣어도 끝나지 않음
    #=> while문 안의 for문이 아니라, for문 시작 전에 if break문을 넣어줬음.

 

2. 정리한 코드(정답 코드)

위의 코드가 조금 지저분한 것 같아서 정리 필요.

while(True):
    tf=0 #오답이 확정일 때 -1로 구분지어주는 역할.
    
    line = input() #입력 받기

    #<괄호의 균형을 맞춰야하는 문제 해결 방법>
        #가장 최근에 열린 괄호가, 자신의 짝과 일치하는지 확인한다.
            #일치하지 않으면 break
            #일치하면 그 색을 뽑아버린다.
    stack=[]
    
    if len(line)==1 and line[-1]=='.':#.이 들어오면 정지
        break

    else:
        for i in line:
            if i == '(' or i=='[':
                stack.append(i)
            elif i==')':
                if len(stack)==0 or stack[-1] !='(':
                    tf=-1
                    break
                else:
                    stack.pop()
            elif i==']':
                if len(stack)==0 or stack[-1] !='[':
                    tf=-1
                    break
                else:
                    stack.pop()

    if len(stack)==0 and tf!=-1:
        print('yes')
    else:
        print('no')

 

3. 최종 코드 : import sys 입력만 더 효율적으로

import sys
input=sys.stdin.readline
#readline은 공백도 받으므로, rstrip()로 공백 제거.
while(True):
    tf=0 #오답이 확정일 때 -1로 구분지어주는 역할.
    
    line = input().rstrip() #입력 받기

    #<괄호의 균형을 맞춰야하는 문제 해결 방법>
        #가장 최근에 열린 괄호가, 자신의 짝과 일치하는지 확인한다.
            #일치하지 않으면 break
            #일치하면 그 색을 뽑아버린다.
    stack=[]
    
    if len(line)==1 and line[-1]=='.':#.이 들어오면 정지
        break

    else:
        for i in line:
            if i == '(' or i=='[':
                stack.append(i)
            elif i==')':
                if len(stack)==0 or stack[-1] !='(':
                    tf=-1
                    break
                else:
                    stack.pop()
            elif i==']':
                if len(stack)==0 or stack[-1] !='[':
                    tf=-1
                    break
                else:
                    stack.pop()

    if len(stack)==0 and tf!=-1:
        print('yes')
    else:
        print('no')

이때 readline은 공백도 받으므로, rstrip()로 공백을 제거해줘야한다.

1. 시간 초과 코드

첫 번째로 풀었을 때 시간 초과  코드. 시간 초과 될 것은 알았지만, 일단 써보자라고 생각하고 작성했다. 역시나 시간 초과.

import sys
input=sys.stdin.readline

m, n = map(int, input().split())

for i in range(m, n+1):
    cnt=False #합성수 수
    for j in range(2, 1000001):
        if i % j ==0 and i !=j:
            cnt=True
    if cnt == False:
        print(i)

에라토스테네스의 체를 활용하여 경우의 수를 확 줄여서 문제를 푸는 것이었다.

결국 1~100 사이의 소수를 찾는다면, 루트 100 = 10 까지만 나눠서 확인해보면(2~10의 배수가 아닌 것이 확인되면)

소수라는 것을 확신할 수 있다는 뜻이다. 2의 배수, 3의 배수, 4의 배수 ... 차례로 모두 제거한다고 생각해보면, 10의 배수까지만 제거할 수 있다. 11*1~11*10까지는 이미 1~10의 배수에서 걸러졌고, 다음 11*11 부터는 해당 범위를 초과해서 볼 필요도 없다.

[에라토스테네스의 체 내용 참고]

https://namu.wiki/w/%EC%97%90%EB%9D%BC%ED%86%A0%EC%8A%A4%ED%85%8C%EB%84%A4%EC%8A%A4%EC%9D%98%20%EC%B2%B4

 

코드는 다음과 같다.

import sys
input=sys.stdin.readline
m, n = map(int, input().split())

for i in range(m, n+1):
	if i == 1: #1제거
    	continue
    for j in range(2, int(i**0.5) + 1): # 해당 수의 제곱근까지만 확인해주면 됨.
    	if i % j == 0: #나누어지면 멈추고,
        	break
    else:
    	print(i) #나누어지지 않으면 출력한다.

+ Recent posts