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

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

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

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

 

문제 꼼꼼하게 읽자..!

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)

 

+ Recent posts