我比较笨,记录一下心得,免得到时候忘记
反转链表 - LC206
反转一个单链表
示例
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
先上代码
private static ListNode reverseList_206(ListNode head) {
ListNode pre = null;
ListNode current = head;
while (current != null) {
//暂存
ListNode temp = current.next;
//改变指向
current.next = pre;
//重新赋值(移动指针位置)
pre = current;
current = temp;
}
return pre;
}
时间复杂度:O(n)
空间复杂度:O(1)
一开始我很纠结这两步,感觉这合并一下不就是current.next = current么,怎么看怎么别扭,后面我才恍然大悟,原来这两代码,执行的是两个不一样的逻辑。。。
current.next = pre;
pre = current;
分步来分析一下逻辑吧,免得下次再看的时候又看不明白了
首先我们初始化是如下状态
当我们执行
current.next = pre时
c的next指向了pre,但是pre此时还是null
结合实际例子,那么就是
此时,1已经指向null了,就是当前节点来说已经完成了局部反转,即current指向了pre,下一步就是指针的移动了,pre往前移动一位(占据现在的current位置),而current也要往前移动一位
//重新赋值(移动指针位置)
pre = current;
current = temp;
之后再进行重复的局部反转操作
然后再是移位,直到最后c移动到了null的位置
这时候
current != null就是false了,这时候我们返回pre,那么pre代表的链表就是反转之后的链表了。
链表中对环的检测 - LC141
快慢指针
用两个指针去遍历链表,一个slow指针一次走一步,一个fast指针一次走两步,如果两个指针相遇了,那么说明有环,如果fast指针遍历完了,那么说明没有环。
private static boolean hasCycleSlowAndFast_141(ListNode head) {
if (head == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
if (fast == null) {
return false;
}
//不相等时进去判断
while (slow != fast) {
//如果fast指针走完了,那么说明没有环
if (fast == null || fast.next == null) {
return false;
}
//走一步
slow = slow.next;
//走两步
fast = fast.next.next;
}
//快慢指针相等了,说明有环
return true;
}
时间复杂度:O(n)
空间复杂度:O(1)
哈希法
将每一个数据存入Hash表中,存入前判断是否已经存在
private static boolean hasCycleHashSet_141(ListNode head) {
if (head == null) {
return false;
}
Set<ListNode> set = new HashSet<>();
while (head != null) {
if (set.contains(head)) {
return true;
} else {
set.add(head);
}
head = head.next;
}
return false;
}
时间复杂度:O(n)
空间复杂度:O(n) :取决于添加到哈希表中的元素数目,最多可以添加 n 个元素。
合并两个有序链表 - LC21
遍历
首先两个链表分别有各自的头结点h1,h2,首先对h1和h2进行大小比较,如果h1较小,那么h1作为新的头结点并且h1指针往后移动一位,之后再用后一位的节点与h2进行大小比较,小的放在新链表中,以此类推
private static ListNode mergeTwoLists_21(ListNode l1, ListNode l2) {
//哨兵节点,方便链表的输出
ListNode preHead = new ListNode(-1, null);
ListNode pre = preHead;
while (l1 != null && l2 != null) {
if (l1.val > l2.val) {
pre.next = l2;
l2 = l2.next;
} else {
pre.next = l1;
l1 = l1.next;
}
pre = pre.next;
}
//l1或者l2肯定有一个是null,那么另外那个就直接拼接到末尾即可
pre.next = l1 == null ? l2 : l1;
//返回哨兵节点的后一位节点就可以了
return preHead.next;
}
时间复杂度:O(n + m)
空间复杂度:O(1)
递归
递归这东西,用文字很难描述,先上代码,等会图解
private static ListNode mergeTwoLists2_21(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
} else if (l2 == null) {
return l1;
} else if (l1.val < l2.val) {
l1.next = mergeTwoLists2_21(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists2_21(l1, l2.next);
return l2;
}
}
时间复杂度:O(n + m)
空间复杂度:O(n + m):其中 n 和 m 分别为两个链表的长度。递归调用 mergeTwoLists 函数时需要消耗栈空间,栈空间的大小取决于递归调用的深度。结束递归调用时 mergeTwoLists 函数最多调用 n+m 次,因此空间复杂度为 O(n+m)
我们假设有两个链表1->2->5->nulll和3->4->6->null,让我们一步一步来看递归的时候发生了什么
删除链表的倒数第N个节点 - LC19
还是先上代码,然后上图解
private static ListNode removeNNode_19(ListNode head, int n) {
//这里fast还是从head开始,并没有牵扯到哨兵节点
ListNode fast = head;
//fast节点先走N步
for (int i = 0; i < n; i++) {
fast = fast.next;
}
//创建一个哨兵节点,指向头节点
ListNode newNode = new ListNode(-1, head);
//slow节点等fast走完了再开始走
ListNode slow = newNode;
//fast如果还没走到链表末尾,那么就和slow一起走
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
//fast走到结尾了,那么slow就把原来的next指针指向下下个
slow.next = slow.next.next;
//通过哨兵节点返回真正的头结点
return newNode.next;
}
时间复杂度:O(N)
空间复杂度:O(1)
看一下图解
链表的中间节点 -- LC876
返回链表的中间节点,这里我就只写快慢指针的方法了,比较简单
private static ListNode middleNode_876(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
时间复杂度:O(N)
空间复杂度:O(1)