java算法day1

  • 二分查找
  • 移除元素
  • 有序数组的平方
  • 最小长度的子数组
  • 螺旋矩阵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;
    }

    
}```

  • 34
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值