面試算法代碼知識梳理系列
面試算法知識梳理(1) - 排序算法
面試算法知識梳理(2) - 字符串算法第一部分
面試算法知識梳理(3) - 字符串算法第二部分
面試算法知識梳理(4) - 數(shù)組第一部分
面試算法知識梳理(5) - 數(shù)組第二部分
面試算法知識梳理(6) - 數(shù)組第三部分
面試算法知識梳理(7) - 數(shù)組第四部分
面試算法知識梳理(8) - 二分查找算法及其變型
面試算法知識梳理(9) - 鏈表算法第一部分
面試算法知識梳理(10) - 二叉查找樹
面試算法知識梳理(11) - 二叉樹算法第一部分
面試算法知識梳理(12) - 二叉樹算法第二部分
面試算法知識梳理(13) - 二叉樹算法第三部分
一、概要
本文介紹了有關(guān)鏈表的算法的Java
代碼實(shí)現(xiàn)裕便,所有代碼均可通過 在線編譯器 直接運(yùn)行绒净,算法目錄:
- 新建鏈表
- 反轉(zhuǎn)鏈表(遞歸和非遞歸實(shí)現(xiàn))
- 獲得鏈表倒數(shù)第
k
個(gè)結(jié)點(diǎn) - 獲得鏈表的中間結(jié)點(diǎn)
- 刪除鏈表結(jié)點(diǎn)
- 交換鏈表結(jié)點(diǎn)
在本章的討論當(dāng)中,所有的鏈表都包含一個(gè)頭結(jié)點(diǎn)Node
偿衰,頭結(jié)點(diǎn)不存儲(chǔ)數(shù)據(jù)挂疆,其next
指針指向第一個(gè)普通鏈表結(jié)點(diǎn)改览,每個(gè)普通鏈表結(jié)點(diǎn)包含一個(gè)int
類型的數(shù)據(jù)項(xiàng)。
二缤言、代碼實(shí)現(xiàn)
2.1 新建鏈表
問題描述
輸入一個(gè)int
類型的數(shù)組宝当,通過該數(shù)組創(chuàng)建一個(gè)鏈表,并打印出該鏈表的所有元素胆萧。
解決思路
首先我們創(chuàng)建一個(gè)首結(jié)點(diǎn)header
庆揩,之后通過遍歷數(shù)組p
的方式獲得數(shù)組中元素并創(chuàng)建對應(yīng)的結(jié)點(diǎn)node
,并進(jìn)行兩步操作:
- 將首結(jié)點(diǎn)的當(dāng)前后繼結(jié)點(diǎn)跌穗,作為新結(jié)點(diǎn)的新后繼結(jié)點(diǎn)
- 將新結(jié)點(diǎn)作為首結(jié)點(diǎn)的新后繼結(jié)點(diǎn)
因此盾鳞,最終構(gòu)建出來的鏈表中結(jié)點(diǎn)順序是和原數(shù)組相反的。
代碼實(shí)現(xiàn)
class Untitled {
static class Node {
public Node next;
public int value;
}
static Node createList(int p[], int len) {
Node header = new Node();
Node curNode = null;
for (int i=0; i<len; i++) {
curNode = new Node();
curNode.value = p[i];
//將舊的第一個(gè)普通鏈表結(jié)點(diǎn)作為新結(jié)點(diǎn)的next瞻离。
curNode.next = header.next;
//將新結(jié)點(diǎn)作為第一個(gè)普通鏈表結(jié)點(diǎn)。
header.next = curNode;
}
return header;
}
static void printList(Node header) {
if (header != null) {
Node node = header.next;
while (node != null) {
System.out.println("value=" + node.value);
node = node.next;
}
}
}
public static void main(String[] args) {
int p[] = {1,2,3,4,5};
Node header = createList(p, p.length);
printList(header);
}
}
運(yùn)行結(jié)果
>> value=5
>> value=4
>> value=3
>> value=2
>> value=1
2.2 反轉(zhuǎn)鏈表
問題描述
將輸入的鏈表進(jìn)行反轉(zhuǎn)乒裆,例如在2.1
中創(chuàng)建的鏈表為header->5->4->3->2->1
套利,那么反轉(zhuǎn)后的鏈表為header->1->2->3->4->5
。
解決思路
這里我們介紹兩種方式:非遞歸實(shí)現(xiàn)和遞歸實(shí)現(xiàn)鹤耍。
實(shí)現(xiàn)代碼
class Untitled {
static class Node {
public Node next;
public int value;
}
static Node createList(int p[], int len) {
Node header = new Node();
Node curNode = null;
for (int i=0; i<len; i++) {
curNode = new Node();
curNode.value = p[i];
//將舊的第一個(gè)普通鏈表結(jié)點(diǎn)作為新結(jié)點(diǎn)的next肉迫。
curNode.next = header.next;
//將新結(jié)點(diǎn)作為第一個(gè)普通鏈表結(jié)點(diǎn)。
header.next = curNode;
}
return header;
}
//(1)非遞歸實(shí)現(xiàn)
static void reverseList(Node header) {
if (header == null) {
return;
}
//curNode表示待反轉(zhuǎn)的結(jié)點(diǎn)稿黄。
Node curNode = header.next;
//nextNode表示待反轉(zhuǎn)結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)喊衫。
Node nextNode = null;
//curHeader表示已經(jīng)完成反轉(zhuǎn)的鏈表部分的第一個(gè)普通結(jié)點(diǎn)。
Node curHeader = null;
while (curNode != null) {
nextNode = curNode.next;
curNode.next = curHeader;
curHeader = curNode;
curNode = nextNode;
}
header.next = curHeader;
}
//(2)遞歸實(shí)現(xiàn)
static void reverseListDi(Node header) {
if (header == null) {
return;
}
reverseListDiCore(header.next, header);
}
static Node reverseListDiCore(Node header, Node listHeader) {
if (header.next == null) {
listHeader.next = header;
return header;
}
//下一個(gè)結(jié)點(diǎn)杆怕。
Node nextNode = header.next;
//對下一個(gè)結(jié)點(diǎn)進(jìn)行反轉(zhuǎn)族购。
Node reverseNode = reverseListDiCore(nextNode, listHeader);
//重新確立當(dāng)前結(jié)點(diǎn)和下一個(gè)結(jié)點(diǎn)的關(guān)系。
reverseNode.next = header;
header.next = null;
return header;
}
static void printList(Node header) {
if (header != null) {
Node node = header.next;
while (node != null) {
System.out.println("value=" + node.value);
node = node.next;
}
}
}
public static void main(String[] args) {
int p[] = {1,2,3,4,5};
Node header = createList(p, p.length);
reverseListDi(header);
printList(header);
}
}
運(yùn)行結(jié)果
>> value=1
>> value=2
>> value=3
>> value=4
>> value=5
2.3 獲得鏈表的倒數(shù)第 k 個(gè)結(jié)點(diǎn)
問題描述
輸入一個(gè)鏈表陵珍,返回該鏈表的導(dǎo)入第k
個(gè)結(jié)點(diǎn)(不包括首結(jié)點(diǎn)寝杖,最后一個(gè)結(jié)點(diǎn)為倒數(shù)第1
個(gè)結(jié)點(diǎn)),如果鏈表的長度小于k
互纯,那么返回null
瑟幕。
解決思路
采用 快慢指針 的思想,讓fast
先走k
步留潦,然后slow
指針開始和fast
指針一起走只盹,當(dāng)fast
位于最后一個(gè)結(jié)點(diǎn)時(shí),那么slow
所在的位置就是倒數(shù)第k
個(gè)結(jié)點(diǎn)兔院。
代碼實(shí)現(xiàn)
class Untitled {
static class Node {
public Node next;
public int value;
}
static Node createList(int p[], int len) {
Node header = new Node();
Node curNode = null;
for (int i=0; i<len; i++) {
curNode = new Node();
curNode.value = p[i];
//將舊的第一個(gè)普通鏈表結(jié)點(diǎn)作為新結(jié)點(diǎn)的next殖卑。
curNode.next = header.next;
//將新結(jié)點(diǎn)作為第一個(gè)普通鏈表結(jié)點(diǎn)。
header.next = curNode;
}
return header;
}
static Node getLastKNode(Node header, int k) {
if (k < 1) {
return null;
}
Node fast = header;
Node slow = header;
int step = 0;
while (fast != null && fast.next != null) {
fast = fast.next;
step++;
if (step >= k) {
slow = slow.next;
}
}
return slow != header ? slow : null;
}
static void printList(Node header) {
if (header != null) {
Node node = header.next;
StringBuilder builder = new StringBuilder();
while (node != null) {
builder.append(String.valueOf(node.value));
node = node.next;
if (node != null) {
builder.append("->");
}
}
System.out.println(builder.toString());
}
}
public static void main(String[] args) {
int p[] = {1,2,3,4,5};
Node header = createList(p, p.length);
printList(header);
Node kNode = getLastKNode(header, 4);
System.out.println("KNode=" + (kNode != null ? kNode.value : ""));
}
}
運(yùn)行結(jié)果
>> 5->4->3->2->1
>> KNode=4
2.4 獲得鏈表的中間結(jié)點(diǎn)
問題描述
輸入一個(gè)鏈表秆乳,獲得鏈表的中間結(jié)點(diǎn):
- 如果鏈表的長度為
1
懦鼠,那么返回唯一的一個(gè)結(jié)點(diǎn) - 如果鏈表的長度為偶數(shù)钻哩,那么返回結(jié)點(diǎn)為其第
len/2
個(gè)結(jié)點(diǎn),其中len
為鏈表的長度 - 如果鏈表的長度為奇數(shù)肛冶,那么
len/2
的值為x.5
街氢,取第x.5+0.5
個(gè)結(jié)點(diǎn)作為返回結(jié)點(diǎn)
解決思路
和2.3
類似,采用 快慢指針 的方式睦袖,fast
每次走兩步珊肃,而slow
每次走一步,當(dāng)fast
遍歷到尾結(jié)點(diǎn)時(shí)馅笙,slow
所處的位置就是中間結(jié)點(diǎn)伦乔。
實(shí)現(xiàn)代碼
class Untitled {
static class Node {
public Node next;
public int value;
}
static Node createList(int p[], int len) {
Node header = new Node();
Node curNode = null;
for (int i=0; i<len; i++) {
curNode = new Node();
curNode.value = p[i];
//將舊的第一個(gè)普通鏈表結(jié)點(diǎn)作為新結(jié)點(diǎn)的next。
curNode.next = header.next;
//將新結(jié)點(diǎn)作為第一個(gè)普通鏈表結(jié)點(diǎn)董习。
header.next = curNode;
}
return header;
}
static Node geMiddleNode(Node header) {
if (header == null || header.next == null) {
return null;
}
Node fast = header;
Node slow = header;
while (fast != null) {
fast = fast.next;
if (fast != null) {
fast = fast.next;
} else {
break;
}
slow = slow.next;
}
return slow;
}
static void printList(Node header) {
if (header != null) {
Node node = header.next;
StringBuilder builder = new StringBuilder();
while (node != null) {
builder.append(String.valueOf(node.value));
node = node.next;
if (node != null) {
builder.append("->");
}
}
System.out.println(builder.toString());
}
}
public static void main(String[] args) {
int p[] = {1,2,3};
Node header = createList(p, p.length);
printList(header);
Node kNode = geMiddleNode(header);
System.out.println("KNode=" + (kNode != null ? kNode.value : ""));
}
}
2.5 刪除鏈表結(jié)點(diǎn)
問題描述
輸入一個(gè)鏈表的頭結(jié)點(diǎn)header
烈和,并給出位于該鏈表中的一個(gè)結(jié)點(diǎn)dNode
,要求從鏈表中刪除該結(jié)點(diǎn)皿淋。
解決思路
這個(gè)問題最容易想到的做法就是找到待刪除結(jié)點(diǎn)的前驅(qū)結(jié)點(diǎn)和后繼結(jié)點(diǎn)招刹,讓前驅(qū)結(jié)點(diǎn)的next
指向后繼結(jié)點(diǎn)來實(shí)現(xiàn)刪除,但是對于 待刪除結(jié)點(diǎn)不是尾結(jié)點(diǎn) 的情況窝趣,我們可以采用一個(gè)小技巧:取出待刪除結(jié)點(diǎn)的后繼結(jié)點(diǎn)疯暑,再刪除該后繼結(jié)點(diǎn),這樣就避免了尋找前驅(qū)結(jié)點(diǎn)的過程哑舒。
實(shí)現(xiàn)代碼
class Untitled {
static class Node {
public Node next;
public int value;
}
static Node createList(int p[], int len) {
Node header = new Node();
Node curNode = null;
for (int i=0; i<len; i++) {
curNode = new Node();
curNode.value = p[i];
//將舊的第一個(gè)普通鏈表結(jié)點(diǎn)作為新結(jié)點(diǎn)的next妇拯。
curNode.next = header.next;
//將新結(jié)點(diǎn)作為第一個(gè)普通鏈表結(jié)點(diǎn)。
header.next = curNode;
}
return header;
}
static Node getLastKNode(Node header, int k) {
if (k < 1) {
return null;
}
Node fast = header;
Node slow = header;
int step = 0;
while (fast != null && fast.next != null) {
fast = fast.next;
step++;
if (step >= k) {
slow = slow.next;
}
}
return slow != header ? slow : null;
}
static void deleteNode(Node header, Node dNode) {
if (header == null && dNode != null) {
return;
}
if (dNode.next != null) {
//如果不是尾結(jié)點(diǎn)洗鸵,那么取其后繼結(jié)點(diǎn)的值替換待刪除結(jié)點(diǎn)越锈。
Node rNode = dNode.next;
dNode.value = rNode.value;
dNode.next = rNode.next;
} else {
//如果是尾結(jié)點(diǎn),那么只能采用遍歷的方式预麸。
Node node = header;
while (node.next != null && node.next.next != null) {
node = node.next;
}
node.next = null;
}
}
static void printList(Node header) {
if (header != null) {
Node node = header.next;
StringBuilder builder = new StringBuilder();
while (node != null) {
builder.append(String.valueOf(node.value));
node = node.next;
if (node != null) {
builder.append("->");
}
}
System.out.println(builder.toString());
}
}
public static void main(String[] args) {
int p[] = {1,2,3};
Node header = createList(p, p.length);
printList(header);
Node kNode = getLastKNode(header, 3);
System.out.println("KNode=" + (kNode != null ? kNode.value : ""));
deleteNode(header, kNode);
printList(header);
}
}
運(yùn)行結(jié)果
>> 3->2->1
>> KNode=2
>> 3->1
2.6 交換鏈表結(jié)點(diǎn)
問題描述
給定一個(gè)單鏈表的頭結(jié)點(diǎn)header
瞪浸,和兩個(gè)待交換的鏈表結(jié)點(diǎn)nodeA
和nodeB
,交換這兩個(gè)鏈表結(jié)點(diǎn)
解決思路
交互鏈表結(jié)點(diǎn)的關(guān)鍵吏祸,在于找到這兩個(gè)結(jié)點(diǎn)的前驅(qū)和后繼結(jié)點(diǎn)对蒲,修改它們和對應(yīng)結(jié)點(diǎn)的引用關(guān)系,這里需要注意的是 交換結(jié)點(diǎn)相鄰 的情況贡翘。
代碼實(shí)現(xiàn)
class Untitled {
static class Node {
public Node next;
public int value;
}
static Node createList(int p[], int len) {
Node header = new Node();
Node curNode = null;
for (int i=0; i<len; i++) {
curNode = new Node();
curNode.value = p[i];
//將舊的第一個(gè)普通鏈表結(jié)點(diǎn)作為新結(jié)點(diǎn)的next蹈矮。
curNode.next = header.next;
//將新結(jié)點(diǎn)作為第一個(gè)普通鏈表結(jié)點(diǎn)。
header.next = curNode;
}
return header;
}
static Node getLastKNode(Node header, int k) {
if (k < 1) {
return null;
}
Node fast = header;
Node slow = header;
int step = 0;
while (fast != null && fast.next != null) {
fast = fast.next;
step++;
if (step >= k) {
slow = slow.next;
}
}
return slow != header ? slow : null;
}
static void swapNode(Node header, Node nodeA, Node nodeB) {
if (header == null || nodeA == null || nodeB == null) {
return;
}
if (nodeA == header || nodeB == header) {
return;
}
if (nodeA == nodeB) {
return;
}
//找到nodeA的前驅(qū)結(jié)點(diǎn)
Node preA = header;
while (preA.next != nodeA) {
preA = preA.next;
}
//找到nodeB的前驅(qū)結(jié)點(diǎn)
Node preB = header;
while (preB.next != nodeB) {
preB = preB.next;
}
//nodeA和nodeB的后繼結(jié)點(diǎn)
Node postA = nodeA.next;
Node postB = nodeB.next;
//nodeA是nodeB的后繼結(jié)點(diǎn)
if (preB == nodeA) {
nodeA.next = postB;
nodeB.next = nodeA;
preA.next = nodeB;
//nodeB是nodeA的后繼結(jié)點(diǎn)
} else if (preA == nodeB) {
nodeB.next = postA;
nodeA.next = nodeB;
preB.next = nodeA;
//nodeA和nodeB不相鄰
} else {
preA.next = nodeB;
nodeB.next = postA;
preB.next = nodeA;
nodeA.next = postB;
}
}
static void printList(Node header) {
if (header != null) {
Node node = header.next;
StringBuilder builder = new StringBuilder();
while (node != null) {
builder.append(String.valueOf(node.value));
node = node.next;
if (node != null) {
builder.append("->");
}
}
System.out.println(builder.toString());
}
}
public static void main(String[] args) {
int p[] = {1,2,3,4,5};
Node header = createList(p, p.length);
printList(header);
Node nodeA = getLastKNode(header, 5);
Node nodeB = getLastKNode(header, 1);
swapNode(header, nodeA, nodeB);
printList(header);
}
}
運(yùn)行結(jié)果
>> 5->4->3->2->1
>> 1->4->3->2->5
更多文章鸣驱,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.reibang.com/p/fd82d18994ce
- Android 面試文檔分享:http://www.reibang.com/p/8456fe6b27c4