5個常見的連結串列操作
阿新 • • 發佈:2018-12-20
1 單鏈表翻轉
1.1 迭代版本
時間複雜度 O(n),空間複雜度 O(1)
ListNode ReverseList(ListNode head)
{
ListNode temp = null, nextNode = null;
while(head != null) {
nextNode = head;
head.setNext(temp);
temp = head;
head = nextNode;
}
return temp;
}
1.2
public static Node reverse(Node list){
Node headNode = null;
Node preNode = null;
Node currNode = list;
while( currNode != null) {
Node nextNode = currNode.next;
if(nextNode == null) {
headNode = currNode;
}
currNode.next = preNode;
preNode = currNode;
currNode = nextNode;
}
return headNode;
}
2 連結串列中環的檢測
Floyd 環判定法,使用在連結串列中具有不同移動速度的指標,一旦它們進入環就會相遇,即表示存在環。
boolean DoesListContainsLoop(ListNode head){
//時間複雜度O(n),空間複雜度O(1)
if(head==null) return fasle;
ListNode slowPtr=fastPtr=head;
while(fastPtr.getNext()!=null && fastPtr.getNext().getNext()!=null) {
slowPtr=slowPtr.getNext();
fastPtr=fastPtr.getNext().getNext();
if(slowPtr==fastPtr)
return true;
}
return false;
}
public static boolean checkCircle(Node list){
if(list == null) return false;
Node fast = list.next;
Node slow = list;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(slow == fast) return true;
}
return fasle;
}
2.1 判斷給定的連結串列是否存在環,如果存在,找到環的起始點
思路分析:在找到環後,初始化 slowPtr 使其指向表頭節點。然後slowPtr 和 fastPtr 從各自的位置開始移動,每次只移動一個節點,它們相遇的位置就是環的起始位置。(可以用這種方法刪除環)
ListNode FindBerginofLoop(ListNode head){
// 時間複雜度 O(n),空間複雜度 O(1)
if(head==null)
return null;
ListNode slowPtr=fastPtr=head;
boolean loopExists=false;
while(fastPtr.getNext()!=null && fastPtr.getNext().getNext()!=null) {
slowPtr = slowPtr.getNext();
fastPtr = fastPtr.getNext().getNext();
if(slowPtr==fastPtr) {
loopExists = true;
break;
}
}
if(loopExists) {
slowPtr=head;
while(slowPtr!=fastPtr) {
slowPtr = slowPtr.getNext();
fastPtr = fastPtr.getNext();
}
return slowPtr; //返回環的開始節點
}
return null; //環不存在
}
2.2 在Floyd 環判定演算法中,如果兩個指標每次分別移動2個節點和3個節點,而不是移動1個節點和2個節點,演算法任然有效嗎?
有效,但是複雜度可能增加。
2.3 判定給定的連結串列中是否存在環,若存在,返回環的長度
在找到連結串列中存在環後,保持slowPtr的指標不變,fastPtr 指標繼續移動。每次移動 fastPtr 指標時,計數器變數加 1 ,直到 再一次回到 slowPtr 指標所在的位置。
// 時間複雜度 O(n),空間複雜度 O(1)
int findLoopLength(ListNode head){
ListNode slowPtr = fastPtr = heaed;
boolean loopExists = fasle;
int count = 0;
if(head == null) return 0;
while(fastPtr.getNext() != null && fastPtr.getNext().getNext() != null) {
slowPtr = slowPtr.getNext();
fastPtr = fastPtr.getNext().getNext();
if(slowPtr == fastPtr) {
loopExists = true;
break;
}
}
if(loopExists) {
fastPtr = fastPtr.getNext();
count++;
while(slowPtr != fastPtr) {
fastPtr = fastPtr.getNext();
count++;
}
count++;
return count;
}
return 0;
}
3 兩個有序連結串列的合併
3.1 方法1
// 第一個 while 迴圈,將l1 和 l2 進行比較,誰小就合併到 listNode,直到l1 或者 l2 為空
// 第二、三個 while 迴圈 將了l1 或者l2 剩下的節點合併到 listNode
ListNode mergeSortedList(ListNode l1,ListNode l2){
ListNode listNode = new ListNode(0);
ListNode head = listNode;
while(l1 != null && l2 != null) {
if(l1.data <= l2.data) {
listNode.next = l1;
l1 = l1.next;
}else{
listNode.next = l2;
l2 = l2.next;
}
listNode = listNode.next;
}
while(l1 != null) {
listNode.next = l1;
l1 = l1.next;
listNode = listNode.next;
}
while(l2 != null) {
listNode.next = l2;
l2 = l2.next;
listNode = listNode.next;
}
return head.next;
}
3.2 方法2 遞迴法
//分治思想,每次拿一個小的出來,每次的動作相同
ListNode MergeLists(ListNode a,ListNode b){
ListNode result = null;
if(a==null) return b;
if(b==null) return a;
if(a.getData() <= b.getData()) {
result = a;
result.setNext(MergeLists(a.getNext(),b));
}else{
result = b;
result.setNext(MergeLists(b.getNext(),a));
}
return result;
}
3.3
public static Node mergeSortedLists(Node la,Node lb){
if(la == null) return lb;
if(lb == null) return la;
Node p = la;
Node q = lb;
Node head;
if(p.data < q.data) {
head = p;
p = p.next;
}else{
head = q;
q = q.next;
}
Node r = head;
while(p != null && q!= null) {
if(p.data < q.data) {
r.next = p;
p = p.next;
}else{
r.next = q;
q = q.next;
}
r = r.next;
}
if(p != null) {
r.next = p;
}else{
r.next = q;
}
return head;
}
4 刪除連結串列倒數第 n 個節點
public static Node deleteLastKth(Node list,int k){
Node fast = list;
int i = 1;
while( fast != null && i < k) {
fast = fast.next;
++i;
}
if(fast == null) return list;
Node slow = list;
Node pre = null;
while(fast.next != null) {
fast = fast.next;
pre = slow;
slow = slow.next;
}
if(pre == null) {
list = list.next;
}else{
pre.next = pre.next.next;
}
return list;
}
5 求連結串列的中間節點
5.1 蠻力法
在連結串列中對每個節點統計其後的節點的個數,然後判定其是否為中間節點。 時間複雜度 O(N^2),空間複雜度 O(1)
5.2 一次掃描搞定
時間複雜度 O(n),空間複雜度 O(1) 讓第一個指標的移動速度是另一個的2倍,當第一個到達表尾的時候,另一個指標則指向中間節點。
ListNode findMiddle(ListNode head){
ListNode slowPtr = fastPtr = head;
//不斷迴圈,直到達到表尾(next的後繼指標為 null,表示達到最後一個節點)
int i = 0;
while(fastPtr.getNext() != null) {
if(i == 0) {
fastPtr = fastPtr.getNext();
i = 1;
}
if(i == 1) {
fastPtr = fastPtr.getNext();
slowPtr = slowPtr.getNext();
i = 0;
}
}
return slowPtr;
}
public static Node findMidNode(Node list){
if(list == null) return null;
Node first = list;
Node slow = list;
while(fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}