文章目录
- 1.二分查找
- 1.1题目
- 1.2思路(核心:区间的定义)
- 1.3左闭右闭
- 1.4左闭右开
- 1.5总结
- 2.移除元素
- 2.1题目
- 2.1思路
- 2.2.1暴力解法
- 2.2.2双指针法
- 23总结
- 3.有序数组的平方
- 3.1题目
- 3.2思路
- 3.2.1暴力解法
- 3.2.2双指针法
- 4.长度最小的子数组
- 4.1题目
- 4.2思路
- 4.2.1暴力解法
- 4.2.2滑动窗口(双指针升级)
- 5.螺旋矩阵2
- 5.1题目
- 5.2思路
1.二分查找
1.1题目
704.二分查找—力扣题目链接
- 题目:给定一个
n
个元素有序的(升序)整型数组nums
和一个目标值target
,写一个函数搜索nums
中的target
,如果目标值存在返回下标,否则返回-1
。 - 示例一:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
- 示例二:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
1.2思路(核心:区间的定义)
- 题目的前提是数组为有序数组,同时题目还强调 数组中无重复元素
- 因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。
1.3左闭右闭
- 定义target在 [left, right] 区间,所以有如下两点:
- while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
- if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
- 下面举例演示:在一组有序,不重复数组中分别查找数据2、数据6的过程
/*** @Description 二分查找第一种写法:左闭右闭* @Param* @Return 下标值:int*/public int binarySearch1(int[] arr,int target){int left=0;int right=arr.length-1;while(left<=right){/*** 写法一:可能出现溢出情况* int mid=(left+right)/2;* 写法二:* int mid=left+(right-left)/2;*///写法三:右移运算符 代替 除号int mid=left+((right-left)>>1);if(arr[mid]>target){ //在左区间,即[left,mid-1]right=mid-1;}else if(arr[mid]<target){ //在右区间,即[mid+1,right]left=mid+1;}else{return mid;}}return -1;}
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
1.4左闭右开
如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right) ,那么二分法的边界处理方式则截然不同。
- while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
- if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]大于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
- 代码示例:
/*** @Description 二分查找第一种写法:左闭右闭* @Param* @Return 下标值:int*/public int binarySearch2(int[] arr,int target){int left=0;int right=arr.length;while(left<right){int mid=left+((right-left)>>1);if(arr[mid]>target){ //在左区间,即[left,mid-1)right=mid;}else if(arr[mid]<target){ //在右区间,即[mid+1,righ)left=mid+1;}else{return mid;}}return -1;}
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
1.5总结
- 使用二分查找的两个前提:
- 数组有序
- 数组元素唯一,不重复
- 二分查找的两个写法区分:
左闭右闭 | 左闭右开 | |
---|---|---|
right初始取值 | right=arr.length-1 | right=arr.length |
循环条件 | while(left<=right) | while(left<right) |
left更新值(到右区间查找) | left=mid+1 | left=mid+1 |
right更新值(到左区间查找) | right=mid-1 | right=mid |
2.移除元素
27.移除元素_力扣链接
2.1题目
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
- 示例1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
- 示例2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
2.1思路
2.2.1暴力解法
两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。
class Solution {public int removeElement(int[] nums, int val) {int total=0;for(int i=0;i<nums.length;i++){if(nums[i]==val){//发现该val值,将该位置后面的数组都往前移动一位for(int j=i+1;j<nums.length;j++){nums[j-1]=nums[j];}i--;//结束内层循环后,执行i++命令,但是原来的nums[i+1]位置上的元素已经被覆盖为nums[i+2]total++;}}return nums.length-total;}
}
2.2.2双指针法
- 定义:通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向更新 新数组下标的位置
- 演示:
- 代码:
class Solution {public int removeElement(int[] nums, int val) {int total=0;//快慢指针int slowIndex=0;for(int fastIndex=0;fastIndex<nums.length;fastIndex++){if(nums[fastIndex]!=val){//将快指针的值赋予慢指针的值,并同时向前移动nums[slowIndex]=nums[fastIndex];slowIndex++;}else{//找到该值,暂停慢指针total++;}}return nums.length-total;}
}
- 注意这些实现方法并没有改变元素的相对位置!
- 时间复杂度:O(n)
- 空间复杂度:O(1)
23总结
双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。
3.有序数组的平方
3.1题目
977.有序数组的平方——力扣题目链接
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
- 示例一:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
- 示例二:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
3.2思路
3.2.1暴力解法
- 遍历数组,将数组的每个元素平方,接着排个序即可
3.2.2双指针法
-
原数组是 有序 的,就是说数字0的左区间(平方后的元素,越左边,越大)、右区间(平方后的元素,越右边,越大)
-
由此可以得出平方后的数组最大值不是在原数组最左边,就是最右边
-
考虑双指针法了,i指向起始位置,j指向终止位置
-
示意图:
class Solution {public int[] sortedSquares(int[] nums) {//思路:原数组有序,那最大值不是在最左边,就是在最右边//双指针:分别指向最左边和最右边,并新建一个数组,将最大值移向数组的最后面int len=nums.length;int j=nums.length-1;int k=nums.length-1;int[] arr=new int[len];for(int i=0;i<=j;i++){ //循环条件 i<=j,最后要处理最后一个元素if(nums[i]*nums[i]>nums[j]*nums[j]){// i位置的元素的绝对值大arr[k--]=nums[i]*nums[i];}else{// j位置的元素的绝对值大arr[k--]=nums[j]*nums[j];i--; //i保持不动j--;}}return arr;}
}
4.长度最小的子数组
4.1题目
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其总和大于等于 target
的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度**。**如果不存在符合条件的子数组,返回 0
。
- 示例一:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
- 示例二:
输入:target = 4, nums = [1,4,4]
输出:1
- 示例三:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
4.2思路
4.2.1暴力解法
- 解法一:暴力解法,两层for循环,第一层为子序列的起始位置,第二层为子序列的终点位置
- 在第二层中,逐渐累加子序列的值,若发现>=target,则与原来的子序列长度比较,是否需要替换
class Solution {public int minSubArrayLen(int target, int[] nums) {int min=Integer.MAX_VALUE; //子序列的值int sum=0;for(int i=0;i<nums.length;i++){ //i为起始位置sum=0; //重置sum=0for(int j=i;j<nums.length;j++){ //j为终点位置sum+=nums[j];if(sum>=target){min=min>(j-i+1)?(j-i+1):min; //更新子序列的长度}}}return min==Integer.MAX_VALUE?0:min;}
}
4.2.2滑动窗口(双指针升级)
- 解法二:滑动窗口
- 问题:如何使用一个for循环,就能确定子序列的起始、终点位置
- 解决 :动态更改子序列的起始位置,for循环确定子序列的终点位置
- 只要当sum>=target,第一更新子序列长度的值,第二滑动子序列的起始位置(先更新子序列总和,再滑动)
class Solution {public int minSubArrayLen(int target, int[] nums) {int min=Integer.MAX_VALUE;int sum=0;int i=0; //i为起始位置for(int j=0;j<nums.length;j++){ //j为终点位置sum+=nums[j];while(sum>=target){//1、更新子序列的长度min=min>(j-i+1)?(j-i+1):min;//2、滑动起始位置:先减去初始位置的值,再滑动sum-=nums[i];i++;}}return min==Integer.MAX_VALUE?0:min;}
}
5.螺旋矩阵2
5.1题目
59. 螺旋矩阵 II
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
-
示例一:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
- 示例二:
输入:n = 1
输出:[[1]]
5.2思路
-
每次赋值可分为4次:上、右、下、左
-
首先明确每次赋值,确保都是==左闭右开==(即1 2;3 4;5 6;7 8)
- 循环的圈数:loop=n/2;n为3时循环一圈;n为4时循环两圈
- 若 n为奇数,则最后中间还有个值,arr(n/2)(n/2)
- 每次循环过后,右边界都要缩小一格
- 注意每次循环过后,起始点的位置!!!
class Solution {public int[][] generateMatrix(int n) {int loop=n/2;int count=1;int[][] arr=new int[n][n];int start=0; //每次循环的开始点(start,start)int i=0;int j=0;int offset=1; //每次循环右边界收缩一位,控制每条边的遍历长度for(int k=0;k<loop;k++){ //k表示正在循环的圈数i=start; //更新起始点j=start;//上:从左到右for(j=start;j<n-offset;j++){arr[i][j]=count++;}//右:从上到下for(i=start;i<n-offset;i++){arr[i][j]=count++;}//下:从右到左for(;j>start;j--){arr[i][j]=count++;}//左:从下到上for(;i>start;i--){arr[i][j]=count++;}start++; //循环起始点加一offset++;}if (n % 2 == 1) {arr[n/2][n/2] = count;}return arr;}
}