Leetcode-二分查找算法

LeetCode-二分查找算法

  • Leetcode 35
    • Leetcode 162
    • Leetcode 53
    • Leetcode 33
    • Leetcode 50(不会)
    • Leetcode 167(继续找方法)
    • Leetcode 287(非自己写)
    • Leetcode 209

Leetcode 35

题目描述:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素

我的解法:

public class Solution {
    public int SearchInsert(int[] nums, int target) {
int left=0;
int right=nums.Length;
int mid=0;
while(left

解法①

class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0, right = nums.length - 1; // 注意
        while(left <= right) { // 注意
            int mid = (left + right) / 2; // 注意
            if(nums[mid] == target) { // 注意
                // 相关逻辑
            } else if(nums[mid] < target) {
                left = mid + 1; // 注意
            } else {
                right = mid - 1; // 注意
            }
        }
        // 相关返回值
        return 0;
    }
}

解法②

class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0, right = nums.length; // 注意
        while(left < right) { // 注意
            int mid = (left + right) / 2; // 注意
            if(nums[mid] == target) {
                // 相关逻辑
            } else if(nums[mid] < target) {
                left = mid + 1; // 注意
            } else {
                right = mid; // 注意
            }
        }
        // 相关返回值
        return 0;
    }
}

最快方法:

public class Solution {
    public int SearchInsert(int[] nums, int target) {
            int l = 0;
            int len = nums.Length;
            int r = len;
            while(l < r) {
                int mid = (l+r)>>1;
                if(nums[mid] < target) {
                    l = mid + 1;
                } else {
                    r = mid;
                }
            }
            return l;
    }
}

总结:

Leetcode 162

题目描述:
峰值元素是指其值大于左右相邻值的元素。

给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。

数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞。

示例 1:
输入: nums = [1,2,3,1]
输出: 2
解释: 3 是峰值元素,你的函数应该返回其索引 2。

解法①

if(num【i+1>num[i])
i++
else index =i;

简化版本:for(int i=0;i<num.Length;i++) if(num[i]>num[i+1]) return i;

解法②
递归二分查找:
在简单的二分查找中,我们处理的是一个有序数列,并通过在每一步减少搜索空间来找到所需要的数字。在本例中,我们对二分查找进行一点修改。首先从数组 numsnums 中找到中间的元素 midmid。若该元素恰好位于降序序列或者一个局部下降坡度中(通过将 nums[i]nums[i] 与右侧比较判断),则说明峰值会在本元素的左边。于是,我们将搜索空间缩小为 midmid 的左边(包括其本身),并在左侧子数组上重复上述过程。

若该元素恰好位于升序序列或者一个局部上升坡度中(通过将 nums[i]nums[i] 与右侧比较判断),则说明峰值会在本元素的右边。于是,我们将搜索空间缩小为 midmid 的右边,并在右侧子数组上重复上述过程。

就这样,我们不断地缩小搜索空间,直到搜索空间中只有一个元素,该元素即为峰值元素。

public class Solution {
    public int findPeakElement(int[] nums) {
        return search(nums, 0, nums.length - 1);
    }
    public int search(int[] nums, int l, int r) {
        if (l == r)
            return l;
        int mid = (l + r) / 2;
        if (nums[mid] > nums[mid + 1])
            return search(nums, l, mid);
        return search(nums, mid + 1, r);
    }
}

解法② 迭代二分查找

public class Solution {
    public int findPeakElement(int[] nums) {
        int l = 0, r = nums.length - 1;
        while (l < r) {
            int mid = (l + r) / 2;
            if (nums[mid] > nums[mid + 1])
                r = mid;
            else
                l = mid + 1;
        }
        return l;
    }
}

我的解法:

public int FindPeakElement(int[] nums) {
int index=0;
int left=0;
int right=nums.Length-1;
while(left!=(right-1)&&left!=right)
{
index=(right+left)/2;
if(nums[index]<nums[index+1])
{
  left=index;
}
else right=index;

}

return right;

    }
出现的问题:
假如出现实例:【21】,则此算法无用。因为我总是默认最后筛选出来的区间,右边的大。

最快方法:

public class Solution {
    public int FindPeakElement(int[] nums) {
            if (nums.Length == 0 || nums.Length == 1) return 0;
            int start = 0;
            int end = nums.Length - 1;
            while (start < end)
            {
                int mid = (start + end) / 2;
                if (nums[mid] < nums[mid + 1])
                {
                    start = mid + 1;
                }
                else
                {
                    end = mid;
                }
            }
            return start;
    }
}

Leetcode 53

题目描述:
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
解法①

左数组nums【i】=i;
 右数组nums【i】!=i;所以只要找到右数组的第一个数字就好了
如果num【mid】==mid,则不断右移;
如果num【mid】!=mid则不断左移。
while要遍历每一个数字,所以跳出的条件是left》right
class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        i, j = 0, len(nums) - 1
        while i <= j:
            m = (i + j) // 2
            if nums[m] == m: i = m + 1
            else: j = m - 1
        return i;

我的解法:

 public int MissingNumber(int[] nums) {  //存在的问题:
int left=0;
int right=nums.Length-1;
int mid=right;
if(nums.Length==1)    //一开始没有考虑只有一个的情况出现
{
if(nums[0]==0)
return 1;
else if(nums[0]==1)
return 0;

}

while(left<right)    //没有考虑只有左边的情况【0,1】
{
 mid=(right+left)/2; //也没有考虑只有右边的情况。比如说【1,2】
 if(nums[mid]==mid&&nums[mid+1]!=mid+1)
    return mid+1;
     else if(nums[mid]==mid&&nums[mid+1]==mid+1)
     left=mid+1;
     else if(nums[mid]!=mid&&nums[mid+1]!=mid+1)
     right=mid-1;
}
return left; // 改成left+1则考虑了第一种 ;但是改了之后 第二种情况又错误了

最后更改:
if(nums[left]!=left)
return left;
else return left+1;

再次更改:把{0}跟第一种情况结合起来:
if(nums[right]==nums.length-1)
return nums.length;

最快方法:

public class Solution
{
    public int MissingNumber(int[] nums)
    {
        if (nums.Last() == nums.Length - 1)
            return nums.Length;

        int left = 0, right = nums.Length - 1;
        while (left < right) {
            int mid = (left + right) / 2;
            if (nums[mid] != mid)
                right = mid;
            else
                left = mid + 1;
        }
        return left;
    }
}

Leetcode 33

题目描述:
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

我的解法:

public class Solution {
    public int Search(int[] nums, int target) {
 int left=0;
  int right=nums.Length-1;
int mid;
if(nums.Length==0) return -1;//加入数组为空
if(nums[0]==target)
return 0;
 else if(nums[0]>target)
{
while(left<=right)
{
    mid=(right+left)/2;
if(nums[mid]==target)
return mid;
else if(nums[mid]<target)
{
 left=mid+1;
}
else if(nums[mid]>target)
{
if(nums[mid]>=nums[0])//[3,1]情况必须要有=号
left=mid+1;
else right=mid-1;
}
}
return -1;

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

}
问题:
看起来太复杂。
有些情况其实可以合并!
如果数组为空,nums【0】会超出界限;
nums={}的情况时,nums==null不顶用,要用length判断

最快方法:

public class Solution {
     public int Search(int[] nums, int target)
        {
            if (nums == null || nums.Length == 0)
            {
                return -1;
            }
            int left = 0;
            int right = nums.Length - 1;
            int mid;
            while(left <= right)
            {
                mid  = left + (right - left) / 2;
                if(nums[mid] == target)
                {
                    return mid;
                }
                if(nums[left] <= nums[mid])
                {
                    if(target >= nums[left] && target < nums[mid])
                    {
                        right = mid - 1;
                    }else
                    {
                        left = mid + 1;
                    }
                }else{
                    if(target > nums[mid] && target <= nums[right])
                    {
                        left = mid + 1;
                    }else{
                        right = mid - 1;
                    }
                }
            }

            return -1;
        }
}

Leetcode 50(不会)

题目描述:
实现pow(x,n)

解法①:

public double MyPow(double x, int n) {
if (n == 0) { return 1; }
        if (n == 1) { return x; }
        if (n == -1) { return 1 / x; }
        double half = myPow(x, n / 2);
        double rest = myPow(x, n % 2);
        return rest * half * half;
}
时间:超越8%。要去看别人的代码!

我的解法:

public double MyPow(double x, int n) {
if(n==0||n==-0) return 1;
if(x==0||x== -0) return 0;
if(n==1) return n;
if(n== -1) return 1/n; 
double result;
int times;
int count=1;
result=x>0 ? x:-x;
if(n==int.MinValue)
{
  times=int.MaxValue;
  result=Powtest(result,times,ref count);
result=x*result;
return 1/result;
}
times=n>0 ? n:-n;//times 要取正数
result=Powtest( result,times,ref count);
if(x>0&&n>0) return  result;
 else if(x>0&&n<0) return  1/result;
else if(x<0&& n%2==0) return result;
else return -result;
    }

static double Powtest(double x,int a,ref int count)
{
if(count ==0)
return 1;
while(count<=a)
{
x*=x;
count*=2;
}
return x*Powtest(x,a-count,ref count);
//这里会提示超出界限。stack overflow exception
}

最快方法:

public class Solution {
    public double MyPow(double x, int n) {
        long N = n;
        if(n < 0){
            N = -1*N;
            x = 1/x;
        }
        double ans = 1;
        double powBase = x;
        while(N!= 0){              
              if(N%2 != 0){
                  ans  = ans * powBase;
              }
              powBase*=powBase;
              N/=2;
        }
        return ans;
    }
}

Leetcode 167(继续找方法)

题目描述:
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

我的解法:
思想:
①将数组分为两部分。左部分小于target/2.右部分大于target/2.
②从左数组遍历,在右数组查找是否存在一个数target-nums【i】。若存在,则返回序列,佛则返回-1。

出现的问题:①最开始查找halfindex代码有问题。

public class Solution {
public  int halfidex;
public int left, right;
    public int[] TwoSum(int[] numbers, int target) {

 int[] index = new int[2];
            if (numbers.Length == 2)
            { 
                index[0] = 1;
                 index[1] = 2; 
                 return index; 
            }
 left = 0;  
 right = numbers.Length - 1;
            halfidex = Findeindex( target / 2,ref numbers);
if(numbers[halfidex-1]==(target/2))
{
if(numbers[halfidex-1]+numbers[halfidex]==target)
{
index[0]=halfidex;index[1]=(halfidex+1);
return index;
}
else if( numbers[halfidex-2]+numbers[halfidex-1]==target)
{
    index[0]=(halfidex-1);index[1]=halfidex;
    return index;
}

}

           for (int i = 0; i < halfidex; i++)
            {
                index[1] = Select(target - numbers[i],ref numbers);
                if (index[1] != -1)
                {
                    index[0] = i + 1;
                    index[1] += 1;
                    break;
                }
            }
       
            return index;
    }

 int Findeindex(int target,ref int[] numbers)
        {
            int mid = 0;         
            while (left <= right)
            {
                mid = (left + right) / 2;
                if (numbers[mid] == target)
                    return mid+1;
                else if (numbers[mid] < target)
                    left = mid + 1;
                else right = mid - 1;
            }
           
            return left;
        }
 int Select(int target,ref int[] numbers)
        {
            int mid = 0;
            left = halfidex;
            right = numbers.Length - 1;
            while (left <= right)
            {
                mid = (left + right) / 2;
                if (numbers[mid] == target)
                    return mid;
                else if (numbers[mid] > target)
                    right = mid - 1;
                else left = mid + 1;
            }
            return -1;
        }
}

解法①:

 int Findeindex(int target, ref int[] numbers)
 //这个时候返回的是左部最大值,而不是右部的最小
{
 int mid = 0;
 Console.Write("half of target is{0}\n", target);
 while (left <right)
 //改为left<=right 则变为返回右部最小值
 {
mid = (left + right) / 2;
if (numbers[mid] == target)//这个时候又要分情况。不如说1,2,3,4,5,6.查找8. 4=8/2,这个时候恰好是右部最小值。但如果
 return mid;                    
  //出现两个4,则很可能定位到第一个4,这样左部就绝对找不到正确答案,所以对于出现两个相同的数字
 else if (numbers[mid] < target)
 //还要额外的判断。
left = mid + 1;
 else right = mid - 1;
}
 Console.Write(" left is {0}\n", left);
 return left;
  }

最快方法:

public class Solution {
    public int[] TwoSum(int[] numbers, int target) {
        int i = 0;
            int j = 0;
            int k = 1;
            int tmp;
            int[] num = new int[2] { 0, 0 };
            for (i = 0; i < numbers.Length - 1; i++)
            {
                tmp = numbers[j] + numbers[numbers.Length - k];
                if (tmp > target)
                {
                    k++;
                    continue;
                }
                else if (tmp < target)
                {
                    j++;
                    continue;
                }
                else
                {
                    num[0] = j + 1;
                    num[1] = numbers.Length - k + 1;
                    return num;
                }
            }
            num[0] = -1;
            return num;
    }
}

Leetcode 287(非自己写)

题目描述:
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

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

算法思想:
①重复的数的特点,遍历整个数组的时候,小于等于它的数等于自身加一
②整个数组就可以分为重复数之前 重复数之后两个数组,则问题就转变为求重复数之后的第一个数。
③left=1,right=n。先去中间值,如果中间值的小于它的个数比它大,说明中间值在重复数之后的数组中,则right=mid,不断逼近
④while跳出的条件是left=right,因为这个时候不用考虑left自身的情况

public class Solution {
    public int FindDuplicate(int[] nums) {

int left=1;
int right=nums.Length-1;
int counts;
int mid;
while(left<right)
{
    counts=0;
   mid=(left+right)/2;
   foreach(int i in nums)
   if(i<=mid) counts++;
   if(counts>mid) right=mid;
   else left=mid+1;


}
return right;


    }
}

最快方法:

public class Solution {
   public int FindDuplicate(int[] nums) {
        int left = 1;
        int right = nums.Length - 1;
        while (left < right) {
            int mid = left+(right-left)/2;
            
            int cnt = 0;
            foreach (int num in nums) {
                if (num <= mid) {
                    cnt += 1;
                }
            }

            // 根据抽屉原理,小于等于 4 的个数如果严格大于 4 个
            // 此时重复元素一定出现在 [1, 4] 区间里

            if (cnt > mid) {
                // 重复元素位于区间 [left, mid]
                right = mid;
            } else {
                // if 分析正确了以后,else 搜索的区间就是 if 的反面
                // [mid + 1, right]
                left = mid + 1;
            }
        }
        return left;
    }
}

Leetcode 209

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

示例:
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
进阶:
如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。

我的解法:两个指针,一个头指针,一个尾指针,从头指针开始往后一直找数组相加,直到和大于等于S,计算长度。
代码:

public class Solution {
    public int MinSubArrayLen(int s, int[] nums) {
int minsize=int.MaxValue;
int cums=0;
int length=nums.Length;
for(int i=0;i<length;i++)
{
   cums=0;
   for(int j=i;j<length;j++)
   {
     cums+=nums[j];
     if(cums>=s)
   {
        if(minsize>(j-i+1))  minsize=(j-i+1);
    break;
   }
   }
}
return minsize==int.MaxValue? 0:minsize;
    }
}

分析:内存使用少,但是时间很长.
解法二:双指针法
算法思想:
用双指针left和right表示一个窗口
1.right向右移增大窗口,直到窗口内的数字和大于等于S,进行到第二步
2.记录此时的长度,left向右移动,开始减少长度,每减少一次,就更新最小长度。直到窗口内的数字和小于S,回到第一步
技巧分析:
与暴力破解法相比,省略了重复的步骤,因为要求的是连续的数组,下一个数组与上一个数组有重复的数字,而暴力破解则每次都要从头来过。
代码:

public class Solution {
    public int MinSubArrayLen(int s, int[] nums) {
int left=0,right=0,cnum=0;
int minsize=int.MaxValue;
while(right<nums.Length)//不能是length-1
{
cnum+=nums[right];
while(cnum>=s)
{
if(minsize>(right-left+1)) minsize=(right-left+1);
cnum-=nums[left];
left+=1;
}
right++;
}
return minsize==int.MaxValue? 0:minsize;
    }
}

解法③:二分查找算法
算法原理:二分查找算法针对的是有序数组,在本题中,首先要构造一个有序数组。最小长度的值是从【1,2。。。N】之中选择的,而这个数组恰好是升序数组,所以可以利用二分法找出这个数组中mid长度的值的数组和的最大值,如果大于等于S,则左移,否则右移。
代码:

public class Solution {
    public int MinSubArrayLen(int s, int[] nums) {
        if(nums.Length==0) return 0;
int mid; int left=1,right=nums.Length;
while(left<right)
{
mid=(left+right)/2;
if(GetMax(mid,nums)>=s) right=mid;
else if (GetMax(mid,nums)<s) left=mid+1;
}
if(GetMax(left,nums)<s) return 0;//针对情况 S=3,nums=【1,1】
 else return left;
    }

public int GetMax(int index,int []nums)
{
int max=0;
int left=0,right=left+index-1,cnums=0;
for(int i=left;i<=right;i++) 
cnums+=nums[i];
max=cnums;
while(right<nums.Length-1)
{
right++;
left++;
cnums+=nums[right];cnums-=nums[left-1];
max=max<cnums? cnums:max;
}
return max;

}


}

作者改良版:

public class Solution {
    public int MinSubArrayLen(int s, int[] nums) {
        if(nums.Length==0) return 0;
int mid; int left=1,right=nums.Length; int min=-1;
while(left<=right)
{
mid=(left+right)/2;
if(GetMax(mid,nums)>=s) {right=mid-1; min=mid;}
else if (GetMax(mid,nums)<s) { left=mid+1;}
}
return min==-1? 0:min;

    }
public int GetMax(int index,int []nums)
{
int max=0;
int left=0,right=left+index-1,cnums=0;
for(int i=left;i<=right;i++) 
cnums+=nums[i];
max=cnums;
while(right<nums.Length-1)
{
right++;
left++;
cnums+=nums[right];cnums-=nums[left-1];
max=max<cnums? cnums:max;
}
return max;

}


}

最快的方法:双指针法

public class Solution {
    public int MinSubArrayLen(int s, int[] nums) {
        if (nums == null || nums.Length == 0) {
            return 0;
        }

        int result = int.MaxValue;
        int left = 0;
        int sum = 0;
        for(int i = 0; i < nums.Length; i++) {
            sum += nums[i];
            while(sum >= s) {
                result = Math.Min(result, i - left + 1);
                sum -= nums[left++];
            }
        }

        return result == int.MaxValue ? 0 : result;
    }
}

你可能感兴趣的:(数据结构)