最近整理了一下关于单链表的各种算法题,代码如下,欢迎大家提问与转载,转载请注明出处
package com.special;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
public class LinkedList {
//新建一个单链表
public ListNode getLinkedList(int n) {
ListNode head = new ListNode(1);
ListNode cur = head;
for(int i = 2;i <= n;i ++) {
ListNode temp = new ListNode(i);
cur.next = temp;
cur = temp;
}
return head;
}
//获取单链表的长度
public int getLinkedListLength(ListNode head) {
if(head == null) return 0;
int count = 0;
ListNode temp = head;
while(temp != null) {
count ++;
temp = temp.next;
}
return count;
}
/*删除链表中某个节点,当该节点不是最后一个节点时,时间复杂度是O(1)
*当该节点是最后一个节点时,需要循环遍历找到该节点的前一个节点,时间复杂度是O(n)
*思路:把当前节点后面一个节点的值赋给当前节点,然后把后面一个节点删除,相当于删除了当前节点
*/
public void deleteNode(ListNode head, ListNode node) {
if(head == null || node == null) return;
if(node.next != null) { //不是最后一个节点
ListNode next = node.next;
node.val = next.val;
node.next = next.next;
}
else {
ListNode pre = head; //是最后一个节点
ListNode cur = head.next;
while(cur != null) {
if(cur == node) {
pre.next = null; //令尾节点的前一个节点的next为空,可以删除尾节点
break;
}
else {
pre = pre.next;
cur = cur.next;
}
}
}
}
//打印单链表
public void printLinkedList(ListNode node) {
while(node != null) {
System.out.print(node.val + " ");
node = node.next;
}
}
/*查找单链表中倒数第k个节点
*快指针先走k步,然后慢指针才开始走
*/
public ListNode findKNode(ListNode node, int k) {
if(node == null || k <= 0) return null;
ListNode fp = node;
int count = 0;
while(fp != null) {
fp = fp.next;
count ++;
if(count == k) {
break;
}
}
if(count < k) { //长度不够
return null;
}
ListNode fp2 = node;
while(fp != null) {
fp = fp.next;
fp2 = fp2.next;
}
return fp2;
}
/*
* 查找单链表的中间节点:思路与前面大同小异,定义两个指针均指向头结点,
* 一个指针一次走两步,当走到头的时候共走了length/2步,
* 另一个指针一次走一步,则当第一个指针走到头的时候
* 比第二个指针多走了length/2步,第二个指针刚好走到中间
*/
public ListNode findMiddleNode(ListNode head) {
if(head == null || head.next == null) return head;
ListNode target = head;
ListNode temp = head;
while(temp.next != null && temp.next.next != null) {
target = target.next;
temp = temp.next.next;
}
return target;
}
//反转单链表
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode pre = head;
ListNode cur = head.next;
ListNode next = null;
while(cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
head.next = null;
return pre;
}
//从尾到头打印单链表:法1,先反转,再顺序打印;法2,借助栈
public void printLinkedListFromTailToHead(ListNode head) {
if(head == null) return;
ListNode node = head;
Stack<ListNode> s = new Stack<ListNode>();
while(node != null) {
s.push(node);
node = node.next;
}
while(!s.isEmpty()) {
ListNode temp = s.pop();
System.out.print(temp.val + " ");
}
}
//合并两个有序的单链表:递归法
public ListNode mergeList(ListNode node, ListNode node2) {
if(node == null && node2 == null) {
return null;
}
if(node == null && node2 != null) {
return node2;
}
if(node != null && node2 == null) {
return node;
}
ListNode head = null;
if(node.val > node2.val) { //node2是头节点
//head = node2;
node2.next = mergeList(node, node2.next);
return node2;
}
else { //node是头节点
//head = node;
node.next = mergeList(node.next, node2);
return node;
}
//return head;
}
//合并两个有序的单链表:非递归法
public ListNode mergeList2(ListNode node, ListNode node2) {
if(node == null && node2 == null) {
return null;
}
if(node == null && node2 != null) {
return node2;
}
if(node != null && node2 == null) {
return node;
}
//拿到头部
ListNode mergeHead = null;
if(node.val > node2.val) {
mergeHead = node2;
node2 = node2.next;
}
else {
mergeHead = node;
node = node.next;
}
ListNode temp = mergeHead;
while(node != null && node2 != null) {
if(node.val > node2.val) {
temp.next = node2;
node2 = node2.next;
}
else {
temp.next = node;
node = node.next;
}
temp = temp.next;
}
if(node != null) { //链表本来就是直接接上去
temp.next = node;
}
if(node2 != null) {
temp.next = node2;
}
return mergeHead;
}
//单链表的归并排序
public ListNode mergeSort(ListNode node) {
if(node == null || node.next == null) return node;
ListNode middleNode = findMiddleNode(node); //获取中间节点
ListNode backStart = middleNode.next;
middleNode.next = null; //拆分链表
return mergeList(mergeSort(node), mergeSort(backStart));
}
/*
* 单链表的插入排序:与数组的插入排序不同,单链表的插入排序只能从前往后遍历(画图),
* 所以要借助辅助节点fake(因为有可能是第一个节点比待插入节点值大),这题非常能考察对链表和指针的理解
*/
public ListNode insertSort(ListNode head) {
if(head == null || head.next == null) return head;
ListNode fakeNode = new ListNode(-1);
fakeNode.next = head;
ListNode pre = head; //前后两个指针用于比较前后两个数的大小
ListNode cur = head.next;
while(pre.next != null) {
if(pre.val < cur.val) { //不用插入,指针右移
pre = pre.next;
cur = cur.next;
}
else {
ListNode insertNode = cur; //将插入节点剥离出来
cur = cur.next;
pre.next = cur;
//从前往后找,找到第一个比cur2大的节点,插到它的后面
ListNode temp = fakeNode;
while(temp.next.val < insertNode.val) {
temp = temp.next;
}
ListNode tempNext = temp.next;
temp.next = insertNode;
insertNode.next = tempNext;
}
}
return head;
}
//判断一个单链表中是否有环:法一,定义快慢指针,若快慢指针能相遇,则说明存在环(类似于环形赛道)。
public boolean hasLoop(ListNode head) {
if(head == null || head.next == null) return false; //若链表仅存在头节点,不认为是环
boolean flag = false;
ListNode pFast = head;
ListNode pSlow = head;
while(pFast.next != null && pFast.next.next != null) { //这个地方不能判断pFast.next.next != null,否则会跳出空指针错误
pFast = pFast.next.next;
pSlow = pSlow.next;
if(pFast == pSlow) {
flag = true;
break;
}
}
return flag;
}
//判断一个单链表中是否有环:法二,当判断某个节点时,定义一个辅助容器暂存前面所有节点,若在辅助容器里找到与该节点相同的节点,则认为存在环。
public boolean hasLoopUsingSet(ListNode head) {
if(head == null || head.next == null) return false; //若链表仅存在头节点,不认为是环
Set<ListNode> set = new HashSet<ListNode>();
ListNode temp = head;
while(temp != null) {
if(set.contains(temp)) {
return true;
}
else {
set.add(temp);
temp = temp.next;
}
}
return false;
}
//已知一个链表里有环,求进入该环的第一个节点
public ListNode getFirstNodeInCycleUsingSet(ListNode head) {
if(head == null || head.next == null) return null;
Set<ListNode> set = new HashSet<ListNode>();
ListNode temp = head;
while(temp != null) {
if(set.contains(temp)) { //Set contains()方法的查询速度比List快很多,不要用List
return temp;
}
set.add(temp);
temp = temp.next;
}
return null;
}
//已知一个链表里有环,求进入该环的第一个节点
public ListNode getFirstNodeInCycle(ListNode head) {
if(head == null || head.next == null) return null;
ListNode pFast = head;
ListNode pSlow = head;
while(pFast.next != null && pFast.next.next != null) { //这个地方不能判断pFast.next.next != null,否则会跳出空指针错误
pFast = pFast.next.next;
pSlow = pSlow.next;
if(pFast == pSlow) {
break;
}
}
//此时pSlow只是环里的一个节点,并不是入口节点,需要从头开始跑求环的入口节点
ListNode temp = head;
while(temp != null) {
if(temp == pSlow) {
return temp;
}
else {
temp = temp.next;
pSlow = pSlow.next;
}
}
return null;
}
/*判断两个单链表是否相交:
*如果链表都无环(链表里没有重复的数据),则先判断链表的尾指针是否一样,如果不一样,则没有相交。(这一步可以不做)
*如果一样,则找出两个链表的长度差,长的链表先走差值的步数,然后两个链表再一起走,
*如果相交,则必然有一处扫描节点相同。
*/
public ListNode getIntersectNode(ListNode node, ListNode node2) {
if(node == null || node2 == null)
return null;
ListNode target = null;
ListNode pTail = findKNode(node, 1);
ListNode pTail2 = findKNode(node2, 1);
if(pTail != pTail2) {
return target;
}
int len = getLinkedListLength(node);
int len2 = getLinkedListLength(node2);
if(len < len2) {
int i = 0;
while(i < len2 - len) {
node2 = node2.next;
i ++;
}
}
else if(len > len2) {
int j = 0;
while(j < len - len2) {
node = node.next;
j ++;
}
}
while(node != null && node2 != null) {
if(node == node2) { //两个节点相等,则其后面的节点也相等
target = node;
break;
}
node = node.next;
node2 = node2.next;
}
return target;
}
/*删除链表的奇数节点
* 1-2-3-4-5,删除后 2-4
* 1-2-3-4-5-6,删除后2-4-6
*/
public ListNode deleteOddNode(ListNode head) {
ListNode fake = new ListNode(-1);
fake.next = head;
ListNode pre = fake;
ListNode cur = null;
while(pre != null && pre.next != null) {
cur = pre.next;
pre.next = cur.next;
pre = pre.next;
}
return fake.next;
}
public static void main(String[] args) {
LinkedList t = new LinkedList();
ListNode l1 = new ListNode(1);
ListNode l2 = new ListNode(2);
ListNode l3 = new ListNode(3);
ListNode l4 = new ListNode(4);
ListNode l5 = new ListNode(5);
l1.next = l2;
l2.next = l3;
l3.next = l4;
l4.next = l5;
// l5.next = l3;
// System.out.println(t.getFirstNodeInCycle(l1).val);
ListNode n1 = new ListNode(6);
// ListNode n2 = new ListNode(4);
// ListNode n3 = new ListNode(5);
n1.next = l4;
ListNode res = t.getIntersectNode(l1, n1);
if(res != null) {
System.out.println(res.val);
}
}
}