본문 바로가기
🧩 Algorithm

LeetCode : 148. Sort List

by iirin 2023. 9. 5.

문제 링크

문제 먼저 읽기

  • linkedlist의 head인 ListNode가 하나 주어집니다.
  • 이 리스트를 오름차순으로 정렬하고 head를 반환합니다.
  • 시간복잡도 O(nlogN), 공간복잡도 O(1)의 답변이 있는 모양입니다 🫠
    • Can you sort the linked list in O(n logn) time and O(1) memory (i.e. constant space)?

어떻게 풀까?

생각

  • 처음 문제를 읽고 좀 당황했습니다. linked list를 정렬을 다시 해본적이 없었기 때문입니다.
  • 가장 먼저 든 생각은 새로운 LinkedList를 만들어서 이전 리스트를 탐색함과 동시에 새로운 리스트에 값을 넣어주는 것입니다. 이 경우 시간복잡도는 O(n^2)이 될 것으로 예상됩니다. 새로운 리스트에 값을 넣을 때 매번 처음부터 다시 탐색해야 하기 때문입니다.
  • 두번째 생각한 것은 LinkedList의 중간 노드를 찾고 병합 정렬을 해보는 것입니다. 이 경우 시간복잡도는 O(nlogN) 으로 좀 더 개선할 수 있어보입니다.
  • 여기서 LinkedList의 중간노드를 찾는 로직을 처음으로 시도해보아야 했는데요.

수도코드

class Solution {
    public ListNode sortList(ListNode head) {
        // 더 분할할 수 없으면 멈추는 코드
        
        ListNode mid = null; // 중간 지점을 찾는다.
        ListNode slow = head;
        ListNode fast = head; // 같은 시작지점

        while (fast != null) {
          mid = slow; // 연결을 끊을지점
          slow = slow.next;
          fast = fast.next.next; // 2씩 증가
        }
        mid.next = null; // 연결 끊기
        
        ListNode head1 = sortList(head);  // 분할하여 정렬
        ListNode head2 = sortList(slow);
        return merge(head1, head2);
    }
    
    private ListNode merge(ListNode node1, ListNode node2) {
        ListNode temp = new ListNode(Intenger.MIN_VALUE);
        ListNode cur = temp;
        
        while (node1 != null && node2 != null) { // 둘 중 하나가 끝날 때까지
            if (node1.val < node2.val) {
                cur.next = node1;
                node1 = node1.next; // 다음 노트로 커서 옮기기
            } else {
                cur.next = node2;
                node2 = node2.next;
            }
            cur = cur.next;
        }
        
        // 남은 노드랑 연결해주는 코드
        
        return temp.next;
    }
}

결과

java.lang.NullPointerException: Cannot read field "next" because "<local4>.next" is null
  at line 26, Solution.sortList
  at line 54, __DriverSolution__.__helper__
  at line 84, __Driver__.main
  • 테스트는 통과했는데 null point exception이 제출 중에 발생하였습니다.
  • fast = fast.next.next; 와 if (head.next==null)코드가 발생 원인이었습니다. 조건문에 추가로 null exception 방지를 위한ㄴ 조건을 써주었습니다.
class Solution {
    public ListNode sortList(ListNode head) {
        // 더 분할할 수 없으면 멈추는 코드
        if (head==null || head.next==null) { // 체크 순서에 유의하여야 합니다.
            return head;
        }
        
        // 중간 지점을 찾는다.
        ListNode mid = null;
        ListNode slow = head;
        ListNode fast = head; // 같은 시작지점

        while (fast != null && fast.next != null) { // fast가 null이 아니어도 fast.next가 null 이면 fast.next.next가 불가능합니다.
          mid = slow; // 연결을 끊을지점
          slow = slow.next;
          fast = fast.next.next; // 2씩 증가
        }
        
        mid.next = null; // 연결 끊기
        
        // 분할하여 정렬
        ListNode head1 = sortList(head);
        ListNode head2 = sortList(slow);
        return merge(head1, head2);
    }

<aside> 💡 Success

Details

Runtime: 9 ms, faster than 91.35% of Java online submissions for Sort List.

Memory Usage: 56.3 MB, less than 22.30% of Java online submissions for Sort List.

</aside>

다른 풀이 방식과 문제 회고

  • 시간복잡도가 8ms 인 풀이
    • int[] 에 값을 저장하고 sort하여 푼 방법입니다.
class Solution {
    public ListNode sortList(ListNode head) {
        int count = 0;
        ListNode temp = head;
        while(temp!=null){
            count++;
            temp = temp.next;
        }
        int[] arr = new int[count];
        temp = head;
        count = 0;
        while(temp!=null){
            arr[count++] = temp.val;
            temp = temp.next;
        }
        Arrays.sort(arr);
        temp = head;
        count = 0;
        while(temp!=null){
            temp.val = arr[count++];
            temp = temp.next;
        }
        return head;
    }
}
  • 시간복잡도가 2ms 인 풀이
    • 분할정복의 다른 예시입니다. 이미 정렬된 head는 건너뛰고 분할해준 것으로 보입니다.
class Solution {
    public ListNode sortList(ListNode head) {
        return sortList(head, null);
    }

    ListNode sortList(ListNode p, ListNode q) {
        if (p == null || p == q) {
            return p;
        }
        boolean sorted = true;
        ListNode curr = p;

        while (curr.next != null && curr != q) {
            if (curr.val > curr.next.val) {
                sorted = false;
                break;
            }
            curr = curr.next;
        }

        if (sorted) {
            return p;
        }

        ListNode pre = p;
        ListNode head = p;
        curr = pre.next;

        while (curr != null && curr != q) {
            ListNode next = curr.next;
            if (curr.val < p.val) {
                pre.next = next;
                curr.next = head;
                head = curr;
            } else {
                pre = curr;
            }
            curr = next;
        }

        p.next = sortList(p.next, q);
        return sortList(head, p);
    }
}