算法设计与分析 中级提升四

简介: 算法设计与分析 中级提升四

中级提升四

题目一:最小路径和

  • 题目:image.png
  • 暴力递归:
    (1)尝试方法:往下或者右的位置一次尝试
    (2)basecase的确定:右下角
    (3)特殊情况:在最右边,只能往下走;在最下边,只能往右走
    public static int minPath(int[][] matrix){
        if (matrix == null){
            return 0;
        }
        return process(matrix, 0, 0);
    }
    public static int process(int[][] martix, int x, int y){
        if (x == martix.length - 1 && y == martix[0].length - 1){
            //basecase
            return martix[x][y];
        }
        if (x == martix.length - 1){
            //最右端,只能往下走
            return martix[x][y] + process(martix, x, y + 1);
        }
        if (y == martix[0].length - 1){
            //最底端
            return martix[x][y] + process(martix, x + 1, y);
        }
        int right = process(martix, x + 1, y);
        int down = process(martix, x, y + 1);
        return martix[x][y] + Math.min(right, down);
    }
  • 动态规划:
    (1)两个可变参数,建立二维的dp表
    (2)dp表的右下角,以及最右边,和最下边,可以直接确定结果
    (3)dp表中的普遍位置依赖关系:由其右边与下边的较小值决定
    (4)确定填表顺序,从dp的右下角到左上角
    (5)最终位置:dp[0][0]
    public static int minPath(int[][] matrix){
        if (matrix == null){
            return 0;
        }
        int[][] dp = new int[matrix.length][matrix[0].length];
        dp[matrix.length - 1][matrix[0].length - 1] = matrix[matrix.length - 1][matrix[0].length - 1];
        for (int x = matrix.length - 2; x >= 0 ; x--){
            //只能往右走
            dp[x][matrix[0].length - 1] = dp[x + 1][matrix[0].length - 1] + matrix[x][matrix[0].length - 1];
        }
        for (int y = matrix[0].length - 2; y >= 0 ; y--){
            //只能往下走
            dp[matrix.length - 1][y] = dp[matrix.length - 1][y + 1] + matrix[matrix.length - 1][y];
        }
        for (int x = matrix.length - 2; x >= 0; x--){
            for (int y = matrix[0].length - 2; y >= 0; y--){
                dp[x][y] = Math.min(dp[x + 1][y], dp[x][y + 1]) + matrix[x][y];
            }
        }
        return dp[0][0];
    }

题目二:容器装水问题

  • 题目:
    image.pngimage.png
  • 思路:
    (1)先找到每一个位置可以容纳水的值,其由左侧的最大值与右侧的最大值决定
    (2)单个位置存水量的表达式:max(min(leftMax,rightMax)- 当前位置高度,0)与0比较防止负数的出现
    (3)最终将每一个位置加起来,就是其整体的容纳水量
    (4)实现过程:leftMax与rightMax数组,存储每个位置左右的最大值,再求每一个位置的存水量
    public static int maxWater(int[] arr) {
        int[] leftMax = new int[arr.length];
        int[] rightMax = new int[arr.length];
        int sum = 0;
        int max = Integer.MIN_VALUE;
        for (int i = 1; i < arr.length; i++) {
            max = Math.max(max, arr[i - 1]);
            leftMax[i] = max;
        }
        max = Integer.MIN_VALUE;
        for (int i = arr.length - 2; i >= 0; i--) {
            max = Math.max(max, arr[i + 1]);
            rightMax[i] = max;
        }
        for (int i = 0; i < arr.length; i++) {
            sum += Math.max((Math.min(leftMax[i], rightMax[i]) - arr[i]), 0);
        }
        return sum;
    }
  • 优化:
    (1)不使用leftMax与rightMax数组,只使用两个变量记录最值
    (2)使用双下标left,right遍历数组,两边可以通过leftMax与rightMax中,较小的值确定一边的存水量,同时不断更新leftMax与rightMax
    public static int maxWater(int[] arr){
        int left = 0;
        int right = arr.length - 1;
        int leftMax = arr[left];
        int rightMax = arr[right];
        int sum = 0;
        while (left < right){
            if (leftMax > rightMax){
                right--;
                if (arr[right] < rightMax){
                    sum += (rightMax - arr[right]);
                }else {
                    rightMax = arr[right];
                }
            }else {
                left++;
                if (arr[left] < leftMax){
                    sum += (leftMax -  arr[left]);
                }else {
                    leftMax = arr[left];
                }
            }
        }
        return sum;
    }

题目三:分割数组最大值

  • 题目:image.png
  • 思路:
    (1)预处理,得到每个位置左侧与右侧的最大值
    (2)按照要求找到最大差值即可
    public static int maxDevide(int[] arr) {
        int[] leftMax = new int[arr.length];
        int[] rightMax = new int[arr.length];
        int res = 0;
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < arr.length; i++) {
            max = Math.max(max, arr[i]);
            leftMax[i] = max;
        }
        max = Integer.MIN_VALUE;
        for (int i = arr.length - 1; i >= 0; i--) {
            max = Math.max(max, arr[i]);
            rightMax[i] = max;
        }
        for (int i = 0; i < arr.length; i++) {
            res = Math.max(res, Math.abs(leftMax[i] - rightMax[i]));
        }
        return res;
    }
  • 优化
    (1)先找到整个数组的最大值,因为是整体划分成两部分,结果一定是max - 某个数
    (2)若max划分到左部分,那么为了使得差值最大,右部分就是最右侧的数
    (3)同理,若max划分到右部分,那么为了使得差值最大,左部分就是最左侧的数
    (4)找到这两种情况产生的最大值即可
    public static int maxDevide(int[] arr) {
        int leftMax = arr[0];
        int rightMax = arr[arr.length - 1];
        int max = 0;
        for (int i : arr){
            max = Math.max(max, i);
        }
        return Math.max(max - leftMax, max - rightMax);
    }

题目四:旋转字符串

  • 题目:image.png
  • 思路:
    (1)先判断a,b字符串的长度是否相同,若不相同直接放回false
    (2)a = a + a
    (3)判断b是否是a的字串,可以使用kmp算法
    public static boolean circleString(String a, String b){
        if (a.length() != b.length()){
            return false;
        }
        a = a + a;
        return kmp(a, b);
    }
    public static boolean kmp(String str1, String str2){
        if (str1 == null || str2 == null || str1.length() < 1 || str2.length() < 1){
            return false;
        }
        char[] chs1 = str1.toCharArray();
        char[] chs2 = str2.toCharArray();
        int index1 = 0;
        int index2 = 0;
        int[] next = getNextArray(chs2);//str2的nextArr数组
        while (index1 < chs1.length && index2 < chs2.length){
            if (chs1[index1] == chs2[index2]){
                index1++;
                index2++;
            } else if (index2 == 0){
                //等同与:else if (next[index2] == -1)
                //index2回到0位置,且str1仍不匹配,直接使index1移动
                index1++;
            } else {
                //index2的跳跃,就是对应下标next数组中的值
                index2 = next[index2];
            }
        }
        //判断是否匹配成功
        return index2 == chs2.length;
    }
    public static int[] getNextArray(char[] str) {
        if (str.length == 1){
            return new int[] {-1};
        }
        //默认,0位置是-1,1位置是1
        int[] nextArr = new int[str.length];
        nextArr[0] = -1;
        nextArr[1] = 1;
        int index = 2; //next数组的位置
        int pre = 0; //要比较字符的位置(前缀的下一个)也有前缀长度的含义
        while (index < str.length){
            if (str[index - 1] == str[pre]){
                //index - 1 与 pre 位置相同
                //nextArr[index] 就是上一个位置加一
                //pre:前缀长度增加
                nextArr[index] = nextArr[index - 1] + 1;
                index++;
                pre++;
            } else if (pre > 0){
                //不相同,跳到上一个前缀进行比较
                pre = nextArr[pre];
            }else {
                //跳到了最前面,也不相同
                nextArr[index] = 0;
                index++;
            }
        }
        return nextArr;
    }

题目五:咖啡杯问题

  • 题目

首先,给你几个数据:

数组arr:表示几个咖啡机,这几个咖啡机生产一杯咖啡所需要的时间就是数组中的值,例如arr=[2,3,7]就表示第一台咖啡机生产一杯咖啡需要2单位时间,第二台需要3单位时间,第三台需要7单位时间。

int N:表示有N个人需要用咖啡机制作咖啡,每人一杯,同时,假设制作完咖啡后,喝咖啡时间为0,一口闷。

int a:表示用洗碗机洗一个咖啡杯需要的时间,串行运行。

int b:表示咖啡杯也可以不洗,自然晾干的时间,咖啡杯要么洗,要么晾干。

现在,请你求出这N个人从开始用咖啡杯制作咖啡到杯子洗好或者晾干的最少时间?

  • 思路:
    (1)先生成每一个人对应最短时间喝完咖啡的时间drinkTimes
    (2)drinkTimes可以通过小根堆的结果获得,小根堆元素由两个元素组成:机器空闲的时间,机器生成一次咖啡的时间。根据二者的和来确定小根堆的比较方式
    (3)便可以根据dirnkTimes尝试a,b两个洗杯子的时间,暴力递归得到最小时间
    public static class Machine{
        //堆中放入的数据类型
        public int timePoint; //一个机器可以开始使用的时间
        public int workTime; //生成一杯咖啡的时间
        public Machine(int t, int w){
            timePoint = t;
            workTime = w;
        }
    }
    public static class MachineComparator implements Comparator<Machine>{
        //小根堆的比较器
        public int compare(Machine o1, Machine o2){
            return (o1.workTime + o1.timePoint) - (o2.workTime + o2.timePoint);
        }
    }
    public static int minTime(int[] arr, int n, int a, int b){
        PriorityQueue<Machine> heap = new PriorityQueue<Machine>(new MachineComparator());
        for (int i : arr){
            //初始的开始时间均为0,机器运行时间在arr中
            heap.add(new Machine(0, i));
        }
        int[] drinkTimes = new int[n];
        for (int i = 0; i < n; i++){
            //使用堆排序填drinkTimes
            Machine cur = heap.poll();
            cur.timePoint += cur.workTime;
            drinkTimes[i] = cur.timePoint;
            heap.add(cur);
        }
        return process(drinkTimes, a, b, 0, 0);
    }
    public static int process(int[] drinkTimes, int a, int b, int index, int washLine){
        //washTime:洗咖啡的机器有空的时间
        //index:目前遍历到drinkTimes的位置
        if (index == drinkTimes.length - 1){
            //basecase
            //使用机器要受机器是否有空的时间控制
            return Math.min(Math.max(washLine, drinkTimes[index]) + a, drinkTimes[index] + b);
        }
        //选择机器洗,计算时间
        int wash = Math.max(washLine, drinkTimes[index]) + a;
        //洗完剩余咖啡杯最早的结束时间
        int next1 = process(drinkTimes, a, b, index + 1, wash);
        //需要完成所有事情的时间,是由前二者的较大值决定的
        int p1 = Math.max(wash, next1);
        //选择风干
        int dry = drinkTimes[index] + b;
        //风干剩余咖啡杯最早的结束时间
        int next2 = process(drinkTimes, a, b, index + 1, washLine);
        //需要完成所有事情的时间,是由前二者的较大值决定的
        int p2 = Math.max(wash, next1);
        return Math.min(p1, p2);
    }

题目六:arr数组的特殊要求

  • 题目:image.png
  • 思路:
    (1)将arr中的数分为3类,奇数,因子含4的偶数,因子不含4的偶数,假设个数为a,b,c
    (2)因为:奇数 * 奇数为奇数,奇数 * 因子不含4的偶数不满足题意,故奇数一定要与含4的偶数相邻;因子不含4的偶数要与偶数相邻
    (3)故我们可以通过三种数字的个数判断arr的要求能否成功
  • 情况分析
    (1)c == 0:奇 _ 含4因子 _ 奇 _ 含四因子 _ 奇 ; b >= a - 1
    (2)c != 0:将不含二因子的偶数放到一起 _ 含4因子 _ 奇 _ 含四因子 _ 奇 ;b >= a
  • 代码:
    public static boolean adjustArr(int[] arr){
        int a = 0;
        int b = 0;
        int c = 0;
        for (int i : arr){
            if (i % 4 == 0){
                b++;
            }else if (i % 2 == 0){
                c++;
            }else {
                a++;
            }
        }
        return c == 0 && b >= a - 1 || c != 0 && b >= a;
    }

题目七:仅用栈实现队列

  • 题目:在只有栈的数据结构的情况下,如何实现队列
  • 应用:在一些必须使用队列的题目中,只允许使用栈的情况下,可以使用提供的栈,实现队列的功能。例如:使用栈的结构实现图的广度优先遍历
  • 思路:
    (1)构造两个栈push,pop
    (2)每次加入数据时先加入push栈
    (3)若要弹出数据先检查pop栈是否为空,若pop栈为空,一次性将push栈中的数据全部加入pop栈,再弹出pop栈的栈顶元素;若pop栈不为空则直接弹出pop的栈顶
  • 代码:
    public static class TwoStacksQueue{
        private Stack<Integer> pushStack;
        private Stack<Integer> popStack;
        public TwoStacksQueue(){
            pushStack = new Stack<Integer>();
            popStack = new Stack<Integer>();
        }
        public void push(int value){
            pushStack.push(value);
        }
        public int pop(){
            if (pushStack.empty() &&popStack.empty()){
                throw new RuntimeException("Queue is empty");
            }
            if (popStack.isEmpty()){
                while (!pushStack.isEmpty()){
                    popStack.push(pushStack.pop());
                }
            }
            return popStack.pop();
        }
        public int peek(){
            if (pushStack.empty() &&popStack.empty()){
                throw new RuntimeException("Queue is empty");
            }
            if (popStack.isEmpty()){
                while (!pushStack.isEmpty()){
                    popStack.push(pushStack.pop());
                }
            }
            return popStack.peek();
        }
    }

题目八:仅用队列实现栈

  • 题目:在只有队列的数据结构的情况下,如何栈
  • 思路:
    (1)构造两个队列one,two
    (2)每次所有的数据都只在一个队列中
    (3)若要加入数据只能加入到目前有数据的一个队列中,若目前两个队列均为空,默认加入队列one
    (4)若要弹出数据,先将有数据的队列将除了最后一个全部加入另一个空队列,最后将最后一个元素返回即可
  • 代码:
    public static class TwoQueueStack{
        private Queue<Integer> one;
        private Queue<Integer> two;
        public TwoQueueStack(){
            one = new LinkedList<Integer>();
            two = new LinkedList<Integer>();
        }
        public void push(int value){
            if (two.isEmpty()){
                one.add(value);
            }else {
                two.add(value);
            }
        }
        public int pop(){
            int res = 0;
            if (one.isEmpty() && two.isEmpty()){
                throw new RuntimeException("Stack is empty");
            }else {
                if (one.isEmpty()){
                    while (!two.isEmpty()){
                        res = two.poll();
                        if (!two.isEmpty()){
                            one.add(res);
                        }
                    }
                }else {
                    while (!one.isEmpty()){
                        res = one.poll();
                        if (!one.isEmpty()){
                            two.add(res);
                        }
                    }
                }
            }
            return res;
        }
    }


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