linkedList-cutListHalves切分链表

链表有很多不同的题目,这里写那些由一个链表切分成两个或多个链表的问题。

题目简介
86. Partition List比x小的放前,大的放后
328. Odd Even Linked List隔一个串一个
143. Reorder List头一个,尾一个(翻转后半list)
234. Palindrome Linked List回文链表(翻转后半list)
148. Sort List快速排序quick sort

(有些答案代码和分析是抄的别人的,但实在是年代久远没有留具体链接,如有侵犯知识产权问题还请联系我删掉,谢谢啦)

86. Partition List

Input: head = 1->4->3->2->5->2, x = 3
Output: 1->2->2->4->3->5 比x小的放前面,大于等于x的放后面。

有点像撸一遍list,有一个标准“比x小”,把元素分成两类,分别构建一个新的list。构建新list要用small和large两个dummy node,“curS.next = head; curS = curS.next;” -->这个是典型的构建新list的基本操作。

最后把两个list衔接起来。

class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode small = new ListNode(-1);//相当于dummy node
        ListNode large = new ListNode(-1);
        ListNode curS = small, curL = large;
        while (head != null) {
            if (head.val < x) {
                curS.next = head;
                curS = curS.next;
            } else {
                curL.next = head;
                curL = curL.next;
            }
            head = head.next;
        }
        curS.next = large.next;
        curL.next = null;
        return small.next;
    }
}

328. Odd Even Linked List

Input: 1->2->3->4->5->NULL
Output: 1->3->5->2->4->NULL,奇数位在前,偶数位在后。

86的标准是“比x小”,这里328是“奇数位”。以前我总觉得多个串纠缠不容易想清楚,其实可以放弃想原来的链表的link们,直接认为和86类似,构建新链表。类似于86的dummy,这里把1叫做head(oddHead), 把2叫做eHead(evenHead),然后构建1的时候借助2的next,构建2的时候借助1的next。

class Solution {
    public ListNode oddEvenList(ListNode head) {
        if (head == null || head.next == null) {return head;}
        ListNode eHead = head.next;
        ListNode odd = head, even = eHead;
        while (even != null && even.next != null) {
            odd.next = even.next;//构建1的时候借助2的next
            odd = odd.next;
            even.next = odd.next;//构建2的时候借助1的next
            even = even.next;
        }
        odd.next = eHead;
        return head;
    }
}

上面是不需要翻转列表的,下面是需要翻转部分列表的。

143. Reorder List

Given a singly linked list L: L0→L1→…→Ln-1→Ln,
reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→…
头一个,尾一个,头一个,尾一个……
Given 1->2->3->4->5, reorder it to 1->5->2->4->3.

尾部的需要逆序访问,这个在链表是不可能的,所以必须翻转链表。
于是分三步:

  1. 找到中点
  2. 翻转后一半链表
  3. merge前半和后半两个链表

有个cheatsheet背一下:
dummy->1->2->3->4->null
dummy->1->2->3->4->5->null
fast和slow都初始化指向dummy。

  • while (fast != null && fast.next != null): //slow:2 in 4, 3 in 5
  • while (fast != null && fast.next != null && fast.next != null): //slow:2 in 4, 2 in 5

在step 3合并两个链表时,因为若链表长为奇数,前一半可能比后一半长,于是“衔接‘前一半’的一个元素”这个操作会多一步,“衔接‘后一半’的一个元素”这个需要检查if (pre != null)。

class Solution {
    public void reorderList(ListNode head) {
    	//step 1: 找到中点
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode fast = dummy, slow = dummy;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        } //slow:2 in 4, 3 in 5
        //step 2: 翻转后一半链表
        ListNode pre = null;
        ListNode cur = slow.next;
        slow.next = null; //cut off
        while (cur != null) {
            ListNode temp = cur.next;
            cur.next = pre;
            //挪指针
            pre = cur;
            cur = temp;
        } //此时,pre是翻转的后一半的新head       
        //step 3: merge前半和后半两个链表:head and pre
        //head:1->2->3,pre=5->4
        ListNode ptr = head;
        while (ptr != pre) {
            //衔接“前一半”的一个元素
            ListNode temp1 = ptr.next;
            ptr.next = pre;
            ptr = temp1; //挪指针
            //衔接“后一半”的一个元素
            if (pre != null) {
                ListNode temp2 = pre.next;
                pre.next = temp1;
                pre = temp2; //挪指针
            }
        }
    }
}

234. Palindrome Linked List

Input: 1->2->2->1 判断链表是否是“回文链表”
Output: true

同样是需要翻转后一半的链表。这部分代码和上面143一样。唯一区别是这里234不需要merge两个子链表,而是只比较一下元素即可。

class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) {return true;}
        //step 1: 找到中点
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode fast = dummy, slow = dummy;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }//slow:2 in 4, 3 in 5
        //step 2: 翻转后一半链表
        ListNode pre = null;
        ListNode cur = slow.next;        
        slow.next = null; //cut off
        while (cur != null) {
            ListNode temp = cur.next;
            cur.next = pre;
            //挪指针
            pre = cur;
            cur = temp;
        }
        //step 3: 比较两个子链表(head和pre)
        //head:1->2->3, pre:1->2
        while (head != null && pre != null) {
            if (head.val != pre.val) {return false;}
            head = head.next;
            pre = pre.next;
        }
        return true;
    }
}

还可以Recursive做。
这么做的核心逻辑是什么呢?实际上就是把链表节点放入一个栈,然后再拿出来,这时候元素顺序就是反的,只不过我们利用的是递归函数的堆栈而已。算法的时间和空间复杂度都是 O(N)。
如果我想正序打印链表中的val值,可以在前序遍历位置写代码;反之,如果想倒序遍历链表,就可以在后序遍历位置操作:借助二叉树后序遍历的思路,不需要显式反转原始链表也可以倒序遍历链表。

//recursive
class Solution {
    ListNode left; //左侧指针

    boolean isPalindrome(ListNode head) {
        left = head;
        return traverse(head);
    }

    boolean traverse(ListNode right) {
        if (right == null) {return true;}
        boolean result = traverse(right.next);
        // 后序遍历代码
        result = result && (right.val == left.val);
        left = left.next;
        return result;
    }
}

148. Sort List

Input: 4->2->1->3
Output: 1->2->3->4
Sort a linked list 时间 O(n log n),空间O(1)。

时间 O(n log n),方法有quick sort,Merge sort。
这里用Merge sort。分三步(就是个模板,背过就好啦):

  1. 链表切成两半
  2. 对每个half排序
  3. merge两个链表
//merge sort
class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) {return head;}
        //step 1: 链表切成两半
        ListNode pre = null, fast = head, slow = head;
        while (fast != null && fast.next != null) {//3 in 4,3 in 5
            pre = slow;
            fast = fast.next.next;
            slow = slow.next;
        }
        pre.next = null; //cut-off
        //step 2: 对每个half排序
        ListNode l1 = sortList(head);
        ListNode l2 = sortList(slow);
        //step 3: merge两个链表
        return merge(l1, l2);
    }
    
    private ListNode merge(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                cur.next = l1;
                l1 = l1.next;
            } else {
                cur.next = l2;
                l2 = l2.next;
            }
            cur = cur.next;
        }
        if (l1 != null) {
            cur.next = l1;
        }
        if (l2 != null) {
            cur.next = l2;
        }
        return dummy.next;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值