发布 算法设计与分析 中级提升一

简介: 发布算法设计与分析 中级提升一

中级提升一

题目一:绳子覆盖点问题(滑动窗口)

  • 题目;image.png
  • 思路一:枚举 + 贪心
    将绳子右端放在arr[]数组的每一个端点上,依次尝试得到最大值image.png
  • 例:
    arr数组如下,L为5
    image.png
    当绳子的右端点来到0位置,2 - 5 = - 3,前面没有覆盖,只覆盖了0位置的一个点
    image.png
    当绳子的右端点来到1位置,4 - 5 = - 1,找最左侧端点,0位置的2,故覆盖2个端点
    image.png
    当绳子端点来到3位置,9 - 5 = 4,最左侧为1位置,故从1 ~ 3,覆盖3个点image.png
  • 思路二:滑动窗口
    (1)左右指针(l,r)
    (2)l每次从最左端点开始,r到达下一个端点,
    (3)r - l 是否 < L,若小于,点数加一,移动r,继续判断;若大于,则直接移动l
  • 例:
    l 在0位置时,r可以移动一次到达1位置,覆盖2个点
    image.png
    l 移动到1位置与 r相遇, 此时r可以连续移动2次到达3位置,覆盖3个点image.png
  • 时间复杂度分析:数组大小为N
    (1)思路一:若采用二分发查找每次可以覆盖的位置,O(N * logN)
    (2)思路二:只用将 l 指针在数组遍历一遍,O(N)
  • 代码实现
思路一:
    public static int maxPoint(int[] arr, int L){
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < arr.length; i++){
            //每个点均作为绳子的最右端
            max = Math.max(max, process(arr, L, i));
        }
        return max;
    }
    public static int process(int[] arr, int L, int index){
        //index最右端
        int point = 1; //点数的记录
        int pre = arr[index] - L;
        while (index - 1 >= 0 && arr[--index] >= pre){
          //index - 1 >= 0 防止越界
            point++;
        }
        return point;
    }
思路二:
    public static int maxPoint(int[] arr, int L){
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < arr.length; i++){
            //每个点均作为绳子的最左端,l指针移动
            max = Math.max(max, process(arr, L, i));
        }
        return max;
    }
    public static int process(int[] arr, int L, int l){
        int point = 1; //点数的记录
        int r = l + 1;
        while (r < arr.length && arr[r] - arr[l] <= L){
            //r指针移动
            point++;
            r++;
        }
        return point;
    }

题目二:装袋子问题(打表法)

打表法:只有一个输入和输出,通过观察结果总结规律,得到技巧

  • 题目:image.png
  • 思路:
    (1)若n为奇数一定不行,直接返回 -1
    (2)贪心策略:使用最多的8类型的袋子
    (3)n / 8,得到袋子数后,检查剩余的苹果能否被6的袋子装下,若能,则成功;若不能,减少8类型的袋子,分给6类型的袋子进行测试
  • 例:
    若 n = 100,100 / 8 = 12,剩余4个,6类型无法装下,12 - 1 = 11,4 + 8 = 12,6类型可以装下,尝试成功
  • 优化:
    在对剩余苹果能否被6类型袋子装下进行优化判断,若剩余的个数已经超过24(6与8的最小公倍数)则剩余个数再次增加,也不肯能装下,便可提前结束判断。
    解析:若剩余为x > 24,x = 24 + y = 3 * 8 + y;那么可以判断,24能由8类型解决,而剩余的y一定在前面就判断过不能被6类型的袋子装下
    public static int minBags(int apple){
        if (apple < 0 || apple % 2 != 0){
            //一定不成功的情况
            return -1;
        }
        int bag8 = apple / 8;
        int bag6 = 0;
        int rest = apple - bag8 * 8; //剩余个数
        while (bag8 >= 0 && rest <= 24){
            //将8的给了6
            if (rest % 6 == 0){
                bag6 = rest / 6;
                return bag8 + bag6;
            }
            bag8--;
            rest += 8;
        }
        return -1;
    }
  • 打表法:只有一个输入,一个输出,直接观察结果
    image.png
    image.png
    通过结果可以看出,前16个每有规律可以一一列举出来,从第17个开始每八个数为一组,偶数为3,4,5……,奇数全部是负一
    那么我们无需计算,通过输入数子,便可以直接得出答案
    public static int minBags(int apple){
        if (apple < 0 || apple % 2 != 0){
            //一定不成功的情况
            return -1;
        }
        if (apple <= 16){
            //小于16的一一对应输出
            if (apple == 0){
                return 0;
            }
            if (apple == 6 || apple == 8){
                return 1;
            }
            if (apple == 12 || apple == 14 || apple == 16){
                return 2;
            }
            return -1;
        }
        return (apple - 17) / 8 + 3;
    }

题目三:吃草问题(打表法)

  • 题目:

假设现在有两只动物吃草,每次只能吃4的次幂的草,也就是1、4、16等等,现在,给你一个整数N,代表总共有多少草,两只动物,只能交替吃草,有先手和后手区别,假设动物都绝顶聪明,哪只动物先吃完草,哪只就赢,请你求出N份草时,是先手赢还是后手赢?

  • 思路:
    (1)先手占据主动权,所以只要对于N份草,只要先手有机会赢,一定可以成功
    (2)那么,可以让先手尝试所有的可能,若尝试均不成功,则后手成功
    (3)使用递归函数表示,不断进行缩小规模,进行先后手的尝试
    (4)basecase,若N <= 0,无意义,若N = 1先手赢,N = 2 后手赢,N = 3 先手赢,N = 4 先手赢
  • 代码实现:
    public static String win(int N){
        if (N <= 0){
            return "输入错误";
        }
        if (N <= 4){
            //basecase
            return N == 2 ? "后手赢" : "先手赢";
        }
        int num = 1; //目前尝试的数值
        while (num <= N){
            if (win(N - num).equals("后手赢")){
                //若递归的后手赢,则代表目前的先手赢
                return "先手赢";
            }
            if (num  > N / 4){
                //越界
                break;
            }
            num *= 4;
        }
        //最后情况,先手无法获胜,只可能后手赢
        return "后手赢";
    }
  • 打表法:规律总结
    image.png
    处了0以外,其余能除以5余数为:2或是0的,结果均是后手
    public static String win(int N){
        if (N <= 0){
            return "输入错误";
        }
        if (N % 5 == 2 || N % 5 == 0){
            return "后手赢";
        }
        return "先手赢";
    }

题目四:涂染问题(预处理)

预处理:空间换时间,总结待处理数据的规律,减少时间复杂度

  • 题目:image.png
  • 思路:(枚举)分为左右侧,左侧全部是R,右侧全部是G,从左侧0个到左侧全部
    例:s = RGRGR
    (1)左侧0个:GGGGG,3个
    (2)左侧1个:RGGGG,2个
    (3)左侧2个:RRGGG,3个
    (4)左侧3个:RRRGG,2个
    (5)左侧4个:RRRRG,3个
    (6)左侧5个:RRRRR,2个
    时间复杂度:O(N ^ 2)
    public static int minPaint(String s){
        if (s == null || s.length() < 2){
            return 0;
        }
        char[] chs = s.toCharArray();
        int min = chs.length;
        for (int index = 0; index <= chs.length; index++){
            //目前左侧区域的大小,类似于分界线
            int test = 0;
            for (int i = 0; i < chs.length; i++){
                //遍历字符串
                if (i < index){
                    //左侧
                    if (chs[i] == 'G'){
                        test++;
                    }
                }
                else if (chs[i] == 'R'){
                    //右侧
                    test++;
                }
            }
            min = Math.min(min, test);
        }
        return min;
    }
  • 优化:预处理,时间复杂度O(N)
    (1)通过左侧分界线得到对应的左,右侧分别有几个需要染色,得到两个数组
    (2)例如s = RGRGR,left[]:左边几个G,right[]:右侧几个R
    left = [0,0,1,1,2,2]
    right = [3,2,2,1,1,0]
    (3)将left与right对应位置相加,取最小值
    public static int minPaint(String s){
        if (s == null || s.length() < 2){
            return 0;
        }
        char[] chs = s.toCharArray();
        int min = chs.length;
        int[] left = new int[chs.length + 1];
        int[] right = new int[chs.length + 1];
        int num = 0;
        for (int index = 0; index < chs.length; index++){
            //目前左侧区域的大小
            if (chs[index] == 'G'){
                num++;
            }
            //left要错位设置
            left[index + 1] = num;
        }
        num = 0;
        for (int index = chs.length - 1; index >= 0; index--){
            //目前左侧区域的大小
            if (chs[index] == 'R'){
                num++;
            }
            //right的最后一个不用设置,一定为0
            right[index] = num;
        }
        for (int index = 0; index < left.length; index++){
            min = Math.min(min, left[index] + right[index]);
        }
        return min;
    }

题目五:最大正方形(预处理)

  • 题目:image.png
  • 思路:
    (1)N * N的矩阵能产生多少种子矩阵,矩阵产生任意一个点为O(N ^ 2),任意两个点,分别作为子矩阵的左上角与右下角,便可确定一个矩阵,虽然会有重复,但是时间复杂度的数量级为O(N ^ 4)
    (2)N * N的矩阵能产生多少种子正方形矩阵,矩阵产生任意一个点为O(N ^ 2),使得该点为左上角,边长的取值可以从1 ~ N(边长根据矩阵的大小限制),故时间复杂度的数量级为O(N ^ 3)
    (3)那么通过(2)的思路,便可以遍历通过每一个点形成的正方形的边长大小,判断正方形的四条边是否全部为1
    public static int maxSquare(int[][] matrix){
        int max = Integer.MIN_VALUE;
        for (int row = 0; row < matrix.length; row++){
            for (int col = 0; col < matrix[0].length; col++){
                //确定左上角点
                int num = 0;
                for (int border = 1; border < Math.min(matrix.length - row, matrix[0].length - col); border++){
                    //边长的确定,行和列最小的限制边长的最大值
                    boolean flag = true;
                    if (matrix[row][col] == 0){
                        break;
                    }
                    //四条边的检测
                    for (int i = row; i < row + border; i++){
                        if (matrix[i][col] == 0) {
                            flag = false;
                            break;
                        }
                    }
                    for (int i = col; i < col + border; i++){
                        if (matrix[row][i] == 0) {
                            flag = false;
                            break;
                        }
                    }
                    for (int i = row; i <= row + border; i ++){
                        if (matrix[i][col + border] == 0) {
                            flag = false;
                            break;
                        }
                    }
                    for (int i = col; i <= col + border; i++){
                        if (matrix[row + border][i] == 0) {
                            flag = false;
                            break;
                        }
                    }
                    if (flag){
                        num = border;
                    }
                }
                max = Math.max(max, num);
            }
        }
        //包括开始位置,边长加一
        return max + 1;
    }

时间复杂度:O(N ^ 4),因为要判断四条边,时间复杂度上升

  • 优化:预处理
    (1)准备right数组,与down数组规模与原数组相同
    (2)right数组:表示该数字位置的右方有几个连续的1,数组从右下角开始,从右往左,再从下往上依次填写
    (3)down数组:表示该数字位置的下方有几个连续的1,数组从右下角开始,从下往上,再从右往左
    ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/be364da94cce4b598b68d8481c3d7341.png?x-oss-process=image/watermark,type_image.png
    (4)四条边的判断可以通过数组种的right[右上角点],right[右下角],down[右上角],down[左上角]进行判断
    (5)这样便可以减少一层for循环,将时间复杂度优化为O(N ^ 3)
    public static int maxSquare(int[][] matrix){
        int max = Integer.MIN_VALUE;
        int[][] right = new int[matrix.length][matrix[0].length];
        int[][] down = new int[matrix.length][matrix[0].length];
        for (int row = matrix.length - 1; row >= 0; row--){
            int num = 0;
            for (int col = matrix[0].length - 1; col >= 0; col--){
                if (matrix[row][col] == 1){
                    num++;
                    right[row][col] = num;
                }
            }
        }
        for (int col = matrix[0].length - 1; col >= 0; col--){
            int num = 0;
            for (int row = matrix.length - 1; row >= 0; row--){
                if (matrix[row][col] == 1){
                    num++;
                    down[row][col] = num;
                }
            }
        }
        for (int row = 0; row < matrix.length; row++){
            for (int col = 0; col < matrix[0].length; col++){
                //确定左上角点
                int num = 0;
                for (int border = 1; border < Math.min(matrix.length - row, matrix[0].length - col); border++){
                    //边长的确定,行和列最小的限制边长的最大值
                    boolean flag = true;
                    if (matrix[row][col] == 0){
                        break;
                    }
                    if (right[row][col] < border || down[row][col] < border || right[row + border][col] < border || down[row][col + border] < border){
                        //四条边的检测
                        flag = false;
                    }
                    if (flag){
                        num = border;
                    }
                }
                max = Math.max(max, num);
            }
        }
        //包括开始位置,边长加一
        return max + 1;
    }

题目六:随机数

  • 题目:image.png
  • 思路:
    (1)要扩大原随机数的范围,可通过二进制的转化实现
    (2)创建中间函数将 f 产生的1,2作为0;4,5作为1;3重新尝试,即将 f 函数的结果转化为0或1
    (3)1 ~ 7可以通过3位二进制表示,g调用3次 f 分别产生3个位置对应的数字,若产生0,重新调用
    public static int f(){
        return (int)(Math.random() * 5) + 1;
    }
    public static int temp(){
        //中间函数,将f函数的结果转化为0 / 1
        int res;
        do {
            res = f();
        }while (res == 3);
        return res < 3 ? 0 : 1;
    }
    public static int g(){
        int res;
        do {
            res = temp() * 4 + temp() * 2 + temp();
        }while (res == 0);
        return res;
    }
  • 题目image.png
  • 思路:
    (1)使用 f 连续产生二个数字组合
    (2)若为00或是11,重新产生
    (3)若是01,产生1;若是10,产生0;使得两者概率相同


目录
相关文章
|
1月前
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【10月更文挑战第4天】在大数据时代,算法效率至关重要。本文从理论入手,介绍时间复杂度和空间复杂度两个核心概念,并通过冒泡排序和快速排序的Python实现详细分析其复杂度。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1);快速排序平均时间复杂度为O(n log n),空间复杂度为O(log n)。文章还介绍了算法选择、分而治之及空间换时间等优化策略,帮助你在大数据挑战中游刃有余。
60 4
|
3月前
|
数据采集 机器学习/深度学习 算法
|
3月前
|
人工智能 算法 BI
第一周算法设计与分析 D : 两面包夹芝士
这篇文章介绍了解决算法问题"两面包夹芝士"的方法,通过找出两个数组中的最大最小值,计算这两个值之间的整数个数,包括特判不存在整数的情况。
|
24天前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
1月前
|
算法
PID算法原理分析
【10月更文挑战第12天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
2月前
|
算法 搜索推荐 开发者
别再让复杂度拖你后腿!Python 算法设计与分析实战,教你如何精准评估与优化!
在 Python 编程中,算法的性能至关重要。本文将带您深入了解算法复杂度的概念,包括时间复杂度和空间复杂度。通过具体的例子,如冒泡排序算法 (`O(n^2)` 时间复杂度,`O(1)` 空间复杂度),我们将展示如何评估算法的性能。同时,我们还会介绍如何优化算法,例如使用 Python 的内置函数 `max` 来提高查找最大值的效率,或利用哈希表将查找时间从 `O(n)` 降至 `O(1)`。此外,还将介绍使用 `timeit` 模块等工具来评估算法性能的方法。通过不断实践,您将能更高效地优化 Python 程序。
58 4
|
1月前
|
算法
PID算法原理分析及优化
【10月更文挑战第6天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
2月前
|
算法 程序员 Python
程序员必看!Python复杂度分析全攻略,让你的算法设计既快又省内存!
在编程领域,Python以简洁的语法和强大的库支持成为众多程序员的首选语言。然而,性能优化仍是挑战。本文将带你深入了解Python算法的复杂度分析,从时间与空间复杂度入手,分享四大最佳实践:选择合适算法、优化实现、利用Python特性减少空间消耗及定期评估调整,助你写出高效且节省内存的代码,轻松应对各种编程挑战。
41 1
|
2月前
|
算法 数据可视化
基于SSA奇异谱分析算法的时间序列趋势线提取matlab仿真
奇异谱分析(SSA)是一种基于奇异值分解(SVD)和轨迹矩阵的非线性、非参数时间序列分析方法,适用于提取趋势、周期性和噪声成分。本项目使用MATLAB 2022a版本实现从强干扰序列中提取趋势线,并通过可视化展示了原时间序列与提取的趋势分量。代码实现了滑动窗口下的奇异值分解和分组重构,适用于非线性和非平稳时间序列分析。此方法在气候变化、金融市场和生物医学信号处理等领域有广泛应用。
125 19
|
3月前
|
算法
算法设计与分析作业
这篇文章是关于算法设计与分析的作业,其中包含了两个算法实现:一个是使用分治算法实现的十进制大整数相乘(包括加法、减法和乘法函数),并进行了正确性和健壮性测试;另一个是使用快速排序思想实现的分治查找第K小元素的程序,并分析了其平均和最坏时间复杂度。
算法设计与分析作业