約瑟夫環使用單向環形鏈表解題

  • 2020 年 10 月 7 日
  • 筆記

約瑟夫環又稱丟手絹是一個非常有名的數學問題,它規定一個環形鏈表中從N的位置開始第M個Node被刪除,直到元素內沒有元素,算出它們的刪除順序。

首先我們做一個環形鏈表,也就是lastNode.next=fristNode。將鏈表變成一個環。其實這題使用雙向環形鏈表最簡單,但往往面試題要求使用單向鏈表。

 

 

package com.dfsn.cloud.eureka;

public class AnnulusLinkedList<T> {

    // 頭節點
    private Node frist;

    // 元素個數
    private int size;

    public void add(T t) {
        // 添加第一個元素時,它的下一個指向它自己
        if (frist == null) {
            Node newNode = new Node(t, null);
            frist = newNode;
            newNode.next = frist;
        } else {
            Node newNode = new Node(t, null);
            Node temp = frist;
            // 如果Node.next是first說明它是最後一個節點了,在它後邊添加
            while (temp.next != frist) {
                temp = temp.next;
            }
            // 既然找到了最後一個,就將新創建的添加到最後一個的末尾
            temp.next = newNode;
            // 最後新添加Node.next指向frist
            newNode.next = frist;
        }
        size++;
    }

    // 獲取長度
    public int size() {
        return size;
    }

    // 約瑟夫環又稱丟手絹殺人遊戲
    // 從鏈表的start位置開始數countNum個位置並且刪除該位置的Node
    // 其實這個題用雙向環形鏈表的難度會低一點,只要做一個計數器 l,遞增l並且不斷地node.next
    // l==countNum後刪除該node即可具體做法如下兩行程式碼
    // node.prev.next=node.next
    // node.next.prev=node.prev
    // 然後重置l,繼續自增查詢。
    // 但是如果是單向環形鏈表就要考慮一個問題,當l==countNum時,該如何刪除這個元素?
    // 單向鏈表無法自己刪除自己,只能通過上級刪掉自己。問題是,單向鏈表又沒有prev
    // 所以這需要我們維護兩個node,一個是frist,兩一個是last,last.next就是frist。
    // 也就是當l滿足條件後,frist=frist.next;last.next=frist;
    // 然後重置l,繼續自增查詢。
    public void josehu(int start, int countNum) {
        if (size == 0) {
            throw new RuntimeException("沒有元素無法計算");
        }
        if (countNum < 1) {
            throw new RuntimeException("countNum不能小於1");
        }
        if (start < 1) {
            throw new RuntimeException("start不能小於1");
        }
        if (start > size) {
            throw new RuntimeException("start位置不能大於" + size);
        }

        // 首先找到最後一個節點,最後一個節點的條件就是它的next==frist
        Node last = this.frist;
        while (true) {
            if (last.next == this.frist) {
                break;
            } else {
                last = last.next;
            }
        }

        // 第一個節點就是frist
        Node frist = this.frist;
        // 因為要從start位置開始數數,所以要找到start
        // frist和last要同時向前移動,但last始終在frist前邊
        for (int i = 1; i < start; i++) {
            frist = frist.next;
            last = last.next;
        }

        // 計數器
        int l = 1;
        while (true) {
            // 如果==1說明就剩下一個Node就停止,最後把它自殺就行
            if (size == 1) {
                break;
            } else {
                if (l == countNum) {// 如果l==countNum表示當前frist要被殺掉
                    System.out.println(frist.item);
                    // frist的下一個成為新的frist
                    frist = frist.next;
                    // last的下一個還是frist
                    last.next = frist;
                    // 重置計數器
                    l = 1;
                    size--;
                } else {// 如果l!=countNum那麼把計數器+1,frist和last同時向後偏移
                    frist = frist.next;
                    last = last.next;
                    l++;
                }
            }
        }
        // 列印最後一個元素
        System.out.println(frist.item);
        // 置為null表示自殺
        frist = null;
        size--;
    }

    // 查看鏈表
    public void show() {
        Node temp = frist;
        while (true) {
            System.out.println(temp);
            temp = temp.next;
            if (temp.next == frist) {
                System.out.println(temp);
                break;
            }
        }
    }

    // 節點對象
    class Node {
        private T item;
        private Node next;

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }

        @Override
        public String toString() {
            return "Node [item=" + item + "]";
        }

    }
}

View Code

public static void main(String[] args) {
        AnnulusLinkedList<String> annulusLinkedList = new AnnulusLinkedList<>();
        annulusLinkedList.add("A");
        annulusLinkedList.add("B");
        annulusLinkedList.add("C");
        annulusLinkedList.add("D");
        annulusLinkedList.add("E");
        annulusLinkedList.add("F");
        annulusLinkedList.add("G");
        annulusLinkedList.add("H");
        annulusLinkedList.josehu(2, 2);
    }

View Code