目录
我们进行整数二分,经常需要确定边界问题,而很多时候会出错,超时。
因此,我们二分时,需要确定一段范围的数,他区间的开闭情况。
下面代码所示例子为leetcode上下面链接的题目
一般有以下几种情况:
1.左闭右开
此时我们会发现,因为右边是开,所以每次判断时,右边坐标便无需减一。而左区间因为闭合,所以需要加一。因为闭表示能取到这种情况,但是这种情况本身就是小于mid的,所以加一。
同时while(判断条件) 中的判断条件是 < 而非 <=。 这是因为当left == right 时,在左闭右开区间中不成立,为空集。
int search(vector& nums, int target) {
int left = 0; int right = nums.size() ;
while(left < right)
{
int mid = left + (right - left) / 2;
if(nums[mid] < target) left = mid + 1;
else if(nums[mid] > target) right = mid;
else return mid;
}
return -1;
2.左闭右闭(闭区间)
此时while()中的判断条件变成了left<=right,因为此时left == right 的情况成立,
同时left = mid + 1。right= mid - 1。这是因为取到这种情况的时候,这种情况本身就不成立,因此缩小范围。
int search(vector& nums, int target) {
int left = 0; int 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;
在上述区间中,我们初步了解了区间的闭合问题,然后我们有时候也会面临mid的取值问题,有时候mid = (left + right) / 2;有时候mid = (left + right + 1) / 2; 这其实是向上取整或者向下取整的问题
例如 对 有8 个数的数组而言,3,4都是中间值的下标,left = 3,right = 4. 如果直接相加除2,那mid = (3 + 4) / 2 = 3,余数舍弃。如果mid = (3 + 4 + 1) / 2;mid = 4。因此,中间值 + 1涉及向下还是向上取整。
下面是这种类型的leetcode题目(文章直接给解题代码)
https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/
主函数
vector searchRange(vector& nums, int target) {
if(nums.size() < 1) return {-1, -1};
int leftbound = get_left(nums,target,0,nums.size()-1);
int rightbound = get_right(nums,target,0,nums.size()-1);
vector p;
if(nums[leftbound] != target || nums[rightbound] != target){
leftbound = -1; rightbound = -1;
}
p.push_back(leftbound);p.push_back(rightbound);
return p;
}
1.确定左边界
我们首先做两件事,确定区间闭合情况,判断mid向上取整还是向下取整。
此处使用左闭右开(全闭也可),这里就是套用左闭右开的模板,return返回l 或者r 都可以,
因为最后l == r。
int get_left(vector& nums,int target,int l,int r)
{
while(l < r)
{
int mid = l + (r - l) / 2;
if(nums[mid] < target) l = mid + 1;
else if(nums[mid] >= target) r = mid;
}
return r;
}
2.确定右边界
区间情况为左开右闭合,此时我们右边界向上取整 mid = (r - l + 1) / 2。
套用左开右闭模板,此处return时,l == r。
int get_right(vector& nums,int target,int l,int r)
{
while(l < r)
{
int mid = l + (r - l + 1) / 2;
if(nums[mid] <= target) l = mid;
else if(nums[mid] > target) r = mid - 1;
}
return l;
}
浮点数比整数二分简单的多。
下面用的是Acwing中的算法基础课的浮点数二分的题目
求的是一个数的立方根,例如输入1000.00 输出是10.00。
https://www.acwing.com/problem/content/792/
本题,while中的判断条件只要符合r - l 的差值为特别小的数,使得近似相同,然后反回结果即可
#include
using namespace std;
int main()
{
double x;scanf("%lf",&x);
double l = -10000,r = 10000;
while(r-l>1e-8)
{
double mid = (l+r)/2;
if(mid*mid*mid>=x) r = mid;
else l = mid;
}
printf("%lf",l);
return 0;
}
思路:双指针常规思路就是设定一个快指针,一个慢指针,然后根据题目的某种性质,进行指针的移动,从而达到解题目的。如下:
下面为力扣题目
https://leetcode.cn/problems/remove-element/
int removeElement(vector& nums, int val) {
if(nums.size() == 0) return 0;
int fast = 0; int slow = 0;
while(fast != nums.size())
{
if(nums[fast] != val){
nums[slow++] = nums[fast++];
}
else fast ++;
}
return slow;
下面为Acwing算法基础课中双指针的题目
两个数组目标和
#include
using namespace std;
const int N = 1e6+10;
int a[N],b[N];
int main()
{
int n,m,x;cin>>n>>m>>x;
for(int i = 0; i>a[i];
for(int i = 0; i>b[i];
int i = 0;int j = m-1;
while(i < n && j < m){
if(a[i]+b[j]
下面是力扣的题目链接
本题的滑动窗口就是动态的截取数组中的一部分,然后判断是否满足某种性质。
长度最小的子数组
int minSubArrayLen(int target, vector& nums) {
int i = 0; int j = 0;
int res = 1e9 + 10;
int sum = 0;
for(j = 0; j= target)
{
res = min(res, j - i + 1);
sum -= nums[i++];
}
}
if(res == 1e9 + 10) return 0;
return res;
该类型没有具体算法类型,根据题目类型具体分析,模拟代码运行逻辑从而解题。
下面是经典模拟题目
螺旋矩阵
建立数组,通常用来记录某个数字出现过几次。
题目如下: 有效的字母异位词
bool isAnagram(string s, string t) {
if(s.size() != t.size()) return false;
int hash1[27] = {0}; int hash2[27] = {0};
for(int i = 0; i
该容器通常是key与value有对应关系时使用。
如下面题目:
两数之和
此处对应的是每个数对应一个数组中的下标。
vector twoSum(vector& nums, int target) {
unordered_map res;
vector tem;
for(int i = 0; i:: iterator it = res.find(target - nums[i]);
if(it != res.end()){
return {i, it->second};
}
res.insert(pair(nums[i], i));
}
return {};
}
下方题目链接:
快乐数
此处set集合主要应用于判断是否有重复的数,例如本题,只要有重复数字,表示将会进入死循环。
int get_num(int n)
{
int tem = 0;
while(n)
{
tem += (n % 10) * (n % 10);
n /= 10;
}
return tem;
}
bool isHappy(int n) {
unordered_set set;
while(1)
{
int tem = get_num(n);
if(tem == 1) return true;
if(set.find(tem) != set.end()){
return false;
}
else set.insert(tem);
n = tem;
}
1.&的运算法则是有0为0,全1为1。
2.|的运算法则是有1为1,全0为0。
3.^的运算法则是相同为0,相异为1。
4、-x = ~x + 1;
5.~x = (x所有二进制位置取反)。
eg: 6(2进制) : 110
5(2进制) : 101
6 & 5 = 100(2进制表示)。6 | 5 = 111。6^5 = 011。
以下为代码运行结果:
a & b = 4
a | b = 7
a ^ b = 3
题目链接:
二进制中1的个数
#include
using namespace std;
const int N = 100010;
int arr[N];
int main()
{
int n;scanf("%d",&n);
while(n--)
{
int tem = 0,x;
scanf("%d",&x);
while(x)
{
x-=(x&-x);
tem++;
}
printf("%d ",tem);
}
}
#include
using namespace std;
const int N = 1e5+10;
int main()
{
int n;cin>>n;int x;
for(int i = 0;i>x;
int tem = 0;
while(x){
x=(x&(x-1));
tem++;
}
cout<
废话不多说,直接上模板
下面是对一个乱序数组进行升序排序。
void quick_sort(int arr[], int l, int r)
{
if(l >= r) return;
int mid = l + (r - l) / 2;
int i = l -1; int j = r + 1;
while(i < j)
{
do i++; while(arr[i] < arr[mid]);
do j--; while(arr[j] > arr[mid]);
if(i < j) swap(arr[i], arr[j]);
}
quick_sort(arr, l, j); quick_sort(arr, j + 1, r);
}
直接模板走起
void merge_sort(int* arr,int l,int r)
{
if(l>=r) return;
int mid = l+r>>1;
merge_sort(arr,l,mid);merge_sort(arr,mid+1,r);//此处利用递归将数组变为升序。
int i = l;int j = mid+1;int k = 0;
while(i<=mid&&j<=r)
{
if(arr[i]
此处我们从各位开始相加,利用t来记录相加的值,然后根据t % 10确定当前位置的值。
根据t / 10是否为0 确定下位是否进1。
#include
using namespace std;
#include
#include
vector add(vector& A, vector& B)
{
if(A.size() < B.size()) return add(B, A);//此处确保A表示的是最大数量的数组
vector C;
int t = 0;
for(int i = 0; i>a>>b;
vector A,B;
for(int i = a.size() - 1; i>=0; i--){
A.push_back(a[i] - '0');
}
for(int i = b.size() - 1; i>=0; i--){
B.push_back(b[i] - '0');
}
vector C = add(A, B);
for(int i = C.size() - 1; i >= 0; i--)
{
cout<
首先减法我们首先要确定是大减小还是小减大,正负情况区分开来求解。
随后,同样用t 记录差,然后如果小于零,那么t = 1, 方便下次计算进行减去操作。
#include
using namespace std;
#include
#include
bool cmp(vector& A,vector& B)
{
if(A.size()!=B.size()) return A.size()>B.size();
else{
for(int i = A.size()-1;i>=0;i--){
if(A[i]!=B[i]) return A[i]>B[i];
}
}
return true;
}
vector sub(vector&A,vector&B)
{
vectorC; int t = 0;
for(int i = 0; i 1){
C.pop_back();
}
return C;
}
int main()
{
string a,b;
cin>>a>>b;
vector A,B;
for(int i = a.size()-1;i>=0;i--){
A.push_back(a[i]-'0');
}
for(int i = b.size()-1;i>=0;i--){
B.push_back(b[i]-'0');
}
if(cmp(A,B)){
vector C = sub(A,B);
for(int i = C.size()-1;i>=0;i--){
cout< C = sub(B,A);
for(int i = C.size()-1;i>=0;i--){
cout<
此处的乘法,我们对第一个数据采取同样处理,但是我们将第二个数看作一个整体,即b与A的个位开始乘,同时记录各个乘完的和,然后取个位,for循环判断时候,需要加t非0。因为很可能当i遍历完A,t还没有完全放进vector中。
#include
using namespace std;
#include
#include
vector mul(vector& A,int b)
{
int t = 0;vector C;
for(int i = 0;i1&&C.back()==0){
C.pop_back();
}
return C;
}
int main()
{
string a;int b;
cin>>a>>b;
vectorA;
for(int i = a.size()-1;i>=0;i--){
A.push_back(a[i]-'0');
}
vector C = mul(A,b);
for(int i = C.size()-1;i>=0;i--){
cout<
余数用r来表示,r从高位开始相加,reverse函数是因为在进行操作的时候,我们最高位可能是0,reverse之后0尾部方便去除,打印继续反着打印即可。
#include
using namespace std;
#include
#include
#include
vector div(vector& A,int b,int& r)
{
vector C;
r = 0;
for(int i = A.size() - 1; i >= 0; i--){
r = r*10 + A[i];
C.push_back(r / b);
r %= b;
}
reverse(C.begin(), C.end());
while(C.back() == 0 && C.size() > 1){
C.pop_back();
}
return C;
}
int main()
{
string a;int b;int r;
cin>>a>>b;
vectorA;
for(int i = a.size()-1;i>=0;i--)
{
A.push_back(a[i]-'0');
}
r = 0;
vector C = div(A,b,r);
for(int i = C.size()-1;i>=0;i--){
cout<
a[i] 表示每部分的单个数值,此处核心公式为s[i] = s[i - 1] + a[i]。转换一下就是,s[i] - s[i - 1] = a[i]。s[i]表示前缀和。
#include
using namespace std;
const int N = 1e6+10;
int a[N],s[N];
int main()
{
int n,m;scanf("%d%d",&n,&m);
for(int i = 1;i<=n;i++)
{
cin>>a[i];
}
for(int i = 1; i<=n; i++){
s[i] = s[i - 1] + a[i];
}
while(m -- )
{
int l,r;cin>>l>>r;
printf("%d\n",s[r] - s[l-1]);
}
return 0;
}
s表示矩阵左上角所有数的和,a表示每个部分的数字。
重要公式 :s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
下面这则是根据s[i][j]性质求{x1,y1} 和{x2, y2}之间所有数的和。
int x1,y1,x2,y2;cin>>x1>>y1>>x2>>y2;
printf("%d\n", s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]);
#include
using namespace std;
const int N = 1010;
int a[N][N],s[N][N];
int main()
{
int n,m,q;cin>>n>>m>>q;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m ;j++){
cin>>a[i][j];
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
}
}
while(q -- )
{
int x1,y1,x2,y2;cin>>x1>>y1>>x2>>y2;
printf("%d\n", s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]);
}
return 0;
}
此处关键代码 b[i] = a[i] - a[i - 1]。
a[i] 其实就相当于 b[i] 的前缀和。
#include
using namespace std;
const int N = 1e6+10;
int a[N],b[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i = 1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i = 1; i<=n; i++){
b[i] = a[i] - a[i - 1];
}
while(m -- )
{
int l,r,c;cin>>l>>r>>c;
b[l] += c;b[r + 1] -= c;
}
for(int i = 1; i <= n; i++) {
a[i] = b[i] + a[i - 1];
}
for(int i = 1; i<=n; i++) cout<
此处需要需注意,b[i][j] 表示的是a[i][j] 的差分矩阵:
关键公式
1、b[i][j] = a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]; (此处根据公式赋予b[i][j]差分矩阵的性质)
2、b[x1][y1]+=c,b[x1][y2+1]-=c,b[x2+1][y1]-=c,b[x2+1][y2+1]+=c;则是表示了每个范围中的加减的情况,注意,b[i][j] 加减影响的是右下角。 这是因为其差分矩阵的性质。
3.处理结束后
a[i][j] = b[i][j]+a[i-1][j]+a[i][j-1]-a[i-1][j-1];将原数组重新加上b[i][j]。实现数据的回归。
最后a[i][j]就是最后的结果。
#include
using namespace std;
const int N = 1010;
int a[N][N],b[N][N];
int i,j,n,m,q;
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)scanf("%d",&a[i][j]);
//构建差分数组
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)b[i][j] = a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1];
while(q--)
{
int x1,y1,x2,y2,c;
scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&c);
b[x1][y1]+=c,b[x1][y2+1]-=c,b[x2+1][y1]-=c,b[x2+1][y2+1]+=c;
}
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)a[i][j] = b[i][j]+a[i-1][j]+a[i][j-1]-a[i-1][j-1];
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
printf("%d ",a[i][j]);
}
printf("\n");
}
return 0;
}
以上为一部分学过的较基础和重要的算法的总结。(主要是建立一个体系)