반응형

[LeetCode] Rotate Array - TypeScript 문제 풀이 및 최적화

문제 설명

LeetCode의 "Rotate Array" 문제는 배열을 오른쪽으로 k번 회전하는 문제입니다.

배열을 직접 수정해야 하며, 추가적인 공간을 사용하지 않고 O(1) 공간 복잡도로 해결해야 합니다.


처음 문제를 풀었을 때의 생각

처음에는 단순히 배열을 k번 반복해서 이동시키면 되겠다고 생각했어요. 하지만 직접 구현해 보니 비효율적이고 너무 오래 걸린다는 걸 깨달았어요.

이전에는 문제를 풀면서 개선점을 찾아 나가는 방식이었는데, 이번에는 문제의 패턴을 분석한 후 최적화된 방법을 한 번에 떠올렸습니다.

이 문제를 해결하는 과정에서 배열을 회전하는 문제에서 자주 사용되는 "배열 뒤집기" 패턴을 발견할 수 있었습니다.


비효율적인 접근 방식 (배열을 직접 이동시키기)

1️⃣ k번 반복하여 한 칸씩 이동 (O(N * k))

가장 직관적인 해결책은 k번 반복하면서 배열을 오른쪽으로 한 칸씩 이동하는 것입니다.

export function rotate(nums: number[], k: number): void {
  k = k % nums.length; // k가 배열 길이보다 클 경우 처리
  for (let i = 0; i < k; i++) {
    nums.unshift(nums.pop()!);
  }
}

이 방식은 작동은 하지만, 시간 복잡도가 O(N * k)로 매우 비효율적이에요. 배열 길이가 10^5이고 k도 10^5이면, 10^10번 연산해야 하므로 비현실적으로 오래 걸립니다.


최적화 과정: 배열을 한 번에 회전하는 방법 찾기

2️⃣ 추가 배열을 사용 (O(N), O(N))

배열을 복사한 후 (i + k) % n 위치에 배치하면 훨씬 빠르게 해결할 수 있어요.

export function rotate(nums: number[], k: number): void {
  const n = nums.length;
  const rotated = new Array(n);
  
  for (let i = 0; i < n; i++) {
    rotated[(i + k) % n] = nums[i];
  }

  for (let i = 0; i < n; i++) {
    nums[i] = rotated[i];
  }
}

시간 복잡도 O(N), 공간 복잡도 O(N)
추가 배열 사용 → 공간 최적화 필요


3️⃣ 배열 뒤집기를 이용한 최적화 (O(N), O(1))

배열을 직접 이동하지 않고, 배열을 뒤집는 방식으로 해결할 수 있을까? 🤔

핵심 패턴 발견

배열을 k번 회전하는 건 두 개의 블록을 서로 교체하는 것과 같다!

nums = [1,2,3,4,5,6,7], k = 3
결과:    [5,6,7,1,2,3,4]

💡 배열을 3개의 부분으로 나눠서 뒤집으면 해결 가능!

  1. 배열 전체를 뒤집는다 → [7,6,5,4,3,2,1]
  2. 처음 k개의 원소를 다시 뒤집는다 → [5,6,7,4,3,2,1]
  3. 나머지 n-k개의 원소를 다시 뒤집는다 → [5,6,7,1,2,3,4]

이제 정답이 나왔다!


최적화된 TypeScript 풀이 (O(N), O(1))

export function rotate(nums: number[], k: number): void {
  k = k % nums.length; // 배열 길이를 초과하는 k 처리

  function reverse(start: number, end: number) {
    while (start < end) {
      [nums[start], nums[end]] = [nums[end], nums[start]];
      start++;
      end--;
    }
  }

  reverse(0, nums.length - 1);  // 배열 전체 뒤집기
  reverse(0, k - 1);            // 처음 k개 뒤집기
  reverse(k, nums.length - 1);  // 나머지 뒤집기
}

시간 복잡도 O(N), 공간 복잡도 O(1)
추가 배열 없이 제자리에서 해결 가능
배열 뒤집기 패턴을 활용한 최적화된 방식


Jest 테스트 코드

describe("Medium 189: Rotate Array", () => {
  it("rotates an array of length 7 by 3 positions", () => {
    const nums = [1, 2, 3, 4, 5, 6, 7];
    rotate(nums, 3);
    expect(nums).toEqual([5, 6, 7, 1, 2, 3, 4]);
  });

  it("rotates an array of length 4 by 2 positions", () => {
    const nums = [-1, -100, 3, 99];
    rotate(nums, 2);
    expect(nums).toEqual([3, 99, -1, -100]);
  });
});

✅ it 설명을 배열 크기 + 회전 횟수 기준으로 작성하여 가독성 향상
✅ 추가적인 엣지 케이스도 테스트 가능


결론

이전에는 문제를 해결한 후 개선점을 찾아가며 최적화하는 방식이었어요. 하지만 이번에는 배열 회전의 패턴을 분석한 후 최적화된 방법을 한 번에 떠올릴 수 있었습니다. 🎯

배열 뒤집기 패턴은 회전 관련 문제에서 자주 사용되는 패턴이므로 기억해 두면 좋습니다! 🚀

 

반응형
반응형

[LeetCode] Remove Duplicates from Sorted Array - TypeScript 문제 풀이 및 최적화

문제 설명

LeetCode의 "Remove Duplicates from Sorted Array" 문제는 정렬된 배열에서 중복된 요소를 제거하고, 남은 요소의 개수를 반환하는 문제입니다.

배열을 직접 수정해야 하며, 추가적인 공간을 사용하지 않고 O(1) 공간 복잡도로 해결해야 합니다. 반환값으로는 중복 제거 후 남은 요소의 개수를 제공합니다.


처음 문제를 풀었을 때의 생각

이전에는 코드를 작성한 후 개선점을 찾아가면서 최적화하는 방식으로 문제를 해결했어요. 그런데 이번에는 문제를 분석한 후 바로 최적화된 코드가 한 번에 떠올랐습니다. 이전보다 문제 해결 능력이 향상된 것을 느낄 수 있었어요. 🚀

이 문제는 배열이 정렬되어 있다는 점을 활용하면 효율적으로 해결할 수 있습니다. 중복된 값이 나오면 무시하고, 중복이 아닌 값만 순차적으로 채워 넣는 방식을 사용하면 됩니다.


최적화된 TypeScript 풀이

export function removeDuplicates(nums: number[]): number {
  let current = 0;
  for (const n of nums) {
    if (nums[current] === n) continue;
    nums[++current] = n;
  }
  return current + 1;
}

코드의 핵심 개념

배열이 정렬되어 있으므로 중복이 연속적으로 등장한다는 점 활용
새로운 중복되지 않은 값이 나오면 current를 증가시키고 해당 위치에 저장
최종적으로 current + 1을 반환하여 남은 요소 개수를 리턴


Jest 테스트 코드

코드의 정확성을 검증하기 위해 Jest 테스트 코드를 작성했습니다.

describe("Easy 26: Remove Duplicates from Sorted Array", () => {
  it("removes duplicates from a short sorted array", () => {
    const input = [1, 1, 2];
    const ret = removeDuplicates(input);
    expect(ret).toEqual(2);
    expect(input.slice(0, ret)).toEqual([1, 2]);
  });

  it("removes duplicates from a longer sorted array", () => {
    const input = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4];
    const ret = removeDuplicates(input);
    expect(ret).toEqual(5);
    expect(input.slice(0, ret)).toEqual([0, 1, 2, 3, 4]);
  });

  it("handles an already unique array", () => {
    const input = [1, 2, 3, 4, 5];
    const ret = removeDuplicates(input);
    expect(ret).toEqual(5);
    expect(input.slice(0, ret)).toEqual([1, 2, 3, 4, 5]);
  });

  it("handles an array with all duplicates", () => {
    const input = [2, 2, 2, 2, 2];
    const ret = removeDuplicates(input);
    expect(ret).toEqual(1);
    expect(input.slice(0, ret)).toEqual([2]);
  });
});

각 테스트의 목적을 명확히 설명 → it("...")에 의미 있는 설명 추가
다양한 엣지 케이스 추가 → 중복 없는 배열, 모든 값이 같은 배열까지 검증
expect(input.slice(0, ret))을 활용해 최종 배열 검증


결론

이전에는 코드를 여러 번 수정하면서 최적화를 해나갔는데, 이번에는 처음부터 가장 효율적인 코드가 한 번에 작성되었다는 점이 흥미로웠어요. 🎯

이 문제에서는 배열이 정렬되어 있다는 특징을 활용하는 것이 핵심이었습니다.

이제 TypeScript 알고리즘 문제 풀이를 더 효율적으로 작성하는 방법을 배웠습니다! 🚀

 

반응형
반응형

💻 맥북 모니터 받침대 솔직 리뷰! 목 건강까지 챙기는 거치대

책상에서 맥북을 그냥 쓰다 보니 목이 너무 아픈 거예요. 🥲
그래서 모니터 받침대를 찾아보다가 드디어 하나 샀어요!
기존에 회사에서는 회전이 안 되는 제품을 썼는데, 이번엔 회전까지 되는 제품으로 선택!
과연 만족스러울지? 직접 써본 솔직 후기 남겨볼게요! 📝


📌 맥북 받침대, 왜 필요할까?

맥북은 디자인은 예쁘지만 화면 높이가 낮아요.
그래서 오래 쓰면 목이랑 어깨가 뻐근해져요. 💀

✔️ 모니터 받침대가 있으면?
시선이 높아져서 거북목 예방
타이핑할 때 손목 부담 감소
책상 공간 활용도 UP

저는 특히 목이 너무 아파서 바로 구매했어요! 🤯


🛍️ 내가 선택한 맥북 모니터 받침대!

👉 제품 보러 가기

이번에 선택한 제품은 회전 기능까지 되는 맥북 거치대예요!
회사에서 썼던 건 회전이 안 돼서 불편했거든요.
이제 화면 공유할 때도 슉~ 돌려주면 끝! 👍

🔥 주요 특징

✔️ 360도 회전 가능
✔️ 높이 & 각도 조절 OK
✔️ 튼튼한 알루미늄 소재
✔️ 미끄럼 방지 패드로 안정감
✔️ 무게감이 있어 흔들림 없이 고정

 


🎯 사용 후기: 만족도 90%!

🏆 장점

목이 편해짐! 확실히 화면이 높아지니까 목이 안 아파요.
회전 기능 대박! 화면 공유할 때 너무 편해요.
고급스러운 디자인✨ 인테리어에도 잘 어울려요.
무게감이 있어서 안정적! 흔들리지 않고 단단히 고정돼요.

⚠️ 아쉬운 점

무게감이 있어서 한곳에서 쓰게 될 듯! 이동하면서 쓰기엔 조금 무거워요.
회전이 약간 뻑뻑함! 부드럽게 돌아가는 느낌은 아니고, 힘을 좀 줘야 해요.
❌ 처음에 조립할 때 약간 뻑뻑했어요.


🔚 결론: 맥북 유저라면 강추!

맥북 쓰면서 목 아픈 분들,
화면 공유할 때 회전이 필요했던 분들,
깔끔한 인테리어를 원하는 분들께 강추합니다! 💯

🔗 👉 제품 구매하러 가기

거북목 예방하고, 더 편하게 맥북 쓰자구요! 🖥️✨

반응형
반응형

[LeetCode] Remove Element - TypeScript 문제 풀이 및 최적화

문제 설명

LeetCode의 "Remove Element" 문제는 배열 nums에서 특정 값 val을 제거하고, 남은 요소의 개수를 반환하는 문제입니다.

배열을 직접 수정해야 하며, 순서는 바뀌어도 무관합니다. 반환값으로는 유효한 길이를 제공합니다.


문제를 처음 접했을 때 어려웠던 점

처음에는 단순히 val을 제거하는 문제라고 생각했는데, 실제로는 맨 끝 요소와 바꿔가면서 해결해야 하는 문제였습니다. 처음 문제를 풀 때는 배열에서 val을 그냥 지우려고 했지만, 삭제하는 방식으로는 올바르게 해결되지 않았습니다.

배열의 순서를 유지할 필요가 없기 때문에 맨 끝 요소와 교환하며 해결하는 방식을 이해하는 것이 핵심이었습니다.


최적화된 TypeScript 풀이

문제를 올바르게 해결하기 위해 두 개의 포인터를 사용하여 val을 배열의 끝으로 보내는 방식으로 구현했습니다.

export function removeElement(nums: number[], val: number): number {
  let right = nums.length - 1;
  let index = 0;

  while (index <= right) {
    if (nums[index] === val) {
      nums[index] = nums[right]; // `right`의 값을 `index`에 덮어쓰기
      right--; // right를 줄여 제거한 효과
    } else {
      index++; // val이 아닐 때만 index 증가
    }
  }

  return right + 1; // 유효한 길이 반환
}

코드의 핵심 개념

배열의 순서를 유지할 필요가 없으므로, 뒤쪽 요소와 교환 가능
앞에서부터 순회하며 val을 만나면 맨 끝 요소와 교환
반환값은 유효한 요소 개수 (right + 1)


Jest 테스트 코드

코드의 정확성을 검증하기 위해 Jest 테스트 코드를 작성했습니다.

describe("LeetCode 27: Remove Element", () => {
  it("removes all instances of 3", () => {
    const input = [3,2,2,3];
    const result = removeElement(input, 3);
    expect(result).toEqual(2);
    expect(input.slice(0, result).sort()).toEqual([2, 2]);
  });

  it("removes all instances of 2", () => {
    const input = [0,1,2,2,3,0,4,2];
    const result = removeElement(input, 2);
    expect(result).toEqual(5);
    expect(input.slice(0, result).sort()).toEqual([0, 0, 1, 3, 4]);
  });

  it("handles an array with no occurrences", () => {
    const input = [1, 3, 5, 7];
    const result = removeElement(input, 2);
    expect(result).toEqual(4);
    expect(input.slice(0, result)).toEqual([1, 3, 5, 7]);
  });

  it("handles an empty array", () => {
    const input: number[] = [];
    const result = removeElement(input, 1);
    expect(result).toEqual(0);
    expect(input).toEqual([]);
  });
});

다양한 테스트 케이스 추가 → 엣지 케이스(빈 배열, 없는 값 등)까지 검증
배열의 순서가 바뀌어도 정답이므로 sort()로 정렬하여 검증
result를 활용한 직관적인 검증 → input.slice(0, result)로 올바른 요소 확인


결론

처음에는 val을 배열에서 단순히 제거하는 문제라고 생각했지만, 실제로는 맨 끝 요소와 바꾸면서 해결하는 문제였습니다. 이 점을 이해하는 것이 가장 어려웠고, 이후에는 두 개의 포인터를 활용하여 깔끔하게 해결할 수 있었습니다.

이제 TypeScript 알고리즘 문제 풀이를 더 효율적으로 작성하는 방법을 배웠습니다! 🚀

반응형
반응형

[LeetCode] Remove Duplicates from Sorted Array II - TypeScript 문제 풀이 및 개선 과정

문제 설명

LeetCode의 "Remove Duplicates from Sorted Array II" 문제는 정렬된 배열에서 각 원소가 최대 2번까지만 포함되도록 중복을 제거하는 문제입니다.

배열을 직접 수정해야 하며, 중복을 제거한 후에도 배열 크기는 유지되어야 합니다. 반환값으로 유효한 배열 길이를 제공합니다.


처음 풀이

먼저, 문제를 해결하기 위해 다음과 같은 코드를 작성했습니다.

export function removeDuplicates(nums: number[]): number {
  let current = 1;
  for (let index = 2; index < nums.length; index++) {
    const val = nums[index];
    const prev1 = nums[current];
    const prev2 = nums[current - 1];

    if (prev1 !== val) {
      current++;
      nums[current] = val;
    } else if (prev2 !== val) {
      current++;
      nums[current] = val;
    }
  }
  return current + 1;
}

초기 코드 분석

prev1prev2를 비교하여 현재 숫자가 2번 이하로 등장하는지 검사
✅ 유효한 경우 current 포인터를 이동하며 중복을 제거
if-else 문이 불필요하게 많아 코드가 복잡함
current + 1을 반환하는 방식이 직관적이지 않음

위 코드도 정답이지만, 개선할 여지가 있어 보였습니다.


개선 과정

문제점을 정리해보니, 두 가지 부분을 최적화할 수 있었습니다.

  1. current = 1 대신 current = 2로 설정하면 처음 두 개의 원소는 자동으로 유지됩니다.
  2. prev1prev2를 비교하는 if-else 구조를 단순화하면 코드가 더 깔끔해집니다.

이를 반영하여 최적화된 코드를 작성했습니다.


최적화된 TypeScript 풀이

export function removeDuplicates(nums: number[]): number {
  let current = 2; // 처음 두 개의 원소는 무조건 유지
  for (let index = 2; index < nums.length; index++) {
    if (nums[index] !== nums[current - 2]) {
      nums[current] = nums[index];
      current++;
    }
  }
  return current;
}

개선된 코드의 장점

초기값 설정 개선: current = 2로 설정하여 처음 두 개의 원소를 자동으로 유지
불필요한 조건문 제거: prev2 !== val만 검사하면 되므로 코드가 단순해짐
가독성 향상: current 자체를 반환하여 코드가 직관적임
성능 최적화: 조건문이 줄어들어 실행 속도가 미세하게 향상됨


Jest 테스트 코드

코드의 정확성을 검증하기 위해 Jest 테스트 코드를 작성했습니다.

describe("Medium 80: Remove Duplicates from Sorted Array II", () => {
  it("removes duplicates allowing at most twice per element", () => {
    const input = [1, 1, 1, 2, 2, 3];
    const result = removeDuplicates(input);
    expect(result).toEqual(5);
    expect(input.slice(0, result)).toEqual([1, 1, 2, 2, 3]);
  });

  it("removes duplicates from a larger input", () => {
    const input = [0, 0, 1, 1, 1, 1, 2, 3, 3];
    const result = removeDuplicates(input);
    expect(result).toEqual(7);
    expect(input.slice(0, result)).toEqual([0, 0, 1, 1, 2, 3, 3]);
  });

  it("handles an already unique array", () => {
    const input = [1, 2, 3, 4, 5];
    const result = removeDuplicates(input);
    expect(result).toEqual(5);
    expect(input.slice(0, result)).toEqual([1, 2, 3, 4, 5]);
  });

  it("handles an array with all elements the same", () => {
    const input = [1, 1, 1, 1, 1, 1, 1];
    const result = removeDuplicates(input);
    expect(result).toEqual(2);
    expect(input.slice(0, result)).toEqual([1, 1]);
  });
});

명확한 테스트 설명 추가: it 블록에 구체적인 설명을 추가해 테스트 목적을 쉽게 이해할 수 있음.
다양한 입력 케이스 추가: 엣지 케이스(빈 배열, 모든 원소가 같은 경우 등)도 포함하여 코드가 안전하게 동작하는지 검증함.
result를 활용하여 직관적인 검증: input.slice(0, result) 방식을 적용해 최종 배열이 올바르게 변경되었는지 확인.


결론

처음에는 prev1, prev2를 따로 체크하며 중복을 제거하는 방식을 사용했지만, 이를 단순화하여 더 직관적이고 최적화된 코드로 개선했습니다.

이제 TypeScript 알고리즘 문제 풀이를 더 효율적으로 작성하는 방법을 배웠습니다! 🚀


SEO 키워드

✅ Remove Duplicates from Sorted Array II
✅ LeetCode TypeScript 문제 풀이
✅ TypeScript 중복 제거 알고리즘
✅ LeetCode Top Interview 150 풀이
✅ O(N) 최적화된 중복 제거 코드
✅ Jest 테스트 코드 적용

반응형
반응형

LeetCode 13번 | 로마 숫자를 정수로 변환하기 (TypeScript 풀이)

이번 글에서는 LeetCode 13번 문제 "Roman to Integer" 를 TypeScript로 해결하는 방법을 다룹니다.
이 문제는 코딩 테스트나 개발자 면접에서 자주 등장하는 문자열 처리 & 수학적 규칙 적용 유형입니다.


🔹 문제 설명 (Roman to Integer)

로마 숫자는 다음과 같은 기호로 표현됩니다.

  • I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000
  • IV = 4, IX = 9, XL = 40, XC = 90, CD = 400, CM = 900 처럼 작은 값이 앞에 오면 감산됩니다.

주어진 로마 숫자 문자열을 정수로 변환하는 함수를 구현해야 합니다.


🔹 풀이 전략

  1. 문자를 숫자로 변환하는 객체 (symbol 맵) 생성
  2. 문자열을 순회하면서 현재 값과 이전 값을 비교
    • 이전 값보다 크다면 감산(-2 * prev) 적용
    • 그렇지 않다면 그대로 더하기
  3. 최적화를 위해 symbol[c] 조회 최소화

🔹 TypeScript 풀이 코드

export function romanToInt(s: string): number {
  const symbol: Record<string, number> = {
    I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000,
  };

  let answer = 0;
  let prev = 0;

  for (const c of s) {
    const value = symbol[c];

    if (prev < value) {
      answer -= prev * 2;
    }
    answer += value;
    prev = value;
  }

  return answer;
}

시간 복잡도: O(n) (문자열 한 번 순회)
공간 복잡도: O(1) (고정된 크기의 symbol 맵 사용)

 


🔹 테스트 케이스

console.log(romanToInt("III")); // 3
console.log(romanToInt("IV")); // 4
console.log(romanToInt("IX")); // 9
console.log(romanToInt("LVIII")); // 58
console.log(romanToInt("MCMXCIV")); // 1994

 


🔹 결론 및 정리

이 문제는 문자열 순회와 조건 비교를 활용한 최적화가 핵심입니다.
처음에는 단순 덧셈을 고려할 수 있지만, 감산 규칙을 적용하기 위해 이전 값과 비교하는 방식이 필요합니다.
코딩 테스트나 면접에서 자주 등장하는 유형이므로, TypeScript로 직접 구현하면서 연습해보는 것이 중요합니다.

📌 이해가 안 되는 부분이나 더 좋은 풀이가 있다면 댓글로 공유해주세요! 😊

 

 

#LeetCode #코딩테스트 #알고리즘 #타입스크립트 #로마숫자 #RomanToInteger #개발자 #프로그래밍 #면접문제

반응형
반응형

LeetCode Merge Sorted Array 문제 풀이: 초기 코드와 공간 복잡도 개선

이번 포스트에서는 LeetCode의 Merge Sorted Array 문제를 해결한 과정을 소개합니다. 처음에는 제가 작성한 초기 코드 방식으로 문제를 해결한 후, 추가 공간 사용 문제(공간 복잡도)를 개선하여 in-place 방식으로 최적화한 과정을 다룹니다.

문제 개요

Merge Sorted Array 문제는 두 개의 정렬된 배열을 하나의 정렬된 배열로 병합하는 문제입니다.

  • 입력:
    • nums1: 크기가 m+n인 배열, 처음 m개의 요소는 유효한 값이며 나머지 n개는 추가 공간입니다.
    • nums2: n개의 요소로 구성된 배열.
  • 목표:
    • nums1nums2의 요소들을 in-place로 병합하여 오름차순으로 정렬된 배열을 만드는 것입니다.

1. 초기 코드 접근 방식

먼저, 제가 작성한 초기 코드는 nums1의 유효한 부분을 새로운 배열(nums3)에 복사한 후, nums3nums2를 비교하며 작은 값부터 nums1에 채워 넣는 방식입니다.

초기 코드 예시

export function merge(
  nums1: number[],
  m: number,
  nums2: number[],
  n: number,
): void {
  const nums3 = nums1.slice(0, m);
  let i2 = 0;
  let i3 = 0;

  for (let index = 0; index < m + n; index++) {
    if (i2 >= n) {
      nums1[index] = nums3[i3++];
      continue;
    }

    if (i3 >= m) {
      nums1[index] = nums2[i2++];
      continue;
    }

    nums1[index] = nums2[i2] < nums3[i3] ? nums2[i2++] : nums3[i3++];
  }
}

이 방법은 시간 복잡도 O(m+n)로 효율적이나, nums1의 첫 m개 요소를 복사하기 위해 O(m)의 추가 공간을 사용합니다.

 

2. 공간 복잡도 개선: in-place 방식

문제의 in-place 조건을 완벽하게 만족하기 위해, 추가 공간 없이 nums1의 뒷부분부터 채워 나가는 방식으로 코드를 개선했습니다.
이 방식은 두 배열의 마지막 인덱스부터 비교하며, 큰 값을 nums1의 끝에 배치하는 방법으로 동작합니다. 이를 통해 공간 복잡도를 O(1)로 줄일 수 있습니다.

개선된 코드 예시

export function merge(
  nums1: number[],
  m: number,
  nums2: number[],
  n: number,
): void {
  let p1 = m - 1;         // nums1의 유효 부분 마지막 인덱스
  let p2 = n - 1;         // nums2의 마지막 인덱스
  let p = m + n - 1;      // nums1의 전체 마지막 인덱스

  while (p2 >= 0) {
    if (p1 >= 0 && nums1[p1] > nums2[p2]) {
      nums1[p] = nums1[p1];
      p1--;
    } else {
      nums1[p] = nums2[p2];
      p2--;
    }
    p--;
  }
}

이 개선된 방식의 주요 장점은 다음과 같습니다:

  • 공간 절약: 추가 배열 없이 in-place로 작업합니다.
  • 효율성: 두 배열의 마지막 요소부터 비교하여 한 번의 순회로 병합할 수 있습니다.

결론

초기 코드에서는 추가 공간을 사용해 문제를 해결했지만, in-place 방식으로 전환함으로써 공간 복잡도 문제를 개선할 수 있었습니다. 이번 문제 풀이를 통해 알고리즘 최적화효율적인 코딩의 중요성을 다시 한번 확인할 수 있었습니다.

이 글이 코딩 테스트 준비와 알고리즘 문제 해결에 도움이 되길 바랍니다!

반응형
반응형

🔍 LeetCode 28번 - Find the Index of the First Occurrence in a String (TypeScript)

LeetCode 28번 문제문자열 검색 알고리즘을 활용하여 특정 문자열(needle)이 다른 문자열(haystack) 내에서 처음 등장하는 위치를 찾는 문제입니다.
이 글에서는 여러 가지 알고리즘을 사용하여 TypeScript로 해결하는 방법을 설명합니다.


1️⃣ Brute Force (기본 구현) - O(N*M)

가장 단순한 방법은 Brute Force (완전 탐색) 방식으로, haystack을 순차적으로 검사하며 needle과 일치하는 부분을 찾습니다.

export function strStr(haystack: string, needle: string): number {
  for (let index = 0; index < haystack.length - needle.length + 1; index++) {
    if (haystack.slice(index, index + needle.length) === needle) {
      return index;
    }
  }
  return -1;
}

✅ 특징

  • O(N*M)의 시간 복잡도를 가짐 (N: haystack 길이, M: needle 길이)
  • 작은 입력에서는 충분히 빠름
  • 하지만 긴 문자열에서는 비효율적

2️⃣ KMP 알고리즘 (Knuth-Morris-Pratt) - O(N+M)

KMP 알고리즘LPS (Longest Prefix Suffix) 배열을 사용하여 중복된 비교를 방지하여 성능을 향상시킵니다.

export function strStrKMP(haystack: string, needle: string): number {
  if (needle === "") return 0;

  const lps = new Array(needle.length).fill(0);
  let j = 0;

  // LPS 배열 생성
  for (let i = 1; i < needle.length; i++) {
    while (j > 0 && needle[i] !== needle[j]) {
      j = lps[j - 1];
    }
    if (needle[i] === needle[j]) {
      j++;
      lps[i] = j;
    }
  }

  // 문자열 검색
  j = 0;
  for (let i = 0; i < haystack.length; i++) {
    while (j > 0 && haystack[i] !== needle[j]) {
      j = lps[j - 1];
    }
    if (haystack[i] === needle[j]) {
      j++;
      if (j === needle.length) {
        return i - j + 1;
      }
    }
  }

  return -1;
}

장점: O(N+M)의 시간 복잡도로 빠름
단점: LPS 배열을 먼저 만들어야 해서 코드가 조금 길어짐


3️⃣ Rabin-Karp 알고리즘 (해싱 기반) - O(N) (평균)

Rabin-Karp 알고리즘해싱을 활용하여 needlehaystack의 해시값을 비교하는 방식입니다.

export function strStrRK(haystack: string, needle: string): number {
  const base = 31;
  const mod = 1_000_000_007;
  const m = needle.length;
  const n = haystack.length;

  if (m > n) return -1;

  // 해시 값 계산
  let hashNeedle = 0;
  let hashHaystack = 0;
  let power = 1;

  for (let i = 0; i < m; i++) {
    hashNeedle = (hashNeedle * base + needle.charCodeAt(i)) % mod;
    hashHaystack = (hashHaystack * base + haystack.charCodeAt(i)) % mod;
    if (i > 0) power = (power * base) % mod;
  }

  // 롤링 해시 적용
  for (let i = 0; i <= n - m; i++) {
    if (hashHaystack === hashNeedle) {
      if (haystack.substring(i, i + m) === needle) return i;
    }
    if (i < n - m) {
      hashHaystack =
        (hashHaystack - haystack.charCodeAt(i) * power) % mod;
      hashHaystack = (hashHaystack * base + haystack.charCodeAt(i + m)) % mod;
      if (hashHaystack < 0) hashHaystack += mod;
    }
  }

  return -1;
}

장점: 평균 O(N), 문자열이 매우 길어도 빠르게 탐색
단점: 해시 충돌 가능성 (O(NM)의 최악 케이스)


4️⃣ Boyer-Moore 알고리즘 (문자열 검색 최적화)

Boyer-Moore 알고리즘은 뒤에서부터 탐색하여 불필요한 비교를 줄이는 방식입니다.
TypeScript에서는 indexOf를 활용하면 내부적으로 최적화된 검색이 이루어집니다.


🎯 결론 및 비교

알고리즘 시간 복잡도 장점 단점
Brute Force O(N*M) 간단한 코드 긴 문자열에서 비효율적
KMP O(N+M) 중복 비교 없음 LPS 배열 생성 필요
Rabin-Karp O(N) (평균) 빠른 해싱 해시 충돌 가능
Boyer-Moore O(N/M) (최적) 가장 빠를 수 있음 복잡한 구현
  • 간단한 해결법: Brute Force
  • 최적화 필요: KMP 또는 Rabin-Karp
  • 고급 최적화: Boyer-Moore

📝 마무리

이 글에서는 LeetCode 28번 문제를 해결하는 다양한 방법을 소개했습니다.
코드를 직접 실행하면서 어떤 방식이 가장 효과적인지 테스트해보세요! 🚀

질문이나 의견은 댓글로 남겨주세요! 😊

반응형

+ Recent posts