代码随想录笔记|C++数据结构与算法学习笔记-数组(一)|二分查找

文章目录

C++数据结构与算法学习笔记-数组(一)

C++数据结构与算法学习笔记-数组(一),内容仅包含二分查找课程来自代码随想录。

参考了大佬30w链接:
我佬

二分查找

704、二分查找

基本思路

对于二分查找使用的前提:

  • 数组中无重复元素;
  • 数组是有序数组。

这种方法通过将问题范围不断对半分割,逐步缩小搜索范围,直至找到目标点。

二分法的易错点

  1. 对于while循环,里面到底是写<还是写≤。

  2. 对于right等于多少的问题

cpp 复制代码
if (nums[middle] > target)
{
  right = middle;
  or
  right = middle - 1;
}

循环不变量

在一个区间进行搜索的时候,一定要关注一个点循环不变量,即在循环的过程中保持不变的性质。

在本题中的不变量就是区间范围,所以我们一定要先确定搜索区间,我们想要写出二分查找的代码就是要先找到到底是哪个区间,是左闭右闭还是左闭右开呢?

只有定出搜索的确切范围,才能确定出大于号小于号的问题和减一还是不减的问题。

左闭右闭写法

第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)

在确定使用区间后,那么我们

  • 因为left = right时仍然是有意义的,此时三值相等,故while循环必须是&le;
  • right = middle - 1;因为由于右闭,所以区间应当更新为[left, middle - 1],如果更新成了[left, middle]的话,搜索区域有了范围外middle找个值
cpp 复制代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
        while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
            if (nums[middle] > target) {
                right = middle - 1; // target 在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,所以[middle + 1, right]
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

左闭右开写法

第二种写法,我们定义 target 是在一个在左闭右开的区间里,也就是[left, right) (这个很重要非常重要)

在确定使用区间后,那么我们

  • 当区间是[left, right), 由于left == right在该区间显然没有意义, while循环必须是<;
  • 由于右闭,所以区间应当更新为[left, middle),如果更新成了[left, middle-1)的话,搜索区域就漏了值。故right = middle;
cpp 复制代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
        while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target 在左区间,在[left, middle)中
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,在[middle + 1, right)中
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

35、搜索插入位置

方法:二分查找

略,同理,唯一需要注意的就是当返回插入位置的时候一定要把情况讨论全,一共有四种情况。

cpp 复制代码
// 分别处理如下四种情况
// 目标值在数组所有元素之前  [0, -1]
// 目标值等于数组中某一个元素  return middle;
// 目标值插入数组中的位置 [left, right],return  right + 1
// 目标值在数组所有元素之后的情况 [left, right], 因为是右闭区间,所以 return right + 1
return right + 1;

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

本题有一个很重要的点,就是在区间中查找区间,所以我们不得不查找目标区间的左右边界。

不过还有一个很重要的点就是该程序的不同情况:

  • 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),包不包括target这个很重,一定要先确认好。

cpp 复制代码
// 二分查找,寻找target的右边界(不包括target)
// 如果rightBorder为没有被赋值(即target在数组范围的左边,例如数组[3,3],target为2),为了处理情况一
int getRightBorder(vector<int>& nums, int target) {
    int left = 0;
    int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
    int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
    while (left <= right) { // 当left==right,区间[left, right]依然有效
        int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
        if (nums[middle] > target) {
            right = middle - 1; // target 在左区间,所以[left, middle - 1]
        } else { // 当nums[middle] == target的时候,更新left,这样才能得到target的右边界
            left = middle + 1;
            rightBorder = left;
        }
    }
    return rightBorder;
}

寻找左边界

cpp 复制代码
// 二分查找,寻找target的左边界leftBorder(不包括target)
// 如果leftBorder没有被赋值(即target在数组范围的右边,例如数组[3,3],target为4),为了处理情况一
int getLeftBorder(vector<int>& nums, int target) {
    int left = 0;
    int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
    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;
}

处理三种情况

  • 其实本题最重要的就是两个点,一个是要记住找边界,另一个就是记住本体会出现几种情况。
  • 代码随想录的答案我附在第二段代码中
cpp 复制代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int leftBorder = searchLeftBorder(nums, target);
        int rightBorder = searchRightBorder(nums, target);
        // 情况一:target 在数组范围的右边或者左边
        if (leftBorder == -1 || rightBorder == -1) return {-1, -1};
        // 情况三:target 在数组范围中,且数组中存在target
        if (rightBorder - leftBorder >= 0) return {leftBorder, rightBorder};
        // 情况二:target 在数组范围中,且数组中不存在target
        return {-1, -1};
    }
private:
    int searchLeftBorder(vector<int>& nums, int target)
    {
        int n = nums.size();
        int l = 0 ; int r = n - 1;
        while (l <= r)
        {
            int mid = l + (r - l) / 2;
            if (nums[mid] < target) l = mid + 1;
            else r = mid - 1;
        }
        return l;
    }
    int searchRightBorder(vector<int>& nums, int target)
    {
       int n = nums.size();
        int l = 0 ; int r = n - 1;
        while (l <= r)
        {
            int mid = l + (r - l) / 2;
            if (nums[mid] > target) r = mid - 1;
            else l = mid + 1;
        }
        return r;
    }
};

代码随想录标准答案:

cpp 复制代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int leftBorder = getLeftBorder(nums, target);
        int rightBorder = getRightBorder(nums, target);
        // 情况一:target 在数组范围的右边或者左边
        if (leftBorder == -2 || rightBorder == -2) return {-1, -1};
        // 情况三:target 在数组范围中,且数组中存在target
        if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
        // 情况二:target 在数组范围中,且数组中不存在target
        return {-1, -1};
    }
private:
     int getRightBorder(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 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(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 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;
    }
};

合并边界查找的方法

cpp 复制代码
class Solution { 
public:
    int binarySearch(vector<int>& nums, int target, bool lower) {
        int left = 0, right = (int)nums.size() - 1, ans = (int)nums.size();
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] > target || (lower && nums[mid] >= target)) {
                right = mid - 1;
                ans = mid;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }

    vector<int> searchRange(vector<int>& nums, int target) {
        int leftIdx = binarySearch(nums, target, true);
        int rightIdx = binarySearch(nums, target, false) - 1;
        if (leftIdx <= rightIdx && rightIdx < nums.size() && nums[leftIdx] == target && nums[rightIdx] == target) {
            return vector<int>{leftIdx, rightIdx};
        } 
        return vector<int>{-1, -1};
    }
};

69、x的平方根

方法一:袖珍计算器法

「袖珍计算器算法」是一种用指数函数exp和对数函数ln代替平方根函数的方法。我们通过有限的可以使用的数学函数,得到我们想要计算的结果。

cpp 复制代码
class Solution {
public:
    int mySqrt(int x) {
        if (x == 0) {
            return 0;
        }
        int ans = exp(0.5 * log(x));
        return ((long long)(ans + 1) * (ans + 1) <= x ? ans + 1 : ans);
    }
};

方法二:二分查找

本题中二分查找的条件为 x x x平方根的整数部分 a n s ans ans是满足 k 2 ≤ x k^2\leq x k2≤x的最大 k k k值 ,因此我们可以对 k k k进行二分查找。

与之前题目的最大差别是,这个并不是在找一个中间值,这个可以理解为在找一个边界值。

cpp 复制代码
class Solution {
public:
    int mySqrt(int x) {
        int l = 0, r = x, ans = -1;
        while (l <= r) {
            int mid = l + (r - l) / 2;
            if ((long long)mid * mid <= x) {
                ans = mid;
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        return ans;
    }
};

367、有效完全平方数

方法一:使用内置的库函数

根据完全平方数的性质,我们只需要直接判断num的平方根x是否为整数即可。对于C/C++,则可以通过以下规则判断:

  • 若 n \sqrt {n} n 为正整数,则 ⌊ x ⌋ = ( n u m ) 2 = n u m \lfloor x \rfloor = (\sqrt{num})^2 = num ⌊x⌋=(num )2=num,其中 ⌊ x ⌋ \lfloor x \rfloor ⌊x⌋表示x的向下取整。
cpp 复制代码
class Solution {
public:
    bool isPerfectSquare(int num) {
        int x = (int) sqrt(num);
        return x * x == num;
    }
};

方法二:暴力

cpp 复制代码
class Solution {
public:
    bool isPerfectSquare(int num) {
        long x = 1, square = 1;
        while (square <= num) {
            if (square == num) {
                return true;
            }
            ++x;
            square = x * x;
        }
        return false;
    }
};

方法三:二分查找

cpp 复制代码
class Solution {
public:
    bool isPerfectSquare(int num) {
        int left = 0, right = num;
        while (left <= right) {
            int mid = (right - left) / 2 + left;
            long square = (long) mid * mid;
            if (square < num) {
                left = mid + 1;
            } else if (square > num) {
                right = mid - 1;
            } else {
                return true;
            }
        }
        return false;
    }
};
相关推荐
我爱挣钱我也要早睡!42 分钟前
Java 复习笔记
java·开发语言·笔记
知识分享小能手3 小时前
React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)
前端·javascript·vue.js·学习·react.js·vue·react
汇能感知5 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun6 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
茯苓gao6 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾6 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
利刃大大7 小时前
【高并发内存池】五、页缓存的设计
c++·缓存·项目·内存池
DKPT7 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
aaaweiaaaaaa7 小时前
HTML和CSS学习
前端·css·学习·html
ST.J7 小时前
前端笔记2025
前端·javascript·css·vue.js·笔记