連結串列專題——面試中常見的連結串列問題

Zhaoxi_Zhang發表於2018-12-11

宣告: 連結串列定義如下:

//Java:
class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
複製程式碼
//C++:
typedef struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
}ListNode;
複製程式碼

 

從無頭單連結串列中刪除節點

詳情: 給定一個沒有頭指標的單連結串列,一個指標指向此單連結串列中間的一個節點(不是第一個,也不是最後一個節點),請將該節點從單連結串列中刪除。 題解: 解法一:由於單連結串列並沒有給出頭指標,因此我們無法通過遍歷連結串列的方式找到該節點的前一個節點來改變其 next 指向去指向該節點的 next 節點。換一種思路,我們可以將該節點的元素值全部替換成其 next 節點,然後刪除 next 節點,這樣就相當於把該節點刪除了。

//Java
public void deleteRandomNode(ListNode currentNode) {
    ListNode nextNode = currentNode.next;
    if (nextNode != null) {
        currentNode.val = nextNode.val;
        currentNode.next = nextNode.next;
    }
    nextNode = null;
}
複製程式碼
//C++
void deleteRandomNode(ListNode *current){
	ListNode *next = current->next;
	if (next != NULL){
		current->val = next->val;
		current->next = next->next;
	}
	delete next;
}
複製程式碼

 

反轉連結串列

詳情: 給定一個連結串列的頭指標,要求只遍歷一次,將單連結串列中的元素順序反轉過來。 題解: 解法一:題目較為簡單,每次反轉的時候記錄下一個節點的指標

//Java
public ListNode ReverseList(ListNode head) {
    ListNode pre = null, next = null;
    while (head != null) {
        next = head.next;
        head.next = pre;
        pre = head;
        head = next;
    }
    return pre;
}
複製程式碼
//C++
ListNode *ReverseList(ListNode *pHead) {
    ListNode *current = NULL, *prev = NULL;
    while (pHead != NULL) {
        current = pHead;
        pHead = pHead->next;
        current->next = prev;
        prev = current;
    }
    return current;
}
複製程式碼

 

兩個連結串列的第一個公共節點

詳情: 輸入兩個連結串列,找出它們的第一個公共節點

題解: 解法一:為了找到兩個連結串列的公共節點,那麼我們可以從尾往頭遍歷查詢,但是隻給了我們頭節點,因此類似於棧的先進後出,因此我們可以用兩個棧來儲存節點,然後從棧中取出節點進行比較。 解法二:統計兩個連結串列的長度 len1 和 len2,讓較長的連結串列先走abs(len1 - len2)長度,之後二者同時繼續往下遍歷,查詢第一個公共節點。

//C++
ListNode *FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2) {
    int len1 = SizeLinkedList(pHead1);
    int len2 = SizeLinkedList(pHead2);

    if (len1 > len2) {
        pHead1 = walker(pHead1, len1 - len2);
    } else {
        pHead2 = walker(pHead2, len2 - len1);
    }

    while (pHead1->val != pHead2->val) {
        pHead1 = pHead1->next;
        pHead2 = pHead2->next;
    }

    return pHead1;
}

int SizeLinkedList(ListNode *head) {
    if (head == NULL)   return 0;
    int size = 0;
    ListNode *current = head;
    while (current != NULL) {
        size++;
        current = current->next;
    }
    return size;
}

ListNode *walker(ListNode *head, int cnt) {
    while (cnt--) {
        head = head->next;
    }
    return head;
}
複製程式碼

 

判斷給定連結串列是否存在環

詳情: 給定一個連結串列,判斷這個連結串列是否存在環

題解: 解法一:Floyd判圈演算法

//C++
bool hasRing(ListNode *pHead){
	bool hasRing = false;
	ListNode *fast = pHead, *slow = pHead;
	while (fast != NULL && fast->next != NULL){
		fast = fast->next->next;
		slow = slow->next;
		if (fast == slow)	hasRing = true;
	}
	return hasRing;
}
複製程式碼

 

連結串列中環的入口節點

詳情: 給一個連結串列,若其中包含環,請找出該連結串列的環的入口結點,否則,輸出null。 題解: 解法一:Floyd判圈演算法

//Java
public ListNode EntryNodeOfLoop(ListNode pHead) {
    ListNode fast = pHead, slow = pHead;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow)   break;
    }
    if (fast == null || fast.next == null)  return null;
    fast = pHead;
    while (fast != slow) {
        fast = fast.next;
        slow = slow.next;
    }

    return fast;
}
複製程式碼
//C++
ListNode *EntryNodeOfLoop(ListNode *pHead) {
    ListNode *slow = pHead, *fast = pHead;
    while (fast != NULL && fast->next != NULL) {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)   break;
    }
    if (fast == NULL || fast->next == NULL)   return NULL;
    fast = pHead;
    while (fast != slow) {
        fast = fast->next;
        slow = slow->next;
    }
    return fast;
}
複製程式碼

 

判斷兩個連結串列是否相交

詳情: 給定兩個單連結串列的頭指標,判斷這兩個連結串列是否相交。

題解: 解法一:若兩個連結串列相交,則連結串列的最後一個節點一定是公共的,因此可以利用這個性質求解。

//C++
bool isIntersect(ListNode *pHead1, ListNode *pHead2){
	if (pHead1 == NULL || pHead2 == NULL)	return false;
	while (pHead1->next != NULL)	pHead1 = pHead1->next;
	while (pHead2->next != NULL)	pHead2 = pHead2->next;
	if (pHead1 == pHead2)	return true;
	return false;
}
複製程式碼

解法二:由於都是單項鍊表,也就是都沒有環,那麼我們可以把第一個連結串列連結到第二個連結串列後面,如果新的連結串列有環,證明了有公共節點。

//C++
bool isIntersect(ListNode *pHead1, ListNode *pHead2){
	if (pHead1 == NULL || pHead2 == NULL)	return false;
	pHead1->next = pHead2;
	return hasRing(pHead1);
}
複製程式碼

 

判斷兩個連結串列是否相交變形

詳情: 給定兩個有環連結串列的頭指標,判斷這兩個連結串列是否相交。

題解: 解法一:對於有環連結串列,如果相交,存在以下幾種情況:

連結串列專題——面試中常見的連結串列問題
因此,找到連結串列的入口節點,判斷是否相等,對應情形一和二,對於三,我們可以固定一個節點,然後遍歷連結串列來判斷是否存在相交。

//C++
bool isIntersect(ListNode *pHead1, ListNode *pHead2){
	if (pHead1 == NULL || pHead2 == NULL)	return false;
	ListNode *entry1 = EntryNodeOfLoop(pHead1);
	ListNode *entry2 = EntryNodeOfLoop(pHead2);
	
	if (entry1 == entry2)	return true;
	else{
		ListNode *backup = entry2;
		do
		{
			entry2 = entry2->next;
		}while (entry2 != entry1 && entry2 != backup);
		return entry2 != backup;
	}
}
複製程式碼

 

合併兩個排序的連結串列

詳情: 輸入兩個單調遞增的連結串列,輸出兩個連結串列合成後的連結串列,當然我們需要合成後的連結串列滿足單調不減規則。

題解: 解法一:

//Java
public ListNode Merge(ListNode list1, ListNode list2) {
    if (list1 == null) {
        return list2;
    }
    if (list2 == null) {
        return list1;
    }

    ListNode prev = null;
    ListNode root = list1.val < list2.val ? list1 : list2;
    while (list1 != null && list2 != null) {
        if (list1.val < list2.val) {
            if (prev == null) {
                prev = list1;
            } else {
                prev.next = list1;
                prev = list1;
            }
            list1 = list1.next;
        } else {
            if (prev == null) {
                prev = list2;
            } else {
                prev.next = list2;
                prev = list2;
            }
            list2 = list2.next;
        }
    }
    while (list1 != null) {
        prev.next = list1;
        prev = list1;
        list1 = list1.next;
    }
    while (list2 != null) {
        prev.next = list2;
        prev = list2;
        list2 = list2.next;
    }

    return root;
}
複製程式碼
//C++
ListNode *Merge(ListNode *pHead1, ListNode *pHead2) {
    if (pHead1 == NULL) return pHead2;
    if (pHead2 == NULL) return pHead1;
    ListNode *prev = NULL;
    ListNode *root = pHead1->val < pHead2->val ? pHead1 : pHead2;
    while (pHead1 != NULL && pHead2 != NULL) {
        if (pHead1->val < pHead2->val) {
            if (prev == NULL) {
                prev = pHead1;
            } else {
                prev->next = pHead1;
                prev = pHead1;
            }
            pHead1 = pHead1->next;
        } else {
            if (prev == NULL) {
                prev = pHead2;
            } else {
                prev->next = pHead2;
                prev = pHead2;
            }
            pHead2 = pHead2->next;
        }
    }

    while (pHead1 != NULL) {
        prev->next = pHead1;
        prev = pHead1;
        pHead1 = pHead1->next;
    }
    while (pHead2 != NULL) {
        prev->next = pHead2;
        prev = pHead2;
        pHead2 = pHead2->next;
    }
    return root;
}
複製程式碼

相關文章