数据结构与算法(JAVA)--数组--二分法

数据结构与算法(JAVA)–数组–二分法

有很多图片传上来太麻烦了,所以就缺失了,有的代码很简单,可以直接看,这些 都是我整理的数组的二分法的一些题目,和别人的一些题解,希望方便大家学习,

一维数组

1、基础知识

数组是存放在连续内存空间上的相同类型数据的集合。
(1)创建:
数组存储的数据类型[] 数组名字 = new 数组存储的数据类型[长度];
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3…};
int[] arr = new int[]{1,2,3,4,5};
数据类型[] 数组名 = {元素1,元素2,元素3…};
int[] arr = {1,2,3,4,5};

(2)查找:数组可以方便的通过下表索引的方式获取到下表下对应的数据。
数组名[索引]
索引访问数组中的元素:
数组名[索引]=数值,为数组中的元素赋值
变量=数组名[索引],获取出数组中的元素
(3)数组的长度属性: 每个数组都具有长度,而且是固定的,Java中赋予了数组的一个属性,可以获取到数组的长度,语句为: 数组名.length ,属性length的执行结果是数组的长度,int类型结果。由次可以推断出,数组的最大索引值为数组名.length-1 。
(4)需要注意的是
数组下表都是从0开始的。
数组内存空间的地址是连续的
数组有定长特性,长度一旦指定,不可更改。

正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址
(5)数组作为方法参数和返回值
一个方法可以有0、1、多个参数;但是只能有0或者1个返回值,不能有多个返回值。
如果希望一个方法当中产生了多个结果数据进行返回,怎么办?
解决方案:使用一个数组作为返回值类型即可。
数组作为方法参数传递,传递的参数是数组内存的地址

数组作为方法的返回值,返回的是数组的内存地址
(6)方法的参数类型区别

2、数组内存

Java虚拟机的内存划分

数组在内存中的存储

public static void main(String[] args) {
    
    
int[] arr = new int[3];
System.out.println(arr);//[I@5f150435
}

以上方法执行,输出的结果是[I@5f150435,这个是什么呢?是数组在内存中的地址。new出来的内容,都是在堆内存中存储的,而方法中的变量arr保存的是数组的地址。输出arr[0],就会输出arr保存的内存地址中数组中0索引上的元素

注意:arr = null 这行代码,意味着变量arr将不会在保存数组的内存地址,也就不允许再操作数组了,因此运行的时候会抛出NullPointerException 空指针异常

3、基础题目:

1、数组最大值

public static void main(String[] args) {
    
    
int[] arr = {
    
     5, 15, 2000, 10000, 100, 4000 };
//定义变量,保存数组中0索引的元素
int max = arr[0];
//遍历数组,取出每个元素
for (int i = 0; i < arr.length; i++) {
    
    
//遍历到的元素和变量max比较
//如果数组元素大于max
if (arr[i] > max) {
    
    
//max记录住大值
max = arr[i];
}
}
System.out.println("数组最大值是: " + max);
}

2、数组反转

实现思想:数组最远端的元素互换位置。
实现反转,就需要将数组最远端元素位置交换
定义两个变量,保存数组的最小索引和最大索引
两个索引上的元素交换位置
最小索引++,最大索引–,再次交换位置
最小索引超过了最大索引,数组反转操作结束

public static void main(String[] args) {
    
    
int[] arr = {
    
     1, 2, 3, 4, 5 };
/*
循环中定义变量min=0最小索引
max=arr.length‐1最大索引
min++,max‐‐
*/
for (int min = 0, max = arr.length ‐ 1; min <= max; min++, max‐‐) {
    
    
//利用第三方变量完成数组中的元素交换
int temp = arr[min];
arr[min] = arr[max];
arr[max] = temp;
}
// 反转后,遍历数组
for (int i = 0; i < arr.length; i++) {
    
    
System.out.println(arr[i]);
}
}

4、二分查找法

二分查找也称折半查找(Binary Search),是一种在有序数组中查找某一特定元素的搜索算法。我们可以从定义可知,运用二分搜索的前提是数组必须是有序的,这里需要注意的是,我们的输入不一定是数组,也可以是数组中某一区间的起始位置和终止位置
用二分查找,可以解决如下问题:

  1. 查找一个元素;
  2. 求大于某个元素的最小值;
  3. 求大于等于某个元素的最小值;
  4. 求小于某个元素的最大值;
  5. 求小于等于某个元素的最大值;

https://leetcode-cn.com/problems/search-insert-position/solution/yi-wen-dai-ni-gao-ding-er-fen-cha-zhao-j-69ao/

二分查找的核心思想:
二分查找的核心思想是「减而治之」,即「不断缩小问题规模」。
二分查找的两种思路(请特别留意第 2 种思路,掌握它能思路清晰地解决「力扣」上的所有二分查找问题)
思路 1:在循环体内部查找元素
while(left <= right) 这种写法表示在循环体内部直接查找元素;
退出循环的时候 left 和 right 不重合,区间 [left, right] 是空区间。
思路 2:在循环体内部排除元素(重点)
while(left < right) 这种写法表示在循环体内部排除元素;
退出循环的时候 left 和 right 重合,区间 [left, right] 只剩下成 1 个元素,这个元素 有可能 就是我们要找的元素。第 2 种思路可以归纳为「左右边界向中间走,两边夹」,这种思路在解决复杂问题的时候,可以使得思考的过程变得简单
归纳出最重要的部分:
while (left < right) 退出循环的时候有 left == right 成立,因此无需考虑返回 left 还是 right;
始终思考下一轮搜索区间是什么,如果是 [mid, right] 就对应 left = mid ,如果是 [left, mid - 1] 就对应 right = mid - 1,是保留 mid 还是 +1+1、-1−1 就在这样的思考中完成;
从一个元素什么时候不是解开始考虑下一轮搜索区间是什么 ,把区间分为 2 个部分(一个部分肯定不存在目标元素,另一个部分有可能存在目标元素),问题会变得简单很多,这是一条 非常有用 的经验;
每一轮区间被划分成 2 部分,理解 区间划分 决定中间数取法( 无需记忆,需要练习 + 理解 ),在调试的过程中理解 区间和中间数划分的配对关系:
划分 [left, mid] 与 [mid + 1, right] ,mid 被分到左边,对应 int mid = left + (right - left) / 2;;
划分 [left, mid - 1] 与 [mid, right] ,mid 被分到右边,对应 int mid = left + (right - left + 1) / 2;
要找的目标值性质简单的时候,用 while (left <= right) ,这种思路在循环体里就可以退出,并且向左走和向右走都比较好判断的时候,例如二分查找的最基本问题:「力扣」第 704 题。
相对地,如果目标值性质比较复杂,向左走和向右走比较难分析的时候,用 while (left < right) 这种两边夹的思路会使得问题变得简单很多。这一类问题通常的问法是「找左边第一个等于 target 的元素」、「找不超过 target 的最大数值」这样的「边界」的问题,「力扣」第 34 题、35 题(本题)、第 275 题、第 69 题就是这样的问题。
做题的话,一般来说都没那么好找,所以基本上用 while (left < right) 这种写法能保证写对。while (left < right) 这种思路只用思考两个区间,我觉得更简单
如果你的逻辑把区间 [left, right] 分成 [left, mid] 和 [mid + 1, right],边界设置就是 left = mid + 1 和 right = mid。反之是另外一种。逻辑和具体问题相关。
永远去思考目标元素在哪个区间里,就知道边界怎么设置了。

绝大部分写二分的题解都有写注释:下一轮搜索的区间在什么地方,我是根据这一点决定 left 和 right 该怎么写。

题目:35. 搜索插入位置

难度简单
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素
方法1 暴力方法,也挺简单

public int searchInsert(int[] nums, int target) {
    
    
        //四种情况
        //target<nums[i]  return 0;
        //target>nums[i]  return nums.length
        //targrt=nums[i]  return i;
        //nums[k]>target,return k
        for(int i=0;i<nums.length;i++){
    
    // 一旦发现大于或者等于target的num[i],
        //那么i就是我们要的结果  1 3 4情况
            if(nums[i]>=target){
    
    
                return i;
            }  
        }
        return nums.length;//第二种情况   // 目标值在数组所有元素之后的情况
    }

//方法2:二分法在循环体内部查找元素

 public int searchInsert(int[] nums, int target) {
    
    
      int left=0;
      int right=nums.length-1;
          while(left<=right){
    
       //定义target在左闭右闭的区间里,[left, right]
          // 当left==right,区间[left, right]依然有效 
              int mid=left+((right-left)>>1);
              //mid = left +(right - left)/ 2 或者 left + ((right - left ) >> 1) 
              //两者作用是一样的,都是为了找到两指针的中间
              if(nums[mid]==target){
    
    
                  return mid;
              }
              if(target<nums[mid]){
    
     //target 在左区间,所以[left, middle - 1]
                  right=mid-1;//mid已经是大于目标值的第一个元素,如果使right=mid-1
                  //必定会出现left>right 但是会麻烦
              }
              if(target>nums[mid]){
    
    // target 在右区间,所以[middle + 1, right]
                  left=mid+1;
              }  
          }
            return left;
    }
          

我们来看一下实现时容易出错的地方

1.计算 mid 时 ,不能使用 (left + right )/ 2,否则有可能会导致溢出

2.while (left < = right) { } 注意括号内为 left <= right ,而不是 left < right ,我们继续回顾刚才的例子,如果我们设置条件为 left < right 则当我们执行到最后一步时,则我们的 left 和 right 重叠时,则会跳出循环,返回 -1,区间内不存在该元素,但是不是这样的,我们的 left 和 right 此时指向的就是我们的目标元素 ,但是此时 left = right 跳出循环

3.left = mid + 1,right = mid - 1 而不是 left = mid 和 right = mid。我们思考一下这种情况,见下图,当我们的target 元素为 16 时,然后我们此时 left = 7 ,right = 8,mid = left + (right - left) = 7 + (8-7) = 7,那如果设置 left = mid 的话,则会进入死循环,mid 值一直为7

方法3:二分法在循环体内部排除元素

 public int searchInsert(int[] nums, int target) {
    
    
        int len = nums.length;
        if (len == 0) {
    
    
            return 0;
        }  
        int left = 0;
        // 因为有可能数组的最后一个元素的位置的下一个是我们要找的,故右边界是 len
        int right = len;
        while (left < right) {
    
    // 定义target在左闭右开的区间里,[left, right)  target
            int mid = left + (right - left) / 2;
            // 小于 target 的元素一定不是解
            if (nums[mid] < target) {
    
    
                // 下一轮搜索的区间是 [mid + 1, right]
                //  目标值绝对不再[left,mid]之间  mid也绝对不是
                left = mid + 1;
            } else {
    
    //大于等于目标值
                // 下一轮搜索的区间是 [left, mid] 
                //目标值绝对不再[mid+1,right]之间,但是mid可能是目标值
                right = mid;
            }
        }
        return left;
    }

时间复杂度:O(logN),这里 N 是数组的长度,每一次都将问题的规模缩减为原来的一半,因此时间复杂度是对数级别的;
空间复杂度:O(1),使用到常数个临时变量

上面两种二分法:二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间 [left, right],因为 mid 已经搜索过,应该从搜索区间中去除
后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。
while(left <= right) 的终止条件是 left == right + 1,写成区间的形式就是 [right + 1, right],或者带个具体的数字进去 [3, 2],可见这时候搜索区间为空,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。
while(left < right) 的终止条件是 left == right,写成区间的形式就是 [left, right],或者带个具体的数字进去 [2, 2],这时候搜索区间非空,还有一个数 2,但此时 while 循环终止了。也就是说这区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的

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

难度中等
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
二分法1

public int[] searchRange(int[] nums, int target) {
    
    
        int len=nums.length;
        if(len==0){
    
    
            return new int[]{
    
    -1,-1};
        }
        int first=left_bound(nums,target);
        if(first==-1){
    
    //如果没有左边界 必然没有右边界
            return new int[]{
    
    -1,-1};
        }
        int last=right_bound(nums,target);
        return new int[]{
    
    first,last};
    }
    //查找左边界
    public int left_bound(int[] nums,int target){
    
    
       int left=0;
       int right=nums.length;//左闭右开[left,right)
       while(left<right){
    
    
           int mid=left+((right-left)>>1);
           if(nums[mid]<target){
    
    //下一个搜索区间[mid+1,right)mid 不可能是
               left=mid+1;
           }
           else if(target==nums[mid]){
    
    
               right=mid;//下一个搜索区间[left,mid] mid 可能是
               //nums[mid] 被检测之后,下一步的搜索区间应该去掉 mid 然后分割成两个区间,即 [left, mid) 或 [mid + 1, right)
           }
           else{
    
    
               right=mid;// nums[mid] > target,下一轮搜索区间是 [left, mid )
           }
       }
       if(left==nums.length){
    
    return -1;}
       if(nums[left]==target){
    
    
           return left;
       }
       return -1;
    }
    //右边界
    public int right_bound(int[] nums,int target){
    
    
        int left=0;
        int right=nums.length;
        while(left<right){
    
    
            int mid=left+((right-left)>>1);//下取整改成上取整 元素个数为偶数的时候,中间数有两个,原来我们习惯或者说默认了下取整,事实上,上取整也可以
            if(nums[mid]<target){
    
    
                left=mid+1;// nums[mid] < target,下一轮搜索区间是 [mid + 1, right]
            }
            else if(nums[mid]==target){
    
    
                left=mid+1;// 因为mid已经检查过了,下一轮搜索区间是[left,mid)和 [mid+1, right]
            }
            else{
    
    
                right=mid;
            }
        }
    if (left == 0) {return -1;}//while 的终止条件是 left == right,就是说 left 的取值范围是 [0, nums.length]
    return nums[left-1] == target ? (left-1) : -1;//因为left=mid+1,
    //为什么要减一,这是搜索右侧边界的一个特殊点,关键在这个条件判断:
    // if (nums[mid] == target) {
        //     left = mid + 1;
         // 这样想: mid = left - 1   nums[left] 一定不等于 target 了,而 nums[left-1] 可能是 target。
  
}

二分法2:终止条件是 left == right + 1,
https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/da-jia-bu-yao-kan-labuladong-de-jie-fa-fei-chang-2/

public int[] searchRange(int[] nums, int target) {
    
    
        
        int first=left_bound(nums,target);
        int last=right_bound(nums,target);
       
        if(first>last){
    
    //因为左边界返回left,右边界返回right,
        //所以如果不存在目标值,一定是相同的left,right,所以left>right
            return new int[]{
    
    -1,-1};
        }
        return new int[]{
    
    first,last};
    }
    //查找左边界
    public int left_bound(int[] nums,int target){
    
    
       int left=0;
       int right=nums.length-1;//左闭右闭[left,right]
       while(left<=right){
    
    
           int mid=left+((right-left)>>1);
           if(nums[mid]<target){
    
    //下一个搜索区间[mid+1,right]mid 不可能是
               left=mid+1;
           }
           else if(target==nums[mid]){
    
    
               //nums[mid] == target 的时候,在 [left, mid - 1] 区间里找,
               //有没有可能 nums[mid] 就是第 1 次出现的位置,有可能,但不要紧,
               //退出循环的时候 right 指针在左,left 在右。如果数组里存在 
               //target,那么 left 一定位于 target 出现的第 1 个位置
               right=mid-1;//① 不可以直接返回,应该继续向左边找,下一个搜索区间[left,mid-1]和[mid+1,right] 
           }
           else if(target<nums[mid]){
    
    
               right=mid-1;// nums[mid] > target,下一轮搜索区间是 [left, mid ]
           }
       }
       return left;//返回左边界
    }
    //右边界
    public int right_bound(int[] nums,int target){
    
    
        int left=0;
        int right=nums.length-1;
        while(left<=right){
    
    
            int mid=left+((right-left)>>1);
            if(nums[mid]<target){
    
    
                left=mid+1;// nums[mid] < target,下一轮搜索区间是 [mid + 1, right]
            }
            else if(nums[mid]==target){
    
    
                left=mid+1;// 因为mid已经检查过了,下一轮搜索区间是[left,mid-1]和 [mid+1, right]
            }
            else if(target<nums[mid]){
    
    //下一轮搜索区间是[left,mid-1]
                right=mid-1;
            }
        }
        return right;//返回右边界 
    }

题目:找出第一个大于目标元素的索引:
找出第一个大于目标元素的数,大概有以下几种情况:

1.数组包含目标元素,找出在他后面的第一个元素
2.目标元素不在数组中,数组内的部分元素大于它,此时我们需要返回第一个大于他的元素
3.目标元素不在数组中,且数组中的所有元素都大于它,那么我们此时返回数组的第一个元素即可
4.目标元素不在数组中,且数组中的所有元素都小于它,那么我们此时没有查询到,返回 -1 即可。

public static int lowBoundnum(int[] nums,int target,int left, int right) {
    
    
        while (left <= right) {
    
    
            //求中间值
            int mid = left + ((right - left) >> 1);
            //大于目标值的情况
            if (nums[mid] > target) {
    
    
                 //返回 mid
                if (mid == 0 || nums[mid-1] <= target) {
    
    
                    return mid;
                }
                else{
    
    
                    right = mid -1;
                }
            } else if (nums[mid] <= target){
    
    
                left = mid + 1;
            }
        }
        //所有元素都小于目标元素
        return -1;
    }

题目:找出最后一个小于目标元素的索引:

public static int upperBoundnum(int[] nums,int target,int left, int right) {
    
    
        while (left <= right) {
    
    
            int mid = left + ((right - left) >> 1);
             //小于目标值
            if (nums[mid] < target) {
    
    
                //看看是不是当前区间的最后一位,如果当前小于,后面一位大于,返回当前值即可
                if (mid == right || nums[mid+1] >= target) {
    
    
                    return mid;
                }
                else{
    
    
                    left = mid + 1;
                }

            } else if (nums[mid] >= target){
    
    
                right = mid - 1;
            }
        }
        //没有查询到的情况
        return -1;
    }

题目:33. 搜索旋转排序数组

难度中等
升序排列的整数数组 nums 在预先未知的某个点上进行了旋转(例如, [0,1,2,4,5,6,7] 经旋转后可能变为 [4,5,6,7,0,1,2] )。
请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
这道题中,数组本身不是有序的,进行旋转后只保证了数组的局部是有序的,这还能进行二分查找吗?答案是可以的。可以发现的是,我们将数组从中间分开成左右两部分的时候,一定有一部分的数组是有序的。拿示例来看,我们从 6 这个位置分开以后数组变成了 [4, 5, 6] 和 [7, 0, 1, 2] 两个部分,其中左边 [4, 5, 6] 这个部分的数组是有序的,其他也是如此。
这启示我们可以在常规二分查找的时候查看当前 mid 为分割位置分割出来的两个部分 [l, mid] 和 [mid + 1, r] 哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分查找的上下界,因为我们能够根据有序的那部分判断出 target 在不在这个部分:
如果 [l, mid - 1] 是有序数组,且 target 的大小满足[nums[l],nums[mid]),则我们应该将搜索范围缩小至 [l, mid - 1],否则在 [mid + 1, r] 中寻找。
如果 [mid, r] 是有序数组,且 target 的大小满足(nums[mid+1],nums[r]],则我们应该将搜索范围缩小至 [mid + 1, r],否则在 [l, mid - 1] 中寻找

public int search(int[] nums, int target) {
    
    
        int len=nums.length;
        if(len==0){
    
    
            return -1;
        }
        if(len==1){
    
    
            return nums[0]==target?0:-1;
        }
        int left=0;
        int right=len-1;
        while(left<=right){
    
    
            int mid=left+((right-left)>>1);
             if(nums[mid]==target){
    
    
                      return mid;
                    }
            if(nums[left]<=nums[mid]){
    
    //mid左边是有序的, 
                if(nums[left]<=target&& nums[mid]>target){
    
    //确保目标值在有序数组[left,mid-1]
                    right=mid-1;//只有在有序数组里面才能进入这个if一直循环出来
                }
                else {
    
    //目标值不再有序数组里面,在[mid+1,right]
                    left=mid+1; 
                }
            }
            else{
    
    //进入mid右侧无序数组寻找
                    if(nums[mid]<target&&target<=nums[right]){
    
    //确保在[mid+1,right]
                        left=mid+1;
                    }
                    else{
    
    //在[left,mid-1]
                        right=mid-1;
                    }
            }
        }
        return -1;
    }

题目:81. 搜索旋转排序数组 II

难度中等
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。
示例 1:
输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true
解题思路:
本题是需要使用二分查找,怎么分是关键,举个例子:

第一类
1011110111 和 1110111101 这种。此种情况下 nums[start] == nums[mid],分不清到底是前面有序还是后面有序,此时 start++ 即可。相当于去掉一个重复的干扰项。
第二类
22 33 44 55 66 77 11 这种,也就是 nums[start] < nums[mid]。此例子中就是 2 < 5;
这种情况下,前半部分有序。因此如果 nums[start] <=target<nums[mid],则在前半部分找,否则去后半部分找。
第三类
66 77 11 22 33 44 55 这种,也就是 nums[start] > nums[mid]。此例子中就是 6 > 2;
这种情况下,后半部分有序。因此如果 nums[mid] <target<=nums[end]。则在后半部分找,否则去前半部分找。

 public boolean search(int[] nums, int target) {
    
    
        if(nums.length==0||nums==null){
    
    
            return false;
        }
        int left=0;
        int right=nums.length-1;
        while(left<=right){
    
    
            int mid=left+((right-left)>>1);
            if(nums[mid]==target){
    
    //只有这能返回true 找到了
                return true;
            }
             if(nums[left]<nums[mid]){
    
    //左半部分有序
                if(nums[left]<=target&&target<nums[mid]){
    
    //target在前半部分
                    right=mid-1;
                }
                else{
    
    //否则,去后半部分找
                    left=mid+1;
                }
                }
            else if(nums[left]==nums[mid]){
    
    
                    left++;
                    continue;
                }
                else {
    
    
                    //右侧是有序的
                    if(nums[mid]<target&&target<=nums[right]){
    
    //target在后半部分
                        left=mid+1;
                    }
                    else{
    
     //否则,去后半部分找
                        right=mid-1;
                    }
                }           
        }
        return false;  //一直没找到,返回false
    }

题目:153. 寻找旋转排序数组中的最小值

难度中等
假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] 。
请找出其中最小的元素。 不重复

示例 1:
输入:nums = [3,4,5,1,2]
输出:1
思路:左、中、右三个位置的值相比较,有以下几种情况:

左值 < 中值, 中值 < 右值 :没有旋转,最小值在最左边,可以收缩右边界

        右
  中


左值 > 中值, 中值 < 右值 :有旋转,最小值在左半边,可以收缩右边界




左值 < 中值, 中值 > 右值 :有旋转,最小值在右半边,可以收缩左边界



左值 > 中值, 中值 > 右值 :单调递减,不可能出现



 public int findMin(int[] nums) {
    
    
        int left=0;
        int right=nums.length-1;
        while(left<=right){
    
    
            int mid=left+((right-left)>>1);
        if(nums[left]<=nums[right]){
    
    /<mid<right
            return nums[left];
        }      
        if(nums[left]<=nums[mid]){
    
    //左<mid,mid>right
           left=mid+1;
        }
       else if(nums[left]>nums[mid]){
    
    //左>mid,mid<右  这两种最小值都在左边mid也可能是
            right=mid;
        }
        }
        return -1;
    }

二维数组

二维数组其实就是一个矩阵
那么二维数组在内存的空间地址是连续的么?
我们来举一个例子,例如: int[][] rating = new int[3][4]; , 这个二维数据在内存空间可不是一个 3*4 的连续地址空间
如图所示:
二位数组中其实是一个线性数组存放着 其他数组的首地址

所以二维数据在内存中不是 3*4 的连续地址空间,而是四条连续的地址空间组成!
很多同学会以为二维数组在内存中是一片连续的地址,其实并不是。

1、二分查找

我们需要从一个二维矩阵中,搜索是否含有元素 7,我们如何使用二分查找呢?
其实我们可以完全将二维矩阵想象成一个有序的一维数组,然后用二分,,比如我们的二维矩阵中,共有 9 个元素,那定义我们的 left = 0,right = 9 - 1= 8,是不是和一维数组定义相同,然后我们求我们的 mid 值, mid = left +((right - left) >> 1)此时 mid = 4 ,但是我们的二维矩阵下标最大是,nums[2,2]呀,你这求了一个 4 ,让我们怎么整呀。如果我们理解了二分查找,那么这个题目考察我们的应该是如何将一维数组的下标,变为 二维坐标。其实也很简单,咱们看哈,此时咱们的 mid = 4,咱们的二维矩阵共有 3行, 3列,那我们 mid =4,肯定在第二行,那么这个应该怎么求得呢?

我们可以直接用 (mid/列数)得行,即可,因为我们 mid = 4,4 /3 = 1,说明在 在第二行,那如果 mid = 7 ,7/3=2,在第三行,我们第几行知道了,那么我们如何知道第几列呢?我们可以直接根据 (mid % 列数 )来求得呀,比如我们此时 mid = 7,7%3 = 1,那么在我们一维数组索引为 7 的元素,其处于二维数组的第2列,

题目:74. 搜索二维矩阵

难度中等
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。

示例 1:

 public boolean searchMatrix(int[][] matrix, int target) {
    
    
        if(matrix.length==0){
    
    
            return false;
        }
        int row=matrix.length;//行数
        int col=matrix[0].length;//列数
        int left=0;
        int right=row*col-1; //行数乘列数 - 1,右指针
        while(left<=right){
    
    
            int mid=left+((right-left)>>1);
            //将一维坐标变为二维坐标
            int rownum=mid/col;
            int colnum=mid%col;
            if(matrix[rownum][colnum]==target){
    
    
                return true;
            }
            if(matrix[rownum][colnum]>target){
    
    
                right=mid-1;
            }
             if(matrix[rownum][colnum]<target){
    
    
                left=mid+1;;
            }
        }
        return false;
    }

猜你喜欢

转载自blog.csdn.net/Snailalgorithm/article/details/113886435