這次做了鏈表五個習題:
1、單鏈表反轉
2、鏈表中環的檢測
3、兩個有序的鏈表合併
4、刪除鏈表倒數第 n 個結點
5、求鏈表的中間結點
需要用到下列兩個鏈表類:
package com.freshbin.dataStructAndAlgo.chapter06.mycode.Link.LRU;
/**
* @author freshbin
* @date 2020/4/11 21:59
*/
public class MyNode {
public String token;
public MyNode next;
public MyNode(String token) {
this.token = token;
}
public String getToken() {
return this.token;
}
public void setToken(String token) {
this.token = token;
}
public MyNode getNext() {
return this.next;
}
public void setNext(MyNode next) {
this.next = next;
}
@Override
public String toString() {
return "MyNode{" +
"token='" + token + '\'' +
", next=" + next +
'}';
}
}
package com.freshbin.dataStructAndAlgo.chapter06.mycode.Link.palindromicString;
import com.freshbin.dataStructAndAlgo.chapter06.mycode.Link.LRU.MyNode;
/**
* 使用鏈表實現LRU緩存淘汰策略
* @author freshbin
* @date 2020/4/11 21:24
*/
public class MyLink {
public MyNode firstNode;
public MyLink() {
}
/**
* 每次都從頭結點插入
* @param value
* @return
*/
public MyNode addLink(String value) {
MyNode myNode = new MyNode(value);
MyNode oldFirstNode = this.firstNode;
this.firstNode = myNode;
this.firstNode.next = oldFirstNode;
return this.firstNode;
}
/**
* 每次都從尾部寫入
* @param value
* @return
*/
public MyNode addTailLink(String value) {
MyNode myNode = new MyNode(value);
if(this.firstNode == null) {
this.firstNode = myNode;
return this.firstNode;
}
MyNode nextNode = this.firstNode;
while(nextNode != null) {
if(nextNode.next == null) {
break;
}
nextNode = nextNode.next;
}
nextNode.next = myNode.next;
nextNode.next = myNode;
return this.firstNode;
}
public MyNode getFirstNode() {
return this.firstNode;
}
public void setFirstNode(MyNode firstNode) {
this.firstNode = firstNode;
}
}
完成代碼如下:
package com.freshbin.dataStructAndAlgo.chapter06.mycode.Link.exam;
import com.freshbin.dataStructAndAlgo.chapter06.mycode.Link.LRU.MyNode;
import com.freshbin.dataStructAndAlgo.chapter06.mycode.Link.palindromicString.MyLink;
/**
* @author freshbin
* @date 2020/4/12 15:34
*/
public class MyLinkExam {
/**
* 單鏈表方式實現翻轉單鏈表
* 思路:需要添加一個前節點用於輔助
* 將當前節點的下一節點指向前節點,將前節點和當前節點都往後移動。
* @param node
* @return
*/
public static MyNode reversalLink(MyNode node) {
if(node == null || node.next == null) {
return node;
}
MyNode preNode = null;
MyNode currentNode = node;
MyNode nextNode = null;
while(currentNode != null) {
nextNode = currentNode.next;
currentNode.next = preNode;
preNode = currentNode;
currentNode = nextNode;
}
return preNode;
}
/**
* 檢測鏈表中是否有環的操作
* 一開始我連這題目是什麼意思都不知道,後來搜了一下,才恍然大悟,下面列一下大神們的解法,
* 圖解見此大佬的知乎回答https://zhuanlan.zhihu.com/p/103626709
* 思路:設置一快一慢指針,快指針是慢指針的兩倍。當快指針與慢指針能相遇的時候,說明有環,該點就是鏈表的環內節點,反之沒有環
* 當快慢指針相遇之後,將兩個指針分別重新定位到頭結點和相遇節點,兩個指針繼續往前走,這次都一步一步的走
* 當兩個指針再次相遇的時候,這個點就是環入口節點
*
* @param pHead
* @return
*/
public static MyNode getLinkRingNode(MyNode pHead) {
if(pHead == null || pHead.next == null || pHead.next.next == null) {
return null;
}
MyNode ringNode = null;
MyNode slowNode = pHead.next;
MyNode fastNode = pHead.next.next;
while(slowNode != fastNode) {
if(fastNode == null || fastNode.next == null || fastNode.next.next == null) {
// 快指針一定走得比慢指針快,所以這裏只要判斷快指針爲空的話,那麼就說明鏈表中沒有環,直接退出
return null;
}
slowNode = slowNode.next;
fastNode = fastNode.next.next;
}
// 執行到這裏, 說明鏈表有環,而且快慢指針相遇了,這時候,分別兩個指針放到頭結點和相遇節點,繼續往前走
slowNode = pHead;
while(slowNode != fastNode) {
slowNode = slowNode.next;
fastNode = fastNode.next;
}
// 退出循環執行到這裏,說明已經在環節點相遇了,直接賦值返回即可
ringNode = slowNode;
return ringNode;
}
/**
* 合併兩個有序鏈表
* 思路:1、當兩個鏈表都不爲空,首先遍歷鏈表1,如果值小於等於鏈表2當前節點值,那麼就將鏈表遍歷寫入新鏈表
* 2、遍歷鏈表2,如果值小於等於鏈表1的當前節點值,那麼就寫入新鏈表
* 3、繼續步驟1
* 4、遍歷鏈表1,將值全部寫入新鏈表,遍歷鏈表2,將值全部寫入新鏈表
* @param p1Node
* @param p2Node
* @return
*/
public static MyNode mergeOrderLink(MyNode p1Node, MyNode p2Node) {
MyNode pHeadNode = null;
MyNode currentNode = null;
if(p1Node == null) {
return p2Node;
}
if(p2Node == null) {
return p1Node;
}
if(Integer.valueOf(p1Node.token) <= Integer.valueOf(p2Node.token)) {
pHeadNode = new MyNode(p1Node.token);
p1Node = p1Node.next;
} else {
pHeadNode = new MyNode(p2Node.token);
p2Node = p2Node.next;
}
MyNode returnNode = pHeadNode;
while(p1Node != null && p2Node != null) {
while(p1Node != null && p2Node != null && Integer.valueOf(p1Node.token) <= Integer.valueOf(p2Node.token)) {
currentNode = new MyNode(p1Node.token);
pHeadNode.next = currentNode;
pHeadNode = pHeadNode.next;
p1Node = p1Node.next;
}
while(p1Node != null && p2Node != null && Integer.valueOf(p2Node.token) <= Integer.valueOf(p1Node.token)) {
currentNode = new MyNode(p2Node.token);
pHeadNode.next = currentNode;
pHeadNode = pHeadNode.next;
p2Node = p2Node.next;
}
}
while(p1Node != null) {
currentNode = new MyNode(p1Node.token);
pHeadNode.next = currentNode;
pHeadNode = pHeadNode.next;
p1Node = p1Node.next;
}
while(p2Node != null) {
currentNode = new MyNode(p2Node.token);
pHeadNode.next = currentNode;
pHeadNode = pHeadNode.next;
p2Node = p2Node.next;
}
return returnNode ;
}
/**
* 刪除鏈表倒數第 n 個結點
* 先遍歷一遍鏈表,記錄鏈表總長度,求倒數第n個,就是求鏈表總數減去倒數第n個之後的值
* @param pHeadNode
* @return
*/
public static MyNode deleteBackwardNode(MyNode pHeadNode, int deleteIndex) {
if(pHeadNode == null || deleteIndex < 1) {
return null;
}
MyNode countLengthNode = pHeadNode;
int length = 0;
int targetIndex = 0;
while (countLengthNode != null) {
length++;
countLengthNode = countLengthNode.next;
}
if(deleteIndex > length) {
return null;
}
targetIndex = length - deleteIndex;
if(targetIndex == 0) {
// 如果是刪除頭結點,這裏直接把頭結點刪除,這樣下面的代碼邏輯會比較好操作
pHeadNode = pHeadNode.next;
return pHeadNode;
}
// 當前鏈表從1開始,因爲頭結點在上面已經判斷過了
int currentIndex = 1;
MyNode nextNode = pHeadNode.next;
MyNode preNode = pHeadNode;
while (nextNode != null) {
if(targetIndex == currentIndex) {
System.out.println("需要刪除的節點值:" + nextNode.token);
preNode.next = nextNode.next;
return pHeadNode;
}
currentIndex++;
preNode = nextNode;
nextNode = nextNode.next;
}
return pHeadNode;
}
/**
* 求鏈表的中間結點
* 思路:用快慢指針,快指針每次步長爲2,慢指針步長爲1
* 快慢指針一直前進,判斷快指針的下一個節點是否爲空,
* 如果爲空,說明到尾節點了,而且該鏈表是雙數鏈表,中間節點有兩個,就是當前慢指針的節點和下一節點
* 如果不爲空,快慢指針繼續前進,如果退出循環,那麼表明該鏈表爲單數鏈表,中間節點爲慢指針當前節點
*
* @param pHeadNode
* @return
*/
public static MyNode getMiddleNode(MyNode pHeadNode) {
if(pHeadNode == null) {
return pHeadNode;
}
MyNode deleteNode = null;
MyNode slowNode = pHeadNode;
MyNode fastNode = pHeadNode.next;
MyNode middleNode = null;
while(fastNode != null) {
if(fastNode.next == null) {
// 說明是雙數鏈表,中間節點爲慢指針和其下一節點
middleNode = slowNode;
if(middleNode.next != null && middleNode.next.next != null) {
middleNode.next.next = null;
}
return middleNode;
}
slowNode = slowNode.next;
fastNode = fastNode.next.next;
}
// 退出循環,說明是單數鏈表,中間節點爲當前慢節點
middleNode = slowNode;
middleNode.next = null;
return middleNode;
}
public static void main(String[] arg) {
// 鏈表反轉
String[] reversalLinkTestData = new String[]{"a", "b", "c", "d", "e"};
// reversalLinkTest(reversalLinkTestData);
String[] ringLinkData = new String[]{"a", "b", "c", "d", "b"};
// 獲取鏈表中的環入口節點
// getLinkRingNodeTest(ringLinkData);
// 合併兩個有序鏈表
String[] orderlyLink01 = new String[]{"1", "3", "5", "7", "9"};
String[] orderlyLink02 = new String[]{"0", "2", "4", "6", "8"};
// mergeOrderLinkTest(orderlyLink01, orderlyLink02);
// 刪除鏈表的倒數第n個節點
String[] deleteLinkData = new String[]{"1", "2", "3", "4", "5"};
// deleteBackwardNodeTest(deleteLinkData, 4);
// 求鏈表的中間結點
// String[] getMiddleNodeData = new String[]{"1"};
// String[] getMiddleNodeData = new String[]{"1", "2"};
// String[] getMiddleNodeData = new String[]{"1", "2", "3"};
String[] getMiddleNodeData = new String[]{"1", "2", "3", "4"};
getMiddleNodeTest(getMiddleNodeData);
}
private static void getMiddleNodeTest(String[] getMiddleNodeData) {
MyLink myLink = initLinkFromTail(getMiddleNodeData);
MyNode middleNode = getMiddleNode(myLink.firstNode);
System.out.println("中間節點值爲:");
while (middleNode != null) {
System.out.print(middleNode.token + "->");
middleNode = middleNode.next;
}
}
private static void deleteBackwardNodeTest(String[] deleteLinkData, int deleteBackwardIndex) {
MyLink myLink = initLinkFromTail(deleteLinkData);
MyNode pHeadNode = deleteBackwardNode(myLink.firstNode, deleteBackwardIndex);
displayLink(pHeadNode);
}
private static void mergeOrderLinkTest(String[] orderlyLink01, String[] orderlyLink02) {
MyLink p1Link = initLinkFromTail(orderlyLink01);
MyLink p2Link = initLinkFromTail(orderlyLink02);
MyNode pHeadNode = mergeOrderLink(p1Link.firstNode, p2Link.firstNode);
displayLink(pHeadNode);
}
private static void displayLink(MyNode pHeadNode) {
System.out.println("鏈表數據:");
while (pHeadNode != null) {
System.out.print(pHeadNode.token + "->");
pHeadNode = pHeadNode.next;
}
System.out.println();
}
private static void getLinkRingNodeTest(String[] ringLinkData) {
MyNode pHeadNode = new MyNode("a");
MyNode bNode = new MyNode("b");
MyNode cNode = new MyNode("c");
MyNode dNode = new MyNode("d");
pHeadNode.next = bNode;
bNode.next = cNode;
cNode.next = dNode;
dNode.next = bNode;
MyNode ringNode = getLinkRingNode(pHeadNode);
System.out.println("環入口節點的值爲:" + ringNode.token);
}
private static void reversalLinkTest(String[] reversalLinkTestData) {
MyLink myLink = initLinkFromHead(reversalLinkTestData);
System.out.print("原鏈表數據:");
MyNode oldLink = myLink.firstNode;
while(oldLink != null) {
System.out.print(oldLink.token + "->");
oldLink = oldLink.next;
}
System.out.println();
MyNode myNode = reversalLink(myLink.firstNode);
System.out.print("新鏈表數據:");
while(myNode != null) {
System.out.print(myNode.token + "->");
myNode = myNode.next;
}
}
public static MyLink initLinkFromHead(String[] data) {
MyLink myLink = new MyLink();
for(int i = 0; i < data.length; i++) {
myLink.addLink(data[i]);
}
return myLink;
}
public static MyLink initLinkFromTail(String[] data) {
MyLink myLink = new MyLink();
for(int i = 0; i < data.length; i++) {
myLink.addTailLink(data[i]);
}
return myLink;
}
}