Algorithm/LeetCode

[linked list] palindrome linked list

sw_develop 2025. 2. 16. 18:50

 Problem

https://leetcode.com/problems/palindrome-linked-list/description/

 

 Approach & Solution

공통

  • 리스트의 앞과 뒤의 값을 비교하여 값이 다른 경우 팰린드롬이 아님

 

방식1) 추가 자료구조 사용

더보기

1-1) Stack 사용

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        Stack<Integer> stack = new Stack<>();

        ListNode node = head;
        while (node != null) {
            stack.add(node.val);
            node = node.next;
        }

        while (head != null) {
            // 연결 리스트와 스택에서 추출한 값을 비교해 팰린드롬 판별
            if (head.val != stack.pop()) {
                return false;
            }
            head = head.next;
        }

        return true;
    }
}

 

1-2) Deque 사용

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        Deque<Integer> deque = new LinkedList<>();
        // 연결리스트를 deque에 삽입
        ListNode node = head;
        while (node != null) {
            deque.add(node.val);
            node = node.next;
        }

        // deque이 모두 비거나 1개가 남을 때(데이터 개수 홀수일 때)까지 비교
        while (!deque.isEmpty() && deque.size() > 1) {
            if (deque.pollFirst() != deque.pollLast()) {
                return false;
            }
        }

        return true;
    }
}

 

 

방식2) 기존 자료구조 사용

더보기
  • 러너 기법 활용
    • 빠른 러너(2칸 씩 이동), 느린 러너(1칸 씩 이동)
  • 느린 러너의 위치를 기준으로 리스트의 중간 지점부터 이후 값에 대한 역순 리스트 생성
  • 기존 리스트와 앞에서부터 비교
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        // 리스트의 중간 이후 지점부터 역순 연결 리스트를 생성한다.
        // 기존 연결 리스트와 비교한다. (다른 값이 있을 경우 palindrome이 아님)

        ListNode fast = head, slow = head;

        while (fast != null && fast.next != null) { // fast가 리스트의 끝에 도달했을 때
            fast = fast.next.next; // 2칸 씩 이동
            slow = slow.next; // 1칸 씩 이동
        }

        // 중간 값을 넘어가야 하기 때문에 홀수 개인 경우 slow를 한칸 더 이동
        if (fast != null) {
            slow = slow.next;
        }

        // slow 값 기준으로 역순 연결 리스트 생성
        ListNode rev = null; // 역순 연결 리스트의 시작 노드
        while (slow != null) {
            ListNode next = slow.next;
            slow.next = rev;
            rev = slow;
            slow = next;
        }
        
        // 기존 리스트와 역순 리스트 원소 비교
        // 한 개라도 다르면 palindrome이 아니므로 false 반환
        while (rev != null) {
            if (head.val != rev.val) {
                return false;
            }

            head = head.next;
            rev = rev.next;
        }

        return true;
    }
}

 

추가) 러너 기법

  • 러너는 연결 리스트를 순회할 때 2개의 포인터를 동시에 사용하는 기법
    • 한 포인터가 다른 포인터보다 앞서게 하여 병합 지점이나 중간 위치, 길이 등을 판별할 때 유용하게 사용할 수 있음
  • 2개의 포인터는 빠른 러너와 느린 러너로 명칭하고, 빠른 러너는 2 칸씩 느린 러너는 1 칸씩 이동
  • 이때 빠른 러너가 연결 리스트의 끝에 도달하면, 느린 러너는 정확히 연결 리스트의 중간 지점을 가리키게 됨
    • 중간 위치를 찾아내면 전체 길이를 알아낼 수 있고, 여기서부터 값을 비교하거나 뒤집기를 시도하는 등의 활용 가능