🌻个人主页:路飞雪吖~-CSDN博客
🌠专栏:刷题总结
目录
2.题目:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
3.题目:35. 搜索插入位置 - 力扣(LeetCode)
4.题目:69. x 的平方根 - 力扣(LeetCode)
5.题目:153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)
6.题目:LCR 173. 点名 - 力扣(LeetCode)
3.题目:724. 寻找数组的中心下标 - 力扣(LeetCode)
4.题目:238. 除自身以外数组的乘积 - 力扣(LeetCode)
5.题目:560. 和为 K 的子数组 - 力扣(LeetCode)
6.题目:974. 和可被 K 整除的子数组 - 力扣(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;
}
};