「考研算法」

「考研算法」

前言

本系列文章涉及的算法内容,针对的是哈尔滨工业大学854科目。在本文中通过具体的算法题进行讲解相应算法。

今天涉及的算法主要有:

  1. 线段树,
  2. 归并排序衍生的逆序对的数量的问题
  3. 蓝桥杯中阶乘的正约数个数。

一、线段树算法的使用—掉落的方块(LeetCode 699题)

算法题目:
在二维平面上的 x 轴上,放置着一些方块。

给你一个二维整数数组 positions ,其中 positions[i] = [lefti, sideLengthi] 表示:第 i 个方块边长为 sideLengthi ,其左侧边与 x 轴上坐标点 lefti 对齐。

每个方块都从一个比目前所有的落地方块更高的高度掉落而下。方块沿 y 轴负方向下落,直到着陆到 另一个正方形的顶边 或者是 x 轴上 。一个方块仅仅是擦过另一个方块的左侧边或右侧边不算着陆。一旦着陆,它就会固定在原地,无法移动。

在每个方块掉落后,你必须记录目前所有已经落稳的 方块堆叠的最高高度 。

返回一个整数数组 ans ,其中 ans[i] 表示在第 i 块方块掉落后堆叠的最高高度。 
示例 1:
输入:positions = [[1,2],[2,3],[6,1]]
输出:[2,5,5]
解释:
第 1 个方块掉落后,最高的堆叠由方块 1 组成,堆叠的最高高度为 2 。
第 2 个方块掉落后,最高的堆叠由方块 1 和 2 组成,堆叠的最高高度为 5 。
第 3 个方块掉落后,最高的堆叠仍然由方块 1 和 2 组成,堆叠的最高高度为 5 。
因此,返回 [2, 5, 5] 作为答案。
示例 2:
输入:positions = [[100,100],[200,100]]
输出:[100,100]
解释:
第 1 个方块掉落后,最高的堆叠由方块 1 组成,堆叠的最高高度为 100 。
第 2 个方块掉落后,最高的堆叠可以由方块 1 组成也可以由方块 2 组成,堆叠的最高高度为 100 。
因此,返回 [100, 100] 作为答案。
注意,方块 2 擦过方块 1 的右侧边,但不会算作在方块 1 上着陆。
数据范围:
1 <= positions.length <= 1000
1 <= lefti <= 10^8
1 <= sideLengthi <= 10^6
算法代码:
  将在线段树的完整代码中进行讲解!
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.TreeSet;

public class FallingSquares {
    public static class SegmentTree {
        private int[] max;
        private int[] change;
        private boolean[] update;

        public SegmentTree(int size) {
            int N = size + 1;
            max = new int[N << 2];

            change = new int[N << 2];
            update = new boolean[N << 2];
        }

        private void pushUp(int rt) {
            max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]);
        }

        // ln表示左子树元素结点个数,rn表示右子树结点个数
        private void pushDown(int rt, int ln, int rn) {
            if (update[rt]) {
                update[rt << 1] = true;
                update[rt << 1 | 1] = true;
                change[rt << 1] = change[rt];
                change[rt << 1 | 1] = change[rt];
                max[rt << 1] = change[rt];
                max[rt << 1 | 1] = change[rt];
                update[rt] = false;
            }
        }

        public void update(int L, int R, int C, int l, int r, int rt) {
            if (L <= l && r <= R) {
                update[rt] = true;
                change[rt] = C;
                max[rt] = C;
                return;
            }
            int mid = (l + r) >> 1;
            pushDown(rt, mid - l + 1, r - mid);
            if (L <= mid) {
                update(L, R, C, l, mid, rt << 1);
            }
            if (R > mid) {
                update(L, R, C, mid + 1, r, rt << 1 | 1);
            }
            pushUp(rt);
        }

        public int query(int L, int R, int l, int r, int rt) {
            if (L <= l && r <= R) {
                return max[rt];
            }
            int mid = (l + r) >> 1;
            pushDown(rt, mid - l + 1, r - mid);
            int left = 0;
            int right = 0;
            if (L <= mid) {
                left = query(L, R, l, mid, rt << 1);
            }
            if (R > mid) {
                right = query(L, R, mid + 1, r, rt << 1 | 1);
            }
            return Math.max(left, right);
        }

    }

    public HashMap<Integer, Integer> index(int[][] positions) {
        TreeSet<Integer> pos = new TreeSet<>();
        for (int[] arr : positions) {
            pos.add(arr[0]);
            pos.add(arr[0] + arr[1] - 1);
        }
        HashMap<Integer, Integer> map = new HashMap<>();
        int count = 0;
        for (Integer index : pos) {
            map.put(index, ++count);
        }
        return map;
    }

    public List<Integer> fallingSquares(int[][] positions) {
        HashMap<Integer, Integer> map = index(positions);
        int N = map.size();
        SegmentTree segmentTree = new SegmentTree(N);
        int max = 0;
        List<Integer> res = new ArrayList<>();
        // 每落一个正方形,收集一下,所有东西组成的图像,最高高度是什么
        for (int[] arr : positions) {
            int L = map.get(arr[0]);
            int R = map.get(arr[0] + arr[1] - 1);
            int height = segmentTree.query(L, R, 1, N, 1) + arr[1];
            max = Math.max(max, height);
            res.add(max);
            segmentTree.update(L, R, height, 1, N, 1);
        }
        return res;
    }

}

线段树完整代码:

public class SegmentTree {
    public static class SegmentTree {
        // arr[]为原序列的信息从0开始,但在arr里是从1开始的
        // sum[]模拟线段树维护区间和
        // lazy[]为累加和标记
        // change[]为更新的值
        // update[]为更新标记
        private int MAXN;
        private int[] arr;
        private int[] sum;
        private int[] lazy;
        private int[] change;
        private boolean[] update;

        public SegmentTree(int[] origin) {
            MAXN = origin.length + 1;
            arr = new int[MAXN]; // arr[0] 不用 从1开始使用
            for (int i = 1; i < MAXN; i++) {
                arr[i] = origin[i - 1];
            }
            sum = new int[MAXN << 2]; // 某一个范围的累加和信息
            lazy = new int[MAXN << 2]; // 某一个范围沒有往下传下来的累加任务
            change = new int[MAXN << 2]; // 某一个范围有没有更新操作的任务
            update = new boolean[MAXN << 2]; // 某一个范围更新任务
        }

        private void pushUp(int rt) {
            sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
        }
        // 之前的,所有累加以及更新,从父范围,发给左右两个子范围
        // 分发策略是什么
        // ln表示左子树元素结点个数,rn表示右子树结点个数
        private void pushDown(int rt, int ln, int rn) {
            if (update[rt]) {
                update[rt << 1] = true;
                update[rt << 1 | 1] = true;
                change[rt << 1] = change[rt];
                change[rt << 1 | 1] = change[rt];
                lazy[rt << 1] = 0;
                lazy[rt << 1 | 1] = 0;
                sum[rt << 1] = change[rt] * ln;
                sum[rt << 1 | 1] = change[rt] * rn;
                update[rt] = false;
            }
            if (lazy[rt] != 0) {
                lazy[rt << 1] += lazy[rt];
                sum[rt << 1] += lazy[rt] * ln;
                lazy[rt << 1 | 1] += lazy[rt];
                sum[rt << 1 | 1] += lazy[rt] * rn;
                lazy[rt] = 0;
            }
        }

        // 在初始化阶段,先把sum数组,填好
        // 在arr[l~r]范围上,去build,1~N,
        // rt : 这个范围在sum中的下标
        public void build(int l, int r, int rt) {
            if (l == r) {
                sum[rt] = arr[l];
                return;
            }
            int mid = (l + r) >> 1;
            build(l, mid, rt << 1);
            build(mid + 1, r, rt << 1 | 1);
            pushUp(rt);
        }
        // L~R  所有的值变成C
        // l~r  rt
        public void update(int L, int R, int C, int l, int r, int rt) {
            if (L <= l && r <= R) {
                update[rt] = true;
                change[rt] = C;
                sum[rt] = C * (r - l + 1);
                lazy[rt] = 0;
                return;
            }
            // 线段树更新前有累加任务要往下发
            int mid = (l + r) >> 1;
            pushDown(rt, mid - l + 1, r - mid);
            if (L <= mid) {
                update(L, R, C, l, mid, rt << 1);
            }
            if (R > mid) {
                update(L, R, C, mid + 1, r, rt << 1 | 1);
            }
            pushUp(rt);
        }

        // L~R, C 任务!
        // rt,l~r
        public void add(int L, int R, int C, int l, int r, int rt) {
            // 任务包括当前范围!
            if (L <= l && r <= R) {
                sum[rt] += C * (r - l + 1);
                lazy[rt] += C;
                return;
            }
            // 任务没有包括当前整个范围!
            // l  r  mid = (l+r)/2
            int mid = (l + r) >> 1;
            pushDown(rt, mid - l + 1, r - mid);
            // L~R
            if (L <= mid) {
                add(L, R, C, l, mid, rt << 1);
            }
            if (R > mid) {
                add(L, R, C, mid + 1, r, rt << 1 | 1);
            }
            pushUp(rt);
        }

        // 查询某一段范围的累加和
        public long query(int L, int R, int l, int r, int rt) {
            if (L <= l && r <= R) {
                return sum[rt];
            }
            int mid = (l + r) >> 1;
            pushDown(rt, mid - l + 1, r - mid);
            long ans = 0;
            if (L <= mid) {
                ans += query(L, R, l, mid, rt << 1);
            }
            if (R > mid) {
                ans += query(L, R, mid + 1, r, rt << 1 | 1);
            }
            return ans;
        }
    }
}

二、正约数个数

算法题目:
给定n = 100,求n的阶乘的正约数个数。
输入格式
输入n = 100.
输出格式
输出一个整数,表示n的阶乘的正约数的个数。

解题思路如下:

首先将100的阶乘求出来,然后进行质因数分解,也就是每次除以质数,在判断该质数用了几次。然后套用公式:n=(p₁^ a₁)  * (p₂^ a₂) * (p₃^ a₃)*        (p₄ ^ a₄)...
对于一个大于1正整数n可以分解质因数:n=(p₁^ a₁)  * (p₂^ a₂) * (p₃^ a₃)*        (p₄ ^ a₄)...
则n的正约数的个数就是(1+a₁)(1+a₂)(1+a₃)(1+a₄)...
假设自然数N等于P的a次乘以q的b次乘以r的C次,P、q、r为不同的质数,则N的约数个数等于(a+1)*(b+1)*(C+1)。
假设自然数N等于P的a次乘以q的b次乘以r的C次,P、q、r为不同的质数,则N的约数个数等于(a+1)*(b+1)*(C+1)。
因数和约数:
约数和因数既有联系,又有区别,这主要表现在以下三个方面。
(1) 约数必须在整除的前提下才存在,而因数是从乘积的角度来提出的。如果数a与数b相乘的积是数c,a与b都是c的因数。
(2) 约数只能对在整数范围内而言,而因数就不限于整数的范围。
例如:6×8=48。既可以说6和8都是48的因数,也可以说6和8都是48的约数。
又如:0.9×8=7.2。虽然可以说0.9和8都是7.2的因数,却不能说0.9和8是7.2的约数。

代码如下:

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        long[] ans = new long[10001000];
        long n = sc.nextLong();
        ans[0] = 1;
        String str = largeIntegerFactorial(n, ans);
        int t = 2;
        String mm = "";
        int z = 0;
        ArrayList chuCun = new ArrayList();
        BigInteger a = new BigInteger(str);
        while (1 == 1) {
            if (zhiShu(t)) {
                mm = mm + t;
                if (a.equals(new BigInteger("1"))) {
                    break;
                }
                a = a.divide(new BigInteger(mm));
                z++;
                if (!a.mod(new BigInteger(mm)).equals(new BigInteger("0"))) {
                    chuCun.add(z + 1);//将调用这个质数的次数赋值给集合
                    z = 0;
                    t++;
                }
                mm = "";
            }
            if (zhiShu(t) == false) {
                t++;
            }
        }
        BigInteger kk = new BigInteger("1");
        String gg = "";
        for (int i = 0; i < chuCun.size(); i++) {
            int sss = (int) chuCun.get(i);
            gg = gg + sss;
            kk = kk.multiply(new BigInteger((gg)));
            gg = "";
        }
        System.out.println(kk);
    }

    public static boolean zhiShu(int a) {
        for (int i = 2; i <= a; i++) {
            if (i == a) {
                return true;
            }
            if (a % i == 0) {
                break;
            }
        }
        return false;
    }

    public static String largeIntegerFactorial(long n, long[] ans) {
        int l = 0;
        long num = 0;
        for (int i = 1; i <= n; ++i) {
            num = 0;
            for (int j = 0; j <= l; j++) {
                num = num + ans[j] * i;
                ans[j] = num % 10;
                num /= 10;
            }
            while (num != 0) {
                ans[++l] = num % 10;
                num /= 10;
            }
        }
        StringBuilder sb = new StringBuilder();
        for (int i = l; i >= 0; --i) {
            sb.append(ans[i]);
        }
        return sb.toString();
    }
}

三、逆序对的数量

算法题目:
给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i<j 且 a[i]>a[j],则其为一个逆序对;否则不是。
输入格式:
第一行包含整数 n,表示数列的长度。

第二行包含 n 个整数,表示整个数列。
输出格式:
输出一个整数,表示逆序对的个数。
数据范围:
1 ≤ n ≤ 100000,  
数列中的元素的取值范围 [1,10^9]。
输入样例:
6
2 3 4 5 6 1
输出样例:
5

算法核心:

  其实在本算法中最主要的就是如下两句代码:
    res += arr[p1] > arr[p2] ? (mid - p1 + 1) : 0;
    help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
  其实就是一句话:
     当左边指针p1指向的数arr[p1]和右边指针p2指向的数arr[p2]相等时,要先拿左边的数。

算法代码:

C++代码:
#include <iostream>
using namespace std;
long long res = 0;
void mergeSort(int arr[], int l, int r){
    if(l >= r){
        return;
    }
    int mid = l + ((r - l) >> 1);
    mergeSort(arr, l, mid);
    mergeSort(arr, mid + 1, r);
    int i = 0;
    int help[r - l + 1];
    int p1 = l;
    int p2 = mid + 1;
    while(p1 <= mid && p2 <= r){
        res += arr[p1] > arr[p2] ? (mid - p1 + 1) : 0;
        help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
    }
    while(p1 <= mid){
        help[i++] = arr[p1++];
    }
    while(p2 <= r){
        help[i++] = arr[p2++];
    }
    for(p1 = l, p2 = 0; p1 <= r; ++p1, ++p2){
        arr[p1] = help[p2];
    }
}
int main(){
    int n;
    scanf("%d", &n);
    int arr[n];
    for(int i = 0; i < n; i++){
        scanf("%d", &arr[i]);
    }
    mergeSort(arr, 0, n - 1);
    cout << res;

    return 0;
}
Java代码:
import java.util.Scanner;
public class Main {
    public static long x;
    public static void mergeSort(long[] arr){
        mergeSort(arr, 0, arr.length - 1);
    }
    public static void mergeSort(long[] arr, int l, int r){
        if(l >= r){
            return;
        }
        int mid = l + ((r - l) >> 1);
        mergeSort(arr, l, mid);
        mergeSort(arr, mid + 1, r);

        long[] help = new long[r - l + 1];
        int i = 0;
        int p1 = l;
        int p2 = mid + 1;
        while(p1 <= mid && p2 <= r){

            x += arr[p1] > arr[p2] ? (mid - p1 + 1) : 0;
            help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        while(p1 <= mid){
            help[i++] = arr[p1++];
        }
        while(p2 <= r){
            help[i++] = arr[p2++];
        }
        for(i = 0; i < help.length; ++i){
            arr[l + i] = help[i];
        }
    }

    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
        String[] str1 = in.readLine().split(" ");
        int n = Integer.parseInt(str1[0]);
        long[] arr = new long[n];
        String[] str2 = in.readLine().split(" ");
        for(int i = 0; i < n; i++){
            arr[i] = Integer.parseInt(str2[i]);
        }
        mergeSort(arr, 0, n - 1);
        System.out.println(x);
        out.flush();
        out.close();
        in.close();
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董陌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值