오늘의 공부 키워드

  • 1791 Find Center of Star Graph

1791 Find Center of Star Graph

문제 해석

 이 문제는 n개의 노드(점)로 이루어진 무방향 스타 그래프에 관한 것입니다. 스타 그래프는 하나의 중심 노드가 있고, 나머지 n-1개의 노드가 그 중심 노드와 연결된 구조를 가집니다.

 

 edges라는 2차원 정수 배열이 주어집니다.  edges[i] = [ui, vi]는 노드 ui와 노드 vi 사이에 엣지가 있다는 것을 나타냅니다. 주어진 스타 그래프의 중심 노드를 반환하는 것이 이 문제의 목표입니다.

 

예시

예시 1:

입력: edges = [[1,2],[2,3],[4,2]]

출력: 2

설명: 노드 2는 다른 모든 노드와 연결되어 있으므로 중심 노드입니다.

예시 2:

입력: edges = [[1,2],[5,1],[1,3],[1,4]]

출력: 1

설명: 노드 1은 다른 모든 노드와 연결되어 있으므로 중심 노드입니다.

해결 방안

중심 노드를 찾기 위해서는 각 엣지에서 공통적으로 등장하는 노드를 찾으면 됩니다. 왜냐하면 스타 그래프에서 중심 노드는 모든 엣지에 등장하기 때문입니다. 아래의 방법으로 문제를 해결할 수 있습니다:

  1. 첫 번째 엣지를 선택합니다. (edges[0])
  2. 첫 번째 엣지의 두 노드 중 하나는 중심 노드입니다.
  3. 두 번째 엣지에서도 이 두 노드 중 하나가 등장하는지를 확인합니다.
  4. 등장한다면 그 노드가 중심 노드입니다.

코드

def findCenter(edges):
    # 첫 번째 엣지의 두 노드를 선택합니다.
    a, b = edges[0]
    
    # 두 번째 엣지에서 a 또는 b가 등장하는지를 확인합니다.
    # 만약 a가 등장하면 a가 중심 노드입니다.
    if edges[1][0] == a or edges[1][1] == a:
        return a
    # 그렇지 않으면 b가 중심 노드입니다.
    else:
        return b

# 예시 테스트
print(findCenter([[1, 2], [2, 3], [4, 2]]))  # 출력: 2
print(findCenter([[1, 2], [5, 1], [1, 3], [1, 4]]))  # 출력: 1

 

코드 설명

  1. edges[0]에서 두 노드를 가져옵니다. 여기서 a와 b는 첫 번째 엣지의 노드입니다.
  2. 두 번째 엣지(edges[1])에서 첫 번째 엣지의 노드 a 또는 b가 있는지 확인합니다.
    • 만약 a가 두 번째 엣지에 포함되어 있다면 a가 중심 노드입니다.
    • 그렇지 않다면 b가 중심 노드입니다.
  3. 최종적으로 중심 노드를 반환합니다.

이 방법은 매우 효율적이며, 두 번째 엣지만 확인하면 중심 노드를 찾을 수 있으므로 O(1)의 시간 복잡도를 가집니다.

 
 

오늘의 회고

 

  • 부족함을 느끼지만 실력을 늘리는것이 제일 빠른길임을 명심하자
  • 새로운 개념을 배울수 있어서 좋았다.

 

오늘의 공부 키워드

  • 1351.Count Negative Numbers in a Sorted Matrix

 

1351.Count Negative Numbers in a Sorted Matrix

이 문제는 m x n 행렬(grid)이 행과 열 모두 내림차순으로 정렬되어 있을 때, 행렬 안의 음수 숫자의 개수를 세는 문제입니다.

문제 이해

주어진 행렬은 각 행과 각 열이 내림차순으로 정렬되어 있습니다 예를 들어 아래 행렬에서는 음수 숫자가 8개 있습니다.

grid = [[4, 3, 2, -1],
        [3, 2, 1, -1],
        [1, 1, -1, -2],
        [-1, -1, -2, -3]]

 

접근 방법

이 문제를 해결하기 위한 효율적인 방법은 다음과 같습니다:

  1. 각 행에 대해, 오른쪽에서 왼쪽으로 이동하면서 음수를 찾습니다.
  2. 음수를 찾으면, 그 열 이하의 모든 값이 음수라는 것을 알 수 있습니다(왜냐하면 내림차순이기 때문입니다).
  3. 따라서 그 지점에서 남은 모든 값을 음수로 간주하고, 음수의 개수를 누적합니다.

구체적인 단계는 다음과 같습니다:

  1. 행(row)별로 반복합니다.
  2. 각 행의 오른쪽 끝부터 시작하여 왼쪽으로 이동합니다.
  3. 음수를 만나면 해당 지점에서 그 행의 끝까지 남은 요소의 개수를 더합니다.
  4. 다음 행으로 이동하여 동일한 과정을 반복합니다.

예제

위의 예제 행렬을 통해 설명하면:

  • 첫 번째 행: [-1]에서 음수를 만나므로 1개의 음수 발견
  • 두 번째 행: [-1]에서 음수를 만나므로 1개의 음수 발견
  • 세 번째 행: [-1, -2]에서 음수를 만나므로 2개의 음수 발견
  • 네 번째 행: [-1, -1, -2, -3]에서 음수를 만나므로 4개의 음수 발견

이렇게 총 8개의 음수를 발견하게 됩니다.

 

코드

from typing import List

class Solution:
    def countNegatives(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        count = 0
        # 시작은 마지막 열의 처음 행
        row, col = 0, n - 1
        
        while row < m and col >= 0:
            if grid[row][col] < 0:
                # 음수를 만나면, 해당 열 아래 모든 행이 음수이므로 추가
                count += (m - row)
                col -= 1
            else:
                # 음수를 만나지 않으면 다음 행으로 이동
                row += 1
        
        return count

# 예제 테스트
solution = Solution()

grid1 = [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]]
print(solution.countNegatives(grid1))  # 8

grid2 = [[3,2],[1,0]]
print(solution.countNegatives(grid2))  # 0

 

설명

  1. 시작 지점: 맨 위 행(row)의 마지막 열(col)에서 시작합니다.
  2. 탐색 방식:
    • 현재 위치의 값이 음수일 경우, 해당 열 이하의 모든 값이 음수임을 알 수 있으므로, 그 수만큼 카운트합니다. 그리고 왼쪽 열로 이동합니다.
    • 현재 위치의 값이 음수가 아닐 경우, 다음 행으로 이동합니다.
  3. 반복: 행(row) 또는 열(col)의 끝에 도달할 때까지 반복합니다.
  4. 결과 반환: 모든 음수의 개수를 반환합니다.

이 방식은 매번 음수를 만날 때마다 그 아래 모든 값을 계산하기 때문에 더 효율적이며, 시간 복잡도는 최악의 경우 O(m+n)입니다.

 

오늘의 회고

 

  • 부족함을 느끼지만 실력을 늘리는것이 제일 빠른길임을 명심하자
  • 새로운 개념을 배울수 있어서 좋았다.

 

오늘의 공부 키워드

  • 이진 탐색
  • 35.Search Insert Position

 

이진 탐색의 개념

이진 탐색은 다음과 같은 단계를 거칩니다:

  1. 초기 설정: 배열의 처음과 끝을 가리키는 두 개의 포인터(left와 right)를 설정합니다.
  2. 중간 요소 찾기: left와 right의 중간 지점을 계산하여 mid로 설정합니다.
  3. 비교:
    • nums[mid]가 타겟 값과 같으면, mid를 반환합니다.
    • nums[mid]가 타겟 값보다 작으면, 타겟 값은 mid의 오른쪽에 있으므로 left를 mid + 1로 이동시킵니다.
    • nums[mid]가 타겟 값보다 크면, 타겟 값은 mid의 왼쪽에 있으므로 right를 mid - 1로 이동시킵니다.
  4. 반복: left가 right보다 작거나 같을 때까지 2-3 단계를 반복합니다.
  5. 결과: 탐색 범위가 없으면 타겟 값이 배열에 없음을 의미하며, 이때 삽입 위치를 반환할 수 있습니다.

예시

정렬된 배열 nums = [1, 3, 5, 7, 9]와 타겟 값 target = 7을 찾는 예시를 통해 설명하겠습니다:

  1. 초기 설정: left = 0, right = 4 (배열의 인덱스 범위)
  2. 중간 요소 찾기: mid = (0 + 4) // 2 = 2
    • nums[2] = 5
  3. 비교: 5 (nums[mid]) < 7 (target)
    • left = mid + 1 = 3
  4. 반복:
    • 새로운 중간 요소 찾기: mid = (3 + 4) // 2 = 3
    • nums[3] = 7
  5. 비교: 7 (nums[mid]) == 7 (target)
    • 타겟 값을 찾았으므로 mid = 3 반환

이 과정에서 타겟 값을 효율적으로 찾을 수 있습니다.

 

35. Search Insert Position

이 문제는 주어진 정렬된 배열에서 특정 값을 찾는 문제입니다. 만약 해당 값을 찾으면 그 인덱스를 반환하고, 찾지 못하면 그 값을 정렬된 순서에 맞게 삽입할 때의 인덱스를 반환하는 문제입니다. 이 문제는 이진 탐색 알고리즘을 사용하면 O(log n)의 시간 복잡도로 해결할 수 있습니다.

이진 탐색은 배열의 중간 요소와 타겟 값을 비교하고, 비교 결과에 따라 탐색 범위를 절반으로 줄여가는 방식으로 동작합니다. 정렬된 배열에서 빠르게 값을 찾거나 삽입할 위치를 찾기에 적합합니다.

 

예시

예를 들어보겠습니다:

  1. 입력: nums = [1, 3, 5, 6], target = 5 출력: 2 (타겟 값 5는 배열의 2번 인덱스에 존재합니다.)
  2. 입력: nums = [1, 3, 5, 6], target = 2 출력: 1 (타겟 값 2는 배열에 없으며, 2가 들어가면 [1, 2, 3, 5, 6]가 되므로 인덱스 1에 삽입됩니다.)
  3. 입력: nums = [1, 3, 5, 6], target = 7 출력: 4 (타겟 값 7은 배열에 없으며, 7이 들어가면 [1, 3, 5, 6, 7]가 되므로 인덱스 4에 삽입됩니다.)

코드

def searchInsert(nums, target):
    left, right = 0, len(nums) - 1

    while left <= right:
        mid = (left + right) // 2
        if nums[mid] == target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1

    return left

 

이 코드를 한 단계씩 설명하면 다음과 같습니다:

  1. left와 right 포인터를 배열의 시작과 끝으로 설정합니다.
  2. 배열이 유효한 범위 (left가 right보다 작거나 같을 때)에서 반복합니다.
  3. 중간 인덱스 mid를 계산합니다.
  4. 중간 값 nums[mid]가 타겟 값과 같으면 mid를 반환합니다.
  5. nums[mid]가 타겟 값보다 작으면 타겟은 오른쪽에 있으므로 left를 mid + 1로 이동합니다.
  6. nums[mid]가 타겟 값보다 크면 타겟은 왼쪽에 있으므로 right를 mid - 1로 이동합니다.
  7. 반복이 끝난 후에도 값을 찾지 못하면, 타겟 값을 삽입해야 하는 위치는 left가 됩니다.

이진 탐색을 사용하여 이 문제를 해결하면 O(log n)의 시간 복잡도로 효율적으로 해결할 수 있습니다.

 

오늘의 회고

 

  • 부족함을 느끼지만 실력을 늘리는것이 제일 빠른길임을 명심하자
  • 새로운 개념을 배울수 있어서 좋았다.

 

오늘의 공부 키워드

  • 1025 Divisor Game

1025 Divisor Game

디바이저 게임에서는 앨리스와 밥이 차례로 게임을 합니다. 게임 규칙은 다음과 같습니다:

  1. 게임은 숫자 nn으로 시작합니다. 앨리스가 먼저 시작합니다.
  2. 각 턴마다 플레이어는 0<x<n0 < x < n이면서 n%x==0n \% x == 0인 숫자 xx를 선택해야 합니다. (즉, xxnn의 약수여야 합니다.)
  3. 선택한 숫자 xxnn에서 빼고, 새로운 nn값으로 바꿉니다.
  4. 더 이상 움직일 수 없는 플레이어가 게임에서 집니다.

목표

게임이 시작할 때 n이 주어졌을 때, 앨리스가 이길 수 있는지 여부를 판단해야 합니다. (두 플레이어 모두 최적으로 게임을 한다고 가정합니다.)

핵심 통찰

게임의 결과는 숫자 n이 짝수인지 홀수인지에 따라 달라집니다:

  • n짝수일 때, 앨리스는 항상 밥에게 홀수를 남기도록 만들 수 있습니다. 모든 짝수는 최소 하나의 약수(1)를 가지기 때문에, 앨리스는 항상 움직일 수 있습니다.
  • n홀수일 때, 앨리스가 어떤 움직임을 하든 밥에게 짝수를 남기게 됩니다. 이 경우 밥이 유리하게 됩니다.

따라서, nn이 짝수로 시작하면 앨리스가 이기고, 홀수로 시작하면 밥이 이깁니다.

예시

  • 예시 1:
    • 입력: n=2
    • 앨리스는 1을 선택합니다 (2의 약수).
    • nn2−1=1 이 됩니다.
    • 밥은 더 이상 움직일 수 없기 때문에 앨리스가 이깁니다.
    • 출력: true
  • 예시 2:
    • 입력: n=3
    • 앨리스는 1을 선택합니다 (3의 약수).
    • nn3−1 = 2 가 됩니다.
    • 밥은 1을 선택합니다 (2의 약수).
    • nn2−1=1 이 됩니다.
    • 앨리스는 더 이상 움직일 수 없기 때문에 밥이 이깁니다.
    • 출력: false

해결 방법

앨리스가 이길 수 있는지 확인하려면 n 이 짝수인지 홀수인지 확인하면 됩니다.

 

코드

def divisor_game(n: int) -> bool:
    return n % 2 == 0

# 예시로 함수 테스트
print(divisor_game(2))  # 출력: True
print(divisor_game(3))  # 출력: False

 

설명

  1. 함수 정의: divisor_game이라는 함수를 정의하고, 정수 n을 입력으로 받습니다.
  2. 짝수/홀수 확인: 나머지 연산자 %를 사용하여 nn이 짝수인지 확인합니다. 만약 n%2==0이면 n은 짝수이고, 함수는 true를 반환합니다. 그렇지 않으면 false를 반환합니다.

이 해결 방법은 주어진 범위 1≤n≤1000내의 어떤 n에 대해서도 효율적으로 작동합니다.

 

 

 

오늘의 회고

 

  • 부족함을 느끼지만 실력을 늘리는것이 제일 빠른길임을 명심하자
  • 재미있는 문제여서 풀기 좋았다.

F(0) = 0
F(1) = 1
F(2) = F(1) + F(0) = 1 + 0 = 1
F(3) = F(2) + F(1) = 1 + 1 = 2
F(4) = F(3) + F(2) = 2 + 1 = 3
F(5) = F(4) + F(3) = 3 + 2 = 5
...

 

오늘의 공부 키워드

  • 피보나치수열
  • 509. Fibonaci Number

 

피보나치 수열

피보나치 수열은 수학에서 매우 유명한 수열 중 하나로, 각 숫자가 그 앞의 두 숫자의 합으로 이루어진 수열입니다. 이 수열은 이탈리아의 수학자 피보나치(Leonardo of Pisa, 피사르 피보나치)에 의해 서양에 소개되었습니다. 피보나치 수열은 다음과 같은 규칙을 따릅니다

 

  • 첫 번째 숫자 (F(0))는 0입니다.
  • 두 번째 숫자 (F(1))는 1입니다.
  • 세 번째 숫자부터는 이전 두 숫자의 합입니다. 즉, F(n) = F(n-1) + F(n-2) (n > 1인 경우).

피보나치 수열의 초기 값은 다음과 같습니다:

F(0) = 0
F(1) = 1
F(2) = F(1) + F(0) = 1 + 0 = 1
F(3) = F(2) + F(1) = 1 + 1 = 2
F(4) = F(3) + F(2) = 2 + 1 = 3
F(5) = F(4) + F(3) = 3 + 2 = 5
...

 

이 수열은 다음과 같이 시작됩니다: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

피보나치 수열의 특징

  1. 재귀적 정의: 피보나치 수열은 간단한 재귀적 정의를 가지고 있습니다.
  2. 기하학적 성질: 피보나치 수열은 황금비와 밀접한 관련이 있습니다. 두 연속된 피보나치 수의 비율은 황금비(약 1.618)에 점점 가까워집니다.
  3. 자연과 예술에서의 응용: 피보나치 수열은 자연현상(예: 나선형 조개껍질, 해바라기 씨앗의 배열)과 예술작품에서 종종 발견됩니다.

피보나치 수열 대표적인 방법

1. 재귀적 방법

가장 간단한 방법으로, 피보나치 수열의 정의에 따라 재귀적으로 계산하는 방법입니다.

class Solution:
    def fib(self, n: int) -> int:
        if n <= 1:
            return n
        return self.fib(n - 1) + self.fib(n - 2)

 

 

 

  • 장점: 구현이 매우 간단합니다.
  • 단점: 같은 계산을 반복하기 때문에 시간 복잡도가 O(2^n)으로 매우 비효율적입니다.

2. 메모이제이션 (재귀적 방법의 개선)

재귀적 방법의 단점을 보완하기 위해 메모이제이션을 사용하여 이미 계산된 값을 저장합니다.

class Solution:
    def __init__(self):
        self.memo = {}
    
    def fib(self, n: int) -> int:
        if n in self.memo:
            return self.memo[n]
        if n <= 1:
            return n
        self.memo[n] = self.fib(n - 1) + self.fib(n - 2)
        return self.memo[n]

 

 

 

  • 장점: 중복 계산을 피할 수 있어서 시간 복잡도가 O(n)으로 개선됩니다.
  • 단점: 추가적인 메모리 공간이 필요합니다.

3. 반복적 방법

반복문을 사용하여 피보나치 수를 계산하는 방법입니다

class Solution:
    def fib(self, n: int) -> int:
        if n == 0:
            return 0
        elif n == 1:
            return 1
        
        a, b = 0, 1
        for _ in range(2, n + 1):
            a, b = b, a + b
        return b

 

 

 

  • 장점: 시간 복잡도가 O(n)으로 효율적이며, 추가적인 메모리 공간이 거의 필요하지 않습니다.
  • 단점: 코드가 재귀적 방법보다 조금 복잡합니다.

4. 동적 프로그래밍 (탑다운 접근법)

DP 배열을 사용하여 각 피보나치 수를 저장하면서 계산하는 방법입니다.

class Solution:
    def fib(self, n: int) -> int:
        if n <= 1:
            return n
        
        dp = [0] * (n + 1)
        dp[1] = 1
        
        for i in range(2, n + 1):
            dp[i] = dp[i - 1] + dp[i - 2]
        
        return dp[n]

 

 

 

  • 장점: 중복 계산을 피할 수 있고, 코드가 직관적입니다.
  • 단점: O(n)의 추가 메모리 공간이 필요합니다.

5. 행렬 제곱 방법

피보나치 수열을 행렬을 사용해 계산하는 방법으로, 시간 복잡도가 O(log n)입니다.

class Solution:
    def fib(self, n: int) -> int:
        if n <= 1:
            return n
        
        def matrix_mult(A, B):
            return [
                [A[0][0] * B[0][0] + A[0][1] * B[1][0], A[0][0] * B[0][1] + A[0][1] * B[1][1]],
                [A[1][0] * B[0][0] + A[1][1] * B[1][0], A[1][0] * B[0][1] + A[1][1] * B[1][1]]
            ]
        
        def matrix_pow(M, power):
            result = [[1, 0], [0, 1]]
            base = M
            while power:
                if power % 2 == 1:
                    result = matrix_mult(result, base)
                base = matrix_mult(base, base)
                power //= 2
            return result
        
        F = [[1, 1], [1, 0]]
        result = matrix_pow(F, n - 1)
        return result[0][0]

 

 

 

  • 장점: 시간 복잡도가 O(log n)으로 매우 효율적입니다.
  • 단점: 코드가 복잡하며, 행렬 연산을 이해해야 합니다.

 

 

509 Fibonaci Number

 

피보나치 수열은 각 숫자가 이전 두 숫자의 합으로 구성된 수열입니다. 이 문제에서는 주어진 n에 대해 피보나치 수 F(n)을 계산하는 방법을 찾고자 합니다. 피보나치 수열은 다음과 같이 정의됩니다:

  • F(0) = 0
  • F(1) = 1
  • F(n) = F(n - 1) + F(n - 2) (n > 1인 경우)

이를 바탕으로 몇 가지 예제를 살펴보겠습니다:

예제 1:

  • 입력: n = 2
  • 출력: 1
  • 설명: F(2) = F(1) + F(0) = 1 + 0 = 1

예제 2:

  • 입력: n = 3
  • 출력: 2
  • 설명: F(3) = F(2) + F(1) = 1 + 1 = 2

예제 3:

  • 입력: n = 4
  • 출력: 3
  • 설명: F(4) = F(3) + F(2) = 2 + 1 = 3

문제 해결 방법

이 문제를 해결하기 위해 두 가지 방법을 고려할 수 있습니다: 재귀적 방법과 반복적 방법. 여기서는 반복적 방법을 사용하여 효율적으로 피보나치 수를 계산하겠습니다.

 

반복적 방법

반복적 방법은 시간 복잡도가 O(n)으로, 재귀적 방법보다 더 효율적입니다. 다음과 같은 단계를 따릅니다:

  1. F(0)과 F(1)을 초기화합니다.
  2. 반복문을 통해 F(2)부터 F(n)까지의 값을 계산합니다.
  3. 최종적으로 F(n)을 반환합니다.

코드

class Solution:
    def fib(self, n: int) -> int:
        if n == 0:
            return 0
        elif n == 1:
            return 1
        
        a, b = 0, 1
        for _ in range(2, n + 1):
            a, b = b, a + b
        return b

 

 

코드 설명:

  1. n이 0인 경우 0을 반환합니다.
  2. n이 1인 경우 1을 반환합니다.
  3. a와 b를 각각 0과 1로 초기화합니다.
  4. for 반복문을 통해 n까지 반복하면서 a와 b를 업데이트합니다. a는 이전 값, b는 현재 값이 됩니다.
  5. 반복이 끝난 후, b는 F(n)의 값이 됩니다.

이 코드를 사용하면 주어진 n에 대해 피보나치 수를 효율적으로 계산할 수 있습니다.

 

오늘의 회고

 

  • 새로운 개념인 피보나치 수열을 배울수 있었다.
  • 좀더 끈질기게 고민하고 문제를 풀어야 하는데 쉽게 포기하는게 아닌가 하고 반성하게 된다.
  • 코딩테스트 문제만 풀지말고 기록도 남겨야 함을 느낀다.
  • 공부할것은 더욱 많음을 느낀다.
  • 문제를 지금 거의 풀수는 없지만 복습하고 복습하고 복습하기 위해 기록을 남긴다.
  • 부족함을 느끼지만 실력을 늘리는것이 제일 빠른길임을 명심하자

+ Recent posts