AcWing算法基础课笔记 ------ 第一章 基础算法

本篇博客记录一下AcWing算法基础课第一章节的笔记,以及自己犯的一些错误。
这一章节总体来说,收获还是不少的,清晰的认识了二分的模板,以及高精度的加减乘除,前缀和为什么0号位置不放,选择从1的位置开始算起,自己在使用qsort排序时候的错误等等。。

一. 二分

二分法之前学了很多次,但是这个课程讲解属实是让我对于二分有了一个新的认识,真的就是豁然开朗了。
以前在看二分题解的时候呢,总会看到各种各样的二分方法:

mid = (left + right) / 2;
mid = (left + rignt + 1) / 2;
if(check(mid)) left = mid; || left = mid + 1;
else right = mid || right = mid - 1.

等等还有更多,乱七八糟的。
每一个题解好像对于二分都是不同的,或者说每个人的方式都是不同的,这是我之前的理解。
但是从今天之后,我的现在对于二分只有两种理解,下面利用一道题目来解释一下

1. 数的范围

题目链接
在这里插入图片描述
这道题题目是说从一个数组中输出所给定的数 3 2 5的起始位置(下标),如果没有则返回-1 -1。
我之前在leetcode上确实做过这样一道类似的题目,当时我应该是找到所给的数,然后从中间往左右两边散开的,当时不会

第一种:

  • 算出mid = (left + right) / 2 之后呢。
  • 判断一下当前的mid所落在的区间在那一个范围之内。
  • 就比如说现在所查找的数是3,右边的区域一定是大于等于3的,因为3也是答案,
  • 所以会是下面这种情况:这样一定会找开始位置,除非你这个数不存在
//找起点
        while(left < right)
        {
            int mid = (left + right) / 2;
            if(nums[mid] >= target)
            {
                right = mid;
            }
            else
            {
                left = mid + 1;
            }
        }

在这里插入图片描述
第二种:
那第二种情况呢就是与之相反的。

  • 如果当前的mid落在小于等于3的区间内,那么使left = mid,
  • 那要注意的是,这种情况,需要加上一个1,要不然就会出现死循环。
  • 这也就是开头提到的为什么有人加1有人不加,其实是分情况来判断的。
 while(left < right)
        {
            int mid = (left + right + 1) / 2;
            if(nums[mid] <= target)
            {
                left = mid;
            }
            else
            {
                right = mid - 1;
            }

这道题目的整体代码如下:

#include <stdio.h>
#define N (100000 + 10)


int main()
{
    int n,k,i;  // n 为数组大小,k 查找多少次数
    int nums[N];
    scanf("%d%d",&n,&k);
    
    //录入数组
    for (i = 0; i < n; i++)
    {
        scanf("%d",&nums[i]);
    }
    
    //
    while(k > 0)
    {
        k--;
        int left = 0, right = n - 1, target;
        scanf("%d",&target);
        //找起点
        while(left < right)
        {
            int mid = (left + right) / 2;
            if(nums[mid] >= target)
            {
                right = mid;
            }
            else
            {
                left = mid + 1;
            }
        }
        if(nums[left] != target)
        {
            //找不到
            printf("-1 -1\n");
            continue;
        }
        printf("%d ",left);
        //继续找末尾
        right = n - 1;
        while(left < right)
        {
            int mid = (left + right + 1) / 2;
            if(nums[mid] <= target)
            {
                left = mid;
            }
            else
            {
                right = mid - 1;
            }
        }
        
        printf("%d\n",left);
    }

    return 0;
}

二. 高精度

在这一小节中呢,主要是处理一些很大的数进行的运算,肯定是int 和 long long 存不下的数来进行,或者说以操作然后就溢出的那种情况。
下面分别是模拟加减乘除4道题目

1. 加法

题目链接
实现两个数的相加,之前在leetcode上做过,但是当时的思路确实是没有现在听人讲一遍来的清晰🤣🤣。

  • 主要就是拿一个临时变量来存储进位的数值就好了。
  • 三个条件都必须进入循环,两个数组不全为空,t不为0.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


#define N (100000 + 10)

int* Add(int* A, int Asize, int* B, int Bsize, int* size)
{
	int* ans = (int*)malloc(sizeof(int) * N);
	int i,t = 0;
	for (i = 0; i < Asize || i < Bsize || t; i++)
	{
		if(i < Asize)
		{
			t += A[i];
		}
		if(i < Bsize)
		{
			t += B[i]; 
		}
		
		ans[(*size)++] = t % 10;
		t /= 10;
	}

	return ans;
}


int main()
{
	char a[N],b[N];
	scanf("%s %s",a,b);
	int Alen = strlen(a),Blen = strlen(b);
	int A[N],B[N];	//a 和 b的数组形式 高位在前
	int Asize = 0, Bsize = 0,i; 
	
	//字符串转化为数组	高位在前。 
	for (i = Alen - 1; i >= 0; i--)
	{
		A[Asize++] = a[i] - '0';
	}
	for (i = Blen - 1; i >= 0; i--)
	{
		B[Bsize++] = b[i] - '0';
	}
	//相加
	int ansSize = 0;
	int* ans = Add(A,Asize,B,Bsize,&ansSize);
    
	//打印
	for (i = ansSize - 1; i >= 0; i--)
	{
		printf("%d",ans[i]);
	}
	printf("\n");
	free(ans);
    return 0;
}

2. 减法

题目链接

  • 这道题同样也有一个临时变量 t
  • 而它的目的是为了看是否借位。
  • 还有就是如果比较两个数的大小,不能直接用strcmp函数来比较的,strcmp函数是逐一对字符串中每一个字符的ASCII码去比较,不会考虑整体的,所以需要自己实现一下。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

#define N (100000 + 10)

bool Compar(int* A, int Asize, int* B, int Bsize)
{
    if(Asize != Bsize)
    {
        return Asize > Bsize;
    }
    
    //len same
    int i;
    for (i = Asize - 1; i >= 0; i--)
    {
        if(B[i] != A[i])
        {
            return A[i] > B[i];
        }
    }
    
    return true;
}


int* Sub(int* A,int Asize, int* B, int Bsize, int* size)
{
	int* ans = (int*)malloc(sizeof(int) * N);
	int i, t = 0;
	for (i = 0; i < Asize; i++)
	{ 
		t = A[i] - t;
		if(i < Bsize)
			t -= B[i];
		ans[(*size)++] = (t + 10) % 10;
		//是否借位
		if(t < 0)
			t = 1;
		else
			t = 0;
	}
	
	//前导零。 
	while((*size) > 1 && ans[(*size) - 1] == 0)
		(*size)--;

	return ans;
}


int main()
{
	char a[N],b[N];
	scanf("%s %s",a,b);
	int Alen = strlen(a),Blen = strlen(b);
	int A[N],B[N];	//a 和 b的数组形式 高位在前
	int Asize = 0, Bsize = 0,i; 
	
	//数组转化 
	for (i = Alen - 1; i >= 0; i--)
	{
		A[Asize++] = a[i] - '0';
	}
	for (i = Blen - 1; i >= 0; i--)
	{
		B[Bsize++] = b[i] - '0';
	}
	
	
	
	int ansSize = 0;
	int* ans = NULL;
	
	if(Compar(A,Asize,B,Bsize))
	{
	    // a >= b
    	ans = Sub(A,Asize,B,Bsize,&ansSize);
    	for (i = ansSize - 1; i >= 0; i--)
    	{
    		printf("%d",ans[i]);
    	}
    	printf("\n");
	}
	else
	{
        ans = Sub(B,Bsize,A,Asize,&ansSize);
    	printf("-");
    	for (i = ansSize - 1; i >= 0; i--)
    	{
    		printf("%d",ans[i]);
    	}
    	printf("\n");
        	    
	}
	
	

	free(ans);
    return 0;
}

3. 乘法

高精度乘法会是一个很大的数乘很小的数,所以在下面代码中b就用int来存储了。

  • 在乘法中,是用数字的每一位,去乘以另一个数然后加上t,
  • 对t取模则是当前的答案,然后在对t进行除十的操作。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define N (100000 + 10)


int* Mul(int* A, int Asize, int b, int* size)
{
    int* ans = (int*)malloc(sizeof(int) * N);
    int i,t = 0;
    for (i = 0; i < Asize || t; i++)
    {
        if(i < Asize)
            t += A[i] * b;
        ans[(*size)++] = t % 10;
        t /= 10;
    }
    
    return ans;
}

int main()
{
    char a[N];
    int A[N],b,i;
    scanf("%s %d",a,&b);
    if(b == 0)
    {
        printf("0\n");
        return 0;
    }
    
    int Asize = 0,Alen = strlen(a);
    for (i = Alen - 1; i >= 0; i--)
    {
        A[Asize++] = a[i] - '0';
    }
    
    int ansSize = 0;
    int* ans = Mul(A,Asize,b,&ansSize);
    
    for (i = ansSize - 1; i >= 0; i--)
    {
        printf("%d",ans[i]);
    }
    printf("\n");
    return 0;
}

4. 除法

  • 除法需要注意的就是从高位开始算的。
  • 主要就是模拟除法的过程,认认真真的过上一遍思路就行拉。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define N (100000 + 10)

void Reverse(int* nums, int numsSize)
{
    int left = 0,right = numsSize - 1;
    while(left < right)
    {
        int tmp = nums[left];
        nums[left++] = nums[right];
        nums[right--] = tmp;
    }
    
}


int* Div(int* A, int Asize,int b, int* size, int* rem)
{
    int i, t = 0;
    int* ans = (int*)malloc(sizeof(int) * N);
    for (i = Asize - 1; i >= 0; i--)
    {
        t = t * 10 + A[i];
        ans[(*size)++] = t / b;
        t %= b;
    }
    
    
    *rem = t;
    Reverse(ans,*size);
    while(*size > 1 && ans[(*size) - 1] == 0)
        (*size)--;
        
        
    return ans;
}

int main()
{
    char a[N];
    int A[N],b,i;
    scanf("%s %d",a,&b);
    if(b == 0)
    {
        printf("0\n");
        return 0;
    }
    
    int Asize = 0,Alen = strlen(a);
    for (i = Alen - 1; i >= 0; i--)
    {
        A[Asize++] = a[i] - '0';
    }
    
    int rem = 0;
    int ansSize = 0;
    int *ans = Div(A,Asize,b,&ansSize,&rem);
    
    for (i = ansSize - 1; i >= 0; i--)
    {
        printf("%d",ans[i]);
    }
    printf("\n%d\n",rem);
    
    return 0;
}

三,前缀和与差分

之前在用到的前缀和比较多,差分听的少,基本没用过,这个小节也是收获了差分的知识点,还有就是我以前前缀和是在与元数组一一对应的,看别人题解不懂为啥要空过一个,原来空过一个可以使所有的公式统一,不用进行特判。

在这里说一下,leetcode上是OJ题目,它给咱们一函数,让咱们实现其功能。
而像蓝桥杯还有AcWing网站等等这种的属于OI型题目,也就是需要写主函数,然后自己输出正确的答案,所以我下面的一些矩阵元素就完全可以浪费那个0号位置不存数据,在1号位置存储,只要自己别搞混,而对于leetcode上有的是没有办法,人家给你的数组就是从0开始的。

1. 一维前缀和

求区间[l,r]的和公式: s[l,r] = s[r] - s[l - 1]

#include <stdio.h>

#define N (100000 + 10)

int n;
int prefix[N],nums[N];

int main()
{
    int i,k;
    scanf("%d%d",&n,&k);
    for (i = 0; i < n; i++)
        scanf("%d",&nums[i]);
        
    //求前缀和
    for (i = 1; i <= n; i++)
        prefix[i] = prefix[i - 1] + nums[i - 1];
        
    while(k != 0)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",prefix[r] - prefix[l - 1]);	//前缀和公式
        k--;
    }
    
    return 0;
}

2. 二维前缀和

二维矩阵的前缀和是下图这个样子的,二维矩阵中的第0行和第0列和一维一样,也是存放的0.然后前缀和就是那个位置,往左上角扫面的和。
在这里插入图片描述
那具体该如何求呢,图中6 + 3 - 1 +6 = 14.橙色1多次相加,记得减一次,然后最后记得加上自己
在这里插入图片描述
matrix[x1,x2] = 6.
求前缀和公式:
s[x1,x2] = s[x1,y1 - 1] + s[x1 - 1, y1] - s[x1 - 1, y1 - 1] + matrix[x1,x2].

然后如果要求一个区间的总和如下图:
就会发现我们只需要将54 - 6 - 15 + 1 == 6 + 7 + 10 +11。
因为中黄色区域被减了两次,所以得加回去。
在这里插入图片描述
matrix[x1,x2] = 6,matrix[x2,y2] = 11
所以在二维矩阵中去求一个小矩阵的和公式是:
matrix[x1,y2 -> x2,y2] = s[x2,y2] - s[x2,y1-1] - s[x1-1,y2] + s[x1-1,y1-1]

#include <stdio.h>

#define N (1000 + 10)

int matrix[N][N],prefix[N][N];
int row, col;



int main()
{
    int i, j, k;
    scanf("%d%d%d",&row,&col,&k);
    
    for (i = 1; i <= row; i++)
    {
        for (j = 1; j <= col; j++)
        {
            scanf("%d",&matrix[i][j]);
        }
    }
    
    //创建前缀和
    for (i = 1; i <= row; i++)
    {
        for (j = 1; j <= col; j++)
        {
            prefix[i][j] = prefix[i][j - 1] + prefix[i - 1][j] - prefix[i - 1][j - 1] + matrix[i][j];
        }
    }
    
    while(k--)
    {
        int x1,y1,x2,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        printf("%d\n",prefix[x2][y2] - prefix[x2][y1 - 1] - prefix[x1 - 1][y2] + prefix[x1 - 1][y1 - 1]);
    }
    
    return 0;
}

3. 一维差分

对于差分这个概念呢,就是当前的前一个减去后一个么,因为我举例子是等差数列,d还是1,所以呢差分数组也全都是1了。
在这里插入图片描述

可以发现如果对差分数组求前缀和的话,就会又回到nums数组,那么它具体能干嘛呢
假如我要在数组2~5这个范围内全部加上一个 2,其下标区间为也是[2,5]。
那么就可以在差分数组2上加上一个2,再去5+1的位置减去一个2,就正好是所选的区间,下图所示:只有所选的区间全部加2其余的都没有

在这里插入图片描述
区间d[l,r],一常数C
差分公式: d[l] += C,d[r + 1] -= C
我们可以利用一个插入函数,在创建的时候对当前的位置插入,也就是l == r的时候。

#include <stdio.h>

#define N (100000 + 10)

int nums[N],diff[N],prefix[N];



//区间[l,r]插入c
void DiffInsert(int l, int r, int c)
{
    diff[l] += c;
    diff[r + 1] -= c;
}

int main()
{
    int i,n,k;
    scanf("%d%d",&n,&k);
    
    //原数组
    for (i = 1; i <= n; i++)
    {
        scanf("%d",&nums[i]);
    }
    
    //差分数组
    for (i = 1; i <= n; i++)
        DiffInsert(i,i,nums[i]);
        
    //差分k次
    while(k--)
    {
        int l,r,c;
        scanf("%d%d%d",&l,&r,&c);
        DiffInsert(l,r,c);
    }
    
    //还原
    for (i = 1; i <= n; i++)
    {
        prefix[i] = prefix[i - 1] + diff[i];
        printf("%d ",prefix[i]);
    }
    
    return 0;
}

4. 二维差分

差分的二维矩阵其实和二维的前缀和是一样的.
我们像上面的一维一样,同样用一个插入函数,创建差分数组时候对自己当前的位置进行插入。

diff[x1][y1] += c
diff[x2 + 1][y1] -= c
diff[x1][y2 + 1] -= c
diff[x2 + 1][y2 + 1] += c

#include <stdio.h>

#define N (1000 + 10)

int matrix[N][N],diff[N][N],prefix[N][N];
int row, col;


void DiffInsert(int x1, int y1, int x2, int y2, int c)
{
    diff[x1][y1] += c;
    diff[x2 + 1][y1] -= c;
    diff[x1][y2 + 1] -= c;
    diff[x2 + 1][y2 + 1] += c;
}

int main()
{
    int i,j,k;
    scanf("%d%d%d",&row,&col,&k);
    
    //create matrix
    for (i = 1; i <= row; i++)
    {
        for (j = 1; j <= col; j++)
        {
            scanf("%d",&matrix[i][j]);
        }
    }
    
    //diff
    for (i = 1; i <= row; i++)
    {
        for (j = 1; j <= col; j++)
        {
            DiffInsert(i,j,i,j,matrix[i][j]);
        }
    }
    
    while(k--)
    {
        int x1,y1,x2,y2,c;
        scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&c);
        DiffInsert(x1,y1,x2,y2,c);
    }
    
    for (i = 1; i <= row; i++)
    {
        for (j = 1; j <= col; j++)
        {
            prefix[i][j] = prefix[i - 1][j] + prefix[i][j - 1] - prefix[i - 1][j - 1] + diff[i][j];
            printf("%d ",prefix[i][j]);
        }
        printf("\n");
    }
    
    return 0;
}

四. 双指针

双指针在平时的用的还是非常的多感觉,归并排序,快排等等很多。

1. 数组元素的目标和

  • 利用两个指针 i 和 j 分别指向两个数组的起点和和终点
  • i 指向A j 指向B
  • 因为两个数组都是有序的,所以如果A[i] + B[j] < x.
  • 那么是j–
  • 否则i++。
#include <stdio.h>

#define N (100000  + 10)

int A[N],B[N],map[N];
int n,m,x;

int main()
{
    int i,j;
    scanf("%d%d%d",&n,&m,&x);
    for (i = 0; i < n; i++)
        scanf("%d",&A[i]);
    
    for (i = 0; i < m; i++)
        scanf("%d",&B[i]);
        
    for (i = 0, j = m - 1; i < n; i++)
    {
        while(j >= 0 && A[i] + B[j] > x)
        {
            j--;
        }
        if(A[i] + B[j] == x)
        {
            
            printf("%d %d\n",i,j);
            return 0;
        }
    }
        
    
    
    printf("找不到!\n");
    return 0;
}

五. 位运算

关于位运算我掌握的知识只能说是少的可怜,因为平时刷题好像也不怎能用上,本讲呢,介绍了位运算的两种常用的方式。

1.取出二进制数的第一位: &
就是说一个数 & 上1 就是取出它的第一位了。
&号是有0则0,所以&1除了第一位,前面的都是0,自然就算出第一位了来了,
在这里插入图片描述
2. 看一下n的第k位数字是几
有了取出第一位后的操作,第k位的话就是n >> k & 1

3. lowbit(x)
可以获取x的最后一个1的位置:
公式是x & -x

111000 得到 1000
101010 得到 10
00111100 的到 10

1. 统计出二进制中1的个数

依稀记得在之前统计的时候还是将它的二进制数全部求一遍放到数字中去😓。

  • 利用lowbit(x) 求出最后一位1的位置,然后减去,直到变成0位置。
#include <stdio.h>

int n;

int lowbit(int x)
{
    
    return x & -x;
}


int main()
{
    scanf("%d",&n);
    while(n--)
    {
        int x,count = 0;
        scanf("%d",&x);
        
        while(x != 0)
        {
            x -= lowbit(x);
            count++;
        }
        printf("%d ",count);
    }
    
    
    return 0;
}

六. 区间合并

就比如下图中给定的区间,最后将会返回答案是3,合并成了3个区间
在这里插入图片描述

  • 利用一个二维数组存放x,y下标。
  • 然后对数组进行排序,从左到右进行模拟。
  • 最后是begin = 0, 如果end = 1
  • 如果end 的起点 <= begin 的终点,说明有交集,然后取他俩的并集,
  • 并集:begin = Max(pair[begin][1], pair[end][1])
  • 否则的话 ans += 1,然后使begin = end。
  • ans初始化为1,区间最少最少也是1
#include <stdio.h>
#include <stdlib.h>

#define N (100000 + 10)

int res; //最后的结果 
int n,pair[N][2]; //pair 数组存放的是所输入的下标

int cmp(const void* x, const void* y)
{
    int* a = (int*)x;
    int* b = (int*)y;
    if (a[0] != b[0])
    {
        return a[0] - b[0];
    }
    else
    {
        return a[1] - b[1];
    }
}
void merge()
{
    int begin = 0,end = 1;
    while(end < n)
    {
        if(pair[end][0] <= pair[begin][1])
        {
            //更新合并后的区间最远处,并集
            if(pair[begin][1] < pair[end][1])
            {
                begin = end;
            }
        }
        else
        {
            //获取新的起点开始
            res += 1;
            begin = end;
        }
        end++;
    }
    
}

int main()
{
    int i;
    res = 1;
    scanf("%d",&n);
    //录入数据
    for (i = 0; i < n; i++)
    {
        scanf("%d%d",&pair[i][0],&pair[i][1]);
    }
    
    //排序
    qsort(pair,n,sizeof(pair[0]),cmp);
    
    //合并
    merge();
    
    printf("%d\n",res);
    return 0;
}

七. qsort 排序二维数组

这个qsort排序二维数组是我自己在做区间合并的时候发现的问题,感觉这个错误还好发现的早,这要是比赛的时候再发现,直接原地祭,根本不知道是为什么。
对于matrix[N][N] 初始化和 malloc开辟的两种二维数组qsort的 cmp(比较函数) 是不同的。
在刷leetcode上的题都是malloc出来的,所以用的都是下面的第二个比较函数,但如果换成直接开辟的话就需要用第一个了。

int cmp_martix(const void* x, const void* y)
{
	int* a = (int*)x;
	int* b = (int*)y;
	if(a[0] != b[0])
	{
		return a[0] - b[0];
	}
	else
	{
		return a[1] - b[1];
	} 
}

int cmp_nums(const void* x, const void* y)
{
    int** a = (int**)x;
    int** b = (int**)y;

    if (a[0][0] != b[0][0])
    {
        return a[0][0] - b[0][0];
    }
    else
    {
        return a[0][1] - b[0][1];
    }
}


int main()
{
	int matrix[N][N];
	int i,n;
	scanf("%d",&n);
	int** nums = (int**)malloc(sizeof(int*) * n);
	for (i = 0; i < n; i++)
	{
		nums[i] = (int*)malloc(sizeof(int) * 2);
		scanf("%d%d",&matrix[i][0],&matrix[i][1]);
		nums[i][0] = matrix[i][0];
		nums[i][1] = matrix[i][1];
	}
	
	qsort(matrix,n,sizeof(matrix[0]),cmp_martix);
	qsort(nums,n,sizeof(nums[0]),cmp_nums);
	//
	printf("martix排序后:\n");
	for (i = 0; i < n; i++)
	{
		printf("%d %d\n",matrix[i][0],matrix[i][1]);
	}
	printf("nums排序后:\n");
	for (i = 0; i < n; i++)
	{
		printf("%d %d\n",nums[i][0],nums[i][1]);
	}
	
	return 0;
}

完结★,°:.☆( ̄▽ ̄)/$:.°★ ❀🎈🎈🎈🎈。

  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值