链表的一些算法问题
2018-11-21 本文已影响4人
Tomy_Jx_Li
package com.jiyx.tesr.testLinked;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Random;
/**
* 链表工具类
* auther: jiyx
* date: 2018/9/11.
*/
public class LinkedUtils {
/**
* 定义链表节点
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
private static class Node {
private int value;
private Node next;
}
/**
* 创建单向链表
* @param len
* @return
*/
private static Node createUniaxialLinked(int len) {
if (len <= 0) {
return null;
} else {
return new Node(len, createUniaxialLinked(--len));
}
}
/**
* 创建单向带环链表
* @param len
* @return
*/
private static Node createUniaxialLinkedCircle(int len) {
Node linkedCircle = reverse4(createUniaxialLinked(len));
Random random = new Random();
int value = random.nextInt(len);
getTail(linkedCircle).setNext(getNode(linkedCircle, value));
return linkedCircle;
}
/**
* 根据node中的value返回Node对象
*
* @param head
* @param value
* @return
*/
private static Node getNode(Node head, int value) {
Node curr = head;
while (curr != null) {
if (curr.getValue() == value) {
return curr;
}
curr = curr.getNext();
}
return null;
}
/**
* 获取链表的尾节点
* @param head
* @return
*/
private static Node getTail(Node head) {
Node tail = null;
Node curr = head;
while (curr != null) {
tail = curr;
curr = curr.getNext();
}
return tail;
}
/**
* 这里是单向链表,所以如果翻转的时候是从中间节点开始的话,会丢失前面的节点
* 思路:获取当前节点,然后让当前节点指向他的头结点,然后他的下一个节点指向他
* 这个是自己第一时间想到的,是最麻烦的
*
* @param node
* @return
*/
public static Node reverse(Node node) {
Node curr = null;
Node next = null;
Node dNext = node;
Node pNext = null;
while (dNext != null) {
// 设置当前节点
curr = dNext;
// 获取下一位的节点
next = curr.getNext();
// 将当前的链表和已经反转的链表相连
curr.setNext(pNext);
if (next != null) {
// 获取链表的第三个节点,也就是需要进行下次翻转的开头
dNext = next.getNext();
} else {
// 将当前的链表和已经反转的链表相连,这里处理的是只剩一位
return curr;
}
// 翻转
next.setNext(curr);
// 记录已翻转的头结点
pNext = next;
}
return pNext;
}
/**
* 链表翻转2
* 思路:创建一个新的链表,将原链表由头开始,一个个放入新链表即可,贼简单
* @param node
* @return
*/
public static Node reverse2(Node node) {
// 新链表表头
Node newNodeHead = null;
// 当前节点
Node curr = node;
// 下一个节点
Node next = null;
while (curr != null) {
// 获取下一个节点
next = curr.getNext();
// 将当前节点放入新链表中
curr.setNext(newNodeHead);
// 重置new链表的表头
newNodeHead = curr;
// 下一位
curr = next;
}
return newNodeHead;
}
/**
* 链表翻转3
* 思路:移位,如1->2->3->4->5
* 第一次移位,变为2->1->3->4->5
* 第二次移位,变为3->2->1->4->5
* 以此类推,这个也是蛮简单的
* @param node
* @return
*/
public static Node reverse3(Node node) {
// 下一个节点
Node next = node.getNext();
// 头结点
Node head = node;
while (next != null) {
// node一直指向开始的头结点,设置他的下一个节点
node.setNext(next.getNext());
// 将跳过的节点设置到表头
next.setNext(head);
// 重置表头
head = next;
// 下一位
next = node.getNext();
}
return head;
}
/**
* 链表翻转,跟上一个实现的细节不太一样而已
* 思路:同上
* 原链表:1-2-3-4-5
* 第一步:2-1-3-4-5
* 第二部:3-2-1-4-5
* 知道最后
* @param head
*/
public static Node reverse4(Node head){
if (head == null) {
return null;
}
// 尾节点
Node tail = head;
Node next = null;
while (tail.getNext() != null) {
next = tail.getNext();
// 删除next节点
tail.setNext(next.getNext());
// 将next放于表头
next.setNext(head);
// 重新设置头结点
head = next;
}
return next;
}
/**
* 根据步长翻转,这个方法的思路是根据步长截取每段,然后进行翻转,然后拼接.单向链表,这个写起来感觉很麻烦
* @param node
* @param step
* @return
*/
public static Node reverse(Node node, int step) {
// 当前头结点,翻转之后的尾节点
Node currHead = null;
// 下一个头结点
Node nextHead = node;
// 当前结点,用于计算尾节点
Node curr = null;
// 上一个的尾节点
Node pTail = null;
// 这里设置为null是为了为node重新赋值
node = null;
while (nextHead != null) {
// 存储上步的尾节点,为下面使用
pTail = currHead;
curr = currHead = nextHead;
// 根据步长找到尾节点
for (int i = 0; i < step - 1; i++) {
if (curr.getNext() != null) {
curr = curr.getNext();
}
}
// 记录下一个步长开始位置
nextHead = curr.getNext();
// 将当前节点独立出来进行翻转
curr.setNext(null);
// 链表翻转,这里没用接收结果是因为,翻转完之后头变尾,尾变头
reverse(currHead);
if (pTail != null) {
// 由于单链表翻转丢失前端节点,所以这里在翻转完成之后再进行链表的连接
pTail.setNext(curr);
}
// 这里从新设定表头
if (node == null) {
node = curr;
}
}
return node;
}
/**
* 链表按照指定的步长进行翻转
* 思路:
*
* @param head
* @param step
* @return
*/
public static Node reverse7(Node head, int step) {
// 上一个的尾节点
Node pTail = null;
// 翻转后的表头
Node linkedHead = null;
// 表头是否已经设置
boolean headAready = false;
// 是否是第一次设置
boolean isFirst = true;
int count;
while (head != null) {
// 重置步长
count = step;
// 这一部分就是链表发展,思路就是移位。1-2-3-4,2-1-3-4,3-2-1-4
Node tail = head;
Node next = null;
while (tail.getNext() != null && --count > 0) {
next = tail.getNext();
tail.setNext(next.getNext());
next.setNext(head);
head = next;
}
// 赌一次设置表头
if (!headAready) {
linkedHead = head;
headAready = true;
isFirst = false;
}else {
// 不是第一次要讲新的翻转后的链表和上一步的尾节点对接
pTail.setNext(head);
}
pTail = tail;
head = tail.getNext();
}
return linkedHead;
}
/**
* 校验链表中是否有环
* 思想:使用快慢指针进行校验,如果快慢指针重合的话,这个是根据网上资料获取的思路
* @param head
* @return
*/
public static boolean checkLinkedHaveCircle(Node head) {
Node slow = head;
Node faster = head;
while (slow != null && faster != null) {
// 慢指针前进一步
slow = slow.getNext();
// 快指针前进两步
faster = faster.getNext() != null ? faster.getNext().getNext() : null;
if (slow == faster) {
System.out.println("相同的节点value是:" + slow.getValue());
return true;
}
}
return false;
}
/**
* 合并两个有序的链表
* 思路:两个链表进行比较
* @param curr
* @param other
* @return
*/
public static Node mergeSortedLinked(Node curr, Node other) {
// 新表头,这里有一个哨兵节点
Node newNode = new Node();
Node tail = newNode;
while (curr != null && other != null) {
// 将较小的先放入链表
if (curr.getValue() <= other.getValue()) {
tail.setNext(curr);
curr = curr.getNext();
} else {
tail.setNext(other);
other = other.getNext();
}
// 重置表尾
tail = tail.getNext();
}
// 合并完成剩下的
if (curr != null) {
tail.setNext(curr);
}
if (other != null) {
tail.setNext(other);
}
// 返回的时候将哨兵节点剔除
return newNode.getNext();
}
/**
* 删除链表末尾的n个节点
* 思想:
* 链表翻转,删除n个节点,然后再翻转
* @param head
* @param n
*/
public static Node deleteLastNodes(Node head, int n) {
// n小于1
if (n < 1) {
throw new IllegalArgumentException("删除的节点数不能小于等于0");
}
// head为空
if (head == null) {
throw new IllegalArgumentException("节点不能为空");
}
head = reverse4(head);
while (n-- > 0) {
head = head.getNext();
}
// 如果n超出了链表长度
if (head == null && n > 0) {
throw new IllegalArgumentException("n超出了链表长度");
}
return reverse4(head);
}
/**
* 删除链表的第index个节点
* 思路:遍历删除,感觉很蠢
* @param head
* @param index
*/
public static Node deleteNodeByIndex(Node head, int index) {
if (head == null) {
throw new IllegalArgumentException("链表不能为空");
}
if (index == 1) {
return head.getNext();
}
Node curr = head;
// 前一个节点
Node preNode = null;
while (curr != null && --index > 0) {
preNode = curr;
curr = curr.getNext();
}
if (index > 0 && curr == null) {
throw new IllegalArgumentException("index 超出范围");
}
preNode.setNext(curr.getNext());
return head;
}
/**
* 删除链表倒数的第n个节点
* @param head
* @param reverseIndex
* @return
*/
public static Node deleteNodeByReverseIndex(Node head, int reverseIndex) {
head = reverse4(head);
head = deleteNodeByIndex(head, reverseIndex);
return reverse4(head);
}
/**
* 求链表的中间节点
* 思想:利用快慢指针查找中间节点
* @param head
*/
public static Node findMiddleNode(Node head) {
if (checkLinkedHaveCircle(head)) {
throw new IllegalArgumentException("入参链表是环状链表");
}
Node slow = head;
Node high = head;
while (high != null) {
// 慢指针一位
slow = slow.getNext();
// 快指针两位
high = high.getNext() != null ? high.getNext().getNext() : null;
// 奇数位节点
if (high != null && high.getNext() == null) {
return slow;
}
}
return slow;
}
public static void main(String[] args) {
/*Node node = createUniaxialLinked(2);
node = reverse5(node);
node = reverse7(node, 2);
System.out.println();*/
// Node uniaxialLinked = createUniaxialLinked(10);
// Node linkedCircle = createUniaxialLinkedCircle(6);
// System.out.println(checkLinkedHaveCircle(linkedCircle));
// System.out.println(checkLinkedHaveCircle(uniaxialLinked));
// Node fir = createUniaxialLinked(6);
// Node sec = createUniaxialLinked(5);
// fir = reverse5(fir);
// sec = reverse5(sec);
// Node node = mergeSortedLinked(fir, sec);
// sec = reverse4(sec);
// System.out.println(findMiddleNode(sec));
Node sec = createUniaxialLinked(5);
// sec = deleteNodeByIndex(sec, 5);
// sec = deleteLastNodes(sec, 2);
sec = deleteNodeByReverseIndex(sec, 2);
System.out.println();
}
}