赞
踩
目录
LeetCode 34.在排序数组中查找元素的第一个和最后一个位置
定义:
数组是存储在连续空间且元素类型相同的集合。
特点:
1.数组的下标是从0开始的。
2.在数组当中,要查找或者修改数据,只需要知道你所要的数据的索引即可;但是当你进行删除或者增加的操作的时候,你不仅需要知道你所需要的索引还得进行挪动空间的操作,此时数组就会显得不那么便利。
3.在数组中当中,要删除或者添加数据,通常都需要挪动空间的操作。在进行删除操作中,如果需要删除的元素在数组末尾则不需要挪动空间;在进行添加操作中,如果需要添加的元素在数组最后一个元素后则也不需要挪动空间。除了以上两种情况外,通常都需要进行挪动空间的操作,挪动空间的操作则以覆盖的形式来表现。
目标:
1.熟悉二分查找的逻辑,以及不同区间的写法。
2.熟悉在数组中删除元素其实就是进行覆盖操作。
3.多联系,孰能生巧。
文档讲解:代码随想录
视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili
力扣题目:LeetCode 704.二分查找
题目获取的信息:
第一:有序且为升序,元素不重复的数组
第二:在数组中找到对应target的值
第三:有则返回对应target的下标,无则返回-1
思路:
1.使用二分查找的前提有两个:1是有序的数组,2是数组元素不重复,题目都符合要求。
2.在二分查找过程中,不断进行折半处理。而在折半处理的过程中,数组的范围是在不断缩减的,所以会联想到要用循环体。在这个过程中,影响着3个变量,这3个变量来控制数组范围,也即是left(左边界),right(右边界),middle(中间下标)。
3.定义left = 0;right = nums.length - 1; middle = left + (right - left) / 2;
4.那么循环体的条件该如何定义呢?如果是左闭右开区间,此时右区间要更改为nums的长度。假设一开始的nums[middle]>target,因为nums为升序数组,所以可以得知target是在[left, middle]这个区间的。相应的我们要对判断条件中的right值来进行修改,right = middle。为什么不是right = middle - 1呢?因为我们是左闭右开区间,在循环判断条件中,并没有帮我们判断右区间,所以我们要在循环体内的判断进行相应的修改;同理,要是左闭右闭区间,此时右区间要更改为nums的长度-1。假设一开始的nums[target]>target,因为nums为升序数组,所以可以得知target是在[left, middle)这个区间的。因为循环判断条件中以及帮我们判断了右区间,所以在循环体内的判断条件可以中,right = middle - 1;
所以综上所述代码如下(左闭右闭):
- class Solution {
- public int search(int[] nums, int target) {
-
- //定义数组左边界下标值left
- int left = 0;
- //定义数组右边界下标值right
- int right = nums.length - 1;
- //定义数组中间下标值middle
- int middle = 0;
-
- //1.二分查找的前提是有序数组
- //2.以中间下标为寻找对象,不断缩减范围
- while(left <= right){
-
- middle = left + (right - left) / 2;
-
- if(nums[middle] > target){
- right = middle - 1;
- }else if(nums[middle] < target){
- left = middle + 1;
- }else{
- return middle;
- }
-
- }
-
- return -1;
- }
- }

代码如下(左闭右开):
- class Solution {
- public int search(int[] nums, int target) {
-
- int left = 0;
- int right = nums.length;
-
- while(left < right){
-
- int middle = left + (right - left) / 2;
-
- if(nums[middle ] < target){
- left = middle + 1;
- }else if(nums[middle] > target){
- right = middle;
- }else{
- return middle;
- }
-
- }
-
- return -1;
- }
- }

总结:使用二分查找的时候,前提是数组中的元素是有序的;其次就是数组中的元素是不重复的;最后就是要保持区间不变性的原则,就是你要确定区间的范围是左闭右闭,还是左闭右开区间,两种不同的思想对于代码的编写还是会不一样的。
文档讲解:代码随想录
思路:看到题目要原地修改的时候,会想到双指针的解法。但实际操作下来,会发现还是会用回原来的暴力解法,而没有想到用快慢指针的操作。
暴力解法思路:定义两个循环体,第一个循环体遍历整个数组,第二个循环体则用来更新数组。
代码如下(暴力解法):
- class Solution{
-
- public int removeElement(int[] nums, int val){
-
- int length = nums.length;
-
- for(int oneIndex = 0; oneIndex < nums.length; oneIndex++){
-
- if(nums[oneIndex] == val){
-
- for(int twoIndex = oneIndex + 1; twoIndex < nums.length; twoIndex++){
-
- nums[twoIndex - 1] = nums[twoIndex];
-
- }
-
- oneIndex--;
- length--;
- }
- }
-
- return length;
- }
- }

快慢指针的思路:首先在for循环中定义一个快指针fastIndex,fastIndex就是用来遍历整个数组。其次,思考慢指针在快指针遍历的数组的过程中,如何慢下来,也就是思考判断条件。
代码如下(快慢指针解法):
- class Solution {
- public int removeElement(int[] nums, int val) {
-
- int slowIndex = 0;
-
- for(int fastIndex = 0; fastIndex < nums.length; fastIndex++){
-
- if(nums[fastIndex] != val){
- nums[slowIndex] = nums[fastIndex];
- slowIndex++;
- }
-
- }
-
- return slowIndex;
- }
- }

总结:数组中进行删除操作的本质就是进行数组元素覆盖的操作,然后返回删除后的数组长度。那么,在进行覆盖操作的时候,有两种不同的解法。一是进行暴力解法,第一遍循环进行查找目标元素,第二遍循环把目标元素往后的元素进行向前覆盖的操作,即从目标元素的后一个元素开始,进行向前覆盖的操作 。进行两次循环之后,回退一个第一遍循环所需要索引的位置,这个操作是用来进行删除重复元素的操作;二是进行双指针的解法,定义两个指针,fast指针以及slow指针。fast指针作为循环的变量,slow指针作为符合条件时进行覆盖的位置,通俗来讲就是,fast指针用来寻找符合条件的元素,当找到符合条件的元素的时候,把元素填进slow的位置当中。
文章讲解:代码随想录
力扣题目:LeetCode 35.搜索插入位置
思路:
1.用二分查找的方法找到适合的位置,找到就返回下标
2.当target值不在数组范围内的时候,同样用二分查找找到最接近target值的下标位置,此时你需要找一个相对位置,像left下标或者right下标又或者middle下标,这个就看各人编码习惯。
3.假设我用的是right下标,那么因为这个是有序且为递增数组,没有重复元素。如果target比该数组最小的值还要小,那么在二分查找最后一次循环当中,就要移动right的下标,那么此时right的下标一定为-1,显然插入的位置一定为数组的开头即下标为0的位置,也就是right+1的位置;如果target比该数组的最大的位置还要大,那么在二分查找最后一次循环当中,就要移动left下标,那么此时left下标为数组的长度加一,显然插入的位置一定为数组的长度末尾的位置,即right+1的位置;如果target的位置在数组中间,那么对于二分查找最后一次循环当中,无非就是大一个位置或小一个位置,target值大,那么移动left的位置,此时right+1的位置就是插入的位置。target值小,那么移动right的位置,此时也是right+1的位置就是插入的位置。
代码如下(Java):
- class Solution {
- public int searchInsert(int[] nums, int target) {
-
- int left = 0;
- int right = nums.length - 1;
- int middle = 0;
-
- while(left <= right){
-
- middle = left + (right - left) / 2;
-
- if(target < nums[middle]){
- right = middle - 1;
- }else if(target > nums[middle]){
- left = middle + 1;
- }else{
- return middle;
- }
-
- }
-
- if(target < nums[middle]){
- return middle;
- }else{
- return middle + 1;
- }
- }
- }

代码如下(Java):二分查找
- class Solution {
- public int searchInsert(int[] nums, int target) {
-
- int left = 0;
- int right = nums.length - 1;
-
- while(left <= right){
-
- int middle = left + (right - left) / 2;
-
- if(nums[middle] < target){
- left = middle + 1;
- }else if(nums[middle] > target){
- right = middle - 1;
- }else{
- return middle;
- }
-
- }
-
- return left;
- }
- }

代码如下(Java):二分查找
- class Solution {
- public int searchInsert(int[] nums, int target) {
-
- int left = 0;
- int right = nums.length - 1;
-
- while(left <= right){
-
- int middle = left + (right - left) / 2;
-
- if(nums[middle] < target){
- left = middle + 1;
- }else if(nums[middle] > target){
- right = middle - 1;
- }else{
- return middle;
- }
-
- }
-
- return right + 1;
- }
- }

总结:当你遇到数组有序且不重复的时候,可以考虑一下二分查找。这道题的思路就是搜索位置,然后返回相应插入的位置即可。
思路:
1.数组中删除元素的操作通常是使用覆盖或者新建数组的方式,本题目要求原地删除,所以选择覆盖的方式。
2.那么选择覆盖的方式,自然就会想到用多指针的方式来进行覆盖
3.使用fast指针初始化为1,slow指针初始化为0,fast指针随着循环的次数自然增加。那么当fast指针所指向的元素和slow指针指向的元素不同的时候,slow指针才往前加一,且fast指针所指向的值需要赋给slow+1的位置。
如下图所示:
代码如下(Java):
- class Solution {
- public int removeDuplicates(int[] nums) {
-
- int slow = 0;
-
- for(int fast = 1; fast < nums.length; fast++){
-
- if(nums[fast] != nums[slow]){
- nums[slow+1] = nums[fast];
- slow++;
- }
-
- }
-
- return slow + 1;
- }
- }

总结:在数组中进行删除操作的时候需要进行两个动作,一个动作就是进行查找,一个动作就是进行覆盖,这样就可以尝试使用双指针的操作,看看题目的要求,先整理以下思路,在进行编码。
思路:
1.用两个for循环,一个for循环顺序找出target的开始位置,一个for循环倒序用来找出target的结束位置 。
代码如下(Java):暴力解法
- class Solution {
- public int[] searchRange(int[] nums, int target) {
-
- int[] result = new int[]{-1,-1};
-
- for(int start = 0; start < nums.length; start++){
- if(nums[start] == target){
- result[0] = start;
- break;
- }
- }
-
- for(int end = nums.length - 1; end > -1; end--){
- if(nums[end] == target){
- result[1] = end;
- break;
- }
- }
-
- return result;
- }
- }

力扣题目:LeetCode 69.x的平方根
代码如下(Java):
- class Solution {
- public int mySqrt(int x) {
-
- int left = 0;
- int right = x;
- int res = 0;
-
- while(left <= right){
-
- int middle = left + (right - left) / 2;
-
- if((long)middle * middle <= x){
-
- res = middle;
- left = middle + 1;
-
- }else{
-
- right = middle - 1;
-
- }
-
- }
-
- return res;
- }
- }

代码如下(Java):
- class Solution {
- public boolean isPerfectSquare(int num) {
-
- long x = 1;
- long square = 1;
-
- while(square <= num){
-
- if(square == num) return true;
- ++x;
- square = x * x;
-
- }
-
- return false;
- }
- }

力扣题目:LeetCode 283.移动零
代码如下(java):
- class Solution {
- public void moveZeroes(int[] nums) {
-
- for(int slow = 0, fast = 1; fast < nums.length; fast++){
-
- if(nums[slow] != 0){
- slow++;
- }
-
- if(nums[slow] == 0 && nums[fast] != 0){
- int temp = nums[fast];
- nums[fast] = nums[slow];
- nums[slow] = temp;
- slow++;
- }
-
- }
- }
-
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。