至于什么是链表就不详细展开了,这里放一个用Java实现链表的一些基本操作的代码,可以去看看。链表的遍历,插入。等等。
链表的应用场景
1,如下,有操作系统内存分配区域隔断以后,剩下的空间就是用链表串起来,作为一片连续的空间使用
2,缓存淘汰算法。
缓存的空间是有限的,所以,缓存不能一直放一些固定的东西,可以理解为喜新厌旧,有新的来就有旧的被替换掉,
最近最少使用, 应该需要一个有序的结构, 每加入一个元素, 都放在元素的末尾, 每访问一个元素, 就删除原位置的元素, 并在末尾添加, 这样后面的都是最近被使用过的, 头部是最近没有使用的, 当内存空间不足时, 淘汰头部的元素。
力扣第141题,如何判断一个链表中是否有环形链表
1 哈希表法
public class Solution {
public boolean hasCycle(ListNode head) {
Set <ListNode> hSet=new HashSet<ListNode>();
while(head!=null){
if(!hSet.add(head)){
return true;
}
head=head.next;
}
return false;
}
}
2 快慢指针法
public class Solution {
public boolean hasCycle(ListNode head) {
if (head==null||head.next==null){
return false;
}
ListNode slow=head;
ListNode Fast=head.next;
while(slow!=Fast){
if(Fast==null||Fast.next==null){
return false;
}
slow=slow.next;
Fast=Fast.next.next;
}
return true;
}
}
3 找环的起始位置
p,q在环中相遇点的位置在哪。当p走到环的起点的时候,q已经走了两倍 head--》起点的位置。
所以可以得到结论 p,q相遇的时候,将p放到起始位置,两个指针在同时同速度走,相遇的位置就是环起始点的位置。
力扣142 找环的起始位置
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head==null)
return null;
ListNode slow=head,fast=head;
while(fast!=null){
slow=slow.next;
if(fast.next!=null){
fast=fast.next.next;
}else{
return null;
}
if(fast==slow){
ListNode tmp=head;
while(tmp!=slow){
tmp=tmp.next;
slow=slow.next;
}
return tmp;
}
}
return null;
}
}
链表的反转
用普通遍历实现
class Solution {
public ListNode reverseList(ListNode head) {
ListNode p=null;
ListNode cur=head;
while(cur!=null){
ListNode next =cur.next;
cur.next=p;
p=cur;
cur=next;
}
return p;
}
}
基于递归的实现方法:
递归的回溯就是把链表翻转了,如果链表有两个节点,
接下看有三个节点
看着官方的解释还行,自己画着画着就没了头绪,因为画了三个节点,所以就理解不了
nk.next.next=nk的意思是什么。
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode p = reverseList(head.next);
head.next.next = head;
head.next = null;
return p;
}
}
这里其实按照上面那个公式就可以清晰的明白了,此刻也感受到了递归的魔力,真的栓Q啊!
反转链表二
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
// 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode pre = dummyNode;
// 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
// 建议写在 for 循环里,语义清晰
for (int i = 0; i < left - 1; i++) {
pre = pre.next;
}
// 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
ListNode rightNode = pre;
for (int i = 0; i < right - left + 1; i++) {
rightNode = rightNode.next;
}
// 第 3 步:切断出一个子链表(截取链表)
ListNode leftNode = pre.next;
ListNode curr = rightNode.next;
// 注意:切断链接
pre.next = null;
rightNode.next = null;
// 第 4 步:同第 206 题,反转链表的子区间
reverseLinkedList(leftNode);
// 第 5 步:接回到原来的链表中
pre.next = rightNode;
leftNode.next = curr;
return dummyNode.next;
}
private void reverseLinkedList(ListNode head) {
// 也可以使用递归反转一个链表
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
}
}
K个一组反转链表
dummy 是我们构造出来 的虚拟头结点。
步骤分解:
链表分区为已翻转部分+待翻转部分+未翻转部分
每次翻转前,要确定翻转链表的范围,这个必须通过 k 此循环来确定
需记录翻转链表前驱和后继,方便翻转完成后把已翻转部分和未翻转部分连接起来
初始需要两个变量 pre 和 end,pre 代表待翻转链表的前驱,end 代表待翻转链表的末尾
经过k此循环,end 到达末尾,记录待翻转链表的后继 next = end.next
翻转链表,然后将三部分链表连接起来,然后重置 pre 和 end 指针,然后进入下一次循环
特殊情况,当翻转部分长度不足 k 时,在定位 end 完成后,end==null,已经到达末尾,说明题目已完成,直接返回即可
时间复杂度为 O(n*K)O(n∗K) 最好的情况为 O(n)O(n) 最差的情况未 O(n^2)O(n2 )
空间复杂度为 O(1)O(1) 除了几个必须的节点指针外,我们并没有占用其他空间
作者:reals
链接:https://leetcode.cn/problems/reverse-nodes-in-k-group/solution/tu-jie-kge-yi-zu-fan-zhuan-lian-biao-by-user7208t/
来源:力扣(LeetCode)
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null || head.next == null){
return head;
}
//定义一个假的节点。
ListNode dummy=new ListNode(0);
//假节点的next指向head。
// dummy->1->2->3->4->5
dummy.next=head;
//初始化pre和end都指向dummy。pre指每次要翻转的链表的头结点的上一个节点。end指每次要翻转的链表的尾节点
ListNode pre=dummy;
ListNode end=dummy;
while(end.next!=null){
//循环k次,找到需要翻转的链表的结尾,这里每次循环要判断end是否等于空,因为如果为空,end.next会报空指针异常。
//dummy->1->2->3->4->5 若k为2,循环2次,end指向2
for(int i=0;i<k&&end != null;i++){
end=end.next;
}
//如果end==null,即需要翻转的链表的节点数小于k,不执行翻转。
if(end==null){
break;
}
//先记录下end.next,方便后面链接链表
ListNode next=end.next;
//然后断开链表
end.next=null;
//记录下要翻转链表的头节点
ListNode start=pre.next;
//翻转链表,pre.next指向翻转后的链表。1->2 变成2->1。 dummy->2->1
pre.next=reverse(start);
//翻转后头节点变到最后。通过.next把断开的链表重新链接。
start.next=next;
//将pre换成下次要翻转的链表的头结点的上一个节点。即start
pre=start;
//翻转结束,将end置为下次要翻转的链表的头结点的上一个节点。即start
end=start;
}
return dummy.next;
}
//链表翻转
// 例子: head: 1->2->3->4
public ListNode reverse(ListNode head) {
//单链表为空或只有一个节点,直接返回原单链表
if (head == null || head.next == null){
return head;
}
//前一个节点指针
ListNode preNode = null;
//当前节点指针
ListNode curNode = head;
//下一个节点指针
ListNode nextNode = null;
while (curNode != null){
nextNode = curNode.next;//nextNode 指向下一个节点,保存当前节点后面的链表。
curNode.next=preNode;//将当前节点next域指向前一个节点 null<-1<-2<-3<-4
preNode = curNode;//preNode 指针向后移动。preNode指向当前节点。
curNode = nextNode;//curNode指针向后移动。下一个节点变成当前节点
}
return preNode;
}
}
旋转链表:
注意上面公式这里 n-1 的话是从物理地址来说的,那个起始点 n应该设计为0,而下面代码的n是从1开始的,是逻辑上的第一个节点,所以在add--的时候要想清楚,另外()优先级高,所以先走add--再去判断>0
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(k==0||head==null||head.next==null){
return head;
}
int n=1;
ListNode p=head;
while(p.next!=null){
p=p.next;
n++;
}
int add=n-k%n;
if(add==n){
return head;
}
p.next=head;
while((add--)>0){
p=p.next;
}
ListNode ret=p.next;
p.next=null;
return ret;
}
}
删除链表的第n个节点
这个题的解法比较简单,就不赘述了
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
int length = getLength(head);
ListNode cur = dummy;
for (int i = 1; i < length - n + 1; ++i) {
cur = cur.next;
}
cur.next = cur.next.next;
ListNode ans = dummy.next;
return ans;
}
public int getLength(ListNode head) {
int length = 0;
while (head != null) {
++length;
head = head.next;
}
return length;
}
}
删除链表的重复节点:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return head;
}
ListNode dummy = new ListNode(0, head);
ListNode cur = dummy;
while (cur.next != null && cur.next.next != null) {
if (cur.next.val == cur.next.next.val) {
int x = cur.next.val;
while (cur.next != null && cur.next.val == x) {
cur.next = cur.next.next;
}
} else {
cur = cur.next;
}
}
return dummy.next;
}
}
总结:什么情况下会用到虚拟头结点,只有用在链表的首地址有可能发生改变的情况。