Leetcode初級算法(鏈表篇)
刪除鏈表的倒數第N個節點
感覺自己對於鏈表的知識還是了解的不夠深入,所以沒有想到用雙指針進行操作。我的想法是這樣的,首先計算整個鏈表的長度,然後遍歷到長度減去n的節點處,執行刪除操作。
自己的代碼:
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int n) { ListNode* cur=head; ListNode *cou=head; int i=0; int count=1; while(cou->next!=NULL) { cou=cou->next; count++; } if(count==1) return NULL; if(count==n) {head=head->next; return head;} while(head->next!=NULL && i<count-1-n) { head=head->next; i++; } if(head->next!=NULL) { ListNode *temp; temp=head->next; head->next=temp->next; } return cur; } };
相信看過代碼後都會覺得這個代碼的邏輯比較奇怪,尤其是
if(count==1) return NULL; if(count==n) {
head=head->next;
return head;
}
這段代碼,感覺是根據測試案例試出來的。的確是這樣的,我的代碼在[1,2]去掉倒數第二個元素或者[1,2,3]去掉倒數第三各元素的時候總是會去除中間的額元素,經排查原因應該是head節點的理解有些問題。我認為的head是頭指針,head->next才是第一個結點,但這代碼裏的意思好像是head就已經是第一個節點了,所以在後面判斷的時候有些bug。當然我們還是習慣用比較常規的方法就是用雙指針的方法進行問題的解決:
class Solution{ public: ListNode* removeNthFromEnd(ListNode* head,int n){ if(!head->next) return NULL; LisNode *pre=head,*cur=head; for(int i=0;i<n;i++) cur=cur->next; if(!cur) return head->next; while(cur->next){ cur=cur->next; pre=pre->next; } pre->next=pre->next->next; return head; } };
下面我來解釋一下這段代碼的意思,定義了兩個指針,兩個指針相差n個距離,這樣的話當其中一個指針指向結尾處的時候,另一個指針就自然指向了我們需要刪除的前一個元素位置了,然後執行刪除操作,這樣非常的快捷。
反轉鏈表
感覺這道題有些帥,試著走了一遍邏輯,並不是像我之前以為的一個新鏈表然後反向遍歷讀出數據,但是這個邏輯是一次反轉一個,用到dummy始終指向頭結點、cur始終指向當前的節點、tmp指向下一個結點,邏輯就是將cur下一個指向tmp的下一個結點,將tmp指向頭結點,然後dummy再指向tmp,然後再次進行叠代。仔細思考一下這個反轉轉的真的很有智慧。代碼如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head) return head;
ListNode *dummy=new ListNode(-1);
dummy->next=head;
ListNode *cur=head;
while(cur->next)
{
ListNode *tmp=cur->next;
cur->next=temp->next;
tmp->next=dummy->next;
dummy->next=tmp;
}
return dummy->next;
}
};
合並兩個有序鏈表
這題比較簡單,就是不斷地判斷;如果有一個鏈表已經比較晚了,則直接把後面所有的元素再加到新鏈表中就OK了。代碼如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1!=NULL&&l2==NULL) return l1;
if(l1==NULL&&l2!=NULL) return l2;
ListNode head(0);
ListNode* l3=&head;
while(l1!=NULL&&l2!=NULL)
{
if(l1->val<l2->val)
{
l3->next=l1;
l1=l1->next;
}
else
{
l3->next=l2;
l2=l2->next;
}
l3=l3->next;
}
if(l1!=NULL)
l3->next=l1;
if(l2!=NULL)
l3->next=l2;
return head.next;
}
};
回文鏈表
解釋在代碼註釋裏了,感覺學到了一招快速到達鏈表的中央了(slow與fast的應用,fast->next->next是slow->next速度的兩倍,當fast->next指向結尾,slow指向中間!!)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
//O(n)時間表示不能有嵌套循環, O(1)空間表示不能再次添加新鏈表
if(!head||!head->next) return true;
ListNode *slow=head,*fast=head;
stack<int> s;
s.push(head->val);
while(fast->next!=NULL && fast->next->next!=NULL)
{
slow=slow->next;
fast=fast=fast->next->next; //fast的速度是slow的兩倍,這樣就能保證slow最後能找到鏈表的中間
s.push(slow->val);
}
if(fast->next==NULL)
s.pop(); //說明是奇數個,中間的就不用進行比較了,所以直接pop掉比較後面的
while(slow->next!=NULL)
{
slow=slow->next;
int tmp=s.top();
s.pop();
if(tmp!=slow->val) return false;
}
return true;
}
};
環形鏈表
環形鏈表是循環鏈表的最簡單形式,即首尾是相連的。所以若我們指定兩個指針,fast和slow。兩個指針同時從頭結點開始出發,fast指針走兩步,slow指針走一步;若鏈表有環,這兩個指針肯定會在某點相遇;若鏈表無環,fast會直接先到達NULL。因為不斷的循環,只要有環就一定會相遇,如果沒環則一定會存在NULL。
代碼如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode *slow=head,*fast=head;
while(fast!=NULL && fast->next!=NULL)
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast) return true;
}
return false;
}
};
Leetcode初級算法(鏈表篇)