顺序表(数据结构)

news2024/11/20 6:20:06

目录

线性表

顺序表

1、顺序表创建

2、初始化

3、扩容

4、尾插

5、尾删

6、头插

7、头删

8、指定位置插入

9、指定位置删除

10、查询

11、打印

12、销毁

顺序表总代码

Leetcode编程题

1、移除元素

题目链接:

题目描述:

题目解析:

2、删除有序数组中的重复项

题目链接:

题目描述:

题目解析

3、合并两个有序数组

题目链接:

题目描述:

题目解析:


线性表

概念:线性表是n个具有相同特性的数据元素的有限序列!

使用性:线性表是实际当中广泛使用的数据结构!

常见的线性表:顺序表、链表、栈、队列、字符串 等……


顺序表

概念:

顺序表是一段物理地址连续的存储单元,依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改!

结构:

1、静态顺序表:

使用定长数组来存储元素。也就是给数组指定的大小,来存储数组,但是有一个弊端,空间不知道给多大,给小了不够,给大了浪费,不建议写这类结构的顺序表!

2、动态顺序表:

使用动态开辟的数组来存储。就是用动态申请空间,通过一个指针来访问该空间,此时有一个好处,我们想要多大的空间就开多大的空间,空间不够还可以扩容,用多少开多少,比较静态顺序表,解决了空间不够,以及空间浪费的问题。比较推荐这类结构写顺序表!


本章将选择动态顺序表的结构形式往下写


1、顺序表创建

静态顺序表:指定数组大小

动态顺序表:动态开辟空间


定义:

我们首先定义一个结构体,让结构体里面存放一个数组(动态顺序表里存的是指向数组的指针),再存放一个整型变量,用来记录在数组种存放有效数据的个数!再通过typedef关键字,将这个结构体进行重命名操作!


代码:

//创建静态顺序表结构(不推荐这样写)

//用typedef重命名一下类型,
//后续我们需要放什么类型只需要改这一处即可
typedef int SLDateType;
#define N 100
typedef struct SeqList
{
	SLDateType a[N];//存放数据数组
	int size;//有效数据的个数
}SL;


//创建动态顺序表结构(推荐)

//用typedef重命名一下类型,
//后续我们需要放什么类型只需要改这一处即可
typedef int SLDateType;

//创建结构体,用动态增容的方式来控制空间
typedef struct SeqList
{
	SLDateType* a;//指向空间的指针
	int size;//有效数据的个数
	int capacity;//容量
}SL;

SL sl;//定义全局结构体变量以便我们进行操作

2、初始化

思路:

对于动态的顺序表结构,在初始化的时候需要动态申请一部分空间,让结构中的指针指向申请的空间。在申请的时候我们采用calloc函数来申请空间,因为calloc函数在动态开辟空间的时候会将空间中每个字节的内容都自动初始化为0,也就不需要我们自己再进行空间初始化了。开辟空间完成之后,将记录有效数据元素个数的整型变量初始化为0,再将记录空间个数的变量初始化成我们所开辟的空间个数即可!

在传函数参数的时候我们传的是结构体变量的地址,便于操作!


代码:

//结构体指针可通过->操作符,访问到结构体成员
#define IN 4

//初始化
void SLInit(SL* ps)
{
	//进行动态开辟空间,让指针指向空间
	ps->a = (SLDateType*)calloc(IN, sizeof(SLDateType));

	//判断空间是否开辟成功
	if (ps->a == NULL)
	{
		perror("calloc fail:");//提示失败错误
		return;
	}

	//初始化有效数据个数
	ps->size = 0;

	//初始化容量
	ps->capacity = IN;
}

3、扩容

思路:

当我们在往数组里装数据的时候,当我们一开始开辟的空间被装满了的情况下,我们就得对该空间进行扩容操作,扩容的时候用ralloc函数来进行扩容,每次增大多少容量呢,这是由自己掌控的,我们每次在原空间的基础上两倍两倍的来进行扩容操作。扩容完成之后让指针指向扩容后的空间,也让记录空间容量增加相同的倍数!


代码:

#define IT 2

//判断是否扩容
void SLCheckCapacity(SL* ps)
{
	//断言指针
	assert(ps);

	//当有效数据个数等于容量的时候进行扩容
	if (ps->size == ps->capacity)
	{
		//扩容
		SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * ps->capacity * IT);

		//判断是否开辟成功
		if (NULL == tmp)
		{
			perror("ralloc fail:");//提示失败信息
			return;
		}

		//扩容成功,让指针指向该空间
		ps->a = tmp;
		
		//让记录容量的变量增加
		ps->capacity *= IT;
	}
}

4、尾插

思路:

尾插,顾名思义就是在最后插入数据。在顺序表中尾插,就是在数组的最后一个元素后面插入一个数据,我们知道结构体里面有一个记录有效数据元素个数的变量,也就是数组元素的个数,数组元素的个数恰好是数组最后一个元素的下一个位置的下标。所以尾插的时候我们直接用记录元素个数的变量做为下标放入数据即可。放入之后,让记录元素个数的变量+1即可!但是在插入的时候我们还得关心我们开辟的空间是否够用?够用的情况下我们继续插入。不够用的情况下,我们就要进行扩容了。所以在插入的时候我们得先判断一下是否需要扩容。需要扩容,我们要先扩容再插入数据,不需要就直接插入数据!


代码:

//尾插
void SLPushBank(SL* ps, SLDateType x)
{
    //x是要插入的数据

	assert(ps);//断言一下指针是否为NULL

	//是否要扩容
	SLCheckCapacity(ps);

	//进行尾插
	ps->a[ps->size] = x;

	//记录元素的个数增加1
	ps->size++;
}

5、尾删

思路:

尾删,顾名思义就是将数组最后一个元素删除。要删除最后一个元素,直接将记录有效数据元素个数的变量-1即可,顺便将那个位置的数据改为0!但在删除的时候我们得注意一下数组中的数据是否删完了,如果删完了我们是不能进行删除的。会导致数组越界问题!


注意:

因为我们无法正真的去将最后一个元素所占的空间去释放,动态内存管理中是不允许释放连续空间中的某一部分空间的!用realloc函数去改变空间大小也是不行的,realloc函数是不支持空间缩容的,它要缩,只是缩了我们对空间的使用权!并没有将空间释放!所以我们只能通过这种减小有效数据个数来实现删除操作!


代码:

//尾删
void SLPopBank(SL* ps)
{
	//断言指针
	assert(ps);

	//判断数据是否删完了
	if (ps->size == 0)
	{
		printf("没有数据可删\n");
		return;
	}
	
	//将要删除位置的数据改为0
	ps->a[ps->size - 1] = 0;

	//删除数据,让有效数据个数减少即可
	ps->size--;
}

6、头插

思路:

头插,顾名思义就是在数组的首位置插入一个元素,要在首位置插入元素,就要将数组中的每一个元素都往后移动一位,把首位置空出来,插入数据!插入数据的时候需要判断空间够不够,不够需要进行增容操作。随后让记录有效数据个数的变量+1即可!


代码:

//头插
void SLPushFont(SL* ps, SLDateType x)
{
	//x是要插入的数据
	
	//断言指针
	assert(ps);

	//是否要增容
	SLCheckCapacity(ps);

	//开始移动数据,让每个数据都往后移动一位
	int end = ps->size;
	while (end > 0)
	{
		ps->a[end] = ps->a[end - 1];
		--end;
	}

	//头插数据
	ps->a[0] = x;

	//记录个数
	ps->size++;
}

7、头删

思路:

头删,顾名思义就是将数组的首位置的数据删除,删除数据就是将数组从第二个元素开始之后的所有数据都往前挪动一位,将第一位的数据覆盖掉,实现头删操作。同时让记录元素个数的变量-1即可。在删除的时候先要进行判断数组是否有元素可删,也就是数组是否存在数据,若无数据是不能再删的!


代码:

//头删
void SLPopFont(SL* ps)
{
	//断言指针
	assert(ps);

	//判断数组是否有元素
	if (ps->size == 0)
	{
		printf("没有数据可删\n");
		return;
	}

	//从第二个位置开始数组整体往前挪动一步覆盖掉第一个数据实现删除
	int begin = 0;
	while (begin < ps->size - 1)
	{
		ps->a[begin] = ps->a[begin + 1];
		begin++;
	}

	//让记录个数的变量-1
	ps->size--;
}

8、指定位置插入

思想:

指定位置插入,就是指定数组的下标,在该位置插入一个数据。让数组从该位置开始每一个元素的数据,都往后挪动一步。随后在这个插入要插入的数据,且让记录数据个数的变量+1即可!在插入之前也需要判读是否要进行扩容操作!


代码:

//指定pos位置插入
void SLInsert(SL* ps, int pos, SLDateType x)
{
	//x是插入的数据
	//pos是要插入的位置
	
	//是否要增容
	SLCheckCapacity(ps);

	//断言指针
	assert(ps);

	//pos位置只能是0 - ps->size 之间,不然会越界
	assert(pos >= 0 && pos <= ps->size);
	//pos位置和ps->size相同时实际上是尾插
	//pos位置和0相同时实际上是头插

	//让pos位置之后的数据整体往后挪动一位
	// 再将x插入pos位置
	//开始挪动pos位置后面的数据
	int end = ps->size - 1;
	while (end > pos)
	{
		ps->a[end] = ps->a[end - 1];
		--end;
	}

	//插入数据
	ps->a[pos] = x;

	//记录个数变量+1
	ps->size++;
}

9、指定位置删除

思路:

指定位置删除,指定一个下标,删除数组对应的此下标位置的数据,从此下标位置的下一个位置开始之后数组的每一个数据都往前挪动一位,将该位置覆盖,进行删除操作,随后让记录个数的变量-1,且删除之前还要判断数组是否有元素,若没有元素不可再删除!


代码:

//指定pos位置删除
void SLErase(SL* ps, int pos)
{
	//pos是指定删除的位置
	
	//断言指针
	assert(ps);
	//pos位置只能是0 - ps->size 之间,不然会越界
	assert(pos >= 0 && pos <= ps->size);

	//判断数据是否删完了
	if (ps->size == 0)
	{
		printf("没有数据可删\n");
		return;
	}

	//挪动数据覆盖pos位置删除
	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		++begin;
	}

	//记录个数变量-1
	ps->size--;
}

10、查询

思路:

查询数据,传入一个参数,这个参数是我们要查询的数据,随后遍历数组,找到与其相同的数据返回下标,若找不到则返回-1!


代码:

//查找
int SLFind(SL* ps, SLDateType x)
{
	//断言指针
	assert(ps);

	//遍历查找
	for (int i = 0; i < ps; i++)
	{
		if (ps->a[i] == x)
		{
			//返回下标
			return i;
		}
	}

	//找不到返回-1
	return -1;
}

11、打印

思路:

打印就是将数组从头到尾打印一边,将数组中的有效数据,进行遍历打印即可


代码:

//打印
void PrintSeqList(SL* ps)
{
	//断言指针
	assert(ps);

	//遍历打印
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

12、销毁

思路:

销毁就是,释放动态开辟的空间,随后将记录个数的变量置为0,记录容量的变量也置为0,最后将指向该空间的指针置为NULL,避免野指针!


注意:

这里的销毁不是真正的销毁,是将空间还给操作系统,释放空间,不是物理上的把空间销毁,空间还存在只是将空间还给了操作系统,不再使用到该空间!


代码:

//销毁
void SLDestory(SL* ps)
{
	//断言指针
	assert(ps);

	//释放开辟的空间
	free(ps->a);

	//有效个数置为0
	ps->size = 0;

	//容量置为0
	ps->capacity = 0;

	//将指针置为NULL,避免野指针
	ps->a = NULL;
}

顺序表总代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


//创建动态顺序表结构(推荐)

//用typedef重命名一下类型,
//后续我们需要放什么类型只需要改这一处即可
typedef int SLDateType;

//创建结构体,用动态增容的方式来控制空间
typedef struct SeqList
{
	SLDateType* a;//指向空间的指针
	int size;//有效数据的个数
	int capacity;//容量
}SL;

SL sl;//定义结构体变量以便我们进行操作

#define IN 4
#define IT 2

//初始化
void SLInit(SL* ps)
{
	//进行动态开辟空间,让指针指向空间
	ps->a = (SLDateType*)calloc(IN, sizeof(SLDateType));

	//判断空间是否开辟成功
	if (ps->a == NULL)
	{
		perror("calloc fail:");//提示失败错误
		return;
	}

	//初始化有效数据个数
	ps->size = 0;

	//初始化容量
	ps->capacity = IN;
}


//判断是否扩容
void SLCheckCapacity(SL* ps)
{
	//断言指针
	assert(ps);

	//当有效数据个数等于容量的时候进行扩容
	if (ps->size == ps->capacity)
	{
		//扩容
		SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * ps->capacity * IT);

		//判断是否开辟成功
		if (NULL == tmp)
		{
			perror("ralloc fail:");//提示失败信息
			return;
		}

		//扩容成功,让指针指向该空间
		ps->a = tmp;

		//让记录容量的变量增加
		ps->capacity *= IT;
	}
}


//尾插
void SLPushBank(SL* ps, SLDateType x)
{
	assert(ps);//断言一下指针是否为NULL

	//是否要扩容
	SLCheckCapacity(ps);

	//进行尾插
	ps->a[ps->size] = x;

	//记录元素的个数增加1
	ps->size++;
}

//尾删
void SLPopBank(SL* ps)
{
	//断言指针
	assert(ps);

	//判断数据是否删完了
	if (ps->size == 0)
	{
		printf("没有数据可删\n");
		return;
	}

	//将要删除位置的数据改为0
	ps->a[ps->size - 1] = 0;

	//删除数据,让有效数据个数减少即可
	ps->size--;
}

//头插
void SLPushFont(SL* ps, SLDateType x)
{
	//x是要插入的数据

	//断言指针
	assert(ps);

	//是否要增容
	SLCheckCapacity(ps);

	//开始移动数据,让每个数据都往后移动一位
	int end = ps->size;
	while (end > 0)
	{
		ps->a[end] = ps->a[end - 1];
		--end;
	}

	//头插数据
	ps->a[0] = x;

	//记录个数
	ps->size++;
}

//头删
void SLPopFont(SL* ps)
{
	//断言指针
	assert(ps);

	//判断数组是否有元素
	if (ps->size == 0)
	{
		printf("没有数据可删\n");
		return;
	}

	//从第二个位置开始数组整体往前挪动一步覆盖掉第一个数据实现删除
	int begin = 0;
	while (begin < ps->size - 1)
	{
		ps->a[begin] = ps->a[begin + 1];
		begin++;
	}

	//让记录个数的变量-1
	ps->size--;
}

//指定pos位置插入
void SLInsert(SL* ps, int pos, SLDateType x)
{
	//x是插入的数据
	//pos是要插入的位置

	//是否要增容
	SLCheckCapacity(ps);

	//断言指针
	assert(ps);

	//pos位置只能是0 - ps->size 之间,不然会越界
	assert(pos >= 0 && pos <= ps->size);
	//pos位置和ps->size相同时实际上是尾插
	//pos位置和0相同时实际上是头插

	//让pos位置之后的数据整体往后挪动一位
	// 再将x插入pos位置
	//开始挪动pos位置后面的数据
	int end = ps->size - 1;
	while (end > pos)
	{
		ps->a[end] = ps->a[end - 1];
		--end;
	}

	//插入数据
	ps->a[pos] = x;

	//记录个数变量+1
	ps->size++;
}

//指定pos位置删除
void SLErase(SL* ps, int pos)
{
	//pos是指定删除的位置

	//断言指针
	assert(ps);
	//pos位置只能是0 - ps->size 之间,不然会越界
	assert(pos >= 0 && pos <= ps->size);

	//判断数据是否删完了
	if (ps->size == 0)
	{
		printf("没有数据可删\n");
		return;
	}

	//挪动数据覆盖pos位置删除
	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		++begin;
	}

	//记录个数变量-1
	ps->size--;
}

//查找
int SLFind(SL* ps, SLDateType x)
{
	//断言指针
	assert(ps);

	//遍历查找
	for (int i = 0; i < ps; i++)
	{
		if (ps->a[i] == x)
		{
			//返回下标
			return i;
		}
	}

	//找不到返回-1
	return -1;
}

//打印
void PrintSeqList(SL* ps)
{
	//断言指针
	assert(ps);

	//遍历打印
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

//销毁
void SLDestory(SL* ps)
{
	//断言指针
	assert(ps);

	//释放开辟的空间
	free(ps->a);

	//有效个数置为0
	ps->size = 0;

	//容量置为0
	ps->capacity = 0;

	//将指针置为NULL,避免野指针
	ps->a = NULL;
}


int main()
{
	//以下部分可自行操作

	//初始化
	SLInit(&sl);

	//尾插
	SLPushBank(&sl, 1);
	SLPushBank(&sl, 2);
	SLPushBank(&sl, 3);
	SLPushBank(&sl, 4);
	SLPushBank(&sl, 5);
	PrintSeqList(&sl);//打印

	//尾删
	SLPopBank(&sl);
	SLPopBank(&sl);
	PrintSeqList(&sl);//打印

	//头插
	SLPushFont(&sl, 3);
	SLPushFont(&sl, 2);
	SLPushFont(&sl, 1);
	PrintSeqList(&sl);//打印

	//头删
	SLPopFont(&sl);
	SLPopFont(&sl);
	SLPopFont(&sl);
	PrintSeqList(&sl);//打印

	//指定pos位置插入
	SLInsert(&sl, 1, 5);
	PrintSeqList(&sl);//打印
	SLInsert(&sl, 0, 4);
	PrintSeqList(&sl);//打印
	SLInsert(&sl, 5, 7);
	PrintSeqList(&sl);//打印

	//指定pos位置删除
	SLErase(&sl, 0);
	PrintSeqList(&sl);//打印

	//查找
	int k = SLFind(&sl, 5);
	printf("%d", k);

	//销毁
	SLDestory(&sl);

	return 0;
}

Leetcode编程题

1、移除元素

题目链接:

27.移除元素https://leetcode.cn/problems/remove-element/


题目描述:

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

提示:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 50
  • 0 <= val <= 100

题目解析:

思路1:

思路:

对数组进行遍历,找跟val相同的值,找到相同的值后,让数组的该位置(也就是跟val相同数据的位置)之后的数据整体进行往前挪动一位,覆盖掉该元素,同时让数组个数-1,实现删除操作!


画图理解:


代码: 

int removeElement(int* nums, int numsSize, int val)
{
    //遍历数组,找与val相同的值
    int i = 0;
    for (i = 0; i < numsSize; i++)
    {
        //判断是否与val相同,相同则进行删除
        if (val == nums[i])
        {
            //开始覆盖删除
            int j = 0;
            for (j = i; j < numsSize - 1; j++)
            {
                nums[i] = nums[i + 1];
            }
            //让数组个数进行-1
            numsSize--;

            //覆盖之后的i是与val相同值的下一个数据的下标
            //如果继续,执行,会产生i这个位置覆盖过来的新数据不会被判断到的请款
            //此时让i-1,再继续进行循环执行i++操作就能访问到覆盖过来的新数据
            i--;
        }
    }

    return numsSize;
}

思路2:

思路:

双指针法,直接在数组上进行操作,给定两个整型变量src和det,作为下标,初始值都给0,让src找与val相同的值,如果src位置的值与val相同,则让src++,不同则将src位置的值赋值给det位置,同时src++,det++。最后det就是数组的元素个数!


画图理解:


代码:

int removeElement(int* nums, int numsSize, int val)
{
    //让两个下标变量指向第一个元素
    int det = 0;
    int src = 0;
    
    //让src开始找,直到找完数组借书
    while (src < numsSize)
    {
        //相等让src++
        if (nums[src] == val)
        {
            ++src;
        }
        else
        {
            //不等则赋值过去,同时src++,det++
            nums[det++] = nums[src++];
        }
    }

    //此时det就是删除后数组元素个数
    return det;
}


2、删除有序数组中的重复项

题目链接:

26.删除有序数组中的重复项https://leetcode.cn/problems/remove-duplicates-from-sorted-array/


题目描述:

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。

不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

判题标准:

系统会用下面的代码来测试你的题解:

int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案

int k = removeDuplicates(nums); // 调用

assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
    assert nums[i] == expectedNums[i];
}

如果所有断言都通过,那么您的题解将被 通过

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 不需要考虑数组中超出新长度后面的元素。

示例 2:

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

提示:

  • 1 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • nums 已按 升序 排列

题目解析

思路:

首先,给定两个整型下标变量,src和det,让det表示首位置,src表示数组的第二个位置,

让src开始找与det位置不同的值,找到之后让det先往前走一步也就是++det,随后将src位置的值赋给det位置,同时src往后继续走一步src++,若src位置的值和det位置的值相同,则让src往后走一步src++,继续找,直到src找完数组为止。最后det+1就是去除重复数据后数组元素的个数!


画图理解:


代码: 

int removeDuplicates(int* nums, int numsSize) {

    //让det表示首元素,src表示第二个元素
    int det = 0;
    int src = 1;

    //让src开始找与det不同的值,相同让src继续走
   //不同则让det先走一步,再将src位置的值赋给det位置
    //直到找完数组位置
    while (src < numsSize)
    {
        if (nums[src] != nums[det])
        {
            det++;
            nums[det] = nums[src];
            src++;
        }
        else
        {
            src++;
        }
    }

    //最后det+1就是去重后数组的元素个数
    return det + 1;
}

3、合并两个有序数组

题目链接:

88.合并两个有序数组https://leetcode.cn/problems/merge-sorted-array/


题目描述:

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

示例 1:

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

示例 2:

输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。

示例 3:

输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。

提示:

  • nums1.length == m + n
  • nums2.length == n
  • 0 <= m, n <= 200
  • 1 <= m + n <= 200
  • -109 <= nums1[i], nums2[j] <= 109

题目解析:

思路1:

思路:

首先得理解一下非递减这个概念,非递减就是递增或相等。空间换时间的算法,我们首先开辟一个数组,让两个数组从头开始一一比较,把小的那一个值先放进开辟好的数组中,随后再放大的,相等的情况下放那个都可以。两个数组里面的数据比完之后,还要注意一个情况,两个数组中的某一数组的数据的有可能没有放完,因为不知道是那一个数组没放完,所以此时我们要对两个数组都进行判断,将没放完的数据放入开辟的数组空间中即可!最后将新数组的内容拷贝到nums1中!


画图理解:


代码: 

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
    //开辟新数组
    int* arr = (int*)malloc(sizeof(int) * (n + m + 1));
    int k = 0;
    //定义两个变量分别遍历两个数组
    int i = 0;
    int j = 0;
    while (i < m && j < n)
    {
        //开始比较大小
        if (nums1[i] < nums2[j])
        {
            //小的放进去
            arr[k++] = nums1[i++];
        }
        else
        {
            //相等的时候放nums2的值
            arr[k++] = nums2[j++];
        }
    }


    //看一下那个数组没放完,将剩余的放进去
    while (j < n)
    {
        arr[k++] = nums2[j++];
    }

    while (i < m)
    {
        arr[k++] = nums1[i++];
    }

    //进行拷贝
    memcpy(nums1,arr, sizeof(int) * k);

    //释放新数组
    free(arr);
}

思路2:

思路:

非递减就是递增或相等,我们定义三个下标变量i,j,det,分别表示:i是数组1的有效数据的最后一个数据的下标、j是数组2最后一个数据元素的下标、det是数组1最后一块空间的下标。随后让数组1的有效数据跟数组2的数据从后往前比较,将两个数组中较大的值拿到det的位置,同时让det和拿下数据的数组下标往前走一步,依次比较:最后会产生两种情况:情况1:数组2中的元素都放完了,此时数组1就是我们所要的归并后的最终数组。情况2:数组2中的元素没有放完,此时我们要将数组2中的剩余的数据都拿过来,拿过来之后数组1就是我们所要的最终数组!


画图理解:

 


代码:

//三个下标法
//  三下标写法
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
       //i 是nums1数组有效数据的最后一个元素的下标
    int i = m - 1;

    //j是nums2数组的最后一个元素的下标
    int j = n - 1;

    //det是nums1数组空间中的最后一个空间的下标
    int det = (m + n) - 1;

    //两个数组从前往后走对比大小
    while (i >= 0 && j >= 0)
    {
        //大的放到det位置同时进行--
        if (nums2[j] > nums1[i])
        {
            nums1[det--] = nums2[j--];
        }
        else
        {
            //相等的时候放nums2的值
            nums1[det--] = nums1[i--];
        }
    }

    //看nums2数组中的元素是否放完
    while (j >= 0)
    {
        nums1[det--] = nums2[j--];
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/428457.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

游戏内嵌社区服务开放,助力开发者提升玩家互动与留存

华为 HMS Core 游戏内嵌社区服务提供快速访问华为游戏中心论坛能力&#xff0c;支持玩家直接在游戏内浏览帖子和交流互动&#xff0c;助力开发者扩展内容生产和触达的场景。 一、为什么要游戏内嵌社区&#xff1f; 二、游戏内嵌社区的典型使用场景 1、游戏内打开论坛 您可以在…

[Linux]环境变量

目录 基本概念 常见的环境变量 PATH测试 HOME测试 SHELL测试 和环境变量相关的命令 main函数的三个参数 环境变量的组织方式 通过代码如何获取环境变量 通过系统调用获取或设置环境变量 基本概念 环境变量(environment variables)一般是指在操作系统中用来指定操作系…

FreeRTOS 系统内核控制函数

FreeRTOS 中有一些函数只供系统内核使用&#xff0c;用户应用程序一般不允许使用&#xff0c;这些 API 函数就是系统内核控制函数。 内核控制函数 顾名思义&#xff0c;内核控制函数就是 FreeRTOS 内核所使用的函数&#xff0c;一般情况下应用层程序不使用这些函数&#xff0…

6基于二阶锥规划的主动配电网最优潮流求解

matlab代码&#xff1a;基于二阶锥规划的主动配电网最优潮流求解 参考文献&#xff1a;主动配电网多源协同运行优化研究_乔珊 摘要&#xff1a;最优潮流研究在配 电网规划运行 中不可或缺 &#xff0c; 且在大量分布式能源接入 的主动配 电网环境下尤 为重要 。传统的启发式算…

设计师一定要知道这5个免费样机素材网

本期推荐5个设计师必备的样机素材网站&#xff0c;免费下载&#xff0c;建议收藏~ 1、菜鸟图库 https://www.sucai999.com/searchlist/3217----all-0-1.html?vNTYxMjky 网站有多种类型的设计素材&#xff0c;像平面、电商、UI、办公等素材这里面都能找到。样机素材分类清晰&…

GitHub“疯狂”求阿里内部开源这份10W字Java面试手册,竟遭拒绝

行业风向标&#xff0c;猎聘发布的数据报告显示&#xff1a; 相比以往&#xff0c;2023年企业招聘两大变化体现在&#xff1a;对人才各方面能力要求更高、对人岗的匹配性要求更细。不同规模的企业用人各有侧重&#xff0c;大中型企业更注重人的全面能力&#xff0c;小型企业更…

我写系列博客的缘由

我写系列博客的缘由 每个经历不是一帆风顺的人&#xff0c;都将深刻地体会到&#xff0c;少走弯路对一个人来说是多么重要。人的生存不是浪漫的幻想&#xff0c;而是建立在能立足于现实社会上&#xff0c;教育的真正意义&#xff0c;是培养你的认识水平、提升智慧&#xff0c;借…

JAVA语言-比较器Comparator

目录 一、什么是Comparator 二、Java compare方法和compareTo方法 三、java中Comparable和Comparator的区别 Comparator的例子 一、什么是Comparator Comparator 是javase中的接口&#xff0c;位于java.util包下。 数组工具类和集合工具类中提供的工具方法sort方法都给出…

Vector - CAPL - CAN x 总线信息获取

在CAN&CANFD测试中&#xff0c;我们经常需要获取到CAN总线的负载、错误帧、过载帧、发送错误等等CAN总线上面的信息&#xff0c;这些信息如此重要&#xff0c;但是如果真的要写代码去实现也是相当不易的&#xff0c;那我们该如何去获取到的呢&#xff1f;下面我们就来一起看…

系统集成作业——公司网络系统集成设计,总公司、分公司地跨两个不同城市。

一 实验需求 完成公司网络系统集成设计&#xff08;2000台电脑&#xff09;&#xff0c;总公司、分公司地跨两个不同城市 二实验分析 本次实验继续建立在实验三的基础之上&#xff0c;对其升级改造为地跨不同城市的总公司和分公司的大型局域网网建设。 实验三配置点击链接&…

经营软件公司五年,从外包到SaaS的踩坑笔记

文章目录摘要开公司的两个误区关于管理关于合作关于SaaS其他经验大和强是两码事。大不是目的&#xff0c;强才是。小步试错、慢慢迭代不要掉入流量陷阱摘要 经营公司已有五年&#xff0c;经历了三年的疫情停滞&#xff0c;现在正在转型为一家SaaS公司。虽然曾经迷茫过&#xf…

包装类,String,String的方法

针对八种基本数据类型相应的引用类型-----包装类。有了类的特点&#xff0c;就可以调用类中的方法 基本数据类型包装类booleanBoolean char CharacterbyteByteshortShortintIntegerlongLongfloatFloatdoubleDouble Byte---Double类的父类是Number 关系图 拆箱&#xff0c;装箱 …

如何微调Segment Anything Model

文章目录什么是SAM&#xff1f;什么是模型微调&#xff1f;为什么要微调模型&#xff1f;如何微调 Segment Anything 模型背景与架构创建自定义数据集输入数据预处理训练设置循环训练保存检查点并从中启动模型下游应用程序的微调随着 Meta 上周发布的 Segment Anything Model (…

DAY 33 shell编程 常用的文本命令

sort命令####排序 sort将文件的每一行作为一个单位相互比较&#xff0c;比较原则是从首字符向后依次按ASCII码进行比较&#xff0c;最后将它们按升序输出。&#xff08;以行为单位来排序输出&#xff09; sort [选项] 参数​cat file | sort 选项常用选项&#xff1a; 常用选…

计算机综合题汇总

(数学计算题) 把6个相同的球分到三个不同的学生身上,允许有的学生没有球,请问有多少种不同的方法? C(8,2)=28。 典型的插板问题,直接套公式,C(n+m-1,m-1)。6个球,本身5个空,有同学可以不分,再加3个空,共8个空;插入2个板。 (软件选择题) 软件质量保证是什么? A. 确保…

超外差收音机的制作-电子线路课程设计-实验课

超外差收音机的制作 一、原理部分&#xff1a; 超外差收音机&#xff1a;超外差式收音机是将接收到的不同频率的高频信号全部变成一个固定的中频信号进行放大&#xff0c;因而电路对各种电台信号的放大量基本是相同的&#xff0c;这样可以使中放电路具有优良的频率特性。 超…

Adobe考证

在数字化时代&#xff0c;Adobe软件已成为许多人工作和创造的必备工具。为了证明自己在使用Adobe软件方面的专业能力&#xff0c;许多人选择参加Adobe认证考试并获取Adobe认证证书。 本文将从以下几个方面介绍Adobe考证的相关内容...... 什么是Adobe认证考试&#xff1f; Ado…

我的面试八股(JAVA并发)

重点AQS以及几种锁的底层需要补充&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 程序计数器为什么是线程私有的? 程序计数器主要有下面两个作用&#xff1a; 字节码解释器通过改变程序计数器来依次读取指令&#xff0c;从而实现代码的流程控制&#xff0c…

VS Code配置C/C++开发环境

一、VS Code安装C/C++插件 二、配置MinGW 进入下载页面mingw-w64,找到winlibs-x86_64-mcf-seh-gcc-13.0.1-snapshot20230402-mingw-w64ucrt-11.0.0-r1.7z,点击下载。将文件放到自己想要放置的盘符下面,然后解压,将里面的mingw64目录剪切到最外层。 拷贝目录,将目录添加到…

【静态Web架构】静态站点生成器概述 Gatsby ,Hugo 和Jekyll对比

在本文中&#xff0c;您将看到三种最好的静态站点生成器的比较&#xff0c;它们的优点、缺点以及您应该使用它们的原因。网站统治着网络&#xff0c;无论是静态的还是动态的。虽然现在很多网站都是动态的&#xff0c;但是静态的仍然很受欢迎。事实上&#xff0c;静态网站的使用…