leetcode-二分查找-C

文章目录

  • 序号69
  • 序号278
  • 序号367
  • 序号374
  • 序号441
  • 序号704
  • 序号744
  • 序号852
  • 面试08.03
  • 面试10.05
  • 面试11
  • 面试53 - I
  • 面试题53 - II

序号69

题目:实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

解析

  1. 牛顿法
  2. 二分法
    在大于 2 时,整数平方根一定小于 x/2,每次判断 mid*mid == x
    mid = left + (right - left)/2

采用牛顿法

int mySqrt(int x){
    if (x < 2)
        return x;

    double x0 = x;
    double x1 = 0.5 * (x0 + x/x0);

    while (fabs(x0 - x1) >= 1)
    {
        x0 = x1;
        x1 = 0.5 * (x0 + x/x0);
    }

    return (int)x1;
}

序号278

题目:你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

解析:有序排列

  1. 二分查找
    判断 mid 是否出错,如果出错可能是第一个,也可能不是第一个
    需要二分查找完才知道,所以 right 保持不动,继续查找
    mid = left + (right - left)/2;这样写不用担心,加法溢出
// Forward declaration of isBadVersion API.
bool isBadVersion(int version);

int firstBadVersion(int n) {
    int left = 1;
    int right = n;
    int mid;

    while (left < right)
    {
        mid = left + (right - left)/2;
        if (isBadVersion(mid))
            right = mid;
        else
            left = mid + 1;
    }
    return left;
}

注:类似的有求最小的,right = mid

序号367

题目:给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False。
说明:不要使用任何内置的库函数,如 sqrt。

解析:平方根不大于 num/2

  1. 牛顿法
    x = (x + num/x) / 2;关键的一步迭代,逐渐逼近平方根
bool isPerfectSquare(int num){
    if (num == 1)
        return true;
    
    long long x = num / 2;
    while (x * x > num)
    {
        x = (x + num/x) / 2;
    }
    return (x * x == num);
}
  1. 二分法
bool isPerfectSquare(int num){
    if (num == 1)
        return true;
    int left = 2;
    int right = num/2;
    long long mid;

    while (left <= right)
    {
        mid = left + (right - left)/2;
        if (mid * mid == num)
            return true;
        else if (mid * mid < num)
            left = mid + 1;
        else 
            right = mid - 1;
    }
    return false;
}

序号374

题目:我们正在玩一个猜数字游戏。 游戏规则如下:
我从 1 到 n 选择一个数字。 你需要猜我选择了哪个数字。
每次你猜错了,我会告诉你这个数字是大了还是小了。
你调用一个预先定义好的接口 guess(int num),它会返回 3 个可能的结果(-1,1 或 0):

解析

  1. 二分查找
int guess(int num);
int guessNumber(int n) 
{
    int left = 1;
    int right = n;
    int mid;

    while (left < right)  //必然可以查找到
    {
        mid = left + (right - left) / 2;
        if (guess(mid) == 0)
            return mid;
        else if (guess(mid) == 1)
            left = mid + 1;
        else
            right = mid - 1;
    }
    return left;
}

序号441

题目:你总共有 n 枚硬币,你需要将它们摆成一个阶梯形状,第 k 行就必须正好有 k 枚硬币。
给定一个数字 n,找出可形成完整阶梯行的总行数。
n 是一个非负整数,并且在32位有符号整型的范围内。

解析:运用求和公式 s u m = ( 1 + k ) ⋅ k / 2 sum = (1+ k) \cdot k / 2 sum=(1+k)k/2查找符合条件的n

  1. 二分法
    找小于 n 时,left 会加 1 超过,这时应判断 left 和 right 相等的情况,然后 right 减一回到不超过 n
  2. 运用公式 k = s q r t ( 8 ⋅ s u m + 1 ) / 2 − 0.5 k = sqrt(8\cdot sum + 1)/2 - 0.5 k=sqrt(8sum+1)/20.5
int arrangeCoins(int n){
    if (n < 1)
        return 0;
    int left = 1;
    int right = n;
    long long mid;
    long long temp;

    while (left <= right)  //相等时可能已经超过
    {
        mid = left + (right - left)/2;
        temp = (1 + mid) * mid / 2;
        if (temp == n)
            return mid;
        else if (temp < n)
            left = mid + 1;
        else
            right = mid - 1;
    }
    return right;
}

序号704

题目:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
说明:
你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。

解析:排好序的查找

  1. 二分法
    因为查找的不一定在数组内,所以要遍历完所有元素
int search(int* nums, int numsSize, int target){
    int left = 0;
    int right = numsSize - 1;
    int mid, temp;

    while (left <= right)  //不一定能查找到,遍历完
    {
        mid = left + (right - left)/2;
        temp = nums[mid];
        if (temp == target)
            return mid;
        else if (temp < target)
            left = mid + 1;
        else
            right = mid - 1;
    }
    return -1;
}

序号744

题目:给定一个只包含小写字母的有序数组letters 和一个目标字母 target,寻找有序数组里面比目标字母大的最小字母。
数组里字母的顺序是循环的。举个例子,如果目标字母target = ‘z’ 并且有序数组为 letters = [‘a’, ‘b’],则答案返回 ‘a’。

解析:有序查找

  1. 二分法
    因为找符合条件的最小,所以遍历完,且right = mid
  2. 散列查找
    把数组记录一遍,找到符合条件的
char nextGreatestLetter(char* letters, int lettersSize, char target){
    int left = 0;
    int right = lettersSize;  //这个地方可写成lettersSize - 1
    int mid;
    char temp;

    while (left < right)
    {
        mid = left + (right - left)/2;
        temp = letters[mid];
        if (temp <= target)
            left = mid + 1;
        else
            right = mid;
    }
    //与上面的呼应,这里可改成:再判断一次letters[right]是否大于target,否则输出letters[0]
    return letters[left % lettersSize];  
}

序号852

题目:我们把符合下列属性的数组 A 称作山脉:
A.length >= 3
存在 0 < i < A.length - 1 使得A[0] < A[1] < … A[i-1] < A[i] > A[i+1] > … > A[A.length - 1]
给定一个确定为山脉的数组,返回任何满足 A[0] < A[1] < … A[i-1] < A[i] > A[i+1] > … > A[A.length - 1] 的 i 的值。

解析:找最大值

  1. 二分法
    一部分递增、一部分递减,比较相邻元素
    保留大值,right = mid
  2. 模拟
int peakIndexInMountainArray(int* A, int ASize){
    int left = 0;
    int right = ASize - 1;
    int mid;

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

面试08.03

题目:魔术索引。 在数组A[0…n-1]中,有所谓的魔术索引,满足条件A[i] = i。给定一个有序整数数组,编写一种方法找出魔术索引,若有的话,在数组A中找出一个魔术索引,如果没有,则返回-1。若有多个魔术索引,返回索引值最小的一个。

解析:找到第一个满足条件的

  1. 模拟
    线性查找
  2. 递归二分查找
    因为mid != nums[mid]时,左右两边都有可能是最终结果,所以递归二分
int DivideFind(int* arr, int left, int right)
{
    if (left == right)  //边界返回
    {
        if (left == arr[left])
            return left;  //符合
        else
            return -1;
    }
    int mid = left + (right - left)/2;
    if (mid == arr[mid])
    {
        int ans = DivideFind(arr, left, mid);
        return (ans == -1) ? mid : ans;  //递归查询的是否符合
    }
    else
    {
        int ans = DivideFind(arr, left, mid);  //
        if (ans == -1)  //左半部分不符合,再查询右半部分
            return DivideFind(arr, mid + 1, right);
        else
            return ans;  
    }
}

int findMagicIndex(int* nums, int numsSize){
    return DivideFind(nums, 0, numsSize-1);
}

面试10.05

题目:稀疏数组搜索。有个排好序的字符串数组,其中散布着一些空字符串,编写一种方法,找出给定字符串的位置。

解析

  1. 模拟
    顺序查找符合条件的
  2. 二分查找
    特别处理空字符串,一直向右找
    字符串比较用strcmp()
int findString(char** words, int wordsSize, char* s){
    int left = 0;
    int right = wordsSize - 1;
    int mid, temp;
    char* test;

    while (left <= right)
    {
        mid = left + (right - left) / 2;
        test = words[mid];
        if (strcmp(test, "") == 0)  //与空字符比较,不要直接用== 
        {
            while (strcmp(test, "") == 0 && mid < right-1)
            {
                mid++;
                test = words[mid];
            }
            if (strcmp(test, "") == 0)
            {
                right--;
                continue;
            }
        }
        temp = strcmp(test, s);
        if (temp == 0)
            return mid;
        else if (temp > 0)
            right = mid - 1;
        else
            left = mid + 1;
    }
    return -1;
}

面试11

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

解析:准确说是非递减的数组,左半部分有序、右半部分有序

  1. 二分查找
    用 right 记录最小值坐标
    mid 和 right 相同时,缩小 right 范围
int minArray(int* numbers, int numbersSize){
    int left = 0;
    int right = numbersSize - 1;
    int mid, temp;

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

面试53 - I

题目:统计一个数字在排序数组中出现的次数。

解析

  1. 线性查找
  2. 二分查找第一次出现的位置,然后统计
int search(int* nums, int numsSize, int target){
    int left = 0;
    int right = numsSize - 1;
    int mid, temp;
    int cnt = 0;

    while (left < right)
    {
        mid = left + (right - left)/2;
        temp = nums[mid];
        if (temp == target)
            right = mid;
        else if (temp < target)
            left = mid + 1;
        else
            right = mid - 1;
    }
    while (left < numsSize && nums[left] == target)
    {
        cnt++;
        left++;
    }
    return cnt;
}

查找第一次出现和最后一次出现的位置

int FindFirst(int* arr, int len, int target)
{
    int left = 0;
    int right = len - 1;
    int mid, temp;

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

int FindLast(int* arr, int len, int target)
{
    int left = 0;
    int right = len - 1;
    int mid, temp;

    while (left < right)
    {
        mid = left + (right - left + 1)/2;  //不让mid等于left
        temp = arr[mid];
        if (temp == target)
            left = mid;
        else if (temp < target)
            left = mid + 1;
        else
            right = mid - 1;
    }
    return left;
}

int search(int* nums, int numsSize, int target){
    if (numsSize == 0)
        return 0;
    int firstIndex, lastIndex;

    firstIndex = FindFirst(nums, numsSize, target);
    if (firstIndex == -1)
        return 0;
    lastIndex = FindLast(nums, numsSize, target);
    return lastIndex - firstIndex + 1;
}

面试题53 - II

题目:一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

解析

  1. 线性探测
  2. 二分法
int missingNumber(int* nums, int numsSize){
    int left = 0;
    int right = numsSize;
    int mid;

    while (left < right)
    {
        mid = left + (right - left)/2;
        if (nums[mid] == mid)  //判断条件
            left = mid + 1;
        else
            right = mid;
    }
    return left;
}

你可能感兴趣的:(C/C++)