Skip to content

Minjeong / 4월 2주차 / 3문제#18

Merged
Mingguriguri merged 3 commits intomainfrom
minjeong
Apr 15, 2024
Merged

Minjeong / 4월 2주차 / 3문제#18
Mingguriguri merged 3 commits intomainfrom
minjeong

Conversation

@Mingguriguri
Copy link
Collaborator


name: 민정 PR 템플릿
about: '알고리즘 '

목표 문제 수: 3개

목표 유형: DP
-> 푼 문제

아래는 제가 정리한 내용 중에서, 핵심이 되는 플로우와 TIL 위주를 가져왔습니다.

PGS ##짝지어 제거하기: 스택/큐 Level 2

정리한 노션 링크: 노션

🔍Intuition

을 찾아야 한다? 그러면 스택이 답이다.

그래서 이 문제는 스택이겠구나 싶었다.

(하지만 처음 접근할 때는 맨날 DP풀다가 오랜만에 스택 문제를 접해서, 다른 길로 샜다…)

(그래서 다른 길로 세서 잘못 시간초과가 나왔던 코드와, 스택으로 접근한 코드 모두 기록했다.)

스택으로 접근하려면 2가지를 고려해야 한다.

  1. 언제 push할 것인가?
  2. 언제 pop할 것인가?

  1. 언제 push할 것인가?

    ⇒ pop하지 않을 경우에는 모두 추가한다.

    또는 stack에 아무것도 없다면 추가한다. (왜냐하면, 초기에 아무것도 없는데 pop해버리면 indexError가 발생하기 때문에 stack이 비어있을 경우에 추가하는 조건문을 넣어둬야 한다)

  2. 언제 pop할 것인가?

    ⇒ 이건 쉽다. stack의 top과 현재위치에 해당하는 문자가 같으면 pop한다.

여기서 하나 더 고려해야 한다. 언제 반복을 종료할것인가?

⇒ 주어진 문자열 s를 다 보고 나면 종료해야 한다. 즉, for문을 쓰면 된다.

🚩플로우

접근: 스택

  1. 첫 문자를 stack에 push(append)한다.

  2. stack이 비어 있을 경우 → 문자를 push(append)한다. (스택이 비어있을 경우, pop하면 IndexError 발생)

  3. stack에 있는 top의 값과 현재 값(c)과 일치하다면 → stack의 top을 pop한다.

    → 그게 아닐 경우, stack에 다시 push(append)한다.

  4. 모두 다 짝을 찾아서 stack이 비어있다면 → return 1을, 비어있지 않다면 → return 0을 한다.

🚩My submission

def solution(s):
    stack = []
    for c in s:
        if len(stack) == 0:
            stack.append(c)
        elif stack[-1] == c:
            stack.pop()
        else:
            stack.append(c)
            
    if len(stack) == 0:
        return 1
    else:
        return 0

💡TIL

  • 짝을 찾아야 한다 ⇒ 스택을 활용한다.

백준 #11055. 가장 큰 증가하는 부분수열: DP 실버2

정리한 노션 링크: 노션

🔍Intuition

지난번 [#11053. 가장 긴 증가하는 부분수열](https://www.notion.so/11053-f300eb3a4ac740e09549cdcee0187492?pvs=21) 문제의 시리즈 문제에 접근해보려고 한다. 지난번 #11053다음 단계인 #11055번. 가장 큰 증가하는 부분 수열의 문제는 아래와 같다.
처음에는 오잉 가장 큰 수열하려면 전부 다 더하면 되지 않을까?! 했는데, 그게 아니었다.

조건은 2가지인것.

  1. 증가하는 수열이어야 한다.
  2. 증가하는 수열 내의 합이 가장 큰 수열이어야 한다.

이전에 #11053은 DP 테이블에 가장 “긴” 증가하는 수열의 길이를 저장하는 반면에, 이번 #11055 에서는 DP테이블에 증가하는 수열 중에서 가장 합이 “큰” 수열의 합이 저장되어야 한다. -

따라서 증가하는 수열의 코드를 기반으로 한다.(놀랍게도 일주일전에 풀었는데 기억 안 남;;😓)

🚩플로우

이 방식으로 접근할 경우 dp에는 아래와 같이 저장되게 된다.(예시 기준)

print(nums)
# 1 100 2 50 60 3 5 6 7 8

print(dp)
# [1, 101, 3, 53, 113, 6, 11, 17, 24, 32]
i 0 1 2 3 4 5 6 7 8 9
nums[i] 1 100 2 50 60 3 5 6 7 8
dp[i] 1 101 3 53 113 6 11 17 24 32
  • 만약 nums[i] > nums[j]라면 ⇒ 기존의 dp[i]와 새로 더해져 구해지는 dp[j] + nums[i] 중 큰 값을 d[i]로 갱신한다.
  • 만약 nums[i] <= nums[j라면, 기존의 dp[i]nums[i]중 큰 값으로 갱신하면 된다.
    • ⇒ 이 과정을 수행해야 하는 이유는 극단적으로 해당 인덱스에서 증가하는 부분수열이 전혀 만들어지지 않을 경우, 뒤의 과정의 수행을 위해 현재값(nums[i])이라도 할당해주어야 하기 때문이다. 즉, 뒤의 과정에서 해당 인덱스부터 시작하는 부분 수열을 고려해주어야 한다는 뜻이다.

🚩My submission

50분 남을 때까지 계속 시도했는데 ‘틀렸습니다’가 계속 나와서 다양한 dp점화식을 시도해보았으나 되지 않았다.. 결국 힌트를 보고 최종 답안을 제출하게 되었다.

import sys
input = sys.stdin.readline

n = int(input())
nums = list(map(int, input().split()))
dp = [1] * n
dp[0] = nums[0]

for i in range(len(nums)):
    for j in range(i):
        if nums[j] < nums[i]:
            dp[i] = max(dp[i], dp[j]+nums[i])
        else:
            dp[i] = max(dp[i], nums[i])
print(max(dp))

코드로 확인해보니까 dp에 처음 값은 1로 둬야하고, dp[0]의 값만 따로 초기화해줘야 한다.

그리고 이렇게 해줄 경우, nums[j]가 nums[i]보다 작은 경우(else) 도 처리를 해주어야 한다. (난 이 부분에 대한 처리를 해주지 않았다..)

→ else인 경우에는 dp에 있는 값 혹은 자기 자신의 값을 대입해주면 된다.

(개빡친다……………. 나 자신한테)

💡TIL

  • 처음엔 dp를 초기화할 때 dp = nums로 했다.

    그랬더니 파이썬의 포인터 기능이 적용이 되었는지, 자꾸 dp에 저장한 값이 nums에 연동되는 문제가 발생했다. 그래서 dp를 별도로 선언하고, nums의 값들을 for문을 통해 복사해주니 이 문제가 해결되었다.

  • dp에서는 보여지는 이외로 전혀 부분수열이 만들어지지 않을 경우도 고려해야 한다. 처음에 일차적으로 짰던 점화식 dp[i] += nums[j] 이 전혀 수행되지 않을 수도 있다는 뜻이다. 그렇게 되면 dp에는 초기값만 남게 되고 가장 큰 값을 구하지 못하게 된다.

    • 따라서, max함수를 이용하여 처리해야 한다.
  • 위에 점을 반영했음에도 틀려쓴데 그 이유는 if의 반례인 else인 경우를 생각하지 않아서였다.

    ⇒ 아래와 같이 짜는 이유는 극단적으로 해당 인덱스에서 증가하는 부분수열이 전혀 만들어지지 않을 경우, 뒤의 과정의 수행을 위해 현재값(nums[i])이라도 할당해주어야 하기 때문이다.

    else:
        dp[i] = max(dp[i], nums[i])

    즉, 뒤의 과정에서 해당 인덱스부터 시작하는 부분 수열을 고려해주어야 한다는 뜻이다.

    • 이렇게 고려해야 한다는 것은 전혀 생각지도 못했고, 이걸 고려하지 못했던 것이 내가 ‘틀렸습니다’가 줄기차게 나왔던 이유였다.

백준 #9251. LCS: DP 골드5

정리한 노션 링크: 노션

🔍Intuition

아래는 굉장히 잘 정리해주신 블로거분의 개념정리 게시글이다.

LCS에 대한 개념을 알아야 문제를 풀 수 있기 때문에 제한시간 1시간 내에 개념에 대한 이해를 먼저 하고 시작했다.

[[알고리즘] 그림으로 알아보는 LCS 알고리즘 - Longest Common Substring와 Longest Common Subsequence](https://velog.io/@emplam27/알고리즘-그림으로-알아보는-LCS-알고리즘-Longest-Common-Substring와-Longest-Common-Subsequence)

(표를 만들고 싶었지만 나도 시험기간인지라 이해만 하고 넘어감)

🔍` 문제 풀이에 필요한 내용 : Longest Common Subsequence 길이 구하기

점화식

if i == 0 or j == 0:  # 마진 설정
	LCS[i][j] = 0
elif string_A[i] == string_B[j]:
	LCS[i][j] = LCS[i - 1][j - 1] + 1
else:
	LCS[i][j] = max(LCS[i - 1][j], LCS[i][j - 1])

최장 공통 부분수열의 점화식을 코드로 작성해보았습니다. 위와 마찬가지로 LCS라는 2차원 배열에 매칭하고 마진값을 설정한 후 검사한다.

  1. 문자열A, 문자열B의 한글자씩 비교한다.
  2. 두 문자가 다르다면, 왼쪽 값이나 위쪽 값, 즉 LCS[i - 1][j]와 LCS[i][j - 1] 중에 큰값을 표시한다.
  3. 두 문자가 같다면 이전 값에 +1하기 위해 LCS[i - 1][j - 1] 값을 찾아 +1 한다.
  4. 위 과정을 반복한다.

🚩나의 제출 과정 코드 기록

초안 ⇒ 결과: 틀렸습니다

import sys
input = sys.stdin.readline

str_A = input()
str_B = input()
LCS = [[0 for j in range(len(str_B))] for i in range(len(str_A))]

for i in range(1, len(str_A)):
    for j in range(1, len(str_B)):
        if str_A[i] == str_B[j]:
            LCS[i][j] = LCS[i-1][j-1] + 1
        else:
            LCS[i][j] = max(LCS[i-1][j], LCS[i][j-1])
        
    # print("-----")
    # print(LCS)

#print("answer", max(max(LCS)))
print(max(max(LCS)))

GPT에게 힌트를 얻은 문자열 입력 과정에서 틀린 부분

import sys
input = sys.stdin.readline

str_A = input()
str_B = input()
print(str_A)
print(str_B)
''' 중간과정 생략'''
print(max(max(LCS)))
ACAYKP

CAPCAK

4

sys.stdin.readline를 사용하면, 개행문자 \n 이 포함된다. 문자열에 대해서만 처리를 하려면 strip() 함수를 사용하여 개행문자는 제거해줘야 한다.

1차 수정 : 개행문자를 제거하기 위해 strip()함수 사용

import sys
input = sys.stdin.readline

str_A = input().strip()
str_B = input().strip()
print(str_A)
print(str_B)
''' 중간과정 생략'''
print(max(max(LCS)))
ACAYKP
CAPCAK
3

⇒ 제거해주었더니 개행문자는 출력되지 않았다. 하지만 답이 다르게 나온다. 이 점 때문에 틀리다고 나왔던 것이다.

그렇다면 왜 틀린걸까?? 다시 내 코드를 보면,

import sys
input = sys.stdin.readline

str_A = input().strip()
str_B = input().strip()
LCS = [[0 for j in range(len(str_B))] for i in range(len(str_A))]

for i in range(1, len(str_A)):
    for j in range(1, len(str_B)):
        if str_A[i] == str_B[j]:
            LCS[i][j] = LCS[i-1][j-1] + 1
        else:
            LCS[i][j] = max(LCS[i-1][j], LCS[i][j-1])
        

print(max(max(LCS)))

i = 0 이거나 j = 0 으로 두기 위해 일부러 반복문을 1부터 시작했다. 그리고 이때의 i = 0 또는j = 0 부분은 마진에 대한 부분으로, str_Astr_B가 시작하는 부분이 아니다.

이 말은 즉슨, str_Astr_B가 시작하는 부분은 인덱스 1부터이고 이를 구현하려면 배열의 크기를 1씩 더 키워야 한다는 의미이다.

2차 수정 : 인덱스 범위를 늘려 LCS 배열 초기화 수정, 반복문 범위 수정

LCS = [[0 for j in range(len(str_B)+1)] for i in range(len(str_A)+1)] # 배열의 크기 1 늘림

for i in range(1, len(str_A)+1): # for문의 범위도 1 늘림
    for j in range(1, len(str_B)+1):  # 마찬가지
        if str_A[i-1] == str_B[j-1]:   # 1 늘리면 여기는 1 줄여줘야 우리가 하고 싶은 범위가 됨
            LCS[i][j] = LCS[i-1][j-1] + 1
            LCS[i][j] = max(LCS[i-1][j], LCS[i][j-1]) 

🚩최종 답안

import sys
input = sys.stdin.readline

str_A = input().strip()
str_B = input().strip()
LCS = [[0 for j in range(len(str_B)+1)] for i in range(len(str_A)+1)] #LCS 배열 초기화

for i in range(1, len(str_A)+1):
    for j in range(1, len(str_B)+1):
        # 한 글자씩 비교
        if str_A[i-1] == str_B[j-1]:  # 같으면 이전 값에 +1
            LCS[i][j] = LCS[i-1][j-1] + 1
        else: # 다르다면 A 부분수열과 B 부분수열 중 더 큰 값을 선택
            LCS[i][j] = max(LCS[i-1][j], LCS[i][j-1]) 

print(max(max(LCS))) # LCS 배열에서 가장 큰 값을 리턴

💡TIL

  • 입력 처리: sys.stdin.readline를 사용하면, 엔터(개행 문자 \n)도 입력으로 받아들인다. 이는 **str_A**와 **str_B**가 예상과 달리 개행 문자를 포함하게 될 수 있음을 의미한다. 이를 제거하기 위해 strip() 함수를 사용하여 문자열의 양쪽 끝에서 개행 문자를 제거해야 한다.
  • 일부러 마진은 둔다 ⇒ 이 말은 즉슨, i=0이거나 j=0인 경우, 0으로 두어야 하고, 이 부분을 제외하고 저장을 시작해야 하기 때문에 배열의 크기를 1씩 더 키워야 한다. 또한, str_Astr_B가 시작하는 부분은 인덱스 1부터이고 이를 구현하려면 배열의 크기를 1씩 더 키워야 한다는 의미이다.

Copy link
Collaborator

@zaqquum zaqquum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다b

Copy link
Contributor

@JYP0824 JYP0824 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정리를 잘하시네유

@Mingguriguri Mingguriguri merged commit fdddcaa into main Apr 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants