链表相关(一)

【王道考研笔记一】

线性表Linear list的定义:是具有相同数据类型的n(n>=0)个数据元素的有限序列
如:a1->a2->a3->a4->a5 ai 是线性表中的第i个元素在线性表中的位序
对线性表的常见操作
初始化表、销毁操作、插入操作、删除操作、按值查找、按位查找等操作


区分值传递和引用传递

//值传递的方式
void test(int x)
{
	x = 1024;
	cout << "test函数内部 x= " << x << endl;
}

int main()
{
	int x = 1;
	cout << "调用test前的 x= " << x << endl;
	test(x);//以值传递的方式传入x=1,对test函数内部的x=1024进行覆盖;
	cout << "调用test后的 x= " << x << endl;
	system("pause");
	return 0;
}
输出结果:
调用test前的 x= 1
test函数内部 x= 1024
调用test后的 x= 1
///*************************************************************************
//引用传递的方式

void test(int &x)
{
	x = 1024;
	cout << "test函数内部 x= " << x << endl;
}

int main()
{
	int x = 1;
	cout << "调用test前的 x= " << x << endl;
	test(x);//以值传递的方式传入x=1,对test函数内部的x=1024进行覆盖;
	cout << "调用test后的 x= " << x << endl;
	system("pause");
	return 0;
}

输出结果:
调用test前的 x= 1
test函数内部 x= 1024
调用test后的 x= 1024

//在main函数里面对x进行赋值,当调用test函数后,重新对x进行赋值,
//以值传递的方式调用test意味着调用test之后的结果还是main函数里面赋值的那个 不会更改
//以引用传递的方式调用test意味着调用test之后的结果不再是main函数里面赋值的那个 而是在test函数里面重新赋值的那个


链表理论基础

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个**数据域和指针域(存放指向下一个节点的指针),最后一个节点的指针域指向NULL;
链表的
入口节点称为链表的头节点也就是head**; 如图(单链表)所示
在这里插入图片描述


链表的类型

单链表:如上图所示
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点;如图所示
在这里插入图片描述
循环链表:链表首尾相连 ;如图所示
在这里插入图片描述


链表的存储方式

数组在内存中是连续分布的,但是链表在内存中不是连续分布的,而是散乱分布在内存中的某个地址上,分配机制取决于操作系统的内存管理;
链表通过指针域的指针的链接在内存中各个节点;如图所示
在这里插入图片描述

单链表的定义方式

// 单链表
struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

链表的操作

删除节点,只要将C节点的next指针指向E节点
在这里插入图片描述
添加节点
在这里插入图片描述


数组和链表的区别

数组array

基本概念

相同数据类型的元素按一定顺序排列的集合;其中的每一个数据称为一个元素;
每个元素可以通过一个索引下标来访问它;
数组的存储区间是连续的;占用内存较大,故空间复杂度很大;
数组是查询简单,增删比较难;

1、在内存空间中,数据是一组连续的区域
2、数组的长度是确定的,一旦被创建,大小不可更改;数组需要预留空间
使用前需要提前申请所占内存的大小,如果提前不知道需要的空间大小时,预先申请就可能会浪费内存空间,即数组的空间利用率较低。
注:数组的空间在编译阶段就需要进行确定,所以需要提前给出数组空间的大小(在运行阶段是不允许改变的)
3、插入和删除数据效率低
插入数据时,待插入位置的元素和他后面的所有元素都需要向后搬移
删除数据时,待删除位置后面的所有元素都需要向前搬移。
4、随机访问效率高,时间复杂度可以达到O(1)
因为数组的内存是连续的,想要访问那个元素,直接从数组的首地址向后偏移就可以访问到了。
5、数组开辟的空间,在不够使用的时候需要进行扩容
扩容的话,就涉及到需要把旧数组中的所有元素向新数组中搬移。
6、数组的空间是**从栈分配**的。(栈:先进后出)

数组的优缺点

优点
随机访问性强,查找速度快,时间复杂度是0(1)
缺点
3.1 从头部删除、从头部插入的效率低,时间复杂度是o(n),因为需要相应的向前搬移和向后搬移。
3.2 空间利用率不高
3.3 内存空间要求高,必须要有足够的连续的内存空间。
3.4 数组的空间大小是固定的,不能进行动态扩展。


链表ListNode

基本概念

所谓链表,链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域
相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

链表:链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。
链表的特点是:查询相对于数组困难,增加和删除容易。

1.1 在内存中,元素的空间可以在任意地方,空间是分散的,不需要连续。
1.2 链表中的元素有两个属性,一个是元素的值,另一个是指针,此指针标记了下一个元素的地址。

   每一个数据都会保存下一个数据的内存地址,通过该地址就可以找到下一个数据

1.3 查找数据时间效率低,时间复杂度是o(n)

 因为链表的空间是分散的,所以`不具有随机访问性`,如果需要访问某个位置的数据,需要从第一个数开始找起,依次往后遍历,知道找到待查询的位置,故可能在查找某个元素时,时间复杂度是o(n)

1.4 空间不需要提前指定大小,是动态申请的,根据需求动态的申请和删除内存空间,扩展方便,故空间的利用率较高
1.5 任意位置插入元素和删除元素时间效率较高,时间复杂度是o(1)
1.6 链表的空间是从堆中分配的。(堆:先进先出,后进后出)

链表的优缺点

二、链表的优点
2.1 任意位置插入元素和删除元素的速度快,时间复杂度是o(1)
2.2 内存利用率高,不会浪费内存
2.3 链表的空间大小不固定,可以动态拓展。
三、链表的缺点
随机访问效率低,时间复杂度是o(1)


经典Leetcode题目

leetcode 23 合并k个升序链表H

题目:
给你一个链表数组,每个链表都已经按升序排列
请将所有链表合并到一个升序链表中,返回合并后的链表;

合并两个升序的链表


leetcode 25【 k个一组反转链表H】

算法思路:

在这里插入图片描述

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

class Solution
{
public:
	ListNode* reverseKGroup(ListNode* head, int k)
	{
		ListNode* p = head;//创建一个指针指向每组的第一个节点
		for (int i = 0; i < k; i++)//首先遍历这一组的k个节点 查看是不是够k个
		{
			if (p == NULL)return head;//不够k个直接返回头节点
			p = p->next;
		}
		ListNode* pre = head;//创建pre指针指向头节点
		ListNode* cur = head->next;//创建cur指针指向头节点的下一个节点
		for (int i = 0; i < k - 1; i++)//对第一组的k个节点进行反转
		{
			ListNode* temp = cur->next;
			cur->next = pre;
			pre = cur;
			cur = temp;
		}//这里的for循环结束后,cur指针指向下一组待反转数组的第一个节点 ; pre指针指向上一组k个节点中反转后的头节点(即待反转数组的最后一个节点);
		ListNode* next = reverseKGroup(cur, k);//next指向下一段k个节点的反转后的头节点
		head->next = next;//head已经是当前段k个节点的尾节点了,指向下一段的反转后的头节点,将断开的链表拼接起来;
		return pre;//返回第一组反转后的头节点 也就是的第一组的最后一个节点
	}
};


leetcode 61 【旋转链表 M】

提出需求:

给你一个链表的头节点head,旋转链表,将链表每个节点向右移动k个位置 例如:
输入:1-2-3-4-5 k=2
输出:4-5-1-2-3

算法思路:
在得到链表长度之后,新链表的最后一个节点为原链表的第(n-k%n)个节点,创建变量add==(n-k%n),然后将指针遍历到原链表的第add个位置,在这个位置之后将链表断开(之前会有将链表连接成环的操作),返回断开链表之后的节点即可,那个节点就是新链表的头节点;
在这里插入图片描述


struct ListNode
{
	int val;
	ListNode* next;
	ListNode(int x):val(x),next(nullptr){}
};
class Solution
{
public:
	ListNode* rotate(ListNode* head, int k)
	{
		if (k == 0 || head == nullptr || head->next == nullptr)
		{
			return head;//返回原链表
		}
		int n = 1;//初始化一个变量用来表示链表的长度;现在认为该链表至少是有头节点的,因此长度至少为1;
		ListNode* iter = head;//初始化一个指针指向头节点 用来遍历链表节点
		while (iter->next) //这里之所以是iter->next 是因为后面还有连接成环的操作 iter->next=head 要保证iter不为空才可以
		{    //while循环结束的条件是iter->next为空 保证 iter不空;如是iter 则循环结束的条件是iter为空 后面无法使用iter进行来连接成环的操作
		    n++;
			iter = iter->next;//继续向下遍历链表节点
			//n++;//最后得到的n是链表的长度
		}
		int add = n - k % n;//新链表的最后一个节点为原链表的第(n-k%n)个节点  2%5=2
		if (add == n)//此时k是n的倍数 k%n==0
		{
			return head;
		}
		iter->next = head;//连接成环,此时的iter指针是指向链表的尾节点的;
		while (add--) //在原链表中找新链表的尾节点
		{
			iter = iter->next;//iter此时指向原链表的第add个节点
		}
		ListNode* ret = iter->next;//新的头节点ret 也是原链表的第add+1个节点
		iter->next = nullptr;//断开环
		return ret;//返回新链表的头节点
	}
};


leetcode 83 删除排序链表中的重复元素

提出需求:存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素只出现一次 。
返回同样按升序排列的结果链表。
例如:输入:head=[1,1,2,3,3] 输出:[1,2,3]
在这里插入图片描述

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

class Solution
{
public:
	ListNode* deleteDuplicates(ListNode* head)
	{
		if (!head)return NULL;
		ListNode* pre = head;//pre指针指向第一个节点
		ListNode* cur = head->next;//cur指针指向第二个节点
		while (cur)
		{
			if (cur->val == pre->val)//前两个节点相等
			{
				pre->next = cur->next;//将pre指针指向cur指针的下一个节点,越过cur指针指向的节点,实现删除重复节点的效果
			}
			else
			{
				pre = cur;//pre指针指向下一个节点 继续向下遍历
				cur = cur->next;//cur指针指向下一个节点 继续向下遍历
			}
			return head;
		}
	}
};


leetcode 141 环形链表 (判断有无入环点且返回入环点)E

在这里插入图片描述
思路

不返回入环点只判断是不是存在入环点的思路
设置一个快指针和一个慢指针,让快慢指针同时前进,但是**快指针每次前进2个单位,慢指针每次前进1个单位,如果两个指针可以相遇,则说明链表中是存在环的**;
如果存在入环点需要返回入环点的思路
在上述判断有无入环点的思路上,添加一个标志位,一开始将这个标志位置为-1,当判断存在入环点后,将这个标志位置为1,然后进入找入环点的程序,首先让fast指针再次指向头节点head,然后让快慢指针同时前进,每次都前进一个单位,当两指针再次相遇时,相遇点就是入环点(即快慢指针指向的节点),返回这个点即可;

C++代码实现

struct ListNode
{
	int val;
	ListNode* next;
	ListNode(int x):val(x),next(nullptr){}
};
//判断环形链表 无需返回入环点
class Solution
{
public:
	bool hayCycle(ListNode* head)
	{
		ListNode* fast = head;
		ListNode* slow = head;
		while (fast && fast->next)//同时保证fast->next为空前fast是不为空的;保证fast->next->next为空前fast->next是不为空的
		{
			slow = slow->next;//慢指针一次移动一个单位
			fast = fast->next->next;//快指针一次移动两个单位 这里会同时用到fast和fast->next 来指向下一个节点
			if (fast == slow)return true;//快慢指针相遇 就说明有环
		}
		return false;
	}
};
// ===============================================================================================
//判断环形链表并返回入环点
class Solution
{
public:
	ListNode* detectCycle(ListNode*head)
	{
		ListNode* slow = head, * fast = head;//初始位置 快慢指针均指向头节点
		int flag = -1;//标记符
		//代码块1----判断是否有环
		while (fast && fast->next)
		{
			fast = fast->next->next;
			slow = slow->next;
			if (fast == slow)flag = 1;break;//当判断有环后 直接跳出循环 执行下面的代码块2
		}
		//代码块2----有环的时候返回入环点
		if (flag == 1)
		{
			fast = head;//让fast指针重新指向头节点
			while (fast != slow)
			{
				fast = fast->next;
				slow = slow->next;
			}
			return fast;
		}
		return NULL;
	}
};

leetcode 27 移除链表中指定的元素 && 移除数组中指定的元素

移除链表元素
给定一个链表的头节点head和一个整数val,请你删除链表中所有满足node.val==val 的节点,返回新的头节点;


//移除链表中指定的元素
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)
			{
				cur->next = cur->next->next;//指向下一个节点
			}
			else
			{
				cur = cur->next;//没找到要删除的节点就继续向下遍历
			}
		}
		return dummyHead->next;
	}
};

移除数组中的元素
给定一个数组nums和一个val,你需要原地移除所有数值等于val的元素,并返回移除元素之后新数组的长度;
算法思路:
除了给定的值之外的值重新构建一个数组,这个数组下标从1开始;

//移除数组中指定的元素
//数组的元素在内存地址中是连续的   不能单独删除数组中某个元素   只能覆盖  
//双指针
class Solution
{
public:
	int removeElement(vector<int>& nums, int val)
	{
		int index = 0;//初始化一个变量用来递加数组中的元素而且还可以统计数组的长度;
		for (auto num : nums)
		{
			if (num != val)nums[index++] = num;//以除了给定的值之外的值重新构造一个新数组 这个数组下标从1开始:
		}
		return index;//返回数组中的长度
	}
};

剑指offer 52 两个链表的第一个公共节点

题目要求:输入两个链表,找出他们的第一个公共节点;
在这里插入图片描述
输出 c1

C++实现

//两个链表的第一个公共节点
class Solution
{
public:
	ListNode* getIntersectionNode(ListNode* headA, ListNode* headB)
	{
		ListNode* node1 = headA;//指针node1用来遍历headA
		ListNode* node2 = headB;//指针node2用来遍历headB
		while (node1 != node2)
		{
			node1 = (node1 != NULL ? node1->next : headB);//先遍历链表A ,链表A后开始遍历链表B;
			node2 = (node2 != NULL ? node2->next : headA);//先遍历链表B ,链表B后开始遍历链表A;
		}
		return node1;//当node1=node2时,返回node1即可;
	}
};

回文链表

给定一个单链表的头节点head,请你判断该链表是否为回文链表,如果是返回true 否则返回false;
输入:head=1-2-2-1
输出:true

/**
 * Definition for singly-linked list.
 * 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:
	bool isPalindrome(ListNode* head)
	{
		stack<int>s;
		ListNode* p = head;
		while (p)   //将链表的每个元素进行入栈之后 利用栈的先进后出的特点 实现反转之后的链表
		{
			s.push(p->val);
			p = p->next;
		}
		p = head;//指针重新指向头节点以进行比较
		while (p)//不为空
		{
			if (p->val != s.top()) return 0;//当栈顶元素和链表的头节点元素不一样时 可以判定不是回文链表
			s.pop();//删除栈顶元素
			p = p->next;//继续比较剩余的元素
		}
		return 1;//当所有的元素都比较完后 此时p指向空 说明是回文链表 返回真即可
	}
};


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.liang呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值