数据结构题目①——数组

前言

本篇文章为博主进行代码随想录——数组练习后的总结会涉及到每一道题目的详细的思路整理,以及本人的易错点,希望对大家有所帮助

数组介绍:

数组在C语言中就已经有所涉及,它是一个最基础的数据结构,而在数据结构中,通常它属于线性表中的顺序表(Sequence List),它是一种特殊的线性存储结构。

特点:

逻辑上相邻的数据元素,在物理次序上也是相邻的。——也就是说它们挨着存储

任意元素都可在相同的时间内存取,即顺序存储的数组是一个随即存取结构

顺序存储:行优先和列优先两种顺序

行优先——每一行的第一个元素位于低地址,最后的位于高地址,且连续

列优先——每一行的第一个元素位于低地址,最后的位于高地址,且连续

优点:

它访问速度快,但是无法高效插入和删除元素

数组理论基础

数组是存放在连续内存空间上的相同类型数据的集合

数组的元素是不能删的,只能覆盖

如果使用C++的话,要注意vector 和 array的区别,vector的底层实现是array,严格来讲vector是容器,不是数组。

对于二维数组来说——它的顺序是否连续呢——语言不同,会造成一定的差异,而c++中的二维数组是连续的,Java却不是这样存储的。

 二分查找

题目:. - 力扣(LeetCode)

方法——使用二分查找的方式

注意——

1.要看清题目是否给出有序数组,这是一个二分查找的条件

2.循环不变量的思想——区间设置到底是左闭右开还是左闭右闭

第一次写可能出现的问题——

1.if中的==而不是=

2.left和right是数组的位置标号,而非数组内容

3.要注意从开始就考虑是选取左闭右闭还是左闭右开

4.注意函数最后要在while循环外有一个return 否则错误

二分查找的方法介绍

 二分法就是设置两个指针,让它们一个在最左边,一个在最右边,而且要设置一个中间值然后为了查找有序数组中的值,不断细分这个数组——假如你要寻找的这个数是小于中间值的,那么这个数就在左边,这时候我们只需要在左边再次重复操作——直到左边指针和右边指针找到了那个数(这个结束的条件有两种情况)

那这样写第一遍你会发现——可能还是会发生错误——

在while循环中,结束的条件到底是left<right还是left<=right呢?

在循环中找到要继续查找的是左边或右边后,right或left是等于middle(中间值)呢,还是其他呢?

那么接下来就介绍一个概念——循环不变量 

在整个过程中我们这个数组在查找时,是把它看成左闭右闭还是左闭右开呢?这个需要你在循环之前需要搞清楚的量,就是所谓的循环不变量

左闭右闭时——

  • 1.while括号里的条件为——left<=right因为这时left=right对于闭区间来说,是成立的
  • 2.而当需要移动指针时,right或者是left是直接等于middle-1/middle+1的,因为已经判断好了middle上不是需要找的数.

左闭右开时——

  • 1.while括号里的条件为——left<right因为这时left=right对于半开区间来说,是不成立的
  • 2.而当需要移动指针时,right是直接等于middle的,因为这时候right是一个开区间,只有放在已查看过的middle,才不至于略掉其他的可能为你要寻找的数

学会之后:第二次可能写出现的问题——

对于左闭右开的形式,从一开始就需要做出改变

——

1.定义时,右边的要定义的比左闭右闭多一位

2.对于左边的变到中间,是中间+1,因为这里是闭区间

3.对于右边的变到中间,就是中间了

  • 更多有关二分查找的题(也来自代码随想录)
  • 35.搜索插入位置(opens new window)
  • 34.在排序数组中查找元素的第一个和最后一个位置(opens new window)
  • 69.x 的平方根(opens new window)
  • 367.有效的完全平方数(opens new window)

这里只记录一个二分查找扩展——搜索插入位置

暴力解法

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        for (int i = 0; i < nums.size(); i++) {
        // 分别处理如下三种情况
        // 目标值在数组所有元素之前
        // 目标值等于数组中某一个元素
        // 目标值插入数组中的位置
            if (nums[i] >= target) { // 一旦发现大于或者等于target的num[i],那么i就是我们要的结果
                return i;
            }
        }
        // 目标值在数组所有元素之后的情况
        return nums.size(); // 如果target是最大的,或者 nums为空,则返回nums的长度
    }
};

二分法(这里采用c++其中的vector<int>& nums相当于C语言中传入一个整型数组nums,nums.size()返回这个数组的大小)

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0;
        int right = n; // 定义target在左闭右开的区间里,[left, right)  target
        while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target 在左区间,在[left, middle)中
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,在 [middle+1, right)中
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值的情况,直接返回下标
            }
        }
        // 分别处理如下四种情况
        // 目标值在数组所有元素之前 [0,0)
        // 目标值等于数组中某一个元素 return middle
        // 目标值插入数组中的位置 [left, right) ,return right 即可
        // 目标值在数组所有元素之后的情况 [left, right),因为是右开区间,所以 return right
        return right;
    }
};

移除元素

题目:. - 力扣(LeetCode)

第一遍练习会出现的问题——

对于i不知道如何让它再次看之前的数值——i--即可

第二次循环时,需要覆盖,这时候要设置j=i+1,n[j]=n[j-1]

 方法一——暴力解题法

对于移除元素,在循环中找到所满足条件的数组元素,然后覆盖其位置数值(用循环)……

方法二——快慢指针法

首先来介绍一下什么是快指针和慢指针:

具体方法思路——

首先假设数组为[3,2,2,3]需要去除的值是3

快慢指针初始均指向第一个数,慢指针是为了获取最终数组,它这个位置假如遇到需要去除的值,则要覆盖——快指针获取这个覆盖的值,即最后把所有的需要去除的数给去除。

所以循环是现判断快指针指向的位置是否为需要去除的值——是则往下移一位,不是则把这个值赋给慢指针所指向的值,然后慢指针往后移,循环往复,直到快指针遍历结束

例子:假如第一个遇到的就是需要去除的值,那么快指针会先往下一步,然后如果所指向的值不为去除的值——赋值给慢指针,这样可以覆盖掉第一个需要去除的值,然后慢指针往后一步,如果慢指针还是指向需要去除的值,则重复上述步骤,这样直到最后,快指针可以把所有的不需要去除的值给按顺序覆盖到需要去除的地方——完成代码即可

int removeElement(int* nums, int numsSize, int val) {
    int s=0;
    int q=0;
    for(;q<numsSize;q++)
    {
        if(nums[q]!=val)
        {
            nums[s]=nums[q];
            s++;
        }
    }
    return s;
}

注意写代码时——这里的快慢指针不是真的指针,而是设置的数组下标。

有序数组的平方

题目:. - 力扣(LeetCode)

第一次暴力解题会出现的问题——

1.平方的for循环要i从0——numsSize(<号的时候)

2.排序方式——这里用选择排序示例:

for(int i=0;i<numsSize-1;i++)
    {
        for(int j=i+1;j<numsSize;j++)
        {
            if(nums[i]>nums[j])
            {
                int t=nums[i];
                nums[i]=nums[j];
                nums[j]=t;
            }
        }
    }

第二次用双指针写出现的问题——

1.新建的数组需要动态开辟空间?

双指针法

设计思路:

首先先观察题目——发现如果平方之后,最大值一定是最左边或是最右边的平方,而且是按一定顺序排列的数组,如果是右边的平方大,则下一个就是左边的平方,然后再往里比较,因此这里借助两个指针,一左一右,进行大小比较,然后把大的一个赋值给新数组

 双指针法的具体思路:

首先先创造一个新数组——和原来的数组一样大小,这里使用动态数组的方式,然后设置两个int类型的数据,它们分别为0和数组大小-1,然后比较这两个所指的原数组数据平方谁大——谁就把这个放进新数组的最右边,然后这个指针往中间的方向移动——再次比较——再次赋值给新数组的“指针”……直到两个指针相遇,即可结束循环——代码也就完成了

代码:(result是新数组)

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) {
        int k = A.size() - 1;
        vector<int> result(A.size(), 0);
        for (int i = 0, j = A.size() - 1; i <= j;) { 
            if (A[i] * A[i] < A[j] * A[j])  {
                result[k--] = A[j] * A[j];
                j--;
            }
            else {
                result[k--] = A[i] * A[i];
                i++;
            }
        }
        return result;
    }
};

长度最小的子数组

题目:

. - 力扣(LeetCode)

第一次写出现的问题——

1.没有思路

2.返回值?

3.这个int类型的最大值不知道是怎么写?

INT32_MAX

 这里的思路其实看过代码之后就有点恍然大悟了,但是不知道怎么想的,很妙

这道题依然采用两个指针的方法——不过这里方法叫滑动窗口,因为可能指针的移动像滑动吧🤭

1.由于返回的是长度最小的满足情况的数组大小——因此我们先把这个长度设置为最大——也就是int的最大,用INT32_MAX来表示,如果最后结果仍未这个值,说明没有满足条件的情况,返回0,否则返回长度的最小值。

2.这里使用双指针的原因是——整个数组的满足范围不限制,而其中的每一组组合都可能满足,如果想要简便的实现的话,需要两个指针,它们不断变换,我们求其中的总值,如果满足情况,则赋值给返回值,最后不断更新返回值的较小值,就可找到返回值最小的情况,题目也就满足了

滑动窗口的代码实现:

这里两个分别是指针1和指针2(刚开始都在同一位置),需要之间的值大于7,先设置一个sum的初始值为0,进入循环中,加上n[j],如果这时候的sum>=7,则直接给result赋值1,如果不是——指针j往下移动,然后循环内又再加上n[j]……直到指针指向第二个2时,sum>=7了,则先赋值给result一个较小值,然后指针1开始移动——寻找这个满足条件的区间内可以有更小的可能吗?(这时候需要减去n[i]后移动这个指针),然后发现从3——2区间也成立,然后再循环,直到指针循环到最后,result自然=最小值

代码:

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX;
        int sum = 0; // 滑动窗口数值之和
        int i = 0; // 滑动窗口起始位置
        int subLength = 0; // 滑动窗口的长度
        for (int j = 0; j < nums.size(); j++) {
            sum += nums[j];
            // 注意这里使用while,每次更新 起始位置,并不断比较子序列是否符合条件
            while (sum >= s) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }

 这个时间复杂度为O(n)——这个是看每一个元素被操作的次数,因为有两个指针,每个元素在滑动窗进来一次出去一次,时间复杂度为2n——O(n)

螺旋矩阵

题目:59. 螺旋矩阵 II - 力扣(LeetCode)

这个和二分法一样,需要有不变量——左闭右开,

而这里发生错误的地方在于——

1.要知道每一个矩阵需要绕的圈数——n/2,

2.注意一圈过后,它的起始位置发生改变,要都加1

3.如果n为奇数的话,最后要判断一下,单独把中间的值赋值

⭐注意如何在c语言中动态初始化二维数组:

int** generateMatrix(int n, int* returnSize, int** returnColumnSizes){
    //初始化返回的结果数组的大小
    *returnSize = n;
    *returnColumnSizes = (int*)malloc(sizeof(int) * n);
    //初始化返回结果数组ans
    int** ans = (int**)malloc(sizeof(int*) * n);
}

复习——malloc和free

free参数要么是NULL要么是之前从malloc、calloc、realloc返回的值,无返回值

malloc返回一块连续的数组,返回类型为void*,C和C++规定,void*类型可以强制转换为任何其他类型的指针

而malloc里面放的是需要空间的大小,一般用sizeof来计算 

注意:

要检查所请求的内存是否分配成功

必须要释放

可以使用exit(1)——用于退出整个程序,终止进程,返回1代表异常终止,返回0正常退出

 方法——模拟行为,这里重要的就是考虑循环不变量,而除此之外其它的也很重要——其中的思想,模拟过程

这其实是一个二维数组的初始化,不过更为复杂,这里看代码——

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
        int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
        int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
        int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
        int count = 1; // 用来给矩阵中每一个空格赋值
        int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
        int i,j;
        while (loop --) {
            i = startx;
            j = starty;

            // 下面开始的四个for就是模拟转了一圈
            // 模拟填充上行从左到右(左闭右开)
            for (j = starty; j < n - offset; j++) {
                res[startx][j] = count++;
            }
            // 模拟填充右列从上到下(左闭右开)
            for (i = startx; i < n - offset; i++) {
                res[i][j] = count++;
            }
            // 模拟填充下行从右到左(左闭右开)
            for (; j > starty; j--) {
                res[i][j] = count++;
            }
            // 模拟填充左列从下到上(左闭右开)
            for (; i > startx; i--) {
                res[i][j] = count++;
            }

            // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            startx++;
            starty++;

            // offset 控制每一圈里每一条边遍历的长度
            offset += 1;
        }

        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if (n % 2) {
            res[mid][mid] = count;
        }
        return res;
    }
};
  • 时间复杂度 O(n^2): 模拟遍历二维矩阵的时间
  • 空间复杂度 O(1)

总结

这里的数组题目公涉及了三种方法——

二分法:注意循环不变量

双指针法:通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

滑动窗口:滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)

最好的方法是多用多看多练,而且要及时总结错误及思路。

感谢观看完毕,欢迎点赞收藏😉

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从零学算法289

289.根据 百度百科 &#xff0c; 生命游戏 &#xff0c;简称为 生命 &#xff0c;是英国数学家约翰何顿康威在 1970 年发明的细胞自动机。 给定一个包含 m n 个格子的面板&#xff0c;每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态&#xff1a; 1 即为 活细胞 …

接上Promise()对象处理回调地狱:怎么用.then()?什么是Async、Await?

上一篇基于JavaScript基础的异步、同步操作&#xff0c;promise、.then()-CSDN博客讲了【啥是异步操作、同步操作&#xff1f;】然后简单讲了回调函数是啥、Promise()对象是啥、.then()函数是啥&#xff0c;这一篇讲讲promise()对象到底怎么配合.then()函数解决回调地狱&#x…

学习和工作的投入产出比(节选)

人工智能统领全文 推荐包含关于投入、产出、过剩、市场关注、案例、结果和避雷等主题的信息&#xff1a; 投入与产出&#xff1a; 投入和产出都有直接和间接两类常见形式。常见的四种组合是&#xff1a;直接投入、直接产出、间接投入、间接产出。 过剩&#xff1a; 过剩是一个重…

QtCreator报Failed to parse qmlimportscanner output解决

错误如下: 定位错误位置 增加错误信息打印 打印执行命令 执行打印输出的命令,成功返回JSON 但输出的JSON对象不是json格式,而是命令 增加$$成功输出JSON 使用QtCreator12编译一次后,再使用QtCreator13成功编译通过,问题解决

如何实现WordPress后台显示文章、分类目录、标签等的ID?

我们平时在使用WordPress的过程中&#xff0c;偶尔需要用到文章的ID&#xff0c;或分类目录ID&#xff0c;或标签ID&#xff0c;或媒体库ID&#xff0c;或评论ID&#xff0c;或用户ID等&#xff0c;但是WordPress后台默认是不显示它们的ID的。 今天boke112百科就跟大家分享如何…

刷题第3天(简单题):LeetCode203--移除链表元素--虚拟头结点

LeetCode203:给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5]示例 2&#xff1a;输入…

vue3中的ref和reactive的区别

vue3中的ref和reactive的区别 1、响应式数据2、ref3、reactive4、ref VS reactive5、往期回顾总结&#xff1a; 1、响应式数据 处理响应式数据时到底是该用ref还是reactive... 响应式数据是指在 Vue.js 中&#xff0c;当数据发生变化时&#xff0c;相关的视图会自动更新以反映…

纯css实现-让字符串在文字少时显示为居中对齐,而在文字多时显示为左对齐

纯css实现-让字符串在文字少时显示为居中对齐&#xff0c;而在文字多时显示为左对齐 使用flex实现 思路 容器样式&#xff08;.container&#xff09;: Flex容器的BFC性质使得其内部的子元素&#xff08;.text-box&#xff09;在水平方向上能够居中&#xff0c;通过justify-c…

用node写后端环境运行时报错Port 3000 is already in use

解决方法:关闭之前运行的3000端口,操作如下 1.WindowR输入cmd确定,打开命令面板 2.查看本机端口详情 netstat -ano|findstr "3000" 3.清除3000端口 taskkill -pid 41640 -f 最后再重新npm start即可,这里要看你自己项目中package.joson的启动命令是什…

简单版 git快速上手使用 clone项目 新建/切换分支 提交修改

Git是一个广泛使用的版本控制系统&#xff0c;允许多个用户跟踪文件的更改&#xff0c;并协作开发项目。 首先确定自己电脑已经安装了git&#xff0c;具体安装步骤请查找教程&#xff0c;应该不难。 以windows电脑为例&#xff0c;安装完后在搜索栏搜索git会出现 先解释一下这…

B端系统用户体验最高原则:美观与易用的结合,附大波案例

将美观与易用结合是B端系统用户体验的最高原则&#xff0c; 强调美观忽视易用&#xff0c;那是反人类设计&#xff1b;强调易用忽视美观&#xff0c;那是泯灭人性的设计。本文分享为什么系统要结合二者&#xff0c;欢迎关注点赞&#xff0c;如有需求&#xff0c;可以私信我们。…

租床小程序|租床系统|租赁软件开发功能

随着移动互联网的普及&#xff0c;越来越多的人开始选择在线上完成各种租赁业务&#xff0c;而医院租床也不例外。在这个趋势下&#xff0c;开发一款租赁小程序成为了市场的必然需求。 租床小程序的功能 1、搜索与筛选 为了满足不同用户的需求&#xff0c;小程序应该提供设备…

3/1作业

1.用fwrite和fread将任意bmp图片&#xff0c;修改成德国的国旗 #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> int main(int argc, const char *argv[]) { FILE* fp fopen("1.bmp","r")…

Timeplus-proton流处理器调研

概念 Timeplus是一个流处理器。它提供强大的端到端功能&#xff0c;利用开源流引擎Proton来帮助数据团队快速直观地处理流数据和历史数据&#xff0c;可供各种规模和行业的组织使用。它使数据工程师和平台工程师能够使用 SQL 释放流数据价值。 Timeplus 控制台可以轻松连接到不…

Javaweb之SpringBootWeb案例之 Bean管理的Bean作用域详细的解析

2.2 Bean作用域 在前面我们提到的IOC容器当中&#xff0c;默认bean对象是单例模式(只有一个实例对象)。那么如何设置bean对象为非单例呢&#xff1f;需要设置bean的作用域。 在Spring中支持五种作用域&#xff0c;后三种在web环境才生效&#xff1a; 作用域说明singleton容器…

使用Docker搭建一款实用的个人IT工具箱——It-Tools

作为程序员&#xff0c;在日常工作中&#xff0c;需要借助一些工具来提高我们工作效率&#xff0c;IT-Tools是为开发人员度身打造的一套便捷在线工具。它提供全面功能&#xff0c;使开发者能以更高效方式完成任务。经由IT-Tools&#xff0c;开发人员能轻松应对各类技术挑战&…

yolov9 tensorRT 的 C++ 部署

yolov9 tensorRT C 部署 本示例中&#xff0c;包含完整的代码、模型、测试图片、测试结果。 完整的代码、模型、测试图片、测试结果【github参考链接】 TensorRT版本&#xff1a;TensorRT-7.1.3.4 导出onnx模型 导出适配本实例的onnx模型参考【yolov9 瑞芯微芯片rknn部署、地平…

Consul服务注册与发现 Consul配置步骤

Consul服务注册与发现 Consul配置步骤 consul下载地址 Install | Consul | HashiCorp Developer 启动需要在 下载好的文件夹里 用cmd 运行consul agent -dev启动consul Consul配置 配置pom <!--SpringCloud consul config--> <dependency><groupId>org…

基于JSON的Ollama和LangChain agent

到目前为止&#xff0c;我们都可能意识到&#xff0c;通过为LLMs提供额外的工具&#xff0c;我们可以显著增强它们的功能。 例如&#xff0c;即使是ChatGPT在付费版本中也可以直接使用Bing搜索和Python解释器。OpenAI更进一步&#xff0c;为工具使用提供了经过优化的LLM模型&am…

综合练习(二)

目录 列出薪金比 SMITH 或 ALLEN 多的所有员工的编号、姓名、部门名称、领导姓名、部门人数&#xff0c;以及所在部门的平均工资、最高和最低工资 补充 spool Oracle从入门到总裁:https://blog.csdn.net/weixin_67859959/article/details/135209645 列出薪金比 SMITH 或 AL…