[刷题总结] 二分查找 && 前缀和

🌻个人主页:路飞雪吖~-CSDN博客

       🌠专栏:刷题总结


目录

一、二分查找

1.题目:704. 二分查找 - 力扣(LeetCode) 

2.题目:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

3.题目:35. 搜索插入位置 - 力扣(LeetCode)

4.题目:69. x 的平方根 - 力扣(LeetCode)

5.题目:153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

6.题目:LCR 173. 点名 - 力扣(LeetCode)

二、前缀和

1.题目:【模板】前缀和_牛客题霸_牛客网

2.题目:【模板】二维前缀和_牛客题霸_牛客网

3.题目:724. 寻找数组的中心下标 - 力扣(LeetCode)

4.题目:238. 除自身以外数组的乘积 - 力扣(LeetCode)

5.题目:560. 和为 K 的子数组 - 力扣(LeetCode)

6.题目:974. 和可被 K 整除的子数组 - 力扣(LeetCode)

7.题目:525. 连续数组 - 力扣(LeetCode)

8.题目:1314. 矩阵区域和 - 力扣(LeetCode)


一、二分查找

在一个数组中(不一定有序),随便找一个点(不一定是在中间,看规律),这个点与目标值(target)作比较后,划分出来的两个区域(二段性),其中根据规律,我们可以有选择性的舍去一个区域,在另一个区域去进行查找 ---- 二分查找(“二段性”)。

<1>朴素的二分查找

• 核心

left <= right ① x < t  ---> left = mid + 1  ---> [left, right]
② x > t ---> right = mid - 1  ---> [left, right]
③ x == t  ---> 返回结果

 • 细节 

mid 的取值

① (left + right) / 2

   (left + right + 1)/ 2

② left + (right - left) / 2; // 防止溢出

    left + (right - left + 1) / 2;

+1 vs 不加1

① 奇数不影响,

a[5], left = 0, right = 4(下标);

mid = (4 + 0) / 2 = 2;

mid = (4 + 0 + 1) / 2 = 2;

② 偶数有影响(在中间有两个数)

a[4], left = 0, right = 3;

mid = (3 + 0) / 2 = 1; // 在中间第一个数

mid  = (3 + 0 + 1) / 2 = 2; // 在中间的第二个数

<2> 查找左边界的二分模板(继续往下看)

<3> 查找右边界的二分模板(继续往下看)

1.题目:704. 二分查找 - 力扣(LeetCode) 

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

2.题目:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

<2> 查找左边界的二分模板

✨循环条件  

        • left <= right  (不可取,会导致死循环)

        • left < right (可取)

✨求中点的操作

✨区间左端点二分模板:

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

<3> 查找右边界的二分模板

✨循环条件  同理使用,left < right

✨求中点的操作 

✨区间右端点二分模板:

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

🌠记忆总结 当if...else... 出现 -1 时, mid 就 +1 分类讨论代码,判断是属于那一边。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.size() == 0) return {-1,-1};
        
        // 1.二分区间左端点
        int begin = 0;
        int left = 0, right = nums.size() - 1;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target) left = mid + 1;
            else right = mid;
        }
        if(nums[left] != target) return {-1, -1};// 判断是否有结果
        begin = left;

        // 2.二分右端点
        right = nums.size() - 1;
        while(left < right)
        {
            int mid = left + (right - left + 1) / 2;
            if(nums[mid] <= target) left = mid;
            else right = mid - 1;
        }
        return {begin, right};
    }
};

3.题目:35. 搜索插入位置 - 力扣(LeetCode)

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target) left = mid + 1;
            else right = mid;
        }
        if(nums[left] < target) return left + 1;// 在末尾的位置
        return left;
    }
};

4.题目:69. x 的平方根 - 力扣(LeetCode)

class Solution {
public:
    int mySqrt(int x) {
        if(x < 1) return 0;// 处理边界情况
        int left = 1, right = x;
        while(left < right)
        {
            long long mid = left + (right - left + 1) / 2;
            if(mid * mid <= x) left = mid;// 有可能是刚好等于 
            else right = mid - 1;
        }
        return left;
    }
};

5.题目:153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

class Solution {
public:
    int findMin(vector<int>& nums) {
        int n = nums.size();
        int left = 0, right = n - 1;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[n - 1]) left = mid + 1;// 数组最后一个数作为参考点
            else right = mid;
        }
        return nums[left];
    }
};

6.题目:LCR 173. 点名 - 力扣(LeetCode)

class Solution {
public:
    int takeAttendance(vector<int>& records) {
        int left = 0, right = records.size() - 1;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(records[mid] == mid) left = mid + 1;
            else right = mid;
        }
        return records[left] == left ? left + 1 : left;//  +1 缺失的是最后一个数
    }
};
class Solution {
public:
    int takeAttendance(vector<int>& records) {
        // 1.哈希表
        int arr[10001] = { 0 };
        for(auto e : records) arr[e]++;
        for(int i = 0; i < 10001; i++) 
            if(arr[i] == 0) return i;
        return -1;

        // 2.直接遍历找结果

        int n = records.size();
        for(int i = 0; i < n; i++)
        {
            if(records[i] != i) return i;
        }
        return n;

        // 3.位运算

        int n = records.size() + 1;
        int expected = 0;
        for(int i = 0; i < n; i++)
            expected ^= i;
        
        int actual = 0;
        for(int num : records)
            actual ^= num;
        
        return expected ^ actual;


        // 4.数学(高斯求和公式)
        int n = records.size();
        int sum1 = (n * (n + 1)) / 2;//  0 —— n-1 的所有数的和
        int sum2 = 0;
        for(int num : records) sum2 += num;
        return sum1 - sum2;      


    }
};

二、前缀和

<1> 预处理 前缀和 数组/矩阵

<2> 使用前缀和 数组/矩阵

1.题目:【模板】前缀和_牛客题霸_牛客网

前缀和 ---> 快速求出数组中某一个连续区间地和

1> 预处理出来一个前缀和数组: dp[i] = dp[i - 1] + arr[i];

2> 使用前缀和数组   dp[r] - dp[l - 1]

#include <iostream>
#include<vector>
using namespace std;

int main() 
{
    // 1.读入数据
    int n, q;
   cin >> n >> q;
   vector<int> arr(n+1);
   for(int i = 1; i <= n; i++) cin >> arr[i];// 下标从1开始

   // 2. 预处理出来一个前缀和数组
   vector<long long> dp(n+1);// 防止溢出
   for(int i = 1; i <= n; i++) dp[i] = dp[i - 1] + arr[i];// 下标从1开始

   // 3. 使用前缀和数组
   int l = 0, r = 0;
   while(q--)
   {
    cin >> l >> r;
    cout << dp[r] - dp[l - 1] << endl;
   } 
}

2.题目:【模板】二维前缀和_牛客题霸_牛客网

#include <iostream>
#include <vector>
using namespace std;

int main() 
{
   // 1. 读入数据
   int n = 0, m = 0, q = 0;
   cin >> n >> m >> q;
   vector<vector<int>> arr(n+1, vector<int>(m+1));
   for(int i = 1; i <= n; i++)
    for(int j = 1; j <= m; j++)
        cin >> arr[i][j];

   // 2. 预处理出一个前缀和矩阵
   vector<vector<long long>> dp(n+1, vector<long long>(m+1));
   for(int i = 1; i <= n; i++)
    for(int j = 1; j <= m; j++)
        dp[i][j] = dp[i-1][j] + dp[i][j-1] + arr[i][j] - dp[i-1][j-1];

   // 3.使用前缀和矩阵
   int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
   while(q--)
   {
     cin >> x1 >> y1 >> x2 >> y2;
     cout << dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1] << endl;
   }
   return 0;
}

3.题目:724. 寻找数组的中心下标 - 力扣(LeetCode)

class Solution {
public:
    int pivotIndex(vector<int>& nums) {
        // 1.  前缀和数组
        int n = nums.size();
        vector<int> f(n), g(n);

        for(int i = 1; i < n; i++)
            f[i] = f[i - 1] + nums[i - 1];
        for(int i = n - 2; i >= 0; i--)
            g[i] = g[i + 1] + nums[i + 1];

        for(int i = 0; i < n; i++)
            if(f[i] == g[i]) return i;

        return -1;
    }
};

4.题目:238. 除自身以外数组的乘积 - 力扣(LeetCode)

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n), g(n), answer(n);

        // 1. 预处理前缀积 and 后缀积
        f[0] = g[n - 1] = 1;// 处理细节问题
        for(int i = 1; i < n; i++)
            f[i] = f[i - 1] * nums[i - 1];
        for(int i = n - 2; i >= 0; i--)
            g[i] = g[i + 1] * nums[i + 1];

        // 2.使用
        for(int i = 0; i < n; i++)
            answer[i] = f[i] * g[i];
        
        return answer; 
    }
};

5.题目:560. 和为 K 的子数组 - 力扣(LeetCode)

不能用双指针(滑动窗口)来进行优化,因为此数组没有单调性(含有 0 和 负数)。

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> hash;// int 前缀和, int 次数
        hash[0] = 1;
        
        int sum =0, ret= 0;
        for(auto x : nums)
        {
            sum += x;// 前缀和
            if(hash.count(sum - k))// hash.count()检查hash表中是否存在sum-k这个值 判断是否存在sum-k的前缀和的值(第一个int)
                ret += hash[sum - k];// 统计存在sum-k值的个数(第二个int)
            hash[sum]++;
        }
        return ret;
    }
};

6.题目:974. 和可被 K 整除的子数组 - 力扣(LeetCode)

🌠小贴士:

(1) 同余定理

        (a - b) / p = k 余 0   ---->  a % p = b % p

(2) [负数 % 整数] 的结果以及修正 --->  a % p + p  ---正负统一---> (a % p + p) % p

class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        unordered_map<int, int> hash;
        hash[0 % k] = 1;// 0 这个数的余数

        int sum = 0, ret = 0;
        for(auto x : nums)
        {
            sum += x;// 算出当前位置的前缀和
            int r = (sum % k + k) % k;// 修正后的余数
            if(hash.count(r)) ret += hash[r];// 统计结果
            hash[r]++;
        }
        return ret;
    }
};

7.题目:525. 连续数组 - 力扣(LeetCode)

转化:和为 k 的子数组 --> 和为 0 的子数组

• 将所有的 0 修改成 -1;

• 在数组中,找出最长的子数组,使子数组中所有元素的和为0;

class Solution {
public:
    int findMaxLength(vector<int>& nums) {
        unordered_map<int, int> hash;
        hash[0] = -1;// 默认有一个前缀和为0的情况

        int sum = 0, ret = 0;
        for(int i = 0; i < nums.size(); i++)
        {
            sum += nums[i] == 0 ? -1 : 1;// 把 0 改成 -1,计算当前位置的前缀和
            if(hash.count(sum))// 存在更新长度(只保留前面的那一对<sum, i>)
                ret = max(ret, i - hash[sum]);
            else // 不存在,丢进hash表
                hash[sum] = i;
        }
        return ret;
    }
};

8.题目:1314. 矩阵区域和 - 力扣(LeetCode)

class Solution 
{
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) 
    {
        int m = mat.size(), n = mat[0].size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        // 1. 前缀和矩阵
        for(int i = 1; i <= m; i++)
            for(int j =1; j <= n; j++)
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + mat[i - 1][j - 1];

        // 2.使用
        vector<vector<int>> ret(m, vector<int>(n));
        for(int i = 0; i < m ; i++)
            for(int j = 0; j < n; j++)
            {
                int x1 = max(0, i - k) + 1, y1 = max(0, j - k) + 1;
                int x2 = min(m - 1, i + k) + 1, y2 = min(n - 1, j + k) + 1;
                ret[i][j] = dp[x2][y2] - dp[x1 - 1][y2] - dp[x2][y1 - 1] + dp[x1 - 1][y1 - 1];
            }
            return ret;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值