数据结构04:力扣顺序表3道例题解题思路与代码实现

引言:

先前我们讲解完了顺序表相关知识,包括顺序表的创建、初始化、插入、删除、查找等功能的实现,接下来本节内容就让我们动手练习一下顺序表相关的题目,加深一下理解。

文章目录

引言:

[一、移除元素 - 力扣(LeetCode)](#一、移除元素 - 力扣(LeetCode))

1.1思路1:创建新数组

1.2思路2:双指针法

[二、删除有序数组中的重复项 - 力扣(LeetCode)](#二、删除有序数组中的重复项 - 力扣(LeetCode))

2.1思路1:创建新数组

2.2思路2:双指针法

[三、合并两个有序数组 - 力扣(LeetCode)](#三、合并两个有序数组 - 力扣(LeetCode))


一、移除元素 - 力扣(LeetCode)

我们来看一下第1题的题目要求:需要原地移除值为val的数,并返回不等与val的元素个数k,再通过示例加强理解:我们有一个nums数组,要移除的val值等于3,那就要把数组中的3全部移除,然后还剩下2个2要放在最右边,最后函数返回剩余元素的个数k=2。

cpp 复制代码
int removeElement(int* nums, int numsSize, int val) {
    
}

接着我们看题目给出的已知函数的外壳长什么样,这是一个叫做removeElenment的函数,需要3个参数,数组地址nums,有效元素个数nameSize,要移除的值val,最后函数还要返回一个int整型,也就是我们的k。

1.1思路1:创建新数组

我们现在要对数组进行操作,第一感觉可能是在原数组中直接删除值不好删除,那我们能不能再创建一个新数组呢?遇到不等于val的值就放入新数组中,最后再将新数组赋值给旧数组。这就是我们的第1个思路,代码如下:

cpp 复制代码
// 方法1:创建新数组
int removeElement(int* nums, int numsSize, int val) {
	int* copy_nums = (int*)malloc(numsSize * sizeof(int));
	int k = 0;
	//插入到新数组中
	for (int i = 0; i < numsSize; i++)
	{
		if (nums[i] == val)
		{
			continue;//如果相等则跳过当前循环
		}
		copy_nums[k++] = nums[i];
	}
	//赋值给原数组
	for (int i = 0; i < k; i++)
	{
		nums[i] = copy_nums[i];
	}
	return k;
}

我们使用了malloc开辟了numsSize * sizeof(int)大小的空间,然后进入for循环,对于等于val的值我们跳过当前循环,不等于val的值赋值到新数组copy_nums中,最后在用for循环将新数组赋值给旧数组,同时返回k值。

我们现在已经学完了复杂度的相关概念,自然在写代码的时候就需要考虑我们的算法复杂度如何,我们现在这个创建新数组思路的空间复杂度自然为O(n)因为malloc开辟了numsSize大小的空间,而时间复杂度呢?有2个并列的for循环,所以时间复杂度也为O(n),那还有更加优秀的思路吗?有的兄弟,有的,就是接下来我们要讲的双指针法。

1.2思路2:双指针法

双指针法是指创建2个变量dst,src,如果src指向的数据是val,则src++,如果src指向的数据不是val,赋值(src给dst),src和dst都++。

这里dst和src初始都指向3,现在判断3是否为val,是则src++,因为我们是需要找的是不等于val的值,然后把它放在前面,接下来src指向2不等于val,则将src的值赋给dst,然后sre和dst都++,如此循环往复,直到src指向空。代码实现如下:

cpp 复制代码
int removeElement(int* nums, int numsSize, int val) {
	int src = 0;
	int dst = 0;
	while (src < numsSize)
	{
		if (nums[src] != val)
		{
			nums[dst] = nums[src];
			dst++;
		}
		src++;
	}
	return dst;
}

我们再判断一下这里的复杂度问题,该思路没用额外开辟空间,所以空间复杂度为O(1),只有一个while循环,所以时间复杂度为O(N),所以该思路是要优于上一种创建新数组方法的。

二、删除有序数组中的重复项 - 力扣(LeetCode)

我们来看题目要求,给了一个非严格递增排列的数组nums,啥意思,我们看示例1,nums = [1,1,2]就是说数组中的值整体来说是递增的,但是可以包含重复的值,然后要求我们删除重复的值之后使元素的相对顺序保持一致,也就是说还是维持递增排列,最后返回不重复元素的个数k。

2.1思路1:创建新数组

创建新数组,遍历原数组,将不重复数据导入到新数组中,然后将新数组赋给原数组。这里和之前不一样的一点就是我们要找不重复的数据,所以如果我们只用一个变量去遍历是不够的,因此创建2个变量,一个记录值,一个找是否存在与记录值相同的值,将不重复的值放到新数组中。

我们创建了i和j2个变量,初始 i 等于0, j 等于i + 1,如果 j 等于 i 的值,则 j 继续++,直到找到不等于 i 的值,然后放入新数组中,接着 i 来到 j 的位置,j++,如此循环如上操作,直到 j 指向空,跳出循环。代码如下:

cpp 复制代码
// 思路1:创建新数组
int removeDuplicates(int* nums, int numsSize) {
	int* copy_nums = (int*)calloc(numsSize , sizeof(int));
	int i = 0;
	int j = i + 1;
	int k = 1;//因为i = 0是已经放入新数组中,因此新数组下标从k = 1开始
	copy_nums[i] = nums[i];
	while (j < numsSize)
	{
		if (nums[j] != nums[i])
		{
			copy_nums[k] = nums[j];
			k++;
			i = j;
		}
		j++;
	}
	for (i = 0; i < numsSize; i++)
	{
		nums[i] = copy_nums[i];
	}
	return k;
}

该方法还是时间复杂度和空间复杂度都为O(N),那我们再试试思路2:双指针法,其实第1种创建新数组方法中已经用到了一点双指针的东西,我们在这个基础上改改就可以得到双指针法。

2.2思路2:双指针法

双指针法,创建2个变量,变量dst指向初始位置,变量src指向dst + 1的位置,如果src的值等于dst的值,则src++,如果src的值不等于dst的值,则先让dst++,再将src的值赋给dst,接着src++,如此循环往复直至src指向空。

这里我们让dst初始指向0,则src初始指向1,这时dst的值和src的值都为2,则src++向后移动一位,src的值变成了3,先让dst++向后移动一位,再将src的3赋给了dst,然后src++指向了3等于dst的值,接着src++指向了空,循环结束,最后返回不重复元素的个数为dst + 1。代码实现如下:

cpp 复制代码
int removeDuplicates(int* nums, int numsSize) {
	int dst = 0;
	int src = dst + 1;
	while (src < numsSize)
	{
		if (nums[src] != nums[dst])
		{
			dst++;
			nums[dst] = nums[src];
		}
		src++;
	}
	return dst + 1;
}

这里还有一个可以优化的地方,假如我们的数组为:[2,3,4,4],我们的det初始指向2,src指向3,这时候,src和dst不相等,需要先dst++再赋值,但是我们可以看到,dst++之后和src指向同一个值,这样再交换就重复了,因此这里可以优化为只有不相等时才交换。

cpp 复制代码
// 思路2:双指针法
int removeDuplicates(int* nums, int numsSize) {
	int dst = 0;
	int src = dst + 1;
	while (src < numsSize)
	{
		if (nums[src] != nums[dst])
		{
			dst++;
			if(src != dst)
				nums[dst] = nums[src];
		}
		src++;
	}
	return dst + 1;
}

三、合并两个有序数组 - 力扣(LeetCode)

读完题目要求我们可以知道的是,要求我们合并2个升序的数组,将nums2放在nums1中,同时让nums1也为升序。有一个最简单是思路就是先不管其他的,直接将nums2中的元素全部插入到nums1中,再对nums1进行排序就行,排序算法选择冒泡排序或者qsort排序都行。我们这里讲第2种思路,比较大小,依次将nums2中的元素根据大小插入到nums1中。

比如示例1,我们先拿num2中的2和nums1中的元素比较,遍历nums1,如果我们将num2中的2插入到1的后面,那是不是num1中的2,3都要往后移动,这样的从前往后遍历,谁小谁往前放就非常麻烦,我们可以换一下方向,可以从后往前遍历,谁大谁往后放。

这里我们定义了3个变量,一个L1指向要插入数组nums1中有效元素中最后的一个,一个L2指向插入数据来源nums2中的最后一个元素,一个L3指向nums1最后的位置,即开始插入的位置。初始我们比较nums1中最大的数和nums2中最大的数,二者中最大的数放在L3的位置。例如初始L1为3,L2为6,所以6就放在L3的位置,然后L3--,L2--,L1不变,接着继续比较L1和L2的值,L1为3,L2为5,所以5就放在L3的位置,然后L3--,L2--,L1不变,接着L1为3,L2为2,所以3放在L3的位置,L3--,L1--,L2不变,接着L1为2,L2为2,所以2放在L3的位置,L2--指向空就跳出循环。

这里是L2先越界,那么有没有可能L1先越界呢,我们给出2个新数组。

这里L1初始为6,L2为3,所以6放入L3的位置,L3--,L1--,L2不变,接着L1为5,L2为3,所以5放入L3的位置,L3--,L1--,L2不变,接着L1为2,L2为3,所以3放入L3的位置,L3--,L2--,L1不变,接着L1为2,L2为2,假设L1大,填入L3的位置,则L3--,L1--,L2不变,这时L1就越界了。现在我们发现nums2中还有1,2没放入nums1中,该怎么办,应该直接将1,2循环放入L1的前2个位置,因为我们可以知道L3之后的数据一定时比现在L2中的数据大的,所以可以直接从前放入。代码实现如下:

cpp 复制代码
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {
	int L1 = m - 1;
	int L2 = n - 1;
	int L3 = m + n - 1;
	while (L1 >= 0 && L2 >= 0)
	{
		if (nums1[L1] > nums2[L2])
		{
			nums1[L3--] = nums1[L1--];
		}
		else
		{
			nums1[L3--] = nums2[L2--];
		}
	}
	//L1越界
	while (L2 >= 0)
	{
		nums1[L3--] = nums2[L2--];
	}
	//L2越界(不需要处理)
	//L1、L2同时越界(不存在)
}

该思路的时间复杂度为O(N),空间复杂度为O(1)。

好了,我们顺序表的习题就讲到这里,下次我们将进入单链表的学习,尽情期待。

相关推荐
earthzhang20211 天前
【1028】字符菱形
c语言·开发语言·数据结构·c++·算法·青少年编程
papership1 天前
【入门级-算法-3、基础算法:二分法】
数据结构·算法
hjlgs1 天前
Linux中双向链表介绍
数据结构·链表
earthzhang20211 天前
第3讲:Go垃圾回收机制与性能优化
开发语言·jvm·数据结构·后端·性能优化·golang
承渊政道1 天前
动态内存管理
c语言·c++·经验分享·c#·visual studio
best_virtuoso1 天前
PostgreSQL 常见数组操作函数语法、功能
java·数据结构·postgresql
Narcissiffo1 天前
【C语言】str系列函数
c语言·开发语言
无敌最俊朗@1 天前
数组-力扣hot56-合并区间
数据结构·算法·leetcode
hqyjzsb1 天前
2025年市场岗位能力重构与跨领域转型路径分析
c语言·人工智能·信息可视化·重构·媒体·改行学it·caie
码农多耕地呗1 天前
力扣94.二叉树的中序遍历(递归and迭代法)(java)
数据结构·算法·leetcode