欢迎来到老胡的算法解题思路,本文章主要使用的语言为java,使用的题型为力扣算法题,基于这一篇文章,我将为你介绍数组的基础知识和数组题型,喜欢的朋友可以关注一下,下次更新不迷路!
目录
前言
数组是有序的元素序列。若将有限个类型相同的变量的集合命名,那么这个名称为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。用于区分数组的各个元素的数字编号称为下标。数组是在程序设计中,为了处理方便, 把具有相同类型的若干元素按有序的形式组织起来的一种形式。这些有序排列的同类数据元素的集合称为数组。
数组是用于储存多个相同类型数据的集合。
一、数组的声明和初始化
1.1、一维数组的声明和初始化
// 一维数组的 声明方式
int arr[];
int[] arr;
// 动态初始化:数组声明且为数组元素分配空间与赋值的操作分开进行
int[] arr = new int[3];
arr[0] = 3;
arr[1] = 9;
arr[2] = 8;
// 静态初始化:在定义数组的同时就为数组元素分配空间并赋值
int arr[] = new int[]{ 3, 9, 8};
1.2、二维数组的声明和初始化
// 二维数组的 声明方式
int arr[][];
int[][] arr;
// 动态初始化:数组声明且为数组元素分配空间与赋值的操作分开进行
// int[][]arr = new int[][3];非法
int[][] arr = new int[3][2];
int[][] arr = new int[3][];
// 静态初始化:在定义数组的同时就为数组元素分配空间并赋值
int[][] arr = new int[][]{{2,5,2},{2,8},{9,0,1,6}};
二、数组题型
2.1数组题型一:数组前缀和
例题:力扣523题.连续的子数组和
分析:题目分析:
常规思路是遍历数组,判断是否存在子数组使得子数组的和为常数k的倍数,但是,这样的解法需要使用到O(m^2)的时间复杂度,并且还需要O(m)的时间来计算数组和,这样就导致时间复杂度为O(m^3),存在超时风险。所以,针对本题,可以采用数组前缀和加哈希表的解法,降低复杂度。
分析:前缀和模板
// 构建数组前缀和
int[] ans = new int[nums.length+1];
for(int i = 1;i<ans.length;i++){
ans[i] = nums[i-1] + ans[i-1];
}
解题:完整代码
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
// 构建数组前缀和
int[] ans = new int[nums.length+1];
for(int i = 1;i<ans.length;i++){
ans[i] = nums[i-1] + ans[i-1];
}
// 构建哈希表
Map<Integer, Integer> map = new HashMap<>();
for (int i = 1; i < ans.length; i++) {
int count = ans[i] % k;
// 判断是否为k的倍数
if (count == 0 &&i != 1) {
return true;
}
Integer index = map.get(count);
if (index == null) {
//如果不存在,放入哈希表
map.put(count, i);
// 让子数组的大小至少为2
} else if (i - index >= 2) {
return true;
}
}
return false;
}
}
2.2数组题型二:差分数组
例题:力扣1109.航班预定统计
分析:题目分析:
预订记录实际上代表了一个区间的增量。我们的任务是将这些增量叠加得到答案。因此,我们可以使用差分解决本题。
分析:差分数组模板
int[] diff = new int[nums.length];
// 构造差分数组
diff[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
diff[i] = nums[i] - nums[i - 1];
}
解题:完整代码
class Solution {
public int[] corpFlightBookings(int[][] bookings, int n) {
// 构建差分数组并求解
int[] ans = new int[n];
for (int[] booking : bookings) {
ans[booking[0] - 1] += booking[2];
// 判断是否越界
if (booking[1] < n) {
ans[booking[1]] -= booking[2];
}
}
// 差分数组使用前缀和
for (int i = 1; i < n; i++) {
ans[i] += ans[i - 1];
}
return ans;
}
}
2.3数组题型三:修改数组
例题:力扣80.删除有序数组中的重复项||
分析:题目分析:
针对这题,我们首先想到的是循环遍历,但是注意看题目,要求时间复杂度为O(1),然而,使用遍历不符合题目要求。仔细读题, 题目给定数组是有序的,所以相同元素必然连续。针对这题,我们可以使用双指针解决,遍历数组检查每一个元素是否应该被保留,如果应该被保留,就将其移动到指定位置。
解题:完整代码
class Solution {
public int removeDuplicates(int[] nums) {
int len = nums.length;
if (len <= 2) {
return len;
}
int left = 2;
int right = 2;
while (right < len) {
// 不相等时,左右指针都后移
if (nums[left - 2] != nums[right]) {
nums[left] = nums[right];
left++;
}
//相等时右指针后移
right++;
}
return left;
}
}
2.4数组题型四:二分查找
例题:力扣167.两数之和||-输入有序数组
分析:题目分析:
常规思路是遍历,或者用双指针,都是比较简单的解法,但是针对本题,主要讲解的是二分查找,我会以二分查找的方法来进行本题的讲解。
分析:二分查找模板
// 搜索左侧边界
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0, right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 当找到 target 时,收缩右侧边界
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left;
解题:完整代码
class Solution {
public int[] twoSum(int[] numbers, int target) {
for (int i = 0; i < numbers.length; ++i) {
int left = i + 1;
//注意这为numbers.length
int right = numbers.length;
//搜索左侧边界
while (left < right) {
int mid = (right - left) / 2 + left;
//符合条件输出
if (numbers[mid] == target - numbers[i]) {
return new int[]{i + 1, mid + 1};
} else if (numbers[mid] > target - numbers[i]) {
right = mid;
} else {
left = mid + 1;
}
}
}
return null;
}
}
2.5数组题型五:滑动窗口
例题:力扣1004.最大连续1的个数
分析:题目分析:
当我们使用滑动窗口代替二分查找解决本题时,就不需要显式地计算并保存出前缀和数组了。我们只需要知道 \textit{left}left 和 \textit{right}right 作为下标在前缀和数组中对应的值,因此我们只需要用两个变量 \textit{lsum}lsum 和 \textit{rsum}rsum 记录 \textit{left}left 和 \textit{right}right 分别对应的前缀和即可。
分析:滑动窗口模板
int left = 0, right = 0;
while (right < s.size()) {
// 增⼤窗⼝
window.add(s[right]);
right++;
while (window needs shrink) {
// 缩⼩窗⼝
window.remove(s[left]);
left++;
}
}
解题:完整代码
class Solution {
public int longestOnes(int[] nums, int k) {
int n = nums.length;
//已经翻转0的个数
int lsum = 0;
// 已经翻转多少个0
int rsum = 0;
int ans = 0;
//左指针
int left = 0;
//右指针
int right = 0;
while(right<nums.length) {
rsum += 1 - nums[right];
right++;
// 判断左侧窗⼝是否要收缩
while (lsum < rsum - k) {
lsum += 1 - nums[left];
left++;
}
//数据更新
ans = Math.max(ans, right - left);
}
return ans;
}
}
三、总结
数组是一种效率最高的存储和随机访问对象引用序列的方式,数组就是一个简单的线性序列,这使得元素访问非常快速,但是,为此付出的代价就是数组对象的大小被固定,涉及时间和空间复杂度比较高的且内容大小固定的,推荐优先考虑用数组来解题。
补充说明:数组还存在很多没有总结到的典型题型,后期总结后继续补上,