奔涌的LeetCoder(二):链表(3)
234. 回文链表[简单]
说明:
请判断一个链表是否为回文链表。你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
示例:
输入: 1->2->2->1
输出: true
解答:
本题关键在于判断中间链表节点位置并考虑在不增加空间复杂度情况下完成回文判断。利用快慢指针的移动速度差异完成中间链表节点的定位(当快指针移动到链表末端时,慢指针移动到链表中间)。其次,在回文判断上,需要链表反转按序判断节点数值,所以需要前半部分链表的反转。
解法一:时间复杂度O(n),空间复杂度O(1)。缺陷:破坏了原始链表的结构。(可通过再反转链表解决,不会改变空时复杂度,消耗了鼠标键盘耐久而已)
class Solution {
public boolean isPalindrome(ListNode head) {
while(head == null || head.next == null){
return true;
}
ListNode slow = head;
ListNode fast = head;
ListNode pre = head;
ListNode prepre = null;
//利用快慢指针定位链表中间节点并反转前半部分链表
while(fast != null && fast.next != null){
//快慢指针
slow = slow.next;
fast = fast.next.next;
//反转前半链表
pre.next = prepre;
prepre = pre;
pre = slow;
}
//视奇偶链表长度情况改变判断值是否相等的初始指针位置
if(fast != null){
slow = slow.next;
}
//判断值是否回文
while(prepre != null && slow != null){
if(prepre.val != slow.val){
return false;
}
prepre = prepre.next;
slow = slow.next;
}
return true;
}
}
解法二:在不考虑空间复杂度情况下,可使用栈存放链表节点值,并和链表值对比。
725. 分隔链表[中等]
说明:
- 给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。
- 每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。
- 这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。
- 返回一个符合上述规则的链表的列表。
示例:
输入:
root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
解释:
输入输出各部分都应该是链表,而不是数组。
例如, 输入的结点 root 的 val= 1, root.next.val = 2, \root.next.next.val = 3, 且 root.next.next.next = null。
第一个输出 output[0] 是 output[0].val = 1, output[0].next = null。
最后一个元素 output[4] 为 null, 它代表了最后一个部分为空链表。
输入:
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解释:
输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。
解答:
解法一:因为考虑每段长度尽可能相同,所以首先考虑均匀分隔情况,假设能均匀分割,即L%k==0
,指针移动每隔L/k=m
步将截断链表存入对应数组中。如果无法均匀分割,即L%k==n``n!=0
,则考虑前n段链表的长度在均匀分隔基础上增加1,指针移动每隔m+1
步将截断链表存入对应数组中,后L-k
段链表仍然每隔m
步截断链表存入对应数组中。时间复杂度为O(L+k),空间复杂度为O(max(L,k))。
class Solution {
public ListNode[] splitListToParts(ListNode root, int k) {
ListNode[] output = new ListNode[k];
ListNode pointer1 = root;
int L = 0;
//求出链表长度
while( pointer1 != null){
pointer1 = pointer1.next;
L++;
}
//求出按均分配每段节点数m和分配后剩下节点数n,并考虑前n段节点数为m+1
int m = L / k;
int n = L % k;
int flag = 1;
int mm = 0;
ListNode khead = root;
while(flag <= k){
//每一段的头节点
ListNode newNode = khead;
//通过变量mm的值不同来确定是否为前n段
mm = flag <= n ? m+1 : m;
while(mm-1 > 0){
khead = khead.next;
mm --;
}
//考虑原始链表总长小于K情况,直接设置null
if(khead == null){
output[flag-1] = null;
flag ++;
}
else{
ListNode index = khead;
khead = khead.next;
index.next = null;
output[flag-1] = newNode;
flag ++;
}
}
return output;
}
}
328. 奇偶链表[中等]
说明:
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
- 应当保持奇数节点和偶数节点的相对顺序。
- 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
- 算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例:
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
解答:
解法一:这题思路比较清晰,将链表拆分成奇偶两条链,并将偶链的头节点连接到奇链的末节点,完成链表重组。
class Solution {
public ListNode oddEvenList(ListNode head) {
if(head == null) return head;
ListNode oddhead = head;
ListNode odd = head;
ListNode evenhead = head.next;
ListNode even = head.next;
if(oddhead == null || oddhead.next == null || evenhead.next == null)
return head;
while(odd.next != null && even.next != null){
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
odd.next = evenhead;
return oddhead;
}
}
链表的飞行日记
链表的学习在本次更新之后可能要将先告一段落,这是一段曼妙的旅途中的一处曼妙的风景。本次关于数据结构-链表知识的学习,三讲总共整理10道题,不算多,但算经典。
- 快慢指针的使用可以很容易用于定位链表中的某些对题解有帮助的节点。如:题[19][234]
- 链表的反转本身是个经典问题,并很经常会在其他链表问题求解中用到反转解法,以达到求解。如:题[206][234]
- 不要忘记求解中对一些特殊情况进行提前判断,比如链表本身就单节点或者空节点的情况。
- 链表问题的求解不画图根本做不出来(当然你要是够强能脑补出来指针的丝滑走向,那当我没说)。
- 链表中有些问题可能需要逆序处理,可以考虑栈这种特殊数据结构来辅助解题。如:题[445]
- 链表中出现
NullPointerException
异常,除了循环体的指针移动问题之外,也很有可能在循环终止条件的设置上出错导致的。
欢迎关注“敲代码的阿茄”,相关推文会在公众号中同步更新!!!