算法总结归纳(第一天)(基础算法知识)

目录

  1. 一、二分查找
  2. 1、整数二分(重要)
  3. 1.常规思路
  4. 1.确定区间
  5. 2.中间值(+1 or 不 + 1)
  6. 2.浮点数二分
  7. 二、双指针(重要)
  8. 1.两个指针均从起点开始(一个数组)
  9. 2.两个指针一前一后(两个数组)
  10. 3、滑动窗口(双指针类型的)
  11. 三、模拟(考察代码能力)
  12. 四、哈希表(重要)
  13. 1.数组模拟实现实现(常用)
  14. 2.unordered_map容器实现(常用)
  15. 3.unordered_set集合实现
  16. 五、位运算。
  17. 1.位运算符&、|、^、~、x与 -x的基本概念
  18. 2.具体题目
  19. 解法1:x =x - (x & -x)
  20. 解法2:x&(x - 1)
  21. 六、排序
  22. 1.快速排序
  23. 2.归并排序
  24. 七、高精度加减乘除(适用于数据特别大)
  25. 1.高精度加法
  26. 2.高精度减法
  27. 3.高精度乘法
  28. 4.高精度除法
  29. 八、前缀和与差分
  30. 1.前缀和
  31. 1、一维数组
  32. 2.二维矩阵
  33. 2.差分
  34. 1.一维数组
  35. 2.二维矩阵
  36. 总结


一、二分查找

1、整数二分(重要)

1.常规思路

我们进行整数二分,经常需要确定边界问题,而很多时候会出错,超时。

因此,我们二分时,需要确定一段范围的数,他区间的开闭情况。

下面代码所示例子为leetcode上下面链接的题目

1.确定区间

一般有以下几种情况:

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;
2.中间值(+1 or 不 + 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;
}

2.浮点数二分

浮点数比整数二分简单的多。

下面用的是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/

1.两个指针均从起点开始(一个数组)
  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;
2.两个指针一前一后(两个数组)

下面为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]
3、滑动窗口(双指针类型的)

下面是力扣的题目链接

本题的滑动窗口就是动态的截取数组中的一部分,然后判断是否满足某种性质。

长度最小的子数组

 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;

三、模拟(考察代码能力)

该类型没有具体算法类型,根据题目类型具体分析,模拟代码运行逻辑从而解题。

下面是经典模拟题目

螺旋矩阵

四、哈希表(重要)

1.数组模拟实现实现(常用)

建立数组,通常用来记录某个数字出现过几次。

题目如下: 有效的字母异位词

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
2.unordered_map容器实现(常用)

该容器通常是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 {};
    }
3.unordered_set集合实现

下方题目链接:

快乐数

此处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.位运算符&、|、^、~、x与 -x的基本概念

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
2.具体题目

题目链接:

二进制中1的个数

解法1:x =x - (x & -x)
#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);
    }
}
解法2:x&(x - 1)
#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<

六、排序

1.快速排序

废话不多说,直接上模板

下面是对一个乱序数组进行升序排序。

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);
}
2.归并排序

直接模板走起

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]

七、高精度加减乘除(适用于数据特别大)

1.高精度加法

此处我们从各位开始相加,利用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<
2.高精度减法

首先减法我们首先要确定是大减小还是小减大,正负情况区分开来求解。

随后,同样用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<
3.高精度乘法

此处的乘法,我们对第一个数据采取同样处理,但是我们将第二个数看作一个整体,即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<
4.高精度除法

余数用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<

八、前缀和与差分

1.前缀和

1、一维数组

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;
}
2.二维矩阵

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;
}

2.差分

1.一维数组

此处关键代码 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<
2.二维矩阵

此处需要需注意,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;
}


总结

以上为一部分学过的较基础和重要的算法的总结。(主要是建立一个体系)

你可能感兴趣的:(算法,c++)