https://leetcode.com/problems/rotate-array/description

 

정수 배열 nums를 k번 오른쪽으로 회전시키는 문제이다.

  • 입력
    • int[]int [] nums
    • int k
  • 출력
    • void → nums 자체를 변경해야 한다.

문제 조건에서 nums와 k의 최대 값이 10^5로 상당히 크다

임시 배열을 만들어서 해결

int[] nums와 같은 크기의 임시 배열(int [] tmp)을 만들고 거기에 회전한 배열을 저장한다.

그 후 tmp의 값들로 nums를 덮어 씌우면 끝난다.

k만큼 오른쪽으로 회전한다는 것은 시작점이 이동되는 것과 마찬가지이다.

class Solution {
    public void rotate(int[] nums, int k) {
        int[] tmp = new int[nums.length];
        int size = nums.length;
        int start = size - k % size;

        for (int i = 0; i < size; i++) {
            tmp[i] = nums[(start + i) % size];
        }

        for (int i = 0; i < size; i++) {
            nums[i] = tmp[i];
        }
    }
}

오른쪽으로 회전하고 있기 때문에 시작점 계산을 size - k % size로 처리한다.

이 시작점의 값이 새로운 tmp 배열의 첫 위치에 들어갈 값이 된다.

이제 배열의 크기만큼 순회하면서 채워 넣어주면 된다. 나머지 연산을 통해서 범위를 벗어나지 않도록 처리해 줬다.

하지만 이 풀이에는 문제가 있다. 별도의 추가 공간을 사용하고 있다는 점이다.

결국 nums 배열 자체를 수정해야 하는데 추가 공간을 사용하지 않고 수정할 수는 없을까?

배열의 반전

배열의 회전이란 것은 기준점을 두고 두 부분으로 나뉘고 그 두 부분의 위치가 바뀌는 것이다.

그런데 이 두 부분의 위치를 직접 바꾸면 시간복잡도가 증가하거나 추가 공간이 필요하게 된다.

이때 필요한 게 반전이다.

전체를 반전하게 되면 두 부분의 위치가 교환이 된다.

그러고 나서 각 부분을 다시 반전시키면 정상 순서로 위치만 바뀐 결과가 리턴된다.

즉 여기서 작성해야 할 코드는 특정 범위를 반전시키는 코드이다.

class Solution {
    public void rotate(int[] nums, int k) {
        int size = nums.length;
        k = k % size;

        reverse(nums, 0, size - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, size - 1);
    }

    private void reverse(int[] arr, int l, int r) {
        while (l < r) {
            // 교환
            int tmp = arr[l];
            arr[l] = arr[r];
            arr[r] = tmp;
            // 포인터 이동
            l++;
            r--;
        }
    }
}

 

반전은 해당 범위의 요소를 swap 해가며 반복하면 된다.

이를 통해서 추가 공간을 사용하지 않고 동시에 O(N)의 시간복잡도로 문제를 해결할 수 있었다.

https://school.programmers.co.kr/learn/courses/30/lessons/42861

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

모든 섬이 통행 가능하도록 다리를 놓을 때 최소 비용을 구하는 문제이다.

주어진 정보는 모든 섬의 개수 n, 두 섬의 번호와 비용이 있는 costs 배열이다.

최소 비용을 구하는 문제이기 때문에 먼저 떠오르는 것은 cost가 저렴한 순으로 꺼내서 다리를 설치하는 것이다.

이때 만약 불필요한 다리라면 건설하지 않아야 한다. 여기서 불필요하다는 뜻은 해당 다리가 없어도 방문이 가능한 경우를 뜻한다.

여기서 이 판단을 하는 방법으로 집합을 쓰겠다. 같은 집합에 속한다면 즉 루트가 같다면 해당 집합은 모두 방문이 가능한 상태가 된다.

알고리즘의 초안은 다음과 같다.

  1. 최소 비용의 다리를 꺼낸다. → PriorityQueue를 사용하면 간단해진다.
  2. 이 다리에 추가되는 섬이 이미 같은 집합에 존재하면 다리를 설치하지 않는다. → 설치되는 경우 두 섬을 Union 연산을 통해 같은 집합으로 만든다.
  3. pq가 비게 될 때까지 반복한다.

결국 Union-Find만 잘 구현하면 해결되는 문제다.

우선 Union-Find의 코드부터 보고 이해해 보자.

Union연산은 두 집합을 하나로 합치는 연산이고 Find는 특정 요소가 속하는 집합의 루트(대표)를 찾는 연산이다. 이 알고리즘은 주로 트리 구조를 사용하여 집합을 표현하게 된다.

public class UnionFind {
    private int[] parent; // 각 요소의 부모를 저장하는 배열
    private int[] rank;   // 트리의 높이를 저장하는 배열

    // 생성자: n개의 요소를 각각 독립된 집합으로 초기화
    public UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        for(int i = 0; i < n; i++) {
            parent[i] = i; // 초기에는 각 요소가 자기 자신을 부모로 가짐
            rank[i] = 1;   // 초기 트리의 높이는 1로 설정. 0으로 하는 경우도 있다.
        }
    }

    // Find 연산: x가 속한 집합의 대표(루트)를 찾음
    public int find(int x) {
        if(parent[x] != x) {
            parent[x] = find(parent[x]); // 경로 압축
        }
        return parent[x];
    }

    // Union 연산: x가 속한 집합과 y가 속한 집합을 합침
    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);

        if(rootX == rootY) {
            return; // 이미 같은 집합에 속해 있음
        }

        // 랭크를 비교하여 트리의 높이를 최소화
        if(rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else if(rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else {
            parent[rootY] = rootX;
            rank[rootX] += 1; // 트리의 높이가 증가
        }
    }

    // 두 요소가 같은 집합에 속해 있는지 확인
    public boolean connected(int x, int y) {
        return find(x) == find(y);
    }
}

이해하기 어려운 알고리즘은 아니다. 여기서 주목해야 할 부분이 두 가지 있다.

  1. 경로 압축 parent 배열은 자신의 부모 노드를 가지고 있다. parent [idx]가 idx의 부모를 뜻한다. 하지만 바로 자신의 부모를 가지고 있으면 탐색에 시간이 증가하게 된다. 이를 효율화하기 위해 자신의 부모가 아닌 집합의 루트를 가지고 있게 하는 방식이 경로 압축이다.
  2. Rank 개념 트리의 높이가 커질수록 연산이 비효율적이 됩니다. 따라서 Rank의 개념을 도입해 높이를 최소화한다. Union 연산 시, 두 집합의 트리 높이를 비교하여 작은 트리를 큰 트리 아래에 합친다. 높이가 같다면 어디에 합쳐도 상관없다.

이 알고리즘을 통해 문제를 다음과 같이 풀 수 있습니다.

import java.util.*;

class Solution {
    
    public int solution(int n, int[][] costs) {
        
        int[] parent = new int[n];
        int[] rank = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
        
        int answer = 0;
        
        PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[2]));
        for (int[] info : costs) {
            pq.add(info);
        }
        
        while (!pq.isEmpty()) {
            int[] info = pq.poll();
            int id1 = info[0];
            int id2 = info[1];
            int cost = info[2];
            
            // 이미 같은 집합에 속해있다면 다리를 놓는것은 낭비다.
            if (find(id1, parent) == find(id2, parent)) continue;
            
            union(id1, id2, parent, rank);
            
            answer += cost;
        }
        

        return answer;
    }
    
    private int find(int x, int[] parent) {
        if (x != parent[x]) {
            parent[x] = find(parent[x], parent);
        }
        return parent[x];
    }
    
    private void union(int x, int y, int[] parent, int[] rank) {
        int rootX = find(x, parent);
        int rootY = find(y, parent);
        
        if (rootX == rootY) return;
        
        if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else {
            parent[rootY] = rootX;
            rank[rootX]++;
        }
    }
}

'Algorithm > Programmers' 카테고리의 다른 글

Programmers 12978 - 배달  (0) 2024.09.29
Programmers 159993 - 미로 탈출  (0) 2024.09.29
Programmers 43162 - 네트워크  (0) 2024.09.29
Programmers 1844 - 게임 맵 최단거리  (2) 2024.09.29
Programmers 12981 - 영어 끝말잇기  (0) 2024.09.27

https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii

 

오름차순으로 정렬된 nums가 주어졌을 때 중복된 요소를 제거해야 한다.

단 각각의 중복된 요소들은 최대 2개까지 유지된다. 요소들의 상대적 위치는 유지돼야 한다.

배열의 길이를 수정하는 것이 불가능하기 때문에 제거되지 않은 요소들을 앞에 위치시키고 제거된 요소들은 뒤에 위치시켜야 한다.

최종적으로 nums를 수정한 다음 유효한 숫자들의 개수를 반환해야 한다.

추가 공간을 사용해서는 안된다.

 

https://leetcode.com/problems/remove-duplicates-from-sorted-array

이 문제와 유사한 문제이지만 공간 조건이 추가됐고 중복 요소가 2개까지 유효하다는 조건이 추가됐다.

문제를 분석하고 구상해 보자.

  1. 정렬이 된 상태이다. 즉 중복된 요소들은 인접해 있다.
  2. 추가 공간을 사용할 수 없으므로 int [] nums 배열 내에서 조작을 해야 한다.
  3. 중복 허용 횟수 조건이 생겼다. 최대 두 번까지 허용된다. → 관리가 필요하다.

어떤 방식들이 있을까

  1. 카운터 현재 숫자의 출현 횟수를 세는 카운터를 두고 중복 횟수를 관리한다. → 이동이나 삭제는 어떻게 처리하지. 이 과정에서 비효율적일 수 있겠다.
  2. 해시맵 혹은 해시 셋 추가 공간 사용이 불가능하다.
  3. 투 포인터 배열을 순회하며 중복되지 않은 요소들을 배열의 앞부분에 모은다. 중복 허용 횟수 관리가 필요하다 → 이전 요소와 비교

따라서 투 포인터 방식을 채택한다.

두 개의 포인터를 둔다. s는 조건을 만족하는 유효한 숫자를 저장할 위치, p는 배열을 순회하는 포인터이다.

class Solution {
    public int removeDuplicates(int[] nums) {
        int s = 0;
        
        for (int p = 0; p < nums.length; p++) {
            if (s < 2 || nums[p] != nums[s - 2]) {
                nums[s] = nums[p];
                s++;
            } 
        }

        return s;
    }
}

큰 틀은 이전 문제와 유사하다. 이제 중요한 것은 중복 허용 횟수를 관리하는 방법이다.

우선 s가 2보다 작은 경우 즉 첫 두 개의 경우 무조건 조건을 만족하는 요소가 된다.

그 이후의 요소들을 검사해야 한다.

현재 위치(p)에서 앞의 두 요소와 현재 요소를 비교한다.

이때 nums [p]가 nums [s-2]와 다르다면 중복 횟수가 허용 범위 내라는 소리이다. 왜냐하면 nums[s-2]와 nums [s-1]이 이미 동일하다면 현재 요소까지 포함하면 중복이 3번이 되기 때문이다.

https://leetcode.com/problems/remove-duplicates-from-sorted-array

 

중복되는 숫자를 제거하는 문제이다. int[] nums 배열 자체를 수정해야 한다.

nums는 오름차순으로 정렬 돼 있는 상태로 주어진다.

각 원소들의 상대적 위치는 유지 돼야 한다. 이때 nums 배열의 유니크한 원소의 개수(k)를 리턴하면 된다.

예시를 보자. int[] nums = [1, 1, 2] 일 경우 1 이 중복된다. 따라서 nums = [1, 2, _ ]로 변경되고 k는 2가 된다.

int[] nums = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4] → [0, 1, 2, 3, 4, _, _, _, _, _ ] 이렇게 되고 k는 5가 된다. 기존의 상대적인 순서를 유지한다는 특징이 있다.

구상을 해보자.

  1. 정렬이 돼 있다는 것은 중복이 존재하는 경우 연속으로 나온다는 뜻이다.
  2. 배열 자체를 수정하기 때문에 요소를 제거하는 것이 아니라 유효한 요소들을 앞으로 모으는 작업이 필요하다.
  3. 추가적인 자료구조는 사용할 수 없다. 주어진 int[] nums를 사용해야 한다.

가장 먼저 떠오르는 방법은 중복 요소가 발견되면 가장 뒤로 밀어내는 것이다. 하지만 이 방식은 O(N^2)의 시간복잡도로 매우 비효율적인 알고리즘이 된다.

배열을 순회하면서 교환이 필요하다. 이를 위해 두 개의 포인터를 사용하기로 했다. 즉 투포인터이다.

하나의 포인터는 중복되지 않은 요소의 위치를 가리키고 다른 하나는 배열을 순회하면서 중복을 검사한다.

정렬이 돼 있다 즉 인접한 요소만 보면 중복 검사가 가능하다.

class Solution {
    public int removeDuplicates(int[] nums) {
        int p1 = 0;
        
        for (int p2 = 1; p2 < nums.length; p2++) {
            if (nums[p1] != nums[p2]) {
                p1++;
                nums[p1] = nums[p2];
            }
        }

        return p1 + 1;
    }
}

p1은 유효한 숫자들이 자리 잡을 포인터이다.

p2는 이제 중복이 나오는 지를 확인하고 해당 값의 위치를 이동 시키기 위한 포인터이다.

이 알고리즘은 중복된 숫자가 나오면 마지막 중복 위치를 찾고 지금까지 유효한 위치 다음 위치에 해당 값을 대입하는 동작을 수행한다.

이를 통해 O(N)의 시간복잡도로 문제를 해결할 수 있었다.

코딩테스트 연습 - 영어 끝말잇기 | 프로그래머스 스쿨 (programmers.co.kr)

 

문제 조건

  1. 1번부터 번호 순서대로 한 사람씩 차례대로 단어를 말합니다.
  2. 마지막 사람이 단어를 말한 다음에는 다시 1번부터 시작
  3. 앞사람이 말한 단어의 마지막 문자로 시작하는 단어.
  4. 이미 등장한 단어는 사용 불가
  5. 한 글자는 안된다
  6. 탈락자가 생기지 않는다면 [0, 0]을 반환한다.

이때 가장 먼저 탈락하는 사람의 번호와 그 사람이 자신의 몇 번째 차례에 탈락하는가.

문제의 흐름대로 구현

문제 조건을 읽으며 정리한 단서들을 조합해 보자.

우선은 앞에서부터 순회하며 읽는다.

또한 순환하고 있기 때문에 % 연산을 사용한다.

문자의 비교를 위해 charAt을 사용해서 비교를 한다.

이미 등장했던 단어를 빠르게 검색하기 위해 Hash 기반의 Set을 사용하면 될 것 같다.

예외로 한 글자 조건이 있다.

자신의 턴을 관리해줘야 한다. 이는 Map을 통해 자신의 번호 : 턴으로 관리를 하면 빠르게 찾고 수정할 수 있다.

import java.util.*;

class Solution {
    public int[] solution(int n, String[] words) {
        Map<Integer, Integer> turn = new HashMap<>();
        Set<String> wordSet = new HashSet<>();
        for (int i = 1; i <= n; i++) {
            turn.put(i, 1);
        }
        
        // 첫 턴 예외
        turn.put(1, 2);
        wordSet.add(words[0]);
        int id = 1;
        boolean isEnd = true;
        
        for (int i = 1; i < words.length; i++) {
            id = i % n + 1;
            String prevWord = words[i - 1];
            String word = words[i];
            
            // 탈락 조건
            if (word.length() == 1 ||
                wordSet.contains(word) ||
                prevWord.charAt(prevWord.length() - 1) != word.charAt(0)) {
                isEnd = false;
                break;
            }
        
            turn.put(id, turn.get(id) + 1);
            wordSet.add(word);
        }
        
        if (isEnd) return new int[]{0, 0};
        
        int[] answer = {id, turn.get(id)};
        
        return answer;
    }
}

코드 개선

문제의 흐름을 따라서 구현하다 보면 O(N)의 시간복잡도로 풀이가 가능하다.

이 풀이에서 따져보자면 turn을 관리하고 있는 Map 같은 경우에는 굳이 필요하지 않다.

그리고 첫 턴을 처리하는 것이 코드의 명확성을 떨어뜨린다고 생각한다.

n을 통해서 현재 턴을 (i/n + 1)로 처리하면 공간복잡도를 개선할 수 있을 것 같다.

또한 리턴을 break 시점에 하는 것이 더 빠른 명확한 처리가 될 것 같다.

문제 조건에서 1글자 단어는 인정되지 않는다고 했는데 제한 사항을 보면 단어의 길이가 2 이상 50 이하라는 제한이 있다. 따라서 1글자 검증 과정을 불필요하다.

import java.util.*;

class Solution {
    public int[] solution(int n, String[] words) {
        Set<String> usedWords = new HashSet<>();
        usedWords.add(words[0]);
        
        for (int i = 1; i < words.length; i++) {
            if (usedWords.contains(words[i]) ||
                words[i].charAt(0) != words[i-1].charAt(words[i - 1].length() - 1)) {
                return new int[]{(i % n) + 1, (i / n) + 1};
            }
            usedWords.add(words[i]);
        }
        
        return new int[]{0, 0};
    }
}

개선사항을 통해 훨씬 읽기 좋은 코드가 됐다고 생각한다.

'Algorithm > Programmers' 카테고리의 다른 글

Programmers 12978 - 배달  (0) 2024.09.29
Programmers 159993 - 미로 탈출  (0) 2024.09.29
Programmers 43162 - 네트워크  (0) 2024.09.29
Programmers 1844 - 게임 맵 최단거리  (2) 2024.09.29
Programmers 42861 - 섬 연결하기  (0) 2024.09.28

https://leetcode.com/problems/remove-element

int[] nums 배열에서 int val과 같은 값을 제거하는 문제이다.

특이한 점은 removeElement의 리턴 타입이 int인데 정답 처리가 리턴 값만 맞으면 되는 것이 아니라는 점이다.

추가로 val과 같은 값이 제거된 nums 배열이 in-place 형태로 즉 추가 공간 사용 없이 내부에서 수정돼야 한다.

예를 들어보자.

nums = {0, 1, 2, 2, 3, 0, 4, 2} 이고 val = 2 일 때이다.

nums에서 2를 제거하게 되면 {0, 1, _, _, 3, 0, 4, _}가 되고 이게 최종적으로 {0, 1, 4, 0, 3, _, _, _} 이 돼야 한다. 그리고 리턴 값은 제거되지 않은 유효 숫자인 0, 1, 4, 0, 3의 개수인 5가 된다.

또한 nums의 정렬 순서는 중요하지 않다. 어차피 정답 판정 시에 유효 범위에서 정렬 작업이 수행된다. 위의 예시에서는 {0, 1, 3, 0, 4}가 나오던 {0, 1, 4, 0, 3}가 나오던 문제없다는 것이다.

대신 int[] nums를 조작하는 것이기 때문에 ‘_’라는 부분은 임의의 숫자가 들어가도 괜찮고 정렬이 0, k (k는 유효한 숫자의 개수)에서 동작해서 정답 판정이 들어가기 때문에 유효한 숫자들이 앞에서부터 자리를 차지하고 있어야 한다는 점을 주의해야 한다.

그냥 시키는 대로 풀기

그냥 시키는 대로 풀어보자. nums에서 val을 제거하고 정렬하면 된다.

우선 문제의 조건을 보니 nums의 원소는 최대값이 50이다. 제거되지 않은 요소들을 앞에 두기 위해 제거되는 요소들의 값을 51로 바꾼 다음 정렬하면 자연스럽게 제거되지 않은 요소들만 앞으로 가게 된다.

class Solution {
    static final int MAX = 51;
    public int removeElement(int[] nums, int val) {
        int k = nums.length; // 유효값 카운트
        for (int i = 0; i < nums.length; i++) { // 여기서 i < k를 쓰면 안된다. k값은 계속 변한다.
            if (nums[i] == val) {
                nums[i] = MAX;
                k--; // 전체 갯수에서 하나가 제거됐다.
            }
        }
        Arrays.sort(nums); // 제거되지 않은 값들이 자연스럽게 0~k 범위 내로 몰린다.
        return k;
    }
}

문제없이 정답이지만 이게 좋은 풀이일까? 문제의 설명을 보면 정답을 판정하는 과정에서 (0, k) 사이의 정렬이 한번 일어난다.

이 말은 nums가 정렬되지 않은 상태로 반환 돼도 괜찮다는 것이다.

그런데 위의 풀이에서는 쓸데없이 정렬 작업이 한 번 들어가고 있다. 결국 시간복잡도는 O(NlogN)이 된다.

심지어 nums[i] 를 MAX로 바꾸는 것은 편법에 가깝다는 생각이 든다.

앞에만 몰아 넣으면 된다.

생각해 보자. 유효한 숫자는 앞에 순서에 상관없이 몰아넣으면 된다.

유효하지 않은 숫자는 변경할 필요 없이 뒤로 몰아 넣으면 된다.

이 두 작업을 투 포인터를 이용해서 할 수 있다.

class Solution {
    static final int MAX = 51;
    public int removeElement(int[] nums, int val) {
        int left = 0;
        int right = nums.length - 1;

        while (left <= right) {
            if (nums[left] == val) {
                // val을 right 위치의 값으로 덮어 씌운다.
                nums[left] = nums[right];
                right--;
            } else {
                left++;
            }
        }

        return left;
    }
}

코드를 보면 두 개의 포인터가 이동하고 있다. 목표는 앞의 위치의 val을 제거하는 것이다. 따라서 nums [left]의 값이 val과 일치한다면 nums [right]의 값으로 덮어 씌운다.

여기서 nums[right]가 val과 동일하더라도 상관없다. left의 위치가 그대로 이므로 다음번 검사 때 감소된 right의 위치와 비교를 통해 새롭게 덮어 씌워질 것이다.

이 방법을 통해 시간복잡도를 O(N)으로 줄일 수 있었다.

https://leetcode.com/problems/merge-sorted-array

 

LeetCode 기준 Easy 난이도의 문제입니다. 두 배열을 정렬된 상태로 변환하는 문제인데 특이하게 입력받은 nums1 배열을 정렬하는 문제입니다. nums1을 반환하는 것이 아니라 nums1 자체를 정렬하면 된다는 점을 주의해야 합니다.

또 다른 특이한 점이 있습니다. 병합 후 정렬해야 하는 문제이기 때문에 배열의 특성상 사이즈에 여유가 있어야 합니다. 따라서 nums1에 nums2가 들어갈 공간만큼 0으로 차 있는 상태입니다.

문제를 보자마자 떠오른 풀이법은 그냥 nums1의 0을 앞에서부터 nums2로 채우고 정렬하면 되는 거 아닌가? 였습니다.

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        if (n == 0) return; // n == 0이라는 것은 병합할 nums2가 없다는 것

        int idx = 0; // nums2의 첫 위치
        for (int i = m; i < m + n; i++) {
            nums1[i] = nums2[idx++]; // nums1의 0을 앞에서부터 nums2로 채운다
        }

        Arrays.sort(nums1); //nums1 정렬
    }
}

정답 처리에는 문제가 없었습니다. 하지만 이 풀이는 시간복잡도가 O((m+n)log(m+n))으로 효율적이지도 않고 문제의 의도와도 다르다는 생각이 들었습니다.

문제에서 제대로 사용하지 않은 조건이 하나 있습니다. 바로 nums1, nums2 두 배열 모두 정렬이 돼 있는 상태라는 것입니다. 이 조건을 활용해야 합니다.

이미 정렬이 된 두 배열을 합치는 작업? merge sort에서 병합하는 과정과 유사하다는 생각이 들었습니다.

보통 병합하는 과정에서는 두 정렬된 배열에서 앞에서부터 비교하며 새로운 배열을 생성하는 과정을 반복합니다. 하지만 이 문제에서는 nums1에 합쳐야 합니다. 따라서 가장 큰 값부터 찾아 뒤에서부터 채우는 방식으로 구현하였습니다.

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        if (n == 0) return; // 병합할 것이 없다.
        int p1 = m - 1; // nums1의 유효숫자의 마지막 위치
        int p2 = n - 1; // nums2의 유효숫자의 마지막 위치
        int p = n + m - 1; // 빈 공간의 마지막 위치

        while (p2 >= 0) {
            // p2가 음수가 되는 순간 모든 배열을 병합한 것
            if (p1 >= 0 && nums1[p1] > nums2[p2]) {
                nums1[p--] = nums1[p1--];
            } else {
                // nums2[p2] >= nums1[p1]
                nums1[p--] = nums2[p2--];
            }
        }
    }
}

이 풀이를 통해 O(m+n)의 시간복잡도로 효율적으로 풀 수 있었습니다.

+ Recent posts