数组
一、二分查找(704 简单)
前提: 1. 所要查找的数组必须有序。
2. 无重复元素。
二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right)还是while(left <= right),到底是right = middle呢,还是要right = middle-1呢?
大家写二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
class Solution {
public int search(int[] nums, int target) {
if(target<nums[0]||target>nums[nums.length-1]){
return -1;
}
int head=0;
int end=nums.length-1;
while(head<=end){
int middle=(head+end)/2;
if(nums[middle]==target){
return middle;
}else if(nums[middle]<target){
head=middle+1;
}else{
end=middle-1;
}
}
return -1;
}
}
二、移除元素(27 简单)
2.1 两个for循环
暴力解法:首先对数组进行遍历(一个for循环),若遍历到某一个数组元素为目标值,则数组中后面的元素全部向前进一维将该等于目标值的元素覆盖(一个for循环)。
2.2 双指针法
- 快慢指针:有一个for循环,整个for循环是快指针的循环。
快指针:用来查找不是目标值的数组中的元素。【用来获取新数组中的元素】
慢指针:用来创建新的数组。也就是说用快指针查找到的不是目标值的元素,赋给慢指针的位置。【用来获取新数组中元素的位置】
class Solution {
public int removeElement(int[] nums, int val) {
int slow=0;//慢指针
for(int fast=0;fast<nums.length;fast++){
if(nums[fast]!=val){
nums[slow]=nums[fast];
slow++;//变成++slow也可以运行
}
}
return slow;
}
}
注:注意slow++
- 相向双指针:基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素。
基本思想: 左边的指针找左边等于val的元素,右边的指针找不等于val的元素。
左边的指针每一步循环都前进,当等于val时,用右边的指针的元素替换该元素,此时右边的指针才向左走找到下一个不等于val的元素。所以能够看出,右边的指针把为val的元素都跳过,把不是val的元素都替换给左边等于val的元素的位置。所以当left>right时循环结束,此时left指向的位置为最终数组末尾的下一个元素。
三、有序数组的平方(977 简单)
题目描述:已知一个排好序的数组,把每个数平方然后把平方后的数再排序。
方法一: 先平方,再排序
双指针法
方法二: 双指针法,因为已知数组按升序排序的,则平方后最大的数要么是最小的负数的平方,要么是最大的正数的平方。用双指针法,相向指针,比较大小。
注:
- 在这里使用的指针法因为比较最左和最右的两个数的平方,所以我们需要得到的是最大值,放到新的数组的最后一个元素处。
- 如果左边指针的元素的平方大,则把左边指针加1;如果右边指针的元素的平方大,则把右边指针减1.
- 编程时注意i++和++i的区别。
- a=i++ 先把i的值赋予a,然后再执行i=i+1
- a=++i 先执行i=i+1,然后把i的值赋予a
class Solution {
public int[] sortedSquares(int[] nums) {
int left=0;
int right=nums.length-1;
int[] result=new int[nums.length];
int j=nums.length-1;
while(left<=right){
if(nums[left]*nums[left]>nums[right]*nums[right]){
result[j--]=nums[left]*nums[left];
++left;//left++也能运行出正确结果
}else{
result[j--]=nums[right]*nums[right];
--right;//right--也能运行出正确结果
}
}
return result;
}
}
四、长度最小的子数组(209 中等)
题目描述:给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
注:和移除元素的两个方法的思路类似。
4.1 暴力解法
两个for循环。基本思想:从数组的第一个元素开始,依次做加法,和目标数s比较。因为要找的是长度最小的连续子数组,所以把所有情况都循环,设置一个result保存子数组的长度,一旦满足大于等于s,就更新result,再依次比较。
4.2 滑动窗口
滑动窗口也可以理解为双指针法的一种。只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。
窗口就是满足其和 ≥ s 的长度最小的连续子数组。
-
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
-
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
从左往右看,左边是窗口的起始位置,右边是窗口的结束位置。和暴力解法相比,滑动窗口只需要一个for循环,那么该循环的索引应该是窗口的起始位置还是结束位置呢?应该是结束位置(也可以看作快指针)。
算法:
当首次满足≥s时,对初始位置的指针右移,再判断是否≥s;
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int j=0;//窗口的开始位置
int sum=0;
int result=Integer.MAX_VALUE;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
if(sum>=target){
result=Math.min(i-j+1,result);
sum-=(nums[j]+nums[i]);
j++;
i--;
}
}
//如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result== Integer.MAX_VALUE ? 0 : result;
}
}
注:
- 这里注意if语句的逻辑,但是按照滑动窗口的逻辑,使用while语句更简单。因为if走完了就跳出这个if语句了,执行i++;但是while直到不满足条件语句才会进行下一次的for循环,执行i++。
- Integer.MAX_VALUE ? 0 : result
三元表示符:
(关系表达式) ? 表达式1 : 表达式2;
int x = 10;
int y = 5;
int z;
如果x大于y 则是true,将x赋值给z;
如果x不大于y 则是false,将y赋值给z;
z = (x > y) ? x : y;
```java
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int j=0;//窗口的开始位置
int sum=0;
int result=Integer.MAX_VALUE;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
while(sum>=target){
result=Math.min(i-j+1,result);
sum-=nums[j];
j++;
}
}
return result== Integer.MAX_VALUE ? 0 : result;
}
}
五、螺旋矩阵Ⅱ(59 中等)
题目描述:给定一个正整数 n,生成一个包含1到 n^2所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
要求:当n=3时,输出 [ [1,2,3],[8,9,4],[7,6,5] ]
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
所以思路就是:按从左到右,从上到下,从右到左,从下到上循环,把这个看作是一次循环。所以外面的大循环是一圈一个循环。n=3时,一圈循环,一个中心点;n=4时,两圈循环;n=5时,两圈循环,一个中心点。所以当n为偶数时,外面大循环次数为n/2;当n为奇数时,外面大循环(n-1)/2,但是有个中心点
所以为了在写循环的过程中不乱套,也遵循一个固定原则。如果我们遵循左闭右开的原则,对于一个3×3的矩阵,则每一行(列)的最后一个元素和下一个循环一起。
用左闭右开的原则写的代码如下:
class Solution {
public int[][] generateMatrix(int n) {
int [][] res=new int[n][n];//定义一个二维数组
int loop=0;//控制循环次数
int start = 0; // 每次循环的开始点(start, start)
int count = 1; // 定义填充数字
int i, j;//把i看成是矩阵的行,j看成是矩阵的列,所以二维数组其实就可以看成是矩阵
while (loop++ < n / 2) { // 判断边界后,loop从1开始
// 模拟上侧从左到右,左闭右开
for (j = start; j < n - loop; j++) {
res[start][j] = count++;
}
// 模拟右侧从上到下,j不动
for (i = start; i < n - loop; i++) {
res[i][j] = count++;
}
// 模拟下侧从右到左,i不动
for (; j >= loop; j--) {
res[i][j] = count++;
}
// 模拟左侧从下到上,j不动
for (; i >= loop; i--) {
res[i][j] = count++;
}
start++;//一圈大循环结束之后给start加1,下一次循环从(start,start)开始
}
if (n % 2 == 1) {
res[start][start] = count;
}
return res;
}
}