算法--单链表
1.合并链表
1.合并两个排序的链表
-
解法:这个比较容易,直接对比两个两个链表节点,小的节点直接插入到返回的新链表上
c/** * struct ListNode { * int val; * struct ListNode *next; * ListNode(int x) : val(x), next(nullptr) {} * }; */ class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param pHead1 ListNode类 * @param pHead2 ListNode类 * @return ListNode类 */ ListNode* Merge(ListNode* pHead1, ListNode* pHead2) { ListNode* dummy = new ListNode(0); ListNode* p = dummy; if(pHead1 ==nullptr){ return pHead2; } if(pHead2 == nullptr){ return pHead1; } while (pHead1 && pHead2) { if(pHead1->val > pHead2->val){ p->next = pHead2; p = p->next; pHead2= pHead2->next; } else { p->next = pHead1; p= p->next; pHead1= pHead1->next; } } if(pHead1 ==nullptr){ p->next = pHead2; } if(pHead2==nullptr){ p->next = pHead1; } return dummy->next; } };
2.合并K个有序链表
-
解法:这个稍微复杂一些,需要找到k个链表中节点的最小值。所以我们需要借助优先队列(小根堆),先获取每个链表的第一个节点的大小关系,然后获取当前最小节点链表的后续节点加入到队列。依赖优先队列的自动排序的特性,很容易获得从小到大的链表。
c/** * struct ListNode { * int val; * struct ListNode *next; * ListNode(int x) : val(x), next(nullptr) {} * }; */ #include <list> #include <queue> class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param lists ListNode类vector * @return ListNode类 */ ListNode* mergeKLists(vector<ListNode*>& lists) { if (lists.size() == 0) return nullptr; // 申请虚拟头节点,需要传出函数。 ListNode* dummy = new ListNode(-1); ListNode* p = dummy; // 重载比较方式 struct cmp { bool operator()(ListNode* a, ListNode* b) { return a->val > b->val; } }; // 队列里存放的是链表的头指针,并按照我们自定义的方式排序。 priority_queue<ListNode*, vector<ListNode*>, cmp> pq; for (int i = 0; i < lists.size(); ++i) { if (lists[i] != nullptr) { // 需要考虑到空的链表 pq.push(lists[i]); } } while (!pq.empty()) { ListNode* temp = pq.top(); pq.pop(); p->next = temp; p = p->next; // 如果当前最小节点的链表后续有节点,加入到队列中排序,这样 // 就可以得到当前最小的节点 if (temp->next) { pq.push(temp->next); } } return dummy->next; } };
或者,你可以把所有链表节点值摘下来,放到优先队列里排序(或者你放数组里然后排序都行),然后再重新建立链表
cListNode* mergeKLists(vector<ListNode*>& lists) { ListNode* dummy = new ListNode(-1); ListNode* p = dummy; priority_queue<int, vector<int>, greater<int>> pq; for (int i = 0; i < lists.size(); ++i) { if (lists[i] != nullptr) { while (lists[i]) { pq.push(lists[i]->val); lists[i] = lists[i]->next; } } } while (!pq.empty()) { // 需要重新申请节点,否则无法连接 p->next = new ListNode(pq.top()); pq.pop(); p = p->next; } return dummy->next; }
2.环形链表
1.判断链表是否有环
-
解法:这道题,有同学可能采用暴力解法,把链表节点依次放入到数组中,每放入一个就遍历数组查看是否存在该节点,如果存在就有环,否则没有。这样的确可以通过,但是速度会大大折扣,我们这里采用快慢指针的思想。快慢指针,故名思意一个指针走的快,一个走得慢,如果两个指针同时从链表头部开始走,如果有环一定会在某个地方相遇。
c/** * 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 *fast = head; ListNode *slow = head; // 注意这里,快指针在没有环的情况下,最先到达表尾,且要排除单个节点无环情况 while(fast && fast->next){ slow = slow->next; fast = fast->next->next; if(slow == fast){ return true; } } return false; } };
2.链表中环的入口结点
-
解法:
c/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } }; */ class Solution { public: ListNode* EntryNodeOfLoop(ListNode* pHead) { ListNode* slow = pHead, *fast = pHead; // 大于一个节点才能算环 while (fast && fast->next) { slow = slow->next; fast = fast->next->next; if (slow == fast) { break; } } // 无环情况 if (fast == nullptr || fast->next == nullptr) return nullptr; fast = pHead; while (slow != fast) { slow = slow->next; fast = fast->next; } return fast; } };
还可以采用哈希来解答,把节点依次加入到哈希中,如果某个元素重复了,那么即为入口。
cpp#include <unordered_set> class Solution { public: ListNode* EntryNodeOfLoop(ListNode* pHead) { unordered_set<ListNode*>set; while(pHead){ if(set.count(pHead)) return pHead; // 也可以使用contains(c++20) set.insert(pHead); pHead = pHead->next; } return nullptr; } };
3.删除链表倒数第N个节点
- 解法:采用双指针,由于要删除倒数第n个节点,所以我们一定要获取到待删除节点的前一个节点,所以可以让其中一个指针先走n步,然后两个指针同时走,这样当先走的指针走到链表末尾,后走的指针刚刚好走到倒数第n个节点的前一个节点。算法中我们创建一个虚拟节点,这样方便处理删除第一个头节点的情况。
cpp
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
#include <list>
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode * dummy =new ListNode(-1);
dummy->next = head;
ListNode* p1 = dummy, * p2 = dummy;
if (head == nullptr) return nullptr;
// 快指针先走n步
while (n--) {
p2 = p2->next;
}
// 同时走,直到链表结尾
while (p2->next) {
p1 = p1->next;
p2 = p2->next;
}
// 删除节点
p1->next = p1->next->next;
return dummy->next;
}
};
4.判断两个链表是否相交,相交则返回相交节点
- 解法:
c
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
ListNode* L1 = pHead1, *L2 = pHead2;
while (L1 != L2) {
L1 = (L1 == nullptr) ? pHead2 : L1->next;
L2 = (L2 == nullptr) ? pHead1 : L2->next;
}
return L1;
}
};
同样也可以用哈希做,但会产生额外空间,同前所述。
4.删除有序链表中重复的元素
1.删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次
-
解法:双指针法,两个指针开始都指向头节点,当后续节点值相同时,快指针向后移动,满指针不动。当节点值不同时,当前慢指针的next指向快指针,然后移动到快指针节点处。
c/** * struct ListNode { * int val; * struct ListNode *next; * ListNode(int x) : val(x), next(nullptr) {} * }; */ class Solution { public: ListNode* deleteDuplicates(ListNode* head) { if (head == nullptr) return nullptr; ListNode* fast = head, *slow = head; while (fast) { if (fast->val == slow->val) { fast = fast->next; } else { slow ->next = fast; slow = fast; } } slow->next = nullptr; return head; } };
2.删除有序链表中重复的元素-II(给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。)
-
解法:本题是上一道题的进阶版,上一题删除元素让所有元素只出现一次,这道题是,将所有重复的删除。最常规的还是双指针法,一个指针遍历的元素,并设置标志位,标记元素是否重复,一个指针偏移
c/** * struct ListNode { * int val; * struct ListNode *next; * ListNode(int x) : val(x), next(nullptr) {} * }; */ #include <cmath> #include <type_traits> class Solution { public: ListNode* deleteDuplicates(ListNode* head) { ListNode* dummy = new ListNode(-1); if (head == nullptr) return nullptr; dummy->next = head; bool single = true; // 元素是否是只有一个 ListNode* p1 = dummy, *p2 = head; while(p2->next){ // 如果p2的当前值和下一个值相同就标记这个元素不是唯一,并移动p2 if(p2->val == p2->next->val){ single = false; p2=p2->next; } // 当p2与后面的值不同时,需要判断p2当前指定的值是否是唯一的, // 如果不是唯一的,p1的next指向p2的next(注意,此时不移动p1),实现删除重复元素,并重置标记 // 如果是唯一的,保留元素,那么p1指向p2,并移动到p2,然后p2指向下一个节点。 else { if(!single){ p1->next = p2->next; p2 = p2->next; single = true; } else { p1->next = p2; p1 = p2; p2= p2->next; } } } // 出现重复元素后面无其它元素时。 if(!single){ p1->next = nullptr; } return dummy->next; } };
-
解法2:其实这道题还可以使用哈希来做,记录元素出现次数大于1的元素,并删除,重新建立链表
c/** * struct ListNode { * int val; * struct ListNode *next; * ListNode(int x) : val(x), next(nullptr) {} * }; */ #include <unordered_map> class Solution { public: ListNode* deleteDuplicates(ListNode* head) { if(head == nullptr )return nullptr; unordered_map<int,int>map; ListNode *dummy = new ListNode(-1); dummy->next = head; ListNode* cur = head; // 统计每个元素出现的次数。 while(cur){ map[cur->val]++; cur = cur->next; } cur = dummy; // 重新建立链表 while(cur->next){ if(map[cur->next->val] != 1){ cur->next = cur->next->next; }else { cur = cur->next; } } return dummy->next; } };
5.判断一个单链表是否为回文结构(正反顺序都一样,例如12321)
-
解法:前面我们使用了大量的双指针来解题,这个同样可以用双指针来解决,但是由于是单链表,所以我们需要借助数组,来存储元素,然后使用两个指针从数组的两边来对比元素。
c/** * struct ListNode { * int val; * struct ListNode *next; * ListNode(int x) : val(x), next(nullptr) {} * }; */ #include <vector> class Solution { public: bool isPail(ListNode* head) { vector<int>vec; while (head) { vec.push_back(head->val); head = head->next; } int p = vec.size() - 1; for (int i = 0; i < (vec.size() - 1) / 2; ++i) { if (vec[i] != vec[p - i]) { return false; } } return true; } };