關於鏈表的常見算法題(二)

關於鏈表的常見算法題(一)java

關於鏈表的常見算法題(二)

從有序鏈表中刪除重複的節點

給定一個有序鏈表,請刪除其中的重複元素,使得這個元素僅出現一次node

Input: 1->1->2->3->3
Output: 1->2->3
複製代碼

其實這個題比上篇講的juejin.im/post/5d067f…中刪除重複的節點還要容易一些,由於在此處,有重複的值時,是保留一個節點的,而上一篇中是都刪除。算法

解題思路
  • 遍歷鏈表,若是當前節點與next的下一個節點相同,那麼刪除下一個節點,並把指針next指向next.next。這裏很明顯咱們可使用遞歸來解決問題
public ListNode deleteDuplicates(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        head.next = deleteDuplicates(head.next);
        return head.value == head.next.value ? head.next : head;
    }
複製代碼

刪除重複元素

給定一個有序鏈表,請刪除其中的重複元素,使得原鏈表中的元素只出現一次數組

Input: 2->3->4->2->null
Output: 2->3->4->null
複製代碼
解題思路

可使用雙循環暴力破解法,不過期間複雜度就是O(n^2)了,咱們可使用HashMap和存儲結點值,判斷是否有重複,有重複的時候,把當前節點的上一個節點的next指向當前節點的next,這樣,就至關於把當前節點刪除了,當沒有重複時,把這個值加入map中。bash

/** * 雙循環 */
    public ListNode deleteDulp(ListNode head) {
        if (head==null||head.next==null){
            return head;
        }
        ListNode p = head;
        while (p != null) {
            ListNode q= p;
            while (q.next!=null){
                if (p.value==q.next.value){
                    q.next  = q.next.next;
                }else {
                    q = q.next;
                }
            }
            p = p.next;
        }
        return head;
    }

    /** * 使用hashmap */
    public ListNode deleteDulp2(ListNode head){
        HashMap<Integer,Integer>  map = new HashMap<>();
        ListNode root = head;
        ListNode before = head;
        while (head!=null){
            if (map.containsKey(head.value)) {
                before.next =  head.next;
                head = head.next;
            }else {
                map.put(head.value,1);
                before = head;
                head = head.next;
            }
        }
        return root;
    }
複製代碼

刪除鏈表的倒數第n個節點

給定一個單向鏈表,要求刪除從結尾數第n個結點,並返回鏈表數據結構

輸入:5-6-7-8-9  n=2;
輸出:5-6-7-9
複製代碼
解題思路

核心是找到要刪除的結點的前一個結點位置。咱們可使用雙指針來完成,快指針先走n-1步,而後同時移動快慢雙指針,當快指針的的next爲null時,咱們就找到了當前位置就咱們要找的位置函數

public ListNode removeNthFormEnd(ListNode head, int n) {
        ListNode fast = head;
        while (n-- > 0) {
            fast = fast.next;
        }
        if (fast == null) {
            return head.next;
        }
        ListNode slow = head;
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return head;
    }
複製代碼

交換鏈表中的相鄰結點

給定一個單向鏈表,依次交換每一對相鄰的結點post

1-2-3-4
2-1-4-3
複製代碼
解題思路

首先咱們要明白,鏈表的兩個結點要如何交換:ui

假若有1-2-3-4-5這個鏈表,咱們要交換的是3和4,那麼spa

  • 先把3的上一外結點的指向4,獲得鏈表A 1-2-4-5,另一種形式是3-4-5
  • 再把3指下4的next,獲得3-5
  • 而後整合兩條鏈,將4指向3,獲得1-2-4-3-5

咱們想到頭插法反轉,指定一個pre指針,指向頭插的節點,反轉後兩個節點,pre指針指向之前的next,好比增長頭結點0,那麼0-1-2-3-4,先把pre指向0,此時pre.next爲1,把1和2反轉後,變成0-2-1-3-4,再把pre指針移到1的位置,後面同樣的循環就行了。

public ListNode swapPairs(ListNode head) {
        //在頭部增長一個節點
        ListNode node = new ListNode(-1);

        node.next = head;
        ListNode pre = node;
        while (pre.next != null && pre.next.next != null) {
            ListNode node1 = pre.next;
            ListNode node2 = pre.next.next;

            node1.next = node2.next;
            node2.next = node1;
            pre.next = node2;

            //此時node1已經反轉,這個位置就是咱們要的位置
            pre = node1;
        }

        return node.next;
    }
複製代碼

反轉k個相鄰的結點

給定一個單向鏈表,每次反轉k個相鄰結點,返回最終修改後的鏈表。k是一個正整數,而且不大於鏈表長度。若是鏈表中的節點個數不是k的整數倍,則最後剩餘的k個結點不作修改

輸入:1-2-3-4-5  k=2
輸出:2-1-4-3-5
複製代碼
解題思路

這題實際上是上一個題的升級版,因此咱們依然能夠套用上一個題目的方法

  • 使用頭插法

    創建另一個鏈表頭,而後要處理的列表遞進處理,一個個的插入新鏈表,最後返回這的next。

    核心思想是兩個兩個操做,操做k-1次。操做的內容是用prev與next節點指向新頭節點,而後操做指針關係。

  • 使用遞歸法

    設置好分組的間隔,用cnt和curNode分別標識計數和目前的節點,當分組結束後開始遞歸下一組分組,並開始這一組的反轉操做,仍然用cnt和curNode控制組內收縮。

/** * 使用頭插法 */
    public ListNode reverseKGroup2(ListNode head, int k) {
        if (head == null || head.next == null || k == 1) {
            return head;
        }

        //找到鏈表的長度n
        int n = 0;
        ListNode root = head;
        while (root != null) {
            root = root.next;
            n++;
        }

        //使用頭插
        ListNode node = new ListNode(-1);
        node.next = head;
        for (ListNode prev = node, tail = head; n >= k; n -= k) {
            for (int i = 1; i < k; i++) {
                ListNode next = tail.next.next;
                tail.next.next = prev.next;
                prev.next = tail.next;
                tail.next = next;
            }

            prev = tail;
            tail = tail.next;
        }
        return node.next;

    }

    /** * 使用遞歸法 */
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode curNode = head;
        int cnt = 0;
        //把cnt都移動到k位,curNode移動到下一次反轉開始的地方
        while (curNode != null && cnt != k) {
            curNode = curNode.next;
            cnt++;
        }
        if (cnt == k) {
            curNode = reverseKGroup(curNode, k);
            while (cnt-- > 0) {
                ListNode temp = head.next;
                head.next = curNode;
                curNode = head;
                head = temp;
            }
            head = curNode;
        }

        return head;
    }
複製代碼

鏈表求和

Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 8 -> 0 -> 7
複製代碼
解題思路

咱們能夠發現,其實就是倒序對應相加,有進位的話要保存到下次相加上,想到了棧這樣的數據結構,後進先出

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> l1Stack = buildStack(l1);
        Stack<Integer> l2Stack = buildStack(l2);
        ListNode head = new ListNode(-1);
        int carry = 0;
        while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carry != 0) {
            int x = l1Stack.isEmpty() ? 0 : l1Stack.pop();
            int y = l2Stack.isEmpty() ? 0 : l2Stack.pop();
            int sum = x + y + carry;
            ListNode node = new ListNode(sum % 10);
            //頭插法
            node.next = head.next;
            head.next = node;
            carry = sum / 10;
        }
        return head.next;
    }

    private Stack<Integer> buildStack(ListNode listNode) {
        Stack<Integer> stack = new Stack<>();
        while (listNode != null) {
            stack.push(listNode.value);
            listNode = listNode.next;
        }
        return stack;
    }
複製代碼

迴文鏈表

給定一個單鏈表,判斷它是不是迴文鏈表,即正着遍歷和反着遍歷獲得的序列是相同的。

要求,0(1)空間複雜度

解題思路

由於要求的空間複雜度爲O(1),因此棧啊什麼的數據結構來存儲的就不能用了,這時候須要用到分治的思想

咱們能夠把鏈切成兩半,把後半段反轉,再比較這兩半是否相等就能夠了。

具體能夠這麼來實現,使用快慢兩個指針,快指針每次兩步,慢指針每次一步,當快指針的next或者next.next爲空時,慢指針就在鏈表的中間(偶數個時,慢指針是靠近頭那一側的節點)。而後從慢指針的下一個點開始把後面的鏈表反轉,而後分別從頭和尾(這個時候尾已經轉到了中間)前進,這能夠判斷是不是一個迴文

public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) {
            return true;
        }
        ListNode fast = head;
        ListNode slow = head;
        //找到中點
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }

        //後面一段的開始點
        ListNode secondHead = slow.next;
        ListNode p1 = secondHead;
        ListNode p2 = p1.next;

        slow.next = null;//把原來的置空
        while (p1 != null && p2 != null) {
            ListNode temp = p2.next;
            p2.next = p1;
            p1 = p2;
            p2 = temp;
        }

        //將第二段鏈表的尾巴置空,已經轉過來了,頭就是尾
        secondHead.next = null;

        while (p1 != null) {
            if (head.value != p1.value) {
                return false;
            }
            head = head.next;
            p1 = p1.next;
        }
        return true;
    }
複製代碼

分隔鏈表

給定一個頭結點爲 root 的鏈表, 編寫一個函數以將鏈表分隔爲 k 個連續的部分。

每部分的長度應該儘量的相等: 任意兩部分的長度差距不能超過 1,也就是說可能有些部分爲 null。

這k個部分應該按照在鏈表中出現的順序進行輸出,而且排在前面的部分的長度應該大於或等於後面的長度。

返回一個符合上述規則的鏈表的列表。

輸入: root = [1, 2, 3], k = 5
輸出: [[1],[2],[3],[],[]]
複製代碼
解題思路

計算出鏈表的長度,而後根據n/k求出數組長度,n%k不爲0時說明不能均分,因此從第一個開始,鏈表長度加1,mod--,直到0

public ListNode[] splitListToParts(ListNode root, int k) {
        int n = 0;
        ListNode cur = root;
        
        //計算鏈表長度
        while (cur != null) {
            n++;
            cur = cur.next;
        }
        
        int mod = n % k;
        int size = n / k;
        ListNode[] ret = new ListNode[k];
        //從新把root賦值給cur
        cur = root;
        for (int i = 0; cur != null && i < k; i++) {
            ret[i] = cur;
            int curSize = size + (mod-- > 0 ? 1 : 0);
            for (int j = 0; j < curSize - 1; j++) {
                cur = cur.next;
            }
            ListNode next = cur.next;
            cur.next = null;
            cur = next;
        }
        return ret;
    }
複製代碼

鏈表元素按奇偶彙集

給定一個單鏈表,請將全部的偶數節點拿出來,放在奇數節點後面,注意,偶數指的是節點的位置編號,而不是值。

你須要使用O(N)的時間和O(N)的空間

解題思路

這個仍是能夠經過雙指針來解決,用兩個指針一塊兒遍歷,每次遍歷兩個節點,而後再將偶數鏈表頭接在奇數尾上。

public ListNode oddEvenList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode odd = head;//放奇數
        ListNode even = head.next;//放偶數
        ListNode evenHead = even;
        while (even != null && even.next != null) {
            //先把odd的next指向下一個奇數,而後把odd的指針切換到這個奇數上
            odd.next = odd.next.next;
            odd = odd.next;
            even.next = even.next.next;
            even = even.next;
        }
        odd.next = evenHead;
        return head;
    }
複製代碼

By LeetCode


個人CSDN

下面是個人公衆號,歡迎你們關注我

相關文章
相關標籤/搜索