包含单链表的各种插入、包含和删除关键字key、判断是否有环、是否是回文结构、环的相遇点以及环的入口点、和合并有序的两个单链表等等
目录
11、实现链表左边小于x值,右边大于等于x值(或左边大于x值,右边小于等于x值)
1、单链表的头、尾插法
头插法
头插法分为第一次插入和不是第一次插入
①第一次插入:直接将插入的节点node 赋值给头节点head
②不是第一次插入:将头节点head 连到插入节点 node.next
尾插法
尾插法也是分为第一次插入和不是第一次插入
①第一次插入:直接将插入的节点node 赋值给头节点head,这跟头插法的第一次插入是一样得
②不是第一次插入:定义一个新的节点cur指向头节点,
遍历cur节点,找到链表的的最后一个节点
最后再使插入的节点node指向cur.next
/**
* Created with IntelliJ IDEA.
* Description:
*单链表: 由节点组成
*每一个节点都是一个类 有data next
*
*
* @User:Mingaho
* @Date:2021/04/09
* @Time:21:14
*/
class Node {
public int data;
public Node next;
public Node(int data) {
this.data = data;
this.next = null;
}
}
public class MyLinkedList {
public Node head; //保存单链表的头节点的引用 代表的是整个链表的头 所以定义在这个地方
//头插法
public void addFirst(int data) {
Node node = new Node(data);
if(this.head == null) {
//第一次插入节点 第一个节点
this.head = node;
return;
}
node.next = this.head;
this.head = node;
}
//尾插法
public void addLast(int data) {
Node node = new Node(data);
if(this.head == null) {
this.head = node;
return;
}
Node cur = this.head;
while(cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
//打印单链表
public void display() {
Node cur = this.head;
while(cur != null) {
System.out.print(cur.data + " ");
cur = cur.next;
}
System.out.println();
}
}
2、得到单链表的长度
遍历整个链表,当cur不为空时,使计数count++
//得到单链表的长度
public int size() {
int count = 0;
Node cur = this.head;
while (cur != null) {
cur = cur.next;
count++;
}
return count;
}
3、单链表任意位置插入
①利用searchIndex函数实现index的合法性检查和若合法找到index的前驱节点
②准备插入时,有两种情况:一个是index=0 和 index= size()时,可以利用头插和尾插实现插入
③其他的情况,在找到index的前驱位置后,插入要插入的节点node,就实现了index位置的插入
//找index的前驱
private Node searchIndex(int index) {
//先对index进行合法性检查
if(index < 0 || index > this.size()) {
throw new RuntimeException("index's location is not right.");
}
Node cur = this.head;
//找到index的前驱,只需走index - 1步
while (index > 1) {
cur = cur.next;
index--;
}
return cur;
}
//在任意位置插入 第一个数据节点为0号下标
public void addIndex(int index, int data) {
if(index == 0) {
addFirst(data);
return;
}
if(index == this.size()) {
addLast(data);
return;
}
//先找到index的前一个结点的地址
Node cur = searchIndex(index);
//进行插入
Node node = new Node(data);
node.next = cur.next;
cur.next = node;
}
4、清空单链表
//清空
public void clear() {
this.head = null;
}
5、单链表中是否包含key关键字
遍历整个单链表是否包含key节点
public boolean contains(int key) {
Node cur = this.head;
while(cur != null) {
if(cur.data == key) {
return true;
}
cur = cur.next;
}
return false;
}
6、删除第一次出现key的关键字的节点
//找删除第一次出现关键字的前驱
private Node searchPrev(int key) {
Node prev = this.head;
while(prev.next != null) {
if(prev.next.data == key) {
break; //return prev;
}else {
prev = prev.next;
}
}
return prev; //return null;
}
//删除第一次出现key的关键字的节点
public void remove(int key) {
if(this.head == null) { //链表为空 直接return
return;
}
//key如果是头节点 头节点直接指向下一个节点
if(this.head.data == key) {
this.head = this.head.next;
return;
}
Node prev = searchPrev(key);
if(prev.next == null) {
System.out.println("is not exists the key.");
return;
}
Node del = prev.next;
prev.next = del.next;
}
7、删除所有值为key的节点
//删除所有值为key的节点
public void removeAllKey(int key) {
if(this.head == null) {
return;
}
Node prev = this.head;
Node cur = this.head.next; //代表要删除的节点
while(cur != null) {
if(cur.data == key) {
prev.next = cur.next;
cur = cur.next;
}else {
prev = cur;
cur = cur.next;
}
}
if(this.head.data == key) {
this.head = this.head.next;
}
}
8、反向链表
//反向链表
public Node reverseList() {
Node prev = null;
Node cur = this.head;
Node newHead = null;
while(cur != null) {
Node curNext = cur.next;
if(curNext == null) {
newHead = cur;
}
cur.next = prev;
prev = cur;
cur = curNext;
}
return newHead;
}
下面是我牛客网写的另一种反转
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
Stack<ListNode> stack = new Stack<>();
if(head == null) {
return null;
}
ListNode cur = head; //定义一个cur节点 指向头节点
while (cur != null) { //直至链表节点完全压入栈中
stack.push(cur);
cur = cur.next;
}
head.next = null; //原来的头节点置为null
ListNode ret = stack.pop(); //ret为反向链表的头节点 记录下来,用作链表的返回
ListNode cur1 = ret; //node为剩余栈中的节点
while (!stack.isEmpty()) { //直至栈为空 完全pop()出来为止
cur1.next = stack.pop();
cur1 = cur1.next;
}
return ret;
}
}
9、找到链表的中间节点
定义快、慢的节点,fast一次走两步,slow一次走一步
//找到该链表的中间节点
public Node findMinIndexKey() {
Node fast = this.head;
Node slow = this.head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
10、找到倒数第K个节点
先让fast走K-1步,为什么呢?你品,你细品 可以画图看看
然后fast和slow一次走一步,直到不满足fast.next!=null时,退出循环
//找到倒数第K个节点
public Node FindKNode(int K) {
if(this.head == null) {
return null;
}
if(K <= 0) {
System.out.println("K is not right.");
return null;
}
Node fast = this.head;
Node slow = this.head;
while (K - 1 > 0) {
if(fast.next != null) {
fast = fast.next;
K--;
}else {
System.out.println("没有这个节点");
return null;
}
}
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
11、实现链表左边小于x值,右边大于等于x值(或左边大于x值,右边小于等于x值)
定义两个链表 链表1的头节点为begins 尾部节点为beginEnd, 链表2的头节点为as,尾节点为ae,刚开始begins 和beginEnd指向相同位置 as和ae指向相同位置
定义cur指向原先的链表头
小于x值的节点放入链表1中,然后beginEnd指向begins.next
大于x值的节点放入链表2中,然后ae指向as.next
cur遍历完后,再将链表1 和链表2进行拼接,最后返回链表1 的头节点begins就好了
//给定一个x, x的左边或右边 都大于等于/小于等于 x
public Node partition(int x) {
Node begins = null;
Node beginEnd = null;
Node as = null;
Node ae = null;
Node cur = this.head;
while(cur != null) { //没有遍历完
if(cur.data < x) { //小于x时的插入
if(begins != null) { //第一段分为第一次插入还是第二次
beginEnd.next = cur;
beginEnd = beginEnd.next;
}else {
begins = cur;
beginEnd = cur;
}
}else { //大于x时的插入
if(as != null) { //第二段分为第一次插入还是第二次
ae.next = cur;
ae = ae.next;
}else {
as = cur;
ae = cur;
}
}
cur = cur.next;
}
//1、如果bs为空 返回as
if(begins == null) {
return as;
}
//2、如果bs不为空 需要拼接
beginEnd.next = as;
//3、如果ae不为空 ae.next置为null
if(ae != null) {
ae.next = null;
}
return begins;
}
12、删除重复的节点
//删除重复的节点 1->2->2->3->3->4->5 1->4->5
public Node deleteDuplication() {
Node cur = this.head;
Node newHead = new Node(-1);
Node temp = newHead;
while(cur != null) {
if(cur.next != null && cur.data != cur.next.data) {
while(cur.next != null && cur.data == cur.next.data) {
cur = cur.next;
}
cur = cur.next;
}else {
temp.next = cur.next;
temp = temp.next;
cur = cur.next;
}
}
temp.next = null;
return newHead;
}
13、判断链表是否是回文结构
1、找到中间节点
2、反转单链表的后半部分,slow肯定是在中间节点
3、开始一个从头走一个从尾走
以下是基于链表长度是奇数的情况;偶数的话,在第3步加一个if判断就好了
//判断链表是否是回文结构 节点为奇数的时候12321
public boolean chkPalindrome() {
if(this.head == null) { //单链表为空
return false;
}
if(this.head.next == null) {//只有一个节点
return true;
}
//1、找到中间节点
Node fast = this.head;
Node slow = this.head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//2、反转单链表的后半部分,slow肯定是在中间节点
Node cur = slow.next;
while(cur != null) {
Node curNext = cur.next;
cur.next = slow;
slow = cur;
cur = curNext;
}//完了之后slow是最后一个节点了
//3、开始一个从头走一个从尾走
while(slow != this.head) {
if(slow.data != this.head.data) {
return false;
}
if(this.head.next == slow) { //判断偶数的情况
return true;
}
slow = slow.next;
this.head = this.head.next;
}
return true;
}
14、判定链表是否有环
定义快、慢节点fast slow,fast一次走两步,slow一次走一步,当有fast = slow时,链表便有环
//判定链表是否有环
public boolean isHaveCircle() {
Node fast = this.head;
Node slow = this.head;
if (fast == null) {
return false;
}
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast) {
return true; //break;
}
}
//if(fast == null || fast.next == null) {
//return null;
//}
return false;
}
15、两个链表相遇的节点
//创建两个相交的链表
public static void createCut(Node headA, Node headB) {
headA.next =headB.next.next;
}
//两个链表相遇的节点
public static Node getIntersectionNode(Node headA, Node headB) {
//1、求长度 走差值步
int lenA = 0;
int lenB = 0;
Node pl = headA;
Node ps = headB;
while(pl != null) {
lenA++;
pl = pl.next; //求链表A的长度
}
while(ps != null) {
lenB++;
ps = ps.next; //求链表B的长度
}
pl = headA; //重新指向各自头节点,为走差值做准备
ps = headB;
int len = lenA - lenB;
if(len < 0) {
pl = headB;
ps = headA;
len = lenB - lenA;
}
//pl为最长的链表
for(int i = 0; i < len; i++) {
pl = pl.next;
}
//2、pl ps 在同一起跑线上
while(ps != pl && pl != null && ps != null) {
pl = pl.next;
ps = ps.next;
}
if(pl == ps && pl != null) {
return pl;
}
return null;
}
16、链表中环的入口节点
先判断是否有环,也是利用fast、slow快慢节点判断是否有环
经过数学推导:环外的X长度 = 环内的X长度,推导见下图
在有环的情况下 让slow=this.head,然后fast和slow再一步一步走,直到相遇
//链表中环的入口节点
public Node detectCircle() {
Node fast = this.head;
Node slow = this.head;
if(fast == null) {
return null;
}
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(slow == fast) {
break;
}
}
if(fast == null || fast.next == null) {
return null;
}
//以上判断链表有环,但不能判断入口节点在哪里
slow = this.head; //有环的情况下 让slow=this.head
while (fast != slow) { //相遇时既是环的入口点
fast = fast.next;
slow = slow.next;
}
return slow;
}
17、合并有序两个有序的链表
//合并有序两个有序的链表
public static Node mergeTwoLists(Node headA, Node headB) {
Node newHead = new Node(-1);//定义一个虚拟的头节点,
Node temp = newHead;//定义一个temp节点指向newHead
while (headA != null && headB != null) {//循环条件
if(headA.data < headB.data) {
temp.next = headA;
temp = temp.next;
headA = headA.next;
}else {
temp.next = headB;
temp = temp.next;
headB = headB.next;
}
}
if(headA == null) {//以下两个if判断哪个为空了,
temp.next = headB;//就将另一个链表接到temp
}
if(headB == null) {
temp.next = headA;
}
return newHead.next;//newHead的头节点为-1,所以返回newHead.next
}