当前位置:   article > 正文

力扣刷题day01|704二分查找、27移除元素、35搜索插入位置、34在排序数组中查找元素的第一个和最后一个位置

力扣刷题day01|704二分查找、27移除元素、35搜索插入位置、34在排序数组中查找元素的第一个和最后一个位置

704. 二分查找

力扣题目链接

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

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
  • 1
  • 2
  • 3

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
  • 1
  • 2
  • 3

难点

  1. 二分时的条件到底是 while(left < right) 还是 while(left <= right)

  2. 二分完后根据target的位置不同,指针到底应该指向哪,是right = middle,还是要right = middle - 1

思路

  • target 是在一个在左闭右闭的区间里,即[left, right]

此时left == right是有意义的,如果(nums[middle] > target)right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

public class BinarySearch {
    public int search1 (int[] nums, int target){
        // whlie循环中为左闭右闭区间
        // 定义左右指针及中间位置
        int left = 0;
        int right = nums.length - 1;
        while (left <= right){
            int mid = left + ((right - left) >> 1);
            if (target < nums[mid]){
                right = mid - 1;
            }else if (target > nums[mid]){
                left = mid + 1;
            }else if (target == nums[mid]){
                return mid;
            }
        }
        return -1;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • target 是在一个在左闭右开的区间里,即[left, right)

因为left == right在区间[left, right)是没有意义的,如果(nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,right即使更新为middle也是在开区间上,不会参与比较

public int search2 (int[] nums, int target){
        // whlie循环中为左闭右开区间
        // 定义左右指针及中间位置
        int left = 0;
        int right = nums.length; // 左闭右开时右指针要指出数组右边界后一位,开区间取不到
        while (left < right){
            int mid = left + ((right - left) >> 1);
            if (target < nums[mid]){
                right = mid;
            }else if (target > nums[mid]){
                left = mid + 1;
            }else if (target == nums[mid]){
                return mid;
            }
        }
        return -1;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

时间复杂度:O(logn)

27. 移除元素

力扣题目链接

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案
  • 1
  • 2
  • 3

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
  • 1
  • 2
  • 3

思路

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

双(快慢)指针法

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

当快指针指向的值不等于目标值,那么快指针此时指向的值便可以赋值给慢指针所指向的新数组

public class RemoveElement {
    public int removeElement(int[] nums, int val){
        int slowIndex = 0; // 寻找新数组的元素 ,新数组就是不含有目标元素的数组
        int fastIndex = 0; // 新新数组下标
        for (fastIndex = 0; fastIndex < nums.length; fastIndex++){
            if (nums[fastIndex] != val){
                nums[slowIndex] = nums[fastIndex];
                slowIndex++;
            }
        }
        return slowIndex;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 时间复杂度:O(n)
双向指针法

核心就是把不等于val的全部放在数组左边,等于val的全部放在数组右边

  • 左指针:寻找等于val的元素把它扔到右边
  • 右指针:寻找不等于val的元素,与左边等于val的位置进行交换
// 双向指针的方法
int leftIndex = 0; // 左指针
int rightIndex = nums.length - 1; // 右指针
while(leftIndex <= rightIndex){
    // 找到左边等于val的元素
    while (leftIndex <= rightIndex && nums[leftIndex] != val){
        leftIndex++;
    }
    //找到右边不等于val的元素
    while (leftIndex <= rightIndex && nums[rightIndex] == val){
        rightIndex--;
    }
    //找到左边等于val的就与右边不等于val的元素交换
    if(leftIndex < rightIndex){
        nums[leftIndex++] = nums[rightIndex--];
    }
}
return leftIndex;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

难点是关于while中<<=的使用

while (leftIndex <= rightIndex && nums[leftIndex] != val){
                leftIndex++;
}
  • 1
  • 2
  • 3

leftIndex=rightIndex两个指针重合,说明左边已经没有val要移动到右边,左指针指向两指针重合的下一位,当leftIndex>rightIndex循环停止

while (leftIndex <= rightIndex && nums[rightIndex] == val) {
                -- rightIndex;
}
  • 1
  • 2
  • 3

此时leftIndex已经大于rightIndex,循环停止。

 if (leftIndex < rightIndex)
  • 1

leftIndex=rightIndex时当二指针重合时不需要交换

完整代码:

public int removeElement2(int[] nums, int val){
    // 双向指针的方法
    int leftIndex = 0; // 左指针
    int rightIndex = nums.length - 1; // 右指针
    while(leftIndex <= rightIndex){
        // 找到左边等于val的元素
        while (leftIndex <= rightIndex && nums[leftIndex] != val){
            leftIndex++;
        }
        //找到右边不等于val的元素
        while (leftIndex <= rightIndex && nums[rightIndex] == val){
            rightIndex--;
        }
        //找到左边等于val的就与右边不等于val的元素交换
        if(leftIndex < rightIndex){
            nums[leftIndex++] = nums[rightIndex--];
        }
    }
    return leftIndex;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

35. 搜索插入位置

力扣题目链接

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2
  • 1
  • 2

示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1
  • 1
  • 2

示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4
  • 1
  • 2

思路

只要看到面试题里给出的数组是有序数组,都可以想一想是否可以使用二分法。

同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的。

二分法

如果用二分法遍历完毕之后还没有找到插入位置,则插入位置在数组的最后

完整代码:

public int searchInsert(int[] nums, int target) {
    // 左闭右闭
    int left = 0;
    int right = nums.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] > target) {
            right = mid - 1;
        }else if (nums[mid] < target) {
            left = mid + 1;
        }else {
            return mid;
        }
    }
    return right + 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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

力扣链接

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
  • 1
  • 2

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
  • 1
  • 2

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]
  • 1
  • 2

思路

这道题与35题的区别就是这道题是存在重复元素

寻找target在数组里的左右边界,有如下三种情况:

  • 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
  • 情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
  • 情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}

接下来,在去寻找左边界,和右边界了。

采用二分法来去寻找左右边界,为了让代码清晰,我分别写两个二分来寻找左边界右边界

寻找右边界

寻找右边界实际上是寻找数组里第一个大于等于target的数

确定好:计算出来的右边界是不包含target的右边界,左边界同理。

如果rightBorder为没有被赋值(即target在数组范围的左边

难点

nums[middle] <= target时,说明此时mid右边可能还有大于等于target的数,首先更新left为mid+1,然后就要更新rightBorder为此时的left

// 二分查找,寻找target的右边界(不包括target)
int getRightBorder(int[] nums, int target) {
    // 定义target在左闭右闭的区间里,[left, right]
    int left = 0;
    int right = nums.length - 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
寻找左边界
// 二分查找,寻找target的左边界leftBorder(不包括target)
int getLeftBorder(int[] nums, int target) {
    // 定义target在左闭右闭的区间里,[left, right]
    int left = 0;
    int right = nums.length - 1;
    int leftBorder = -2; // 右边界初始化
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
            leftBorder = right;
        }
    }

    return leftBorder;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
处理三种情况

左右边界计算完之后,看一下主体代码,这里把上面讨论的三种情况,都覆盖了

public 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};
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

完整代码

public 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};
}

// 二分查找,寻找target的右边界(不包括target)
int getRightBorder(int[] nums, int target) {
    // 定义target在左闭右闭的区间里,[left, right]
    int left = 0;
    int right = nums.length - 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;
}


int getLeftBorder(int[] nums, int target) {
    // 定义target在左闭右闭的区间里,[left, right]
    int left = 0;
    int right = nums.length - 1;
    int leftBorder = -2; // 右边界初始化
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
            leftBorder = right;
        }
    }

    return leftBorder;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/812739
推荐阅读
相关标签
  

闽ICP备14008679号