- 二分查找
- 移除元素
- 有序数组的平方
- 最小长度的子数组
- 螺旋矩阵II
二分查找
解法:双指针
算法核心:1.找mid,2.循环啥时候停止
1.mid = (nums[left]+nums[right])/2,但是注意算法中不能这样写,容易溢出。
2.while(left<=right),因为两个相等的时候还是有意义的,所以这么写。
所以这种写法从区间上来看是左闭右闭的。
算法思想,算mid,mid>target,往左区间找,mid<target,往右区间找。找的过程中,修改区间的大小。至于+1或者-1,看看那个位置还有没有可能踩到。没可能就修改区间。
class Solution {
public int search(int[] nums, int target) {
int start = 0; //左边
int end = nums.length-1; //右边
while(start<=end){ //因为start=end这个地方是有意义的,所以可以取等。
int mid = start+(end-start)/2; //这里计算中点值,注意这种计算方法。
if(nums[mid]<target){ //目标值在右区间
start = mid+1;
}else if(nums[mid]>target){ //目标值在左区间
end = mid-1;
}else{
return mid; //如果有答案,那最后的结果就是mid。
}
}
return -1;
}
}
为什么计算中点要这么写?
如果(left + right) / 2这么算会有一个情况:
left和right都非常大的时候就会导致整数溢出。
left + (right - left) / 2 这样写有什么好处?
在计算的时候,会优先计算(right - left),此时得到的值必然小于right和left中的仍和一个,因此再加上left后不会溢出。
这里补上一个极端的情况来证明就是不溢出:
left = 2^31-1, right = 2^31 -1.
(right-left)/2 = 0
left +(right-left)/2 = 2^31 -1
因此在这个最极端的情况下,仍然可以确定,就是不会发生int溢出。
移除元素
解法一:暴力解,遇到目标,后面的元素全部往前依次覆盖。每扫描到一次目标,做完前移操作之后就进行长度–。
难点1:怎么处理前移而且不会导致数组越界。这里我们一般用减的这一种。nums[j-1] = nums[j];
难点2:小心每次移动之后原本的遍历指针的变换,因为有一个特殊情况,那就是下一个元素还是目标元素,一旦i直接+1,那么就会错过这个元素.
class Solution {
public int removeElement(int[] nums, int val) {
int result = nums.length;
for (int i = 0;i<nums.length;i++){
if(nums[i] == val){
for(int j = i+1;j<nums.length;j++){
nums[j-1]=nums[j];//一般防止越界,我们就往前想。
}
result--;
i--;//因为外层循环++了,所以这里必须--,不然你迁移之后,如果本来i那里又是目标数字,不--就跳过了。
}
}
return result;
}
}
解法2:双指针法:快慢指针构造。
慢指针用于构造,快指针用于扫描。一旦快指针扫描到目标元素,那就直接进行跳过,扫描到非目标元素,那就赋值给慢指针进行构造,慢指针构造之后下标也++。
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,因为是先进行构造后进行的slow++,所以最后一次构造后,slow还多加了一次1,因此不用担心下标的影响。
}
}
写的时候的难受:
1.我写的时候老想去省略赋值操作,然而在时间复杂度层面这啥都不影响,所以大胆的去赋值。就是快指针的值直接赋值给慢指针的值。
2.想清楚这个过程。最后slow停留的位置,在完成最后一个元素的构造时,还会slow++,所以最后slow停在的位置就是结果值,不用去+1
有序数组的平方
解法:双指针,这个题的关键就在于发现一个规律,在去平方的情况下,数组中的元素一定是两边大,中间小。所以可以从两边往中间走,比较左右指针,然后进行答案数组的构建,注意答案数组也要从后往前构建。
class Solution {
public int[] sortedSquares(int[] nums) {
int length = nums.length;
int i = 1;
int[] result = new int[length];
int left = 0;
int right = length-1;
while(left<=right){ //因为中间仍然是有效的,所以可以取等。
int realLeft = nums[left];
int realRight = nums[right];
//这部分相当于取绝对值操作,虽然我感觉多此一举了,可以直接用平方比较的
if(nums[left]<0){
realLeft=-nums[left];
}
if(nums[right]<0){
realRight=-nums[right];
}
if(realLeft<=realRight){ //哪边更大就先去构造,构造后就跳过这个元素。
result[length-i] = realRight*realRight;
right--;
}else{//哪边更大就先去构造,构造后就跳过这个元素。
result[length-i] = realLeft*realLeft;
left++;
}
i++; //这个是为了调整结果数组,因为要倒着构造。
}
return result;
}
}
长度最小的子数组
解法:双指针(滑动窗口)。
fast只管往前走,然后走的同时进行求和。一旦求的和大于了target,此时就进入到了内部的滑动窗口的收缩,这个内部的滑动窗口用while来进行判断维护滑动窗口是最好的,由于求和值已经大于了target,此时就该先进行长度值的求解了,所以是先进行长度值求解,求解之后开始进行滑动窗口的收缩,收缩操作是左边slow来进行窗口大小的减小。这个收缩操作是求和sum先减去slow对应的元素值。然后再去判断是否更新最小长度。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int slow = 0;
int length = nums.length;
int sum = 0;
int result = Integer.MAX_VALUE; //上来直接把最大值拉到最大,这是一种比较常用的方法:迭代后方便与更小的值进行比较,然后进行更新的操作。
for(int fast = 0;fast<length;fast++){
sum += nums[fast]; //fast就放心的往前走然后就和,一旦sum>=target了才要进行判断,但是注意这里用if就收缩不了滑动窗口,所以这里用while来收缩滑动窗口才是更好的。
while(sum >= target){ //这里我就不用if判断了,直接三目运算符一步到位,如果result比现在这个窗口还大,那就选择fast-slow+1,否则result还是最小值。
result = result > (fast-slow+1) ? fast-slow+1 : result;
sum -= nums[slow]; //一旦进入到这个里面就是开始收缩窗口的环节了,这个算法的流程就是这样。
slow++; //收缩后必然就是前窗口下标++。
}
}
return result == Integer.MAX_VALUE ? 0 : result; //这里之所以这样写就是为了防止,数组中并没有一个区间满足求和值大于等于target。因为求和如果不满足条件,甚至while循环都进不去。所以必须这么写。如果还是保持原值,那就证明没有更新,不满足条件,不然一旦进了while循环必然会更新的。题目也说了,不满足就返回0,否则才是返回答案。
}
}
螺旋矩阵II
解法:模拟法,核心在于处理的手段要一致,比如按这个图中的手段来就可以轻松左出来。这个题最大的难点就是一旦处理手段不一致,就会做起来非常的头晕。
直接看这个图:
每次都按这种左闭右开处理,就会变得非常的简单。
class Solution {
public int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n]; //要构造的目标矩阵
int startX = 0; //转圈的起点x坐,对二维数组而言就是从[0][0]开始了。
int startY = 0; //转圈的起点y坐标
int loop = 1; //转的圈数,由于题目说了至少为1,所以就设置为1。
int offset = 1; //这里是一个隔板,因为越往里面到就要往内收缩一格,所以隔板值要不断+1,由于采取左闭右开这样的处理,所以这里隔板初始值设置的就是1.
int count = 1; //用于赋值
int i = 0; //这里用i代表行
int j = 0; // j代表列。 转卷的过程用i和j来转,别的不要去动,不然把自己搞懵圈。
while(loop <= n/2){ //这里自己想想,举举例子,这个while循环就是转圈
//比如4,就只转2圈,如果是3,那显然就是转一圈,然后单独处理中心点。所以每次转完之后可以数一下loop,如果loop是奇数,那么就还有一步处理中心点的操作
//处理顶部
for (j = startY;j<n-offset;j++){
matrix[startX][j] = count++;
}
//处理右边
for (i = startX;i<n-offset;i++){
matrix[i][j] = count++;
}
//处理下面
for (;j>startY;j--){
matrix[i][j] = count++;
}
//处理左边
for(;i>startX;i--){
matrix[i][j] = count++;
}
//转完一圈后的操作
offset++; //隔板往里推一格
loop++; //圈数+1
startX++; //起点也要变,因为第一圈转完了,换成了内部的第一圈。那就是x++
startY++; //起点 y++。
}
if(n%2 == 1){ //最后的中心处理。
matrix[startX][startY] = count;
}
return matrix;
}
}```
---
第二次做的感悟就是,这种n*n的有n*n的做法。
而且这个题和螺旋矩阵1还是有一定的区别的。一个是给你现成的二维数组,另一个是让你构造一个二维数组进行返回。以后看到这种有构造的,而且还是n*n的有反应就行了。给现成的让你去转的就是螺旋矩阵的做法。