数据结构与算法------数组

一.二分查找

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   

思路:二分查找最容易混淆的就是边界的选择,我总结了两种,二分查找满足的条件一般是给定一个有序数组而且是不重复,有两种写法,第一种是左闭右闭,第二种是左闭右开

左闭右闭写法:

假设[left,right]则要配合如下条件:

1.while(left<=right)

2.if(nums[mid]>right) right=mid-1

左闭右开写法:

假设[left,right)则要配合如下条件:

1.while(left<right)

2.if(nums[mid)>right right=mid

题解代码:

左闭右闭

class Solution{
     public int search(int[] nums, int target) {
        int n=nums.length;
        int left=0;
        int right=n-1;
        while(left<=right){
            int mid=left+(right-left)/2;
            if(nums[mid]==target){
                return mid;
            }else if(nums[mid]>target){
                right=mid-1;
            }else if(nums[mid]<target){
                left=mid+1;
            }
        }
        return -1;
     }
}

左闭右开

class Solution{
     public int search(int[] nums, int target) {
        int n=nums.length;
        int left=0;
        int right=n-1;
        while(left<right){//和第一种的区别
            int mid=left+(right-left)/2;
            if(nums[mid]==target){
                return mid;
            }else if(nums[mid]>right){
                right=mid;//和第一种的区别
            }else if(nums[mid]<left){
                left=mid+1;
            }
        }
        return -1;
     }
}

与二分查找相似的题目有:

35. 搜索插入位置

34. 在排序数组中查找元素的第一个和最后一个位置​​​​​​​

69. x 的平方根 

367. 有效的完全平方数

=========================================================================

搜索插入位置

2.给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。

示例:

输入: [1,3,5,6], 5
输出: 2
示例 2:

输入: [1,3,5,6], 2
输出: 1
示例 3:

输入: [1,3,5,6], 7
输出: 4
示例 4:

输入: [1,3,5,6], 0
输出: 0

思路:这题和二分查找类似,只需要把二分查找的"框架"写上再修改一些细节,这里的细节指的是返回值,是left还是right还是right+1还是left+1,这个可以采用带入值得出结论,没有更好的办法要么就是多写代码多分析。这里返回的值是right+1.

class Solution {
    public int searchInsert(int[] nums, int target) {
      int n=nums.length;
      int left=0;
      int right=n-1;
      while(left<=right){
          int mid=left+(right-left)/2;
          if(nums[mid]==target){
              return mid;
          }else if(nums[mid]>target){
              right=mid-1;
          }else if(nums[mid]<target){
              left=mid+1;
          }
      }
      return right+1;
    }
}

在排序数组中查找元素的第一个和最后一个位置

思路:这题比之前的有些难度,可以说是综合了二者,我们知道二分查找就是找边界值,但是一般是找一个,而这题的新颖之处在于找左右两个边界,同理我们可以利用最基本的二分查找来找左右边界,一个找左边界,一个找右边界,最后通过左右两边界相减判断是否存在数组内,如果存在则返回边界,否则返回[-1,-1]。

class Solution {
	public int[] searchRange(int[] nums, int target) {
        int left=getLeft(nums,target);//左边界
        int right=getRight(nums,target);//右边界
        if(left==-2 || right==-2) return new int[]{-1,-1};//插入的值在nums左或右
        if(right-left>1) return new int[]{left+1,right-1};        
        return new int[]{-1,-1};
    }
    public int getLeft(int[] nums,int target){//寻找左边界
        int n=nums.length;
        int left=0;
        int right=n-1;
        int leftBorder=-2;
        while(left<=right){
            int mid=left+(right-left)/2;
            if(nums[mid]>=target){
                right=mid-1;
                leftBorder=right;
            }else{
                left=mid+1;
            }
        }
        return leftBorder;
    } 
    public int getRight(int[] nums,int target){//寻找右边界
        int n=nums.length;
        int left=0;
        int right=n-1;
        int rightBorder=-2;
        while(left<=right){
            int mid=left+(right-left)/2;
            if(nums[mid]>target){
                right=mid-1;
            }else{
                left=mid+1;
                rightBorder=left;
            }
        }
        return rightBorder;
    }
}

69. x 的平方根 

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4
输出:2
示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

思路:这题一眼看过去好像没什么头绪,这怎么和二分查找有关?不是要左右边界才能考虑嘛?其实可以把边界写成[0,x],这样只需要判断mid*mid和x的关系即可。

class Solution{
     public int mySqrt(int x){
         int left=0;
         int right=x;
         int ans=0;
         while(left<=right){
             int mid=left+(right-left)/2;
             if((long)mid*mid==x){
                 return mid;
             }else if((long)mid*mid>x){
                 right=mid-1;
             }else if((long)mid*mid<x){
                 ans=mid;
                 left=mid+1;
             }
        }
        return ans;
    }
}

367. 有效的完全平方数

给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。

进阶:不要 使用任何内置的库函数,如  sqrt 。

示例 1:

输入:num = 16
输出:true
示例 2:

输入:num = 14
输出:false

思路:和上面一题几乎一样,这题只需要判断返回true或者false,也是使用二分查找即可,需要注意的是为了防止溢出需要转化为long类型。

class Solution {
    public boolean isPerfectSquare(int num) {
        int left=0;
        int right=num;
       
        
         while(left<=right){
            int mid=left+(right-left)/2;
            
           if((long)mid*mid>num){
                right=mid-1;
            }else if((long)mid*mid<num){
                left=mid+1;
            }else if((long)mid*mid==num){
                return true;
            }
        }
        return false;
    }
}

二.双指针

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的子数组如果不存在符合条件的子数组,返回 0。

输入: [2,3,1,2,4,3], s = 7
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的子数组。

思路:这题考虑用滑动窗口,本质就是双指针,滑动窗口的题需要解决以下问题:1.缩小左右边界 2.窗口内的值的变化 

class Solution{
    public int minSubArrayLen(int target, int[] nums){
        int slow=0;//滑动窗口左边界
        int sum=0;
        int res=Integer.MAX_VALUE;
        for(int fast=0;fast<nums.length;fast++){//滑动窗口右边界
            sum+=nums[fast];
            while(sum>=target){//当窗口内的值大于等于target时需要将左边界缩小
                res=Math.min(res,fast-slow+1);//先记录此时窗口的长度
                sum-=nums[slow];//因为窗口左边界缩小所以里面的值也要相应的减去左边界的值
                slow++;//移动左边界
            }
        }
        return res==Integer.MAX_VALUE?0:res;//如果target不能满足返回0
    }
}

904. 水果成篮

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

示例 1:

输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。
示例 2:

输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。
示例 3:

输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。
示例 4:

输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/fruit-into-baskets
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
    public int totalFruit(int[] fruits) {
        int slow=0;//左边界
        Map<Integer,Integer> map=new HashMap<>();//使用map计数
        int res=0;
        for(int fast=0;fast<fruits.length;fast++){//有边界
            map.put(fruits[fast],map.getOrDefault(fruits[fast],0)+1);//将右边界的值添加
            while(map.size()>2){ //不满足条件 即容量大于2            
                    
                if(map.get(fruits[slow])==1){
                    map.remove(fruits[slow]);//如果size为1 则删除
                    slow++;
                }else{
                    map.put(fruits[slow],map.get(fruits[slow])-1);//如果size不为1 说明不只一个重复 只需要计数减一
                    slow++;//移动左边界
                }
                
            }
            res=Math.max(res,fast-slow+1);//比较
        }
        return res;
    }
}

59. 螺旋矩阵 II

给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。

示例 1:

输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:

输入:n = 1
输出:[[1]]

 思路:解题的核心思路是按照右、下、左、上的顺序遍历数组,并使用四个变量圈定未遍历元素的边界

随着螺旋遍历,相应的边界会收缩,直到螺旋遍历完整个数组: 

class Solution {
    public int[][] generateMatrix(int n) {
        int upper=0;
        int right=n-1;
        int lower=n-1;
        int left=0;
        int[][] matrix=new int[n][n];
        int num=1;
        while(num<=n*n){
                //上边界
                if(upper<=lower){
                    for(int i=left;i<=right;i++){
                        matrix[upper][i]=num++;
                    }
                    upper++;//上边界向下移动
                }

                //右边界
                if(left<=right){
                    for(int j=upper;j<=lower;j++){
                        matrix[j][right]=num++;
                    }
                    right--;//右边界向左移动
                }

                //下边界
                if(upper<=lower){
                    for(int i=right;i>=left;i--){
                        matrix[lower][i]=num++;
                    }
                    lower--;//下边界向上移动
                }

                //左边界
                if(left<=right){
                    for(int j=lower;j>=upper;j--){
                        matrix[j][left]=num++;
                    }
                    left++;//左边界向有移动
                }
        }
        
        return matrix;
    }
}

参考:二维数组的花式遍历技巧 :: labuladong的算法小抄

小总结:总体来说数组类的题目分为两类:第一个是一维数组,第二个是二维数组。对于一维数组最常用的方法就是二分查找法,二分查找法有一定的框架,然后根据题目要求修改细节;还有一个方法就是用双指针,其中包括快慢指针,滑动窗口,一般而言都是让两个指针slow=0,fast=0;还有就是slow=0,fast=nums.length-1具体看题目要求。第二类是二维数组,二维数组的操作主要是观察行和列之间的关系,包括一些变换,对角线对称,旋转等。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

#HashMap#

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值