数组可以说是大家最早接触的数据结构了,上手应该比较快。如果对数组还有不熟悉的可以看这篇:数组的基本操作,可以快速回顾数组查找,删除,添加以及内存分布等
本文主要是leetcode的习题解析。数组方面的算法非常多,一定要多思考思考:二分查找,双指针,滑动窗口,哈希,前缀和,模拟……
常见的技巧有:
本文涉及的题目汇总:
题号 | 标签/分类 | 难度 |
---|---|---|
1. lc35 搜索插入位置 | 二分法 | easy |
2. lc704 二分查找 | 二分法 | easy |
3. lc69 x的平方根 | 二分法 | easy |
4. lc327 移除元素 | 双指针 | easy |
5. lc283 移动零 | 双指针 | easy |
6. lc209 长度最小的子数组 | 滑动窗口 | medium |
7. lc1207 独一无二的出现次数 | 哈希 | easy |
8. lc1365 有多少小于当前数字的数字 | 哈希 | easy |
9. lc941 有效的山脉数组 | 其他 | easy |
10. lc724 寻找数组的中心下标 | 其他 | easy |
11. lc54 螺旋矩阵 | 二维数组 | easy |
推荐:
题号 | 标签/分类 | 难度 |
---|---|---|
lc367 有效的完全平方数 | 二分法 | easy |
lc977 有序数组的平方 | 双指针 / 排序 | easy |
lc1365 有多少小于当前数字的数字 | 其他 | easy |
lc34 在排序数组中找到元素的首位置和末位置 | 二分法 | medium |
这里只列举最基础的二分查找,有关更详细更复杂的二分查找可以参见:
力扣704 链接
描述:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例:
输入: nums = [-1,0,3,5,9,12], target = 9 输出: 4
solution:
标准的二分查找,可以把下面的代码看成一个模板。注意两个地方
left<=right,left = mid+1,right=mid-1
public int search(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
return -1;
}
力扣34链接
描述:
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]。
示例:
输入:nums = [5,7,7,8,8,10], target = 8 输出:[3,4]
Solution:
二分查找升级版! 两次二分查找~
在这里插入代码片
可以说除了暴力的方法,像双指针还有滑动窗口,都是为了减少for循环的层数,因为很多问题两层for循环算法复杂度直接O(n^2),所以双指针就是为了在一个for循环中完成两个for循环的事,滑动窗口也一样。
双指针不算是一种数据结构,但是这个技巧可谓贯穿很多题目,比如数组,链表,字符串……在 双指针详解 中有对双指针详细的介绍,本文主要是针对数组的。
而滑动窗口,其实也逃不开双指针,他更关心两个指针之间的内容(所以像一个窗口,当两个指针在移动时,其实就是窗口在滑动),有关滑动窗口的更多,可参见:力扣滑动窗口详解
力扣27链接
描述:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组
示例:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
Solution:
第一眼暴力
两层for循环~(时间复杂度O(n^2)),这里就不贴代码了。
快慢双指针:
通过一个快指针和慢指针在一个for循环下完成两个for循环的工作
快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
慢指针:指向更新 新数组下标的位置
public int removeElement(int[] nums, int val) {
// 定义快慢指针
int slow = 0;
int fast = 0;
for(fast=0;fast<nums.length;fast++)
{
int num = nums[fast];
if(num != val)
{
nums[slow] = num;
slow++;
}
}
return slow;
}
力扣283链接
描述:
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: nums = [0,1,0,3,12] 输出: [1,3,12,0,0]
solution:
看上去和第一题很相似
注意你在操作数组的时候数组里面的元素都在变
Solution1:
言归正传,模仿第一题的解法,双指针,然后后面添0
public static void moveZeroes(int[] nums) {
int low=0;
int fast;
int len = nums.length;
for(fast=0;fast<len;fast++)
{
int num = nums[fast];
if(num == 0)
{
nums[low]=num;
low++;
}
}
for(int i=low;i<len;i++)
nums[i]=0;
}
Solution2:
同样是双指针,但是利用交换的思想(更地道——更贴近题意的“移动零”)
public void moveZeroes(int[] nums) {
int left = 0, right=0;
while(right<nums.length)
{
if(nums[right]!=0)
{
// 交换
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
left++;
}
right++;
}
}
力扣209链接
描述:
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr],并返回其长度。如果不存在符合条件的子数组,返回 0
示例:
输入:target = 7, nums = [2,3,1,2,4,3] 输出:2
solution:
滑动窗口
public int minSubArrayLen(int target, int[] nums) {
int slow = 0 , right = 0;
int sum=0;
int min = Integer.MAX_VALUE;
for(right=0;right<nums.length;right++)
{
sum += nums[right];
while(sum>=target && slow<nums.length)
{
int tmpLen = right-slow+1;
if(tmpLen<= min)
min = tmpLen;
sum = sum-nums[slow];
slow++;
}
}
return min==Integer.MAX_VALUE?0:min;
}
前缀和技巧适用于快速、频繁地计算一个索引区间内的元素之和
lc303链接
描述:
给定一个整数数组 nums,处理以下类型的多个查询:
计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和
Solution:
经典的一维前缀和
class NumArray {
// 前缀和数组
private int[] preSum;
/* 输入一个数组,构造前缀和 */
public NumArray(int[] nums) {
// preSum[0] = 0,便于计算累加和
preSum = new int[nums.length + 1];
// 计算 nums 的累加和
for (int i = 1; i < preSum.length; i++) {
preSum[i] = preSum[i - 1] + nums[i - 1];
}
}
/* 查询闭区间 [left, right] 的累加和 */
public int sumRange(int left, int right) {
return preSum[right + 1] - preSum[left];
}
}
另外还有力扣724 寻找数组的中心下标 也可以用前缀和
lc304 链接
描述:
给定一个二维矩阵 matrix,以下类型的多个请求:
计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。实现 NumMatrix 类
Solution:
经典的二维前缀和,计算指定矩形中的和
class NumMatrix {
int[][] preNum;
public NumMatrix(int[][] matrix) {
int r = matrix.length;
int c = matrix[0].length;
preNum = new int[r+1][c+1];
for(int i=1;i<r+1;i++)
{
for(int j=1;j<c+1;j++)
{
preNum[i][j] = preNum[i-1][j] + preNum[i][j-1] + matrix[i - 1][j - 1] - preNum[i-1][j-1];
}
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
return preNum[row2+1][col2+1]-preNum[row2+1][col1]-preNum[row1][col2+1]
+preNum[row1][col1];
}
}
区间问题,就是线段问题,让你合并所有线段、找出线段的交集。两个技巧,画图+排序!
lc56链接
描述:
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组
示例:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
Solution:
public int[][] merge(int[][] intervals) {
// 先按照每个数组的左节点进行排序
Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);
int n = intervals.length;
int[][] res = new int[n][2];
int index=-1;
for(int i=0;i<n;i++)
{
if(index==-1|| res[index][1]<intervals[i][0])
{
res[++index] = intervals[i];
}
else{
res[index][1] = Math.max(res[index][1],intervals[i][1]);
}
}
return Arrays.copyOf(res,index+1);
}
lc57链接
描述:
给你一个 无重叠的 ,按照区间起始端点排序的区间列表。
在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠
示例:
输入:intervals = [[1,3],[6,9]], newInterval = [2,5]
输出:[[1,5],[6,9]]
Solution:
与上一题(lc56)相似,并且一种直观的思路就是把新区间插入老区间,再排序,然后就用上一题代码。但是本题已经排好序了,利用这一条件,只需把新区间插入即可。
所以分成三段处理,老区间靠左的和靠右的可以直接加入结果数组中,中间的可能与新数组重叠的需要处理一下左右区间点。
在这里插入代码片
力扣1288链接
描述:
给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。
示例:
输入:intervals = [[1,4],[3,6],[2,8]] 输出:2
在这里插入代码片
模拟或者是没有其他明确标签的题目,模拟也是一种重要的思想!
力扣941链接
描述:
给定一个整数数组 arr,如果它是有效的山脉数组就返回 true,否则返回 false
山脉数组:在 0 < i < arr.length - 1 条件下,存在 i 使得:arr[0] < arr[1] < … arr[i-1] < arr[i];arr[i] > arr[i+1] > … > arr[arr.length - 1]
示例:
输入:arr = [0,3,2,1] 输出:true
输入:arr = [3,5,5] 输出:false
solution:
线性扫描,模拟法
public boolean validMountainArray(int[] arr) {
int left = 0;
int len = arr.length;
int right = len-1;
while(left+1<len && arr[left]<arr[left+1] )
left++;
while(right>0 && arr[right-1]>arr[right] )
right--;
if(left==right && left!=0 && right!=len)
return true;
return false;
}
(待定)
力扣54链接
参考链接:
https://programmercarl.com/
https://leetcode.cn/problems/