基础算法

基础算法合集

常见但是没法归类的一些东西的整理

1. 一些排序算法

会让你手撕的就两个 一个快排 一个归并排序

边界问题好好记一下 撕错了就太丢脸了

1. 快排

void quick_sort(vector<int>& q, int l, int r)
{
    if(l >= r) return;
    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while(i < j)
    {
        do i ++; while(q[i] < x);
        do j --; while(q[j] > x);
        if(i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j);
    quick_sort(q, j + 1, r);
}

快排扩展-数组中第k个数

快排:

  • 找到分界点

  • 分成两个区间并调整

  • 递归排序左边/递归排序右边

这里我们找第K个数,无需排序所有 只要找到一边就可以了

判断依据

  • K <= sl : 递归left

  • K > sl : 递归right K = K - sl;

int quick_select(vector<int>& q, int l, int r, int k)
{
    if(l == r) return q[l];
    int i = l - 1, j = r + 1, x = q[l + r >> 1];
    while(i < j)
    {
        do i++; while(q[i] < x);
        do j--; while(q[j] > x);
        if(i < j) swap(q[i], q[j]);
    }
    // 这里不再是递归两边了
    int sl = j - l + 1;
    if(k <= sl)
        return quick_select(q, l, j, k);
    else return quick_select(q, j + 1, r, k - sl);
}

2. 归并排序

归排:

  • 确定分界点

  • 先递归处理左右两边

  • 把两个有序数组合二为一

void merge_sort(vector<int>& q, int l, int r)
{
    if(l >= r) return;
    int mid = l + r >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);
    vector<int> tmp;
    int k = 0, i = l, j = mid + 1;
    while(i <= mid && j <= r)
        if(q[i] <= q[j]) tmp.push_back(q[i++]);
        else tmp.push_back(q[j++]);
    while(i <= mid) tmp.push_back(q[i++]);
    while(j <= r) tmp.push_back(q[j++]);
    // 贴到原数组中
    for(int i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];
}

归并排序扩展-求数组中逆序对的个数

左半边的逆序对 + 右半边的逆序对 + 交叉的逆序对

交叉的逆序对 左右两边分别排序 不影响其数量

这样就沿用归并排序的结构 当q[i] > q[j]的时候 q[i] 和 q[i]后面的数肯定都比q[j]大

所以直接加上 mid - i + 1 就可以了

class Solution {
public:
    int reversePairs(vector<int>& nums) {
        return merge_sort(nums, 0, nums.size() - 1);
    }
    int merge_sort(vector<int>& q, int l, int r)
    {
        if(l >= r) return 0;
        int mid = l + r >> 1;
        int res = merge_sort(q, l, mid) + merge_sort(q, mid + 1, r);
        // 归并
        int i = l, j = mid + 1;
        vector<int> tmp;
        while(i <= mid && j <= r)
        {
            if(q[i] <= q[j]) tmp.push_back(q[i++]);
            else
            {
                res += mid - i + 1; // i 后面的都是逆序 因为q[i] 后面的数比q[i]大 同样也比 q[j] 大
                tmp.push_back(q[j++]);
            }
        }
        while(i <= mid) tmp.push_back(q[i++]);
        while(j <= r)   tmp.push_back(q[j++]);
        // 赋值回去
        for(int i = 0, j = l; i < tmp.size(); i++, j++)
            q[j] = tmp[i];
        return res;
    }
};

方法2:树状数组

树状数组可以用来求:左边/右边比当前元素大/小的个数

怎么做呢 就是拿值当下标,每次出现就给其加1,这样区间查询就可以查到比当前元素大 or 小的和:即出现次数之和

当然了 由于数据可能很大 这里要保序离散化

// 保序离散化
    vector<int> alls = nums;
    sort(alls.begin(), alls.end());
    alls.erase(unique(alls.begin(), alls.end()), alls.end());
    int find(int x)
    {
        int l = 0, r = alls.size() - 1;
        while(l < r)
        {
            int mid = l + r >> 1;
            if(alls[mid] >= x)
                r = mid;
            else l = mid + 1;
        }
        return r + 1; // 记得是加1!
    }

树状数组的几个操作

class Solution {
public:
    int n;
    // 离散化
    vector<int> tree, alls;
    int lowbit(int i)
    {
        return i & (-i);
    }
    void update(int i, int val)
    {
        while(i <= n)
        {
            tree[i] += val;
            i += lowbit(i);
        }
    }
    int query(int i)
    {
        int sum = 0;
        while(i > 0)
        {
            sum += tree[i];
            i -= lowbit(i);
        }
        return sum;
    }
    int find(int x) // 找到第一个大于等于x的位置
    {
        int l = 0, r = alls.size() - 1;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (alls[mid] >= x) r = mid;
            else l = mid + 1;
        }
        return r + 1; // 映射到1, 2, ...n
    }
    int reversePairs(vector<int>& nums) {
        n = nums.size();
        tree = vector<int>(n + 10);
        // 1. 离散化
        alls = nums;
        sort(alls.begin(), alls.end());
        alls.erase(unique(alls.begin(), alls.end()), alls.end());
        int res = 0;
        for(int x : nums)
        {
            int y = find(x);
            res += query(n) - query(y);
            update(y, 1);
        }
        return res;
    }
};

2. 常见的区间问题

区间问题基本都是贪心来搞的 常见的有一下几个

1. 区间合并

按左端点排序 然后遍历区间 如果ed 和新区间的起点分开的话, 就更新合并结果;

看代码就懂了

注意最后可能还剩下一个区间{st, ed} 没有加进去

void merge(vector<PII> &segs)
{
    vector<PII> res;

    sort(segs.begin(), segs.end());

    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        if (ed < seg.first)
        {
            if (st != -2e9) res.push_back({st, ed});
            st = seg.first, ed = seg.second;
        }
        else ed = max(ed, seg.second);
    if (st != -2e9) res.push_back({st, ed});
    segs = res;
}

除了用pair存之外,二维vector默认也是按照第一个元素排序的 所以不用担心

2. 区间选点

在数轴上选择尽可能少的点, 使得每个区间内至少包含一个选出的点,输出选择的点的最少数量 「请注意:位于区间端点上的点也算在区间内」

方法:

  • 按照区间右端点排序

  • 从前往后以此枚举每个区间的右端点:如果当前取件中已经包含这个右端点 直接pass 否则选择当前区间的右端点

struct Interval{
    int l, r;
    bool operator<(const Interval& w) const{
        return r < w.r;
    }
}intervals[N];

int solution()
{
    sort(intervals, intervals + n);
    int res = 0, ed = -2e9;
    for(int i = 0; i < n; i++)
    {
        int l = intervals[i].l, r = intervals[i].r;
        if(ed >= l) continue;
        else
        {
            ed = r;
            res += 1;
        }
    }
    return res;
}

3. 最大不相交区间数量

选择区间之间互不相交(包括端点)

和上面的题是一样的

4. 区间分组

给定区间,请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点) 没有交集,并使得组数尽可能小

方法:

  • 按照做端点从小到大排序

  • 从前往后处理每个区间 判断能否放入某个现有的组 (记录组内端点最大值)

  • 不能 -> 开新组

找到最小的组:优先队列存右端点

struct Interval{
    int l, r;
    bool operator<(const Interval& w) const
    {
        return l < w.l;
    }
}intervals[N]

int solution()
{
    sort(intervals, intervals + n);
    priority_queue<int, vector<int>, greater<int>> heap;
    for(int i = 0; i < n; i++)
    {
        int l = intervals[i].l, r = intervals[i].r;
        if(heap.empty() || heap.top() >= l)
        {
            heap.push(r)
        }
        else {
            heap.pop();
            heap.push(r);
        }
    }
    return heap.size();
}

3. 常见位运算技巧

常见的一些和 不太常见的一些位运算:

lowbit(x) = i & (-i) 找到最近的1 并扣出来 i & (i - 1) 删除最近的1 i >> k & 1 判断i的第k位是不是1 (注意这里不用写括号 & 的优先级会低一些)

不太常见但是要记的:

  • 数组中出现一次 剩下的元素都出现三次

本来觉得没啥但是ms前两天考了

one = (one ^ x) & (~two);
two = (two ^ x) & (~one);
  • 取奇数位/ 取偶数位 取奇数位:x & 0xAAAA 取偶数位:x & 0x5555

  • 奇偶位对换 ((x & 0xAAAA) >> 1) | ((x & 0x5555) << 1)

4. 滑动窗口合集

滑动窗口分为两种 固定长度的滑动窗口和长度可变的滑动窗口

1. 固定长度的滑动窗口

2. 可变长度的滑动窗口

Last updated