链表的习题,其实比较基础,通常画个图来理解会比较好。


???? 链表

​剑指 Offer 06. 从尾到头打印链表​

class Solution {
public:
vector<int> reversePrint(ListNode* head) {
vector<int> result;
ListNode* cur = head;
while(cur !=nullptr){
result.push_back(cur->val);
cur = cur->next;
}
reverse(result.begin(),result.end());
return result;
}
};

不用reverse

/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
vector<int> reversePrint(ListNode* head) {
vector<int> result;
ListNode* cur = head;
while(cur !=nullptr){
result.push_back(cur->val);
cur = cur->next;
}
//reverse(result.begin(),result.end());
return vector<int>(result.rbegin(),result.rend());
}
};

注意:

c.rbegin() 返回一个逆序迭代器,它指向容器c的最后一个元素

​剑指 Offer 24. 反转链表​

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

???? … 脑子一热

居然就这样做了

执行用时:4 ms, 在所有 C++ 提交中击败了91.54%的用户

内存消耗:8.3 MB, 在所有 C++ 提交中击败了12.35%的用户

通过测试用例:27 / 27

/**
* 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) {
// 第一反应 用一个容器存储 然后在这个链表上修改
vector<int> result;
ListNode* cur = head;
// 当链表长度为空 或者为1的时候 直接返回头结点
if(head == nullptr || head->next == nullptr)
return head;
while(cur!=nullptr){
result.push_back(cur->val);
cur = cur->next;
}
ListNode* newHead = head;
for(int i=result.size()-1; i>=0; i--){
newHead->val = result[i];
//cout<<newHead->val<<endl;
newHead = newHead->next;
}
return head;
}
};

之前做的方法

class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* new_head = nullptr;
while (head)
{
ListNode* next = head->next; //备份
head->next = new_head; // 更新new_head
new_head = head;//移动new_head
head = next;
}
return new_head;
}
};

​剑指 Offer 35. 复杂链表的复制​

class Solution {
public:
Node* copyRandomList(Node* head) {
if(head == NULL) return nullptr;
// 用一个哈希表 value 是当前的链表节点 通过Key找到下个节点
unordered_map<Node*, Node*> umap;
Node* p=head;
// 赋值
while(p){
umap[p] = new Node(p->val);
p = p->next;
}
// 链接
Node* temp_p = head;
while(temp_p){
umap[temp_p]->next = umap[temp_p->next];
umap[temp_p]->random = umap[temp_p->random];
temp_p = temp_p->next;
}
return umap[head];
}
};

​剑指 Offer 18. 删除链表的节点​

class Solution {
public:
ListNode* deleteNode(ListNode* head, int val) {
if(head == NULL || head->next ==NULL) return head;

if(head->val == val){
head = head->next;
return head;
}
//查找如果当前节点的下一个节点的值 = val 则删除
// temp->next = temp->next->next;
ListNode* temp = head;
while(temp->next){
if(temp->next->val == val){
temp->next = temp->next->next;
return head;
}
else{
temp = temp->next;
}
}
return head;
}
};

​剑指 Offer 22. 链表中倒数第k个节点​

class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
if(k<=0) return nullptr;
if(head == NULL || head->next == NULL) return head;
// 遍历得到链表长度 len
// len-- == k 则为head = head->next的依据
int len = 0;
ListNode* temp = head;
while(temp){
temp = temp->next;
len++;
}
while(len-- > k){
head = head->next;
}
return head;
}
};

​剑指 Offer 25. 合并两个排序的链表​


创建一个result链表,

ListNode* result = new ListNode(0);

当两个链表均不为空的时候 比较大小 然后插入、移动

当其中一个链表为空的时,将不为空的链表直接插入到其中


class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *p1=l1;
ListNode *p2=l2;
ListNode *result=new ListNode(0);
ListNode *r=result;
while(p1 && p2){
if(p1->val > p2->val){
r->next=p2;
r=r->next;
p2=p2->next;
}else{
r->next=p1;
r=r->next;
p1=p1->next;
}
}
if(p1){
r->next=p1;
}else if(p2){
r->next=p2;
}
return result->next;
}
};

​剑指 Offer 52. 两个链表的第一个公共节点​

​160. 相交链表​

方法一 用set集合完成


https://blog.csdn.net/ETalien_/article/details/89439892

set和map的区别



将链表A 的地址信息存在集合中, 不过存的是地址

在集合中查找链表B的地址


class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
std::set<ListNode*> node_set;
//将链表A的地址存在集合中
while (headA)
{
node_set.insert(headA);
headA=headA->next;
}
//在集合中查找链表B的地址
while (headB)
{
//如果找到了headB
if (node_set.find(headB) != node_set.end()) {
return headB;
}
headB = headB->next;
}
return NULL;
}
};

map, set, multimap, and multiset

上述四种容器采用红黑树实现,红黑树是平衡二叉树的一种。不同操作的时间复杂度近似为:

插入: O(logN)

查看:O(logN)

删除:O(logN)

hash_map, hash_set, hash_multimap, and hash_multiset

上述四种容器采用哈希表实现,不同操作的时间复杂度为:

插入:O(1),最坏情况O(N)。

查看:O(1),最坏情况O(N)。

删除:O(1),最坏情况O(N)。

方法二双指针方法


一起走着对方走过的路


剑指Offer习题整理-链表笔记_时间复杂度

剑指Offer习题整理-链表笔记_面试_02

class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
// 注意判断链表是否为空
if(headA == nullptr || headB == nullptr) return headA;
ListNode* A = headA;
ListNode* B = headB;
// 注意当A,B个数为1的情况
while(A!=B){
A = A !=nullptr ? A->next : headB;
B = B !=nullptr ? B->next : headA;
}
return A;
}
};

时间复杂度 O(a + b) : 最差情况下(即 |a - b| = 1∣a−b∣=1 , c = 0c=0 ),此时需遍历 a+b 个节点。

空间复杂度 O(1) : 节点指针 A , B 使用常数大小的额外空间。

JZ76删除链表中重复的结点

在一个排序的链表中删除重复节点

class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead) {
ListNode* dummy = new ListNode(-1);
ListNode* tail = dummy;
while(pHead !=nullptr){
if(pHead->next == nullptr || pHead->next->val != pHead->val){
tail->next = pHead;
tail = pHead;
}
// 如果 pHead 与下一个有重复
while(pHead->next != nullptr && pHead->val == pHead->next->val)
pHead = pHead->next;
// 如果有相等也要跳过那个相等的数字
pHead = pHead->next;
}
tail->next = nullptr;
return dummy->next;
}
};

JZ23 链表中环的入口结点

/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead) {
// 快慢指针
ListNode * fast = pHead;
ListNode * slow = pHead;
while(fast){
slow = slow->next;
if(fast->next == nullptr) return nullptr;
fast = fast->next->next;
// 找到相交节点
if(fast == slow){
fast = pHead;// 快指针重新移动到头
// 直到两指针相遇位置,每次向后走一步
while(fast != slow){
fast = fast->next;
slow = slow->next;
}
return fast;
}
}
return nullptr;

}
};

用哈希集合做

**时间复杂度:**O(n), n为链表长度,遍历一次链表的时间复杂度为O(n)

**空间复杂度:**O(n),哈希集合所用的空间

class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead) {
unordered_set<ListNode*> st;
while(pHead){
if(st.count(pHead)) return pHead;
st.insert(pHead);
pHead = pHead->next;
}
return nullptr;
}
};