code freethinking--链表 * 重点笔记 *核心代码*

链表基础认知

链表是一种通过指针串联在一起的结构,相较于数组来说更适合增删不适合查询,增删只需要将前一个结点的指针指向新节点,新节点指向后一个节点即可。

链表分为:单链表,双链表(有两个指针域,一个指向前,一个指向后),循环链表

链表在内存中的分布不连续,散乱分布,取决于操作系统的内存管理。删除节点时候最好手动释放

单链表定义:

struct ListNode{
int val;
ListNode *next;
ListNode(int x): val(x),next(null){}
};

链表的构造函数最好自己定义,如果使用默认的构造函数,不能直接初始化给变量赋值,需要另起一行:head- >val =5(例如)

移除链表元素  

 删除链表中等于给定值 val 的所有节点。 

比较好的方法是使用虚拟头节点,这道题的逻辑比较简单,但细节比较多,避免内存泄漏需要temp来存储被删除的变量然后再手动delete掉,创建虚拟头节点最后也要delete掉

时间复杂度O(n)      空间复杂度O(1)

struct ListNode {
	int val;
	ListNode* next;
	ListNode() : val(0), next(nullptr) {}
	ListNode(int x) : val(x), next(nullptr) {}
	ListNode(int x, ListNode* next) : val(x), next(next) {}
};

//以下是核心代码
class Solution {
public:
		ListNode* removeElements(ListNode* head, int val) {
		ListNode* dummyHead = new ListNode(0);
		dummyHead->next = head;
		ListNode* cur = dummyHead;
		while (cur->next != NULL) {
			if (cur->next->val == val)
			{
				ListNode* temp = cur->next;
				cur->next = cur->next->next;
				delete temp;
			}
			else {
				cur = cur->next;
			}
		}
		head = dummyHead->next;
		delete dummyHead;
		return head;
	}
};

设计链表

题目:实现如下功能 

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
  • 时间复杂度: 涉及 index 的相关操作为 O(index), 其余为 O(1)
  • 空间复杂度: O(n)
  • 前摇
  • class MyLinkedList {
    public:
        struct LinkedNode {
            int val;
            LinkedNode* next;
            LinkedNode(int val):val(val), next(nullptr){}
        };
    
        
        MyLinkedList() {
            _dummyHead = new LinkedNode(0); 
            _size = 0;
        }

注意,链表无法通过size获取他的大小

找数值,链表根据节点找数字需要一个一个从头往后找,所以在确定查找到index范围之后,通过一个while循环确定index的前进格数,cur不断前进(后面赋值给前一个)最后找到目标值

int get(int index) {
    if (index > (_size - 1) || index < 0) {
        return -1;
    }
    LinkedNode* cur = _dummyHead->next;
    while (index--) {
        cur = cur->next;
    }
    return cur->val;
}

头插法,逻辑很简单,就是三步,创建新节点,新节点指向第一个节点,虚拟头节点的指针指向新节点

尾插法,唯一的难点就是要找到这个尾在哪,可以通过while一个个找到尾,毕竟链表只有指针的关系,没有整体意识

void addAtHead(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        newNode->next = _dummyHead->next;
        _dummyHead->next = newNode;
        _size++;
    }
    
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = newNode;
        _size++;
    }

中间插,主要是排除两种特殊情况,挨个寻找元素,然后进行往前的插入,类似于头插法

void addAtIndex(int index, int val) {

        if(index > _size) return;
        if(index < 0) index = 0;        
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(index--) {
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
        _size++;
    }

删节点,也是先排除特殊情况,然后从头开始找目标删除的前一个数index,再把cur->next 置给tmp,链接下下个数值,记得释放内存时候一定要释放数值和指针两步!不然野指针会乱跑

    void deleteAtIndex(int index) {
        if (index >= _size || index < 0) {
            return;
        }
        LinkedNode* cur = _dummyHead;
        while(index--) {
            cur = cur ->next;
        }
        LinkedNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
       
        tmp=nullptr;
        _size--;
    }

后摇

void printLinkedList() {
        LinkedNode* cur = _dummyHead;
        while (cur->next != nullptr) {
            cout << cur->next->val << " ";
            cur = cur->next;
        }
        cout << endl;
    }
private:
    int _size;
    LinkedNode* _dummyHead;

};

 总结: 相对简单的综合题,不费脑但费时,需要仔细仔细

反转链表

直接改变链表指针的指向,通过两个指针cur和pre , 将cur->next指向pre, 核心就四步:1.先给cur->next保存  2.pre赋值给cur->next,反转指向   3.将cur给pre   4.将temp给cur 实现继续向下走

  • 时间复杂度O(n)      空间复杂度O(1)
class Solution {
public:
	ListNode* reverseList(ListNode* head) {
		ListNode* temp;
		ListNode* cur = head;
		ListNode* pre = NULL;
		while (cur) {
			temp = cur->next;
			cur->next = pre;
			pre = cur;
			cur = temp;
		}
		return pre;
	}
};

两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

 

看似两两交换,其实涉及了三个元素的变换,在核心的指针反转之外,要记得要将第一个指针和连着的第三个指针给赋最开始的指针位置,所以要用临时tmp保存,同时因为指针的缘故,设置一个虚拟头节点比较好操作,主要就是,1 2 3变成了1 3 2 这里面的指针变换值得重视。

  • 时间复杂度O(n)      空间复杂度O(1)
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0); 
        dummyHead->next = head; 
        ListNode* cur = dummyHead;
        while( cur->next != nullptr &&cur->next->next != nullptr) {
            ListNode* tmp = cur->next; 
            ListNode* tmp1 = cur->next->next->next; 
            cur->next = cur->next->next;    
            cur->next->next = tmp;          
            cur->next->next->next = tmp1;   
            cur = cur->next->next; 
        }
        ListNode* result = dummyHead->next;
        delete dummyHead;
        return result;
    }
};

删除链表的倒数第N个节点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

这题有前面的基础也比较简单,核心:通过双指针,快的和慢的形成n+1个差 

 时间复杂度O(n)      空间复杂度O(1)

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next =head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        while(n--&&fast!=NULL){
            fast = fast->next;
        }
        fast= fast->next;
        while(fast!=NULL){
            fast = fast->next;
            slow = slow->next;
        }
        ListNode* tmp = slow->next;
        slow->next = tmp->next;
        delete tmp;
        return dummyHead->next;
    }
};

链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交:

 

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

链表相交本质上是求两个链表有没有相同的指针,再取出指针指向的值 ,相交有一个特点是从交点往后,后面的几个元素会相同,换句话说,假如有交点,从后往前一定是一段相同的链表,所以可以通过遍历,让A>B,然后从后往前对齐链表,再两个遍历一个个往前找的方式进行。

思路主要是先从头遍历得到两个链表长度,再用swap得到lenA>lenB,然后通过gap差,先让A自己走到一样的起始位置,再while if,找到交点。注意:判断有没有走到头要用curA/B!=null     

时间复杂度:O(n + m)   空间复杂度:O(1)

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA =0,lenB = 0;
        while(curA!=0){
        lenA++;
        curA= curA->next;}
        while(curB!=0){
        lenB++;
        curB= curB->next;}
        curA= headA;
        curB = headB;
        if(lenB>lenA){
        swap(lenA,lenB);
        swap(curA,curB);
        int gap = lenA-lenB;
        while(gap--){
        curA=curA->next;}
        while(curA != NULL){
        if(curA ==curB){
            return curA;}
        curA = curA->next;
        curB= curB->next;
       

    }
        return NULL;
};

环形链表Ⅱ

题意: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

先是通过两个异步指针找到相交,然后再通过index两个指针同步找到环的入口,详细过程建议看原网站 代码随想录 (programmercarl.com)

  • 时间复杂度: O(n),快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n
  • 空间复杂度: O(1)
class Solution {
public:
	ListNode* deterCycle(ListNode* head) {
		ListNode* fast = head;
		ListNode* slow = head;
		while (fast != NULL && fast->next != NULL) {
			slow = slow->next;
			fast = fast->next->next;
			if (slow == fast) {
				ListNode* index1 = fast;
				ListNode* index2 = head;
				while (index1 != index2) {
					index1 = index1->next;
					index2 = index2->next;
				}
				return index2;
			}
		}
		return NULL;
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值