目錄
(3)快速排序(親測有效,成功運行)---時復=O(nlogn),空復=O(1)
7、(查找/刪除)鏈表的倒數第k個節點---時間複雜度=O(N),空間複雜度=O(1)
(1)判斷是否有環:頭節點開始,1快1慢,快2步,慢1步,快追上慢則有環
(2)環大小:(1)基礎上,相遇節點開始,1個指針1步步走,直至再次回到該節點
(3)環入口節點:2種法1:(2)基礎上,頭節點開始,1指針先走n步,2指針和1指針同時一步步走,直至相遇
法2:(1)基礎上,快指針回到頭節點,快慢指針同時一步步走,直至相遇
(1)判斷1個鏈表是否有環,有環則返回第一個進入環的節點,無返回null
(2)判斷2個無環鏈表是否相交,相交則返回第一個相交節點,不相交則返回null
(3)判斷2個有環鏈表是否相交,相交則返回第一個相交節點,不相交則返回null
(4)判斷兩個(有環/無環)單鏈表是否相交?相交返回第一個相交點,不相交返回null
1、從尾到頭打印單鏈表
Node * findMiddleNode(Node *head)
{
if(null == head || null == head->next) return head;
Node *p1 = *p2 = head;
while(null != p2->next && null != p2->next->next )
{
p2 = p2->next->next;
p1 = p1->next;
}
return p1;
}
2、單鏈表實現約瑟夫環---時間複雜度=O(1)
暴力數數的方式時間複雜度=O(n*m),從1開始報數,報到m就殺死,下一個數從1開始編號,繼續報數。
n個數最後只活了1個,n-1個節點被殺死,每一個被殺死的數都報數到m,所以計算了(n-1)*m次,約等於n*m
優化方法:
(1)取餘函數(剃刀函數,見圖a):y=x%i
(2)報數x與編號y的關係(見圖b):y=(x-1)%i+1
假設環中有i個節點,報數不斷增大,但環中節點個數有限。當環中有3個節點,報數到5就死(環大小<報數),當報到4時,編號回到環起點1。相當於取餘函數,右移1個,再上移一個,即:y=(x-1)%i+1
報數 | 1 | 2 | ... | i | i+1 | i+2 |
編號 | 1 | 2 | ... | i | 1 | 2 |
(3)新編號x與舊編號y的關係(見圖c):y=(x-1+s)%i+1
最後1個活下來的節點的編號=1,那麼可以反推2個節點活下來時該節點編號,3個活下來....n個活下來...
假如,環節點數=8,報到3就死。相當於(2)左移s個,即:y=(x-1+s)%i+1
舊編號:1,2,3,4,5,6,7,8
新編號:6,7,-,1,2,3,4,5
相當於:
舊編號(i個) | 1 | 2 | ... | s-1 | s | s+1 | ... | i |
新編號(i-1個) | ... | ... | i-2 | i-1 | - | 1 | 2 | ... |
(4)舊編號=(新編號+(m-1))%i+1
A: 編號=(報數-1)%i+1
B: 舊編號=(新編號+s-1)%i+1
C: s=(m-1)%i+1
其中,s是被幹掉的編號,因爲報數到m就死,所以得出的C式。
將C式代入B式,舊編號=(新編號+(m-1)%i)%i+1,中間的%i可以化解掉(具體就不關心啦)
最終,舊編號=(新編號+(m-1))%i+1
public static Node josephusKill2(Node head, int m) {
if (head == null || head.next == head || m < 1) {
return head;
}
Node cur = head.next;
int tmp = 1;//tmp爲環的長度
while (cur != head) {
tmp++;
cur = cur.next;
}
tmp = getLive(tmp, m);
while (--tmp != 0) {//從1走到活着的tmp,走tmp-1步驟
head = head.next;
}
head.next = head;//自己指向自己的環形單鏈表
return head;
}
//獲取舊編號,i=舊環的大小,m是從1報數到m就死,getLive(i-1,m)是新編號
public static int getLive(int i, int m) {
if (i == 1) {
return 1;
}
return (getLive(i - 1, m) + m - 1) % i + 1;
}
3、逆置/反轉單鏈表---時間複雜度=O(n)
詳見4(1)
4、單鏈表排序(冒泡排序/快速排序)
(1)選擇排序---時復=O(n^2),空復=O(1)
(2)冒泡排序---時復=O(n^2),空復=O(1)
選擇排序 public static Node selectionSort(Node head) { |
冒泡排序 void BubbleSord(pList plist) { |
(3)快速排序(親測有效,成功運行)---時復=O(nlogn),空復=O(1)
思路:需要兩個指針p和q,這兩個指針均往next方向移動,移動的過程中保持p之前的key都小於選定的key,p和q之間的key都大於選定的key,那麼當q走到末尾的時候便完成了一次支點的尋找。
注意:這裏的GetPartition方法裏判斷條件:if(j->data < key)時,先i++,再swap(i->data, j->data)。因爲i前(包含i)的數據都比key小;i後j前的數據都比key大。如果先swap再i++的話,很有可能把i的數據(<=key)換到j位置了。
完整代碼如下:
#include <iostream>
using namespace std;
struct ListNode {
int data;
ListNode * next;
ListNode(int _data = -1) : data(_data), next(NULL) {}
};//最後的分號不能丟
void swap(int & p, int & q) {//記得用&
int tmp = p;
p = q;
q = tmp;
}
ListNode * createList(int a[], int n) {//用數組值創建單鏈表
ListNode * pHead,* p;
pHead = p = NULL;
for (int i = 0; i < n; i++) {
ListNode * tmp = new ListNode(a[i]);
if (i == 0) pHead = tmp;
else p->next = tmp;
p = tmp;
}
return pHead;
}
ListNode * GetPartion(ListNode * pBegin, ListNode * pEnd)
{
int key = pBegin->data;
ListNode * i = pBegin;
ListNode * j = i->next;
while (j != pEnd)
{
if (j->data < key) {
i = i->next;
swap(i->data, j->data);
}
j = j->next;
}
swap(i->data, pBegin->data);
return i;
}
void QuickSort(ListNode * pBeign, ListNode * pEnd)
{
if (pBeign != pEnd) {
ListNode * partion = GetPartion(pBeign, pEnd);
QuickSort(pBeign, partion);
QuickSort(partion->next, pEnd);
}
}
int main() {
int datalist[8] = { 4,2,5,3,7,9,0,1 };
ListNode * pHead = createList(datalist, 8);
QuickSort(pHead, NULL);
ListNode * tmp = pHead;
while (tmp != NULL) {
cout << tmp->data << ' ';
tmp = tmp->next;
}
return 0;
}
5、k個節點爲一組進行翻轉
(1)反轉單鏈表
A |
B |
C |
假設方法 reverse() 的功能是將單鏈表進行逆轉。採用遞歸的方法時,我們可以不斷着對子鏈表進行遞歸。我們對子鏈表 2->3->4 進行遞歸,即Node newList = reverse(head.next)。遞歸之後的結果如圖B,逆轉之後子鏈表 2->3->變爲了 4->3->2。注意,剛纔假設 reverse() 的功能就是對鏈表進行逆轉。不過此時結點 1 仍然是指向結點 2 的。這個時候,我們再把結點1 和 2逆轉一下,然後 1 的下一個結點指向 null 就可以了。如圖C。遞歸的結束條件就是:當子鏈表只有一個結點,或者爲 null 時,遞歸結束。
//逆序單鏈表
private static ListNode reverse(ListNode head) {
if(head == null || head.next == null)
return head;
ListNode result = reverse(head.next);
head.next.next = head;
head.next = null;
return result;
}
(2)從頭部開始組起的部分反轉
A |
B |
C |
D |
E |
F |
對於上面的這個單鏈表(見圖A),其中 K = 3。我們把前K個節點與後面的節點分割出來(見圖B和C),temp指向的剩餘的鏈表,可以說是原問題的一個子問題。我們可以調用reverseKNode()方法將temp指向的鏈表每K個節點之間進行逆序。再調用reverse()方法把head指向的那3個節點進行逆序(見圖D和E),接着,我們只需要把這兩部分給連接起來就可以了(見圖F)。
//k個爲一組逆序
public ListNode reverseKGroup(ListNode head, int k) {
ListNode temp = head;
for (int i = 1; i < k && temp != null; i++) {
temp = temp.next;
}
//判斷節點的數量是否能夠湊成一組
if(temp == null)
return head;
ListNode t2 = temp.next;
temp.next = null;
//把當前的組進行逆序
ListNode newHead = reverse(head);
//把之後的節點進行分組逆序
ListNode newTemp = reverseKGroup(t2, k);
// 把兩部分連接起來
head.next = newTemp;
return newHead;
}
(3)從尾部開始組起的部分反轉
A |
B |
C |
D |
對於K = 3鏈表(見圖A),我們把它從尾部開始組起,每 K 個節點爲一組進行逆序。先進行逆序(見圖B),逆序之後就可以把問題轉化爲從頭部開始組起,每 K 個節點爲一組進行逆序(見圖C)。接再把結果逆序一次(見圖D)。
public ListNode solve(ListNode head, int k) {
head = reverse(head);//調用逆序函數
// 調用每k個爲一組的逆序函數(從頭部開始組起)
head = reverseKGroup(head, k);
head = reverse(head);//再次逆序
return head;
}
6、(查找/刪除)鏈表中間節點(擴展:返回鏈表a/b節點)
圖A |
圖B |
(1)刪除中間節點
len=0/1---不刪除
len=2-----刪第1個
len=3/4---刪第2個
len=5/6---刪第3個
用2個指針,pre和cur,初始分別爲第1個和第3個。pre每次走1個,cur每次走2個,走不動了,刪除pre+1。詳見圖A
(2)刪除a/b節點
r=(a*n)/b,向上取整
a<1 或 a>b,直接返回。
r=1,刪除頭節點=返回head->next
r>1,如r=5,走r-2步。詳見圖B
(1)刪除中間節點 public static Node removeMidNode(Node head) { |
(2)刪除a/b節點 public static Node removeByRatio(Node head, int a, int b) { |
7、(查找/刪除)鏈表的倒數第k個節點---時間複雜度=O(N),空間複雜度=O(1)
(1)查找單鏈表的倒數第k個節點
思路:如果鏈表爲空,或者k<鏈表長度,直接返回null。
設置兩個指針A和B,相差k-1步,A先走k-1步驟,然後A和B一起走,當A走到最後,B即爲所求。
(2)刪除單鏈表的倒數第k個節點
思路:鏈表爲空或k<1,直接返回。其他:從頭走到尾,每走一步k-1(頭部開始k-1),如果走到尾k=0,說明倒數第k個節點即爲頭節點,如果k<0,說明倒數第k個節點在鏈表中間,需要找到該節點的前一個節點:再次從頭開始,每走一步k+1(頭部開始k+1),直到k=0走到的節點即爲倒數第k個節點的前一個節點
A -> B -> C -> D,k=2 | A | B | C | D |
step1: | 1 | 0 | -1 | -2 |
step2: | -1 | 0 |
(3)刪除雙鏈表的倒數第k個節點
與(2)相同,只不過需要注意指針
(1)查找單鏈表的倒數第k個節點 ListNode * findKthFromTail(ListNode * head, int k) { |
(2)刪除單鏈表的倒數第k個節點 ListNode * removeLastKthNode(ListNode * head, int lastKth) { |
(3)刪除雙鏈表的倒數第k個節點 ListNode * removeLastKthNode(ListNode * head, int lastKth) { |
8、判斷一個單鏈表是否有環,有環則求環大小和環入口節點
(1)判斷是否有環:頭節點開始,1快1慢,快2步,慢1步,快追上慢則有環
ListNode* MeetingNode(ListNode* pHead)
{
if(pHead == nullptr)
return nullptr;
ListNode* pSlow = pHead->m_pNext;
if(pSlow == nullptr)
return nullptr;
ListNode* pFast = pSlow->m_pNext;
while(pFast != nullptr && pSlow != nullptr)
{
if(pFast == pSlow)
return pFast;
pSlow = pSlow->m_pNext;
pFast = pFast->m_pNext;
if(pFast != nullptr)
pFast = pFast->m_pNext;
}
return nullptr;
}
(2)環大小:(1)基礎上,相遇節點開始,1個指針1步步走,直至再次回到該節點
int LenOfLoop(ListNode* pHead)
{
ListNode* meetingNode = MeetingNode(pHead);
if(meetingNode == nullptr) return nullptr;
int nodesInLoop = 1;//環結點數
ListNode* pNode1 = meetingNode;
while(pNode1->m_pNext != meetingNode)
{
pNode1 = pNode1->m_pNext;
++nodesInLoop;
}
return nodesInLoop;
}
(3)環入口節點:2種
法1:(2)基礎上,頭節點開始,1指針先走n步,2指針和1指針同時一步步走,直至相遇
ListNode* getLoopNode(ListNode* pHead)
{
int nodesInLoop = LenOfLoop(pHead);
// 先移動pNode1,次數爲環中結點的數目
pNode1 = pHead;
for(int i = 0; i < nodesInLoop; ++i)
pNode1 = pNode1->m_pNext;
// 再同時移動pNode1和pNode2
ListNode* pNode2 = pHead;
while(pNode1 != pNode2)
{
pNode1 = pNode1->m_pNext;
pNode2 = pNode2->m_pNext;
}
return pNode1;
}
法2:(1)基礎上,快指針回到頭節點,快慢指針同時一步步走,直至相遇
ListNode* getLoopNode(ListNode* pHead)
{
ListNode* meetingNode = MeetingNode(pHead);
ListNode* p1,p2;
p1 = head;
p2 = meetingNode;
while(p1 != p2) {
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
9、兩鏈表相交
(1)判斷1個鏈表是否有環,有環則返回第一個進入環的節點,無返回null
詳見10(1)(3)
public static Node getLoopNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node n1 = head.next; // n1 -> slow
Node n2 = head.next.next; // n2 -> fast
while (n1 != n2) {
if (n2.next == null || n2.next.next == null) {
return null;
}
n2 = n2.next.next;
n1 = n1.next;
}
n2 = head; // n2 -> walk again from head
while (n1 != n2) {
n1 = n1.next;
n2 = n2.next;
}
return n1;
}
(2)判斷2個無環鏈表是否相交,相交則返回第一個相交節點,不相交則返回null
分別計算兩個鏈表的長度len1,len2,還有鏈表尾節點end1,end2。若end1!=end2,說明不相交直接返回null。
若end1==end2,說明相交。求第一個交點:長鏈表先走abs(len1-len2)步,然後兩個同時一步步走,直至相遇。
public static Node noLoop(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int n = 0;
while (cur1.next != null) {
n++;
cur1 = cur1.next;
}
while (cur2.next != null) {
n--;
cur2 = cur2.next;
}
if (cur1 != cur2) {
return null;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
(3)判斷2個有環鏈表是否相交,相交則返回第一個相交節點,不相交則返回null
圖A |
圖B |
圖C |
分別求兩個鏈表的第一個入環節點loop1和loop2
若loop1==loop2(圖A),直接返回第一次相交節點=loop1
若loop1!=loop2,判斷是那種拓撲(圖B還是圖C)
鏈表1從loop1出發,走一圈還會再回到loop1,中間若沒遇到loop2,則說明是不相交(圖B)。若遇到loop2說明是相交(圖C),
只不過loop1是離鏈表1近,loop2離鏈表2近,返回loop1或loop2都可以。
public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
Node cur1 = null;
Node cur2 = null;
if (loop1 == loop2) {
cur1 = head1;
cur2 = head2;
int n = 0;
while (cur1 != loop1) {
n++;
cur1 = cur1.next;
}
while (cur2 != loop2) {
n--;
cur2 = cur2.next;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
} else {
cur1 = loop1.next;
while (cur1 != loop1) {
if (cur1 == loop2) {
return loop1;
}
cur1 = cur1.next;
}
return null;
}
}
(4)判斷兩個(有環/無環)單鏈表是否相交?相交返回第一個相交點,不相交返回null
public static Node getIntersectNode(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node loop1 = getLoopNode(head1);
Node loop2 = getLoopNode(head2);
if (loop1 == null && loop2 == null) {
return noLoop(head1, head2);
}
if (loop1 != null && loop2 != null) {
return bothLoop(head1, loop1, head2, loop2);
}
return null;
}
10、求兩個已排序單鏈表中相同的數據
思路:分別創建兩個結點變量cur1,cur2,如果cur1->data大於cur2->data,則cur2 = cur2->next;如果cur1->data小於cur2->data,則cur1 = cur1->next;如果cur1->data= cur2->data,則cur1和cue2 同時向後走
void commonNode(ListNode * List1, ListNode * List2)
{
ListNode *cur1,*cur2;
cur1 = List1, cur2 = List2;
while (cur1 != NULL && cur2 != NULL)
{
if (cur1->data < cur2->data) {
cur1 = cur1->Next;
}
else if (cur1->data > cur2->data) {
cur2 = cur2->Next;
} else {
printf(" %d ", cur1->data);
cur1 = cur1->Next;
cur2 = cur2->Next;
}
}
}
11、合併兩個有序鏈表,合併後依然有序---O(n)
遞歸方式 ListNode* Merge(ListNode* pHead1, ListNode* pHead2) ListNode* pMergedHead = nullptr; if(pHead1->m_nValue < pHead2->m_nValue) return pMergedHead; |
非遞歸方式 ListNode * Merge(ListNode * pHead1, ListNode * pHead2) { |