数组总结点睛

一、基础知识

1、数据结构概括

  • 数据结构主要围绕 数组、字符串、链表、树、栈、队列 这几种展开。

  • 数组和字符串: 是两种最基本的数据结构。

  • 链表和树: 是在面试中出现频率最高的数据结构,因为操作他们需要大量的指针,故一定要留意代码的鲁棒性,否则程序极易崩溃。

  • 数组: 用连续内存存储数字。

  • 字符串:用连续内存存储字符。

  • 栈: 是与递归紧密相连的数据结构

  • 队列:与广度优先遍历算法密切相关

2、数组的特点

  • 1、空间效率低,时间效率高
  • 2、数组也可以实现简单的哈希表,把数组下标设为哈希值的key值,数组中每个数字设为哈希表的value
  • 3、数组的名字就是一个指针,该指针指向数组中的第一个元素
  • 4、在C++中,把数组作为函数的参数进行传递时,数组就自动退化为同类型的指针
  • 5、数组的查找效率高时间复杂度为O(1),增删插入效率低
  • 6、数组的元素的不能删的,只能覆盖

3、 vector、array、数组 三者区别

vector的底层实现是array,严格来讲,vector是容器,不是数组。

共同点:

  • 1、都可以用下标操作进行处理
  • 2、都可以用迭代器进行操作
  • 3、三者的存储都是连续的,可以进行随机访问
  • 4、都是对同一种类型的数据进行储存。

不同点:

  • 1、数组是不安全的,array和vector是比较安全的(因为可以有效地避免越界等问题)
  • 2、array对象和数组存储在相同的内存区域(栈)中,vector对象存储在自由存储区(堆)
  • 3、array(或vactor)可以将一个对象赋值给另一个array(或vactor)对象,但是数组不行
  • 4、vector属于变长的容器,即可以根据数据的插入和删除重新构造容器容量;但是array和数组属于定长容器
  • 5、vector和array提供了更好的数据访问机制,即可以使用front()和back()以及at()(at()可以避免a[-1]访问越界的问题)访问方式,使得访问更加安全。而数组只能通过下标访问,在写程序中很容易出现越界的错误
  • 6、vector和array提供了更好的遍历机制,即有正向迭代器和反向迭代器
  • 7、vector和array提供了size()和empty(),而数组只能通过sizeof()/strlen()以及遍历计数来获取大小和是否为空
  • 8、vector和array提供了两个容器对象的内容交换,即swap()的机制,而数组对于交换只能通过遍历的方式逐个交换元素
  • 9、array提供了初始化所有成员的方法fill()
  • 10、由于vector的动态内存变化的机制,在插入和删除时,需要考虑迭代的是否有效问题
  • 11、vector和array在声明变量后,在声明周期完成后,会自动地释放其所占用的内存。对于数组如果用new[ ]/malloc申请的空间,必须用对应的delete[ ]和free来释放内存

4、二维数组的存储

在C++中二维数组在地址空间上是连续的

在Java中的二维数组就不是连续的,比如一个3 * 4的二维数组:
二位数组中其实是一个线性数组存放着 其他数组的首地址。
在内存的空间地址不是一个3 * 4的连续地址空间,而是三条连续的地址空间组成!

多维数组的元素存储顺序是按照右边的下标,率先变化,称为行主序。

所以,对于数组a[3] [4],它就表示有一个数组a[3],它的每个元素是指向一个有4个变量的数组的首地址。

5、如何定义并初始化数组?

定义一个大小为26的数组,并将其初始化为 0

int arr[26] = {0};

6、查找和排序

查找算法:

  • 顺序查找
  • 二分查找:考察算法。时间复杂度为 O(log n)
  • 哈希表查找:考察数据结构。优点是时间复杂度为 O(1),缺点是空间复杂度变大,需要额外的空间来实现哈希表
  • 二叉树排序查找:考察数据结构

排序算法

  • 插入排序
  • 冒泡排序
  • 归并排序
  • 快速排序

需要对各种排序算法的特点非常熟悉,从额外空间消耗,平均时间复杂度和最差时间复杂度等方面去比较他们的优缺点。

二、常用解题思想方法

1、二分查找法

  • 二分查找也称为 二分法 或者是 折半查找

  • 只要看到面试题里给出的数组是有序数组(完全有序,或者是部分有序),都可以想一想是否可以使用二分法。

  • 同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下表可能不是唯一的。

  • 时间复杂度

    • 对于一个 O(n) 的数组,二分查找的时间复杂度为 O(log n)
  • 循环不变量:

    常用的有 左闭右闭,或者左闭右开,这个根据自己的习惯来写

  • 二分查找也可以看作双指针的一种特殊情况

    • 双指针类型的题,指针通常是一步一步移动的
    • 而在二分查找里,指针每次移动半个区间长度

步骤:

  • 1、定义 数组长度,左边界,右边界
  • 2、while循环,注意循环条件和循环体内的if语句
    • 在循环体内定义middle变量

两种写法的区别:

  • 1、区间为 左闭右闭 [left, right] ,旋转数组常用这个

    • 判断条件代码:while(left <= right)
    • right 更新代码:right = middle - 1
    • left 更新代码:left = middle + 1
  • 2、区间为 左闭右开 [left, right)

    • 判断条件代码:while(left < right)
    • right 更新代码:right = middle
    • left 更新代码:left = middle + 1

总结:

1、时间复杂度为:O(log n) [对比暴力解法的时间复杂度为 O(n)]

2、二分查找法使用的关键就是:循环不变量

  • 循环不变量left与right就代表了需要查找的这个范围的左右边界,而选择取不同的值,

    对于这个区间来说就是开区间与闭区间的区别,在修改它们的同时,也需要在循环中同步这一定义。这也就是循环不变量的意义。

3、关于取 middle 值

  • 法一:mid = (left + right) / 2 // 旋转数组必须这么写 剑指offer 第11题
  • 法二:mid = left + ((right - left) >> 1) // 注意一定要加括号,因为移位运算符优先级比 + 运算符 低。
  • 法三:mid = left + (right - left) / 2
  • 这里右移一位,就相当是数学上除以2,优点是右移运算符会比除法快很多。

4、分析题意,将数组简化分为两个数组,更有利于解题

  • 比如剑指offer 第11题旋转数组中,分成两个递增的子数组,通过将 mid 对应值与 right 对应值相比较
  • 还有LC81 也是旋转数组,此时要
  • 比如剑指offer 第53-2题缺失的数字中,也是分成两种,nums[i] == i 和 nums[i] != i,且分界点即为所求点

2、双指针法(快慢指针法)

  • 通过一个快指针和一个慢指针,在一个for循环下完成两个for循环的工作
  • 时间复杂度 O(n) [对比暴力双层 for 循环的时间复杂度为 O(n^2)]
  • 双指针法在数组和链表中还有很多应用

快指针: 查找数值,相当于逐次排雷
慢指针: 存储合法的数值。遇到合法(快指针找到的)的数值,则填入本空,然后指针向后移
如果遇到非法数值,则快指针加1,慢指针不变

步骤:

  • 1、用for循环
    • 1、初始化 slow=0, fast=0;
    • 2、建立for循环,循环变量为fast,循环终止条件为 fast < nums.size(),表达式为 fast++
    • 3、在循环体中进行判断,
      • if nums[fast] != target,则有三步:① nums[slow] = nums[fast]; ② slow++ ③ fast++
      • else 就进行下一轮,(此时有fast++)
    • 4、最后返回slow。
  • 2、用while循环
    • 1、定义左右边界:slow=0 ,fast=nums.size()
    • 2、while终止条件为 slow != fast
    • 3、在循环体中进行判断
      • if nums[slow] == targt,则有两步:① nums[slow] = nums[fast-1]; ② fast–
      • else slow ++
    • 4、最后返回slow。

总结:

  • 其实滑动窗口、二分查找这些的思想都是从双指针法延伸出的分支。

3、滑动窗口

所谓滑动窗口,「就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果」

实现滑动窗口,需要首先明确以下三点:

  • 1、窗口内是什么
  • 2、如何移动窗口的起始位置 left
  • 3、如何移动窗口的结束位置 right

步骤:

  • 类似于双指针的处理,一般是外层为 for 循环,内层为 while 循环
  • left 定义在循环体外,for 循环的循环变量就是right,
  • for 循环的终止条件是 数组长度
  • while 循环的终止条件就是 题目要求对数组的限定

4、螺旋矩阵

坚持循环不变量原则。模拟顺时针画矩阵的过程:

  • 填充上行从左到右
  • 填充右列从上到下
  • 填充下行从右到左
  • 填充左列从下到上
  • 由外向内一圈一圈这么画下去。

比如就坚持左闭右开的原则。

5、前缀和

某个区间的元素之和,立马想到 preSum 这个方法。

它的计算方法是从左向右遍历数组,当遍历到数组的 i 位置时,preSum表示 i 位置左边的元素之和。

preSum 方法还能快速计算指定区间段 i ~ j 的元素之和。

假设数组长度为 N,我们定义一个长度为 N+1 的 preSum 数组,

preSum[i] 表示该元素左边所有元素之和(不包含当前元素)。

然后遍历一次数组,累加区间 [0, i) 范围内的元素,可以得到 preSum 数组。

利用 preSum 数组,可以在 O(1) 的时间内快速求出 nums 任意区间 i ~ j 的各元素之和。

preSum [i+1] - preSum [j]

三、做题记录

第34题 在排序好的数组中查找元素的第一个和最后一个位置

mid = (left + right) >> 1;
mid = (left + right) / 2

优点::右移运算符的速度会快很多
右移运算符的运算结果,正好是一个整数的 二分之一 的值,相当于数学上的除以2,

第724题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值