数组和二分查找leetcode练习

数组和二分查找leetcode练习

1 数组理论基础

数组是非常基础的数据结构,考察数组的题目难度低,主要考对代码的编写能力。

数组是存放在连续内存空间上的相同类型数据的集合。

  • 数组的下标都是从0开始的,通过下标访问数组中的数据
  • 数组内存空间的地址是连续的

数组和二分查找leetcode练习_第1张图片

向数组中进行数据的添加或删除时,需要对其它元素进行移动。

关于二维数组:

  • 在C/C++中,二维数组在内存中的地址是连续的;
  • 在Java中,二维数组的没一行数组内存空间的地址连续,而不同行的数组内存空间地址不连续。因为Java没有指针,同时不对程序员暴露其元素地址,寻址操作完全交给虚拟机。

2 二分查找

2.1 例题1 二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。题目链接

思路:当数组满足条件:有序数组、无重复元素时,可以考虑使用二分查找的方法解题。在二分查找中有两种二分方法:左闭右闭和左闭右开,它们的区别是有边界right的值不同,根据right值的不同对逻辑代码中判断条件进行调整(while寻找每一次边界的处理都要根据区间的定义来操作)。我们掌握法一即可。

代码如下(Java):

class Solution {
    public int search(int[] nums, int target) {
        int left = 0, right = nums.length - 1;

        while (left <= right){  //while循环的条件是左边界小于等于右边界
            int mid = (right - left) / 2 + left;  //防溢出方法
            if (nums[mid] < target){
                left = mid + 1;
            }else if (nums[mid] > target){
                right = mid - 1;
            }else {
                return mid; //返回目标值的位置
            }
        }
        return -1; //未找到目标值
    }
}

2.2 例题2 搜索插入位置

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

思路:关键词有排序数组,可以考虑二分法。通过推导可以得知此时有四种情况:

  • 目标值在数组所有元素之前
  • 目标值等于数组中某一个元素
  • 目标值插入数组中的位置
  • 目标值在数组所有元素之后

对于情况2,只需要返回对应的位置即可,对于其它三种情况,返回有边界值right+1即可。

代码如下:

class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0, right = nums.length - 1;

        while (left <= right){
            int mid = (right - left) / 2 + left;
            if (nums[mid] < target){
                left = mid + 1;
            }else if (nums[mid] > target){
                right = mid - 1;
            }else {
                return mid;
            }
        }
        return right + 1; 
    }
}

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

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]。题目链接

进阶:你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

思路:暴力解法,情况分为数组为空和数组不为空的情况,如果数组为空,则返回[-1,-1]即可;如果数组不为空,则利用二分法寻找target位置,找到则使用while循环找到target的范围,如果没有找到,则直接返回[-1,-1].

代码如下:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        if (nums.length == 0) return new int[]{-1,-1};
        int low = 0, high = nums.length - 1;
        int mid = 0;

        while (low <= high) {
            mid = (high - low)/2 + low;
            if (nums[mid] > target) {
                high = mid - 1;
            }else if (nums[mid] < target){
                low = mid + 1;
            }else {
                break;
            }
        }
        if (low <= high) {
            int cntR = mid, cntL = mid;
            while (cntR < nums.length && nums[cntR] == target) {
                cntR++;
            }
            while (cntL >= 0 && nums[cntL] == target) {
                cntL--;
            }
            return new int[]{cntL+1, cntR-1};
        }
        return new int[]{-1,-1};
    }
}

以上方法为原创暴力解法,以下为力扣题解方法

思路:分别找左右边界,以右边界为例,如果nums[mid]的值大于target,则有边界为mid-1;如果nums[mid]的值小于等于target,则左边界和右边界都更新为mid+1.

代码如下:

class Solution {
    int[] searchRange(int[] nums, int target) {
        int leftBorder = getLeftBorder(nums, target);
        int rightBorder = getRightBorder(nums, target);
        // 情况一
        if (leftBorder == -2 || rightBorder == -2) return new int[]{-1, -1};
        // 情况三
        if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1};
        // 情况二
        return new int[]{-1, -1};
    }

    int getRightBorder(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
        while (left <= right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] > target) {
                right = middle - 1;
            } else { // 寻找右边界,nums[middle] == target的时候更新left
                left = middle + 1;
                rightBorder = left;
            }
        }
        return rightBorder;
    }
    
    int getLeftBorder(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
        while (left <= right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
                right = middle - 1;
                leftBorder = right;
            } else {
                left = middle + 1;
            }
        }
        return leftBorder;
    }

}

2.4 例题4 sqrt(x)

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

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

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

思路:使用二分查找的思想

tips: 为了防止溢出,将 m*m > x 改为 x/m < m

代码如下:

class Solution {
    public int mySqrt(int x) { 
        if (x == 1){
            return 1;
        }
        int min = 0, max = x;
        while (max - min > 1){
            int m = (max + min) / 2;
            if (x / m < m){
                max = m;
            }
            else {
                min = m;
            }
        }
        return min;
    }
}

2.5 例题5 有效的完全平方数

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

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

思路:二分法

代码如下:

class Solution {
    public boolean isPerfectSquare(int num) {
        int min = 1, max = num;
        int m = 0, t = 0;

        while (max >= min) { 
            m = (max - min) / 2 + min;
            t = num / m;
            if (t == m) {
                if(num % m == 0) {
                    return true;
                }
                min = m + 1;
            }else if (t < m) {
                max = m - 1;
            }else {
                min = m + 1;
            }
        }
        return false;
    }
}

你可能感兴趣的:(算法,leetcode,数据结构,数组)