【数据结构】顺序表OJ

简介: 【数据结构】顺序表OJ

1. 移除元素


链接27. 移除元素


描述:


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


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


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


示例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:


循环遍历nums数组,当碰到val时,将所有元素向前移动,覆盖掉这个下标的值,numsSize–。


注意点:当连续的两个元素都为val时,如果删除了一个元素,后一个元素依然会删除,但是下标已经越过了这个位置,所以需要回退,然后重新判断是否删除该元素。


最后返回numsSize(数组长度)即可。


当数组中元素大多数元素为val时,为时间复杂度最坏情况。


时间复杂度:O(N^2) 空间复杂度:O(1)


1b253225e0655d9393192c7d544bfff6.png



但是这种方法时间复杂度太高了,能不能优化到O(N)?看思路2 ↓


思路2


这个思路就很简单了。额外创建一个数组,把不是val的值放入数组中,最后把元素移动到原数组中,返回新数组的长度。


注意点:当数组为空时,需要额外判断。


402387b948e3adc7c9a2c84475c83e29.png


能否在不降低时间复杂度的情况下,将空间复杂度优化到O(1)?看思路3↓

思路3(精讲):


题目要求使用O(1)的额外空间,原地修改数组,那么我们肯定是尽量降低时间复杂度,并且不额外开辟数组。


所以,我们使用双指针法:


以给定两个下标:dest、src分别从0下标开始。


src遍历数组。若src碰到不等于val的值,就将src对应下标的值,放到原数组中dest下标对应的位置。然后dest++,src++。


如果src碰到等于val的值,就将src++,dest不动。


当src = numsSize时,说明数组已经遍历过一遍了,而元素也已经成功移除。


最后返回dest,就是我数组的长度。


时间复杂度:O(N) 空间复杂度:O(1)


54879baf15386129816cd583d79aa07e.png

98e01757f2513924ed5c2d163a6b1fc0.png



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



链接:26. 删除有序数组中的重复项


描述:


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


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


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


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


示例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 已按 升序 排列


思路:


本题依旧是需要原地修改数组,所以我们采用三指针法。


说是三指针,其实也就是三个下标。


给定从0开始的下标i、dest,从1开始的下标j。


j用来遍历数组,i则是用来划定重复数字的区域。为什么这么说?接下来我们讲解i和j的分工后,大家就可以理解这个意思。


在数组升序的前提下,j下标在遍历数组时,会遇到两种情况:


(1) 由于第一个数字一定不是重复项,所以j从下标1开始。如果j遇到的数字和下标i所对应的数字相同,那么j++,继续向后走。

// 对应代码段
if (nums[i] == nums[j])
{
  j++;
}


(2) 一旦j碰到的元素和i不相等,那么就将dest下标对应的位置填上i下标的值。并且dest++。重点来了,此时就说明j现在所在位置就是一组重复数的下一个位置。那么此时就让i = j,让i到新的区域内,然后让j++,让j继续走,直到找到新边界。

// 对应代码段
if (nums[i] != nums[j])
{
  nums[dest++] = nums[i];
  i = j;
  j++;  
}



以上两种情况循环进行,直到j == numsSize停止。


但是请注意,对于最后一个数据来说,此时j++的话,那么j就走到了数组后面,最后一个元素没有放置,但是这时我的 i 对应的元素就是最后一个元素,所以需要处理一下:nums[dest++] = nums[i]。

最后返回dest,就是不含重复项数组的大小。


时间复杂度:O(N) 空间复杂度:O(1)



ac91d95fd673e258b12d8d05f2093ded.png

4157b63a0dfaf2be11da16cee3080594.png


3. 合并两个有序数组



链接:88. 合并两个有序数组


描述:


给你两个按 非递减顺序 排列的整数数组 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

   -10^9<= nums1[i], nums2[j] <= 10^9


思路1:


另外创建一个数组,分别遍历两个数组。比较两个数组对应下标的元素,将较小的数组放入新数组中,直到一个数组元素放置完毕停止。由于数组是有序的,于是将未放置完毕的数组的数据放置到新数组中。最后拷贝到原数组中。


时间复杂度:O(M + N) 空间复杂度:O(M + N)



7085697c0d8b819e03af49596b455ab8.png


由于代码过长,再贴一份~

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
    // 额外创建一个数组
    int* newArr = (int*)malloc((m + n) * sizeof(int));
    int start1 = 0, start2 = 0;
    int count = 0;
    while (start1 < m && start2 < n)
    {
        if (nums1[start1] < nums2[start2])
        {
            newArr[count++] = nums1[start1++];
        }
        else
        {
            newArr[count++] = nums2[start2++];
        }
    }
    // 将未放置的数据放入新数组中
    if (start1 == m)
    {
        for ( ; start2 < n; start2++)
        {
            newArr[count++] = nums2[start2];
        }
    }
    else
    {
        for ( ; start1 < m; start1++)
        {
            newArr[count++] = nums1[start1];
        }
    }
    // 拷贝回原数组
    for (int i = 0; i < count; i++)
    {
        nums1[i] = newArr[i];
    }
}


能否在不改变时间复杂度的情况下,将空间复杂度优化到O(1)?看思路2↓

思路2(精讲):


这道题目其实有一个坑,m不是nums1的长度,而是有效数据的长度。n既是nums2的有效数据的长度,也是nums2的长度。nums1的总长度为m + n。


所以我们便可以使用如下方法:


给定end1、end2下标,分别对应着nums1、nums2数组有效数据的末尾位置。给定end下标,为nums1数组的末尾。


首先明确一点,数组是升序的,之后我们的步骤才能正确执行。


如果end1下标对应元素大于end2下标对应元素,则把end1下标对应元素放到end下标处,然后end1- -,end- -;如果end1下标对应元素小于等于end2下标对应元素,则把end2下标对应元素放到end下标处,end2- -,end- -。直到end1或end2中一个值小于0。


如果nums2元素没有放置完毕(end2 >= 0),则将nums2中剩余元素,按照原本顺序依次放入nums1数组;如果nums1数组没有放置完毕,则无需处理,因为这些元素本来就在nums1数组中。


时间复杂度:O(M + N) 空间复杂度:O(1)



cdeea1e2bae0c677da00ea9f2bfa5a6c.png

92f4ce5d39903b836736a2190f95adb9.png


到这里,三道题目就讲解完成了。本篇博客也就到此结束了。


如果有兴趣的小伙伴也可以去刷题练练手。毕竟看懂了并不代表会写了,还得多写多练嘛。


下篇博客我会为大家带来链表的相关知识,我们敬请期待~


如果觉得anduin写的还不错的话,还请一键三连!如有错误,还请指正!


我是anduin,一名C语言初学者,我们下期见!









相关文章
|
存储 编译器 C语言
数据结构-顺序表详解(看这篇就足够了,哈哈哈)
数据结构-顺序表详解(看这篇就足够了,哈哈哈)
168 2
|
9月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
10月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
本文档介绍了线性表的基本运算任务,涵盖顺序表和链表的初始化、销毁、判定是否为空、求长度、输出、查找元素、插入和删除元素等内容。通过C++代码示例详细展示了每一步骤的具体实现方法,并提供了测试说明和通关代码。 主要内容包括: - **任务描述**:实现顺序表的基本运算。 - **相关知识**:介绍线性表的基本概念及操作,如初始化、销毁、判定是否为空表等。 - **具体操作**:详述顺序表和链表的初始化、求长度、输出、查找、插入和删除元素的方法,并附有代码示例。 - **测试说明**:提供测试输入和预期输出,确保代码正确性。 - **通关代码**:给出完整的C++代码实现,帮助完成任务。 文档
292 5
|
11月前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找等具体详解步骤以及举例说明
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
338 3
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之顺序表习题精讲【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找习题精讲等具体详解步骤以及举例说明
|
存储 Java
数据结构第三篇【链表的相关知识点一及在线OJ习题】
数据结构第三篇【链表的相关知识点一及在线OJ习题】
124 7
|
存储
数据结构(顺序表)
数据结构(顺序表)
101 0
|
存储 算法
【数据结构】新篇章 -- 顺序表
【数据结构】新篇章 -- 顺序表
113 0

热门文章

最新文章