接雨水的四种姿势——一篇文章彻底弄懂接雨水问题

前言

leetcode 42. 接雨水是一道业内著名的hard题,多次出现在面试场上,经久不衰,难住了一届又一届的候选人。

作为leetcode上热度最高的题目之一,题目评论区也是好一番热闹景象。有人表示看了三天做不出来,有人在评论区洋洋洒洒五六种解法。

其实在这么多的解法中,我们只需要着重掌握双指针和单调栈两种即可。

当然,暴力解法可以不屑,但不能不会。

所有的解法大致可以分为两类:按行求和按列求,所谓按“列”求,是指将雨水部分按列拆分,分别计算数组0位置,1位置,…,n-1位置的答案。

所谓按行求,是指将雨水部分按行拆分n个部分,然后分别计算每个部分能积攒多少水,最后再将结果汇总。

在本文给出的几种解法中,解法 1、2、3 都是按列求的,解法 4 是按行求的。

题目描述

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

解法一:暴力

超出时间限制,不能通过

算法思路

逐个计算每一列能存下的水量。

遍历整个数组。针对数组中的每个元素arr[i],都分别向左、向右遍历一遍数组,找到arr[i]左侧和右侧的最大值,计为leftMaxrightMax,如果leftMax <= arr[i]或者rightMax <= arr[i],说明当前这一列存不出水。否则当前列能存储的水量为min(leftMax, rightMax) - arr[i]

所以数组的i位置能存储的水量res[i] = max{0, min{max(arr[0...i-1]), max(arr[i+1...n])} - arr[i] }

再思考一下,如果max(arr[0...i-1]) > arr[i],那么数组0i-1位置的最大值和0i位置的最大值一定是相等的;如果max(arr[0...i-1]) <= arr[i],那么数组0i位置的最大值一定等于arr[i],所以为了避免和0之间取max的计算,上述公式可以化简为下面的形式。
r e s [ i ] = m i n { m a x { a r r [ 0... i ] } , m a x { a r r [ i . . . n − 1 ] } } − a r r [ i ] res[i] = min\{max\{arr[0...i]\}, max\{arr[i...n-1]\}\} - arr[i] res[i]=min{max{arr[0...i]},max{arr[i...n1]}}arr[i]

代码实现

int trap(vector<int>& height) {
	int sum = 0;
    int n = height.size();
    for (int i=0; i<n; i++) {
        int leftMax = 0;
        for (int j=0; j<=i; j++) {
            if (height[j] > leftMax) {
                leftMax = height[j];
            }
        }

        int rightMax = 0;
        for (int j=i; j<n; j++) {
            if (height[j] > rightMax) {
                rightMax = height[j];
            }
        }

        sum += min(leftMax, rightMax) - height[i];
    }

    return sum;
}

复杂度分析

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( 1 ) O(1) O(1)

解法二:双指针

从暴力解法中我们可以看到,要求数组i位置可以存储的水量,需要先求出0i位置的最大值max(arr[0...i]),再求出in-1位置的最大值max(arr[i...n-1]),两个值中取最小与arr[i]做差。

暴力解法之所以时间复杂度比较差,是因为对于数组中的每一个元素,都需要再遍历一遍数组才能得到它左右两侧的最大值。

所以我们可以通过预处理数组得到leftMax[]rightMax[]两个数组,leftMax[i]代表数组0i位置的最大值,leftMax[i] = max(leftMax[i-1], arr[i])rightMax[i]代表数组i位置到n-1位置的最大值,rightMax[i] = max(rightMax[i+1], arr[i])

这样我们就得到了如下的算法流程。

首先遍历数组,从左向右得到数组leftMax[],再从右向左得到rightMax[]。然后再遍历一遍数组,对于数组的每一个位置i,通过leftMax[i]rightMax[i]arr[i]得到结果,将结果汇总得到的值就是最终答案。

代码实现

C++
int trap(vector<int>& height) {
    int n = height.size();
    vector<int> leftMax(n, 0);
    leftMax[0] = height[0];
    for (int i=1; i<n; i++) {
        leftMax[i] = max(leftMax[i-1], height[i]);
    }

    vector<int> rightMax(n, 0);
    rightMax[n-1] = height[n-1];
    for (int i=n-2; i>=0; i--) {
        rightMax[i] = max(rightMax[i+1], height[i]);
    }

    int res = 0;
    for (int i=0; i<n; i++) {
        res += min(leftMax[i], rightMax[i]) - height[i];
    }

    return res;
}
Java
public int trap(int[] height) {
	int n = height.length;
    int[] leftMax = new int[n];
    leftMax[0] = height[0];
    for (int i=1; i<n; i++) {
        leftMax[i] = Math.max(leftMax[i-1], height[i]);
    }

    int[] rightMax = new int[n];
    rightMax[n-1] = height[n-1];
    for (int i=n-2; i>=0; i--) {
        rightMax[i] = Math.max(rightMax[i+1], height[i]);
    }

    int res = 0;
    for (int i=0; i<n; i++) {
        res += Math.min(leftMax[i], rightMax[i]) - height[i];
    }

    return res;
}
Go
func trap(height []int) int {
    n := len(height)
    leftMax := make([]int, n)
    leftMax[0] = height[0]
    for i:=1; i<n; i++ {
        leftMax[i] = max(leftMax[i-1], height[i])
    }

    rightMax := make([]int, n)
    rightMax[n-1] = height[n-1]
    for i:=n-2; i>=0; i-- {
        rightMax[i] = max(rightMax[i+1], height[i])
    }

    res := 0
    for i:=0; i<n; i++ {
        res += min(leftMax[i], rightMax[i]) - height[i]
    }
    return res
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n),借助了两个数组

解法三:双指针进阶

解法二中的双指针需要遍历三次数组,第一次得出数组leftMax[],第二次得出数组rightMax[],第三次才是根据leftMax[i]rightMax[i]height[i]得出结果。那我们能不能想办法把这三次遍历合并成一次呢?

我们设置两个指针,left指向数组的0位置,right指针指向数组的n-1位置。再使用两个变量leftMaxrightMaxleftMax的含义是数组0...left位置的最大值,rightMax的含义是数组right...n-1位置的最大值,这几个变量设置好后就有以下几种情况。

  • leftMax < rightMax,此时可以使用leftMax来结算height[left]位置的储水量。它的右侧可能还会有比rightMax更高的元素,但不会影响left位置的储水量。因为这种情况下left位置左侧的最大值是影响该位置储水量的瓶颈,此时res[left] = leftMax - height[left]
  • leftMax > rightMax,此时可以使用rightMax来结算right位置的储水量。同样的,它的左侧可能还会有比leftMax更高的元素,但都不影响right位置的储水量。因为这种情况下right位置右侧的最大值是影响该位置储水量的瓶颈。此时res[right] = rightMax - height[right]
  • leftMax == rightMax,此时既可以结算左侧,也可以结算右侧,或者左右两侧可以同时结算储水量。res[left] = leftMax - height[left]res[right] = rightMax - height[right]。但是要注意如果结算前left == right,此时只能结算一侧。

不断重复上述流程,哪侧结算就将哪侧的指针相应移动,并在移动的过程中更新leftMaxrightMax,直到两个指针会合,结算完最后一个位置的水量为止。

以题目中的示例1为例,height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]。初始状态left指针指向0位置,right指针指向n-1位置,leftMax = height[0] = 0rightMax = height[n-1] = 1

此时leftMax < rightMax,所以可以结算left位置的水量,res[0] = leftMax - height[0] = 0 - 0 = 0,计算后left指针向右偏移一位,leftMax更新为1

此时,leftMax == rightMax,我们既可以结算左侧,也可以结算右侧,也可以双侧结算。我们这里采用双侧结算的方式,首先计算左侧水量res[1] = leftMax - height[1] = 1 - 1 = 0left指针向右偏移;然后计算右侧水量res[11] = rightMax - height[11] = 1 - 1 = 0right指针向左偏移,同时更新rightMax变量。

此时,leftMax < rightMax,结算左侧水量。res[2] = leftMax - height[2] = 1 - 0 = 1left指针向右偏移一位,同时更新leftMax

此时,leftMax == rightMax,双侧结算。res[3] = leftMax - height[3] = 2 - 2 = 0left指针向右偏移;res[10] = rightMax - height[10] = 2 - 2 = 0right指针向左偏移。

此时leftMaxrightMax依然相等,所以双侧结算。res[4] = leftMax - height[4] = 2 - 1 = 1res[9] = rightMax - height[9] = 2 - 1 = 1left指针向左偏移,right指针向右偏移。

此时leftMaxrightMax依然相等,所以双侧结算。res[5] = leftMax - hegiht[5] = 2 - 0 = 2left指针向右偏移;res[8] = rightMax - height[8] = 2 - 2 = 0right指针向左偏移,rightMax变量同步更新为3

此时,leftMax < rightMax,所以左侧结算,res[6] = leftMax - height[6] = 2 - 1 = 1left指针右移,leftMax同步更新为3

注意,此时leftMaxrightMax相等,所以双侧结算。首先结算左侧,res[7] = leftMax - height[7] = 3 - 3 = 0left指针偏移。left指针偏移之后,就不再满足left <= right的条件了,证明流程应该终止,所以不再结算右侧,流程结束。

至此,整个求解流程就结束了。题目要求的结果,就是数组各个位置求得答案的汇总,即0 + 0 + 1 + 0 + 1 + 2 + 1 + 0 + 0 + 1 + 0 + 0 = 6

代码实现

C++
int trap(vector<int>& height) {
    int n = height.size();
    int leftMax = 0, rightMax = 0, left = 0, right = n-1, res = 0;

    while (left <= right) {
        leftMax = max(leftMax, height[left]);
        rightMax = max(rightMax, height[right]);

        if (leftMax < rightMax) {
            // 左侧结算
            res += leftMax - height[left++];
        } else if (leftMax > rightMax) {
            // 右侧结算
            res += rightMax - height[right--];
        } else {
            // 双侧结算
            res += leftMax - height[left++];
            if (left <= right) {
                res += rightMax - height[right--];
            }
        }
    }
    return res;
}
Java
public int trap(int[] height) {
    int n = height.length;
    int leftMax = 0, rightMax = 0, left = 0, right = n - 1, res = 0;
    while (left <= right) {
        leftMax = Math.max(leftMax, height[left]);
        rightMax = Math.max(rightMax, height[right]);

        if (leftMax < rightMax) {
            // 左侧结算
            res += leftMax - height[left++];
        } else if (leftMax > rightMax) {
            // 右侧结算
            res += rightMax - height[right--];
        } else {
            // 双侧结算
            res += leftMax - height[left++];
            if (left <= right) {
                res += rightMax - height[right--];
            }
        }
    }
    return res;
}
Go
func trap(height []int) int {
    n := len(height)
    leftMax, rightMax, left, right, res := 0, 0, 0, n-1, 0

    for left <= right {
        leftMax = max(leftMax, height[left])
        rightMax = max(rightMax, height[right])

        if leftMax < rightMax {
            res += leftMax - height[left]
            left++
        } else if leftMax > rightMax {
            res += rightMax - height[right]
            right--
        } else {
            res += leftMax - height[left]
            left++
            if left <= right {
                res += rightMax - height[right]
                right--
            }
        }
    }

    return res
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

解法四:单调栈

算法思路

到目前为止,我们已经得到了时间复杂度为 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)的解法。再想一下,什么时候能存住雨水呢?一定是左右两侧都有大于元素存在。那如何求解左右两侧的大于元素呢?单调栈!

算法流程是这样的。设置一个单调栈,栈底到栈顶元素单调递减。遍历数组,将遍历的元素arr[i]准备放入单调栈,此时有以下三种情况。(假设栈顶元素为stack[top])

  • 栈中元素为空或arr[i] < stack[top],此时arr[i]放入栈中不会破坏单调栈结构,直接放入。
  • arr[i] > stack[top],此时的arr[i]如果放入栈中,会破坏单调栈结构。此时将栈顶元素弹出,如果栈不为空,说明弹出的栈顶元素左侧有大于元素(就是此时弹栈后的栈顶元素),右侧也有大于元素(就是arr[i]),说明这里存在积水,可以结算积水的量。
    • 积水的高等于arr[i]弹栈之后的栈顶元素中的最小值 减去 弹出元素的值
    • 用一个变量index代表弹栈之后的栈顶元素在数组中的索引值,那么积水的宽等于i - index - 1
    • 两者相乘即为此处的积水量
  • arr[i] == stack[top],此时arr[i]既可以直接入栈,也可以弹出栈顶元素并结算积水量。为什么会这样呢?大家往下看。

我们给出一个示例:height = [4, 2, 2, 1, 3],它代表的柱状图如下所示,根据图片我们可以知道,这个示例的储水量等于4

根据上面描述的算法流程,首先准备将数组0位置的4放入单调栈。初始状态栈为空,0位置的4放入不会破坏单调栈结构,所以可以直接放入。

1位置的2入栈也不会破坏单调栈结构,所以也直接入栈。

接下来是2位置的2,栈顶元素也是2,命中了第三种情况,我们先让元素直接入栈。3位置的1也可以直接入栈。

接下来遍历到数组4位置的3,它大于现在的栈顶元素,不能直接入栈。所以将栈顶元素弹出。

栈顶元素弹出后,栈不为空,说明此时可以结算水量。结算的高是此时的栈顶元素2位置的2,与arr[i]之间的最小值2与弹出元素1的差,所以积水的高= 1。结算的宽是i与栈顶元素索引值的差再减1,所以是4 - 2 - 1 = 1,这就是本次结算的储水量。这个结果计算的其实是下图中红色框内的水量。

继续上述流程,此时4位置的3依然不能入栈,所以将栈顶元素弹出。

弹出后栈不为空,可以结算水量。积水的高为arr[i] = 3和栈顶元素2的最小值,也就是2,减去弹出元素2,所以积水的高为0,无论宽度为多少,本次结算结果都为0。大家发现了吗?如果元素相等时选择入栈,那么其实是产生了一次无意义的计算,对计算结果并没有影响。

此时,4位置的3依然不能入栈,将栈顶元素1位置的2弹出。

栈不为空,所以可以结算水量。水量的高是min(3, 4) - 2 = 3 - 2 = 1,水量的宽是i - 栈顶元素index - 1 = 4 - 0 - 1 = 3,本次结算结果为1 * 3 = 3,也就是下图中黄色框内的区域。

现在,4位置的3可以入栈了,数组遍历完成,流程结束。整体结果就是流程中每一轮结算的总和,即1 + 0 + 3 = 4。

我们再来看一下,如果元素相等时,我们将栈中元素弹出并结算会发生什么呢?

假设现在遍历到数组2位置的2,此时的状态应该是下面这样的。

此时我们将栈顶元素弹出,弹出后栈不为空,所以结算水量,结算的高等于遍历到的元素2位置的2和弹出后的栈顶元素0位置的4中的最小值,与弹出元素的差,所以显然结算水量的高等于0

随后,数组3位置的1入栈,4位置的3不能入栈,所以弹出栈顶元素,这些都和上述流程一样。此时4位置的3依然不能入栈,所以将2位置的2弹出并结算水量。结算水量的高等于min(3, 4) - 2 = 1,结算水量的宽等于i - 栈顶元素的index - 1 = 4 - 0 - 1 = 3,本次结算结果为1 * 3 = 3,结果与上面流程也是完全一致的。

代码实现

java
public int trap(int[] height) {
    int res = 0;
    Stack<Integer> stack = new Stack<>();

    for (int i=0; i<height.length; i++) {
        while (!stack.isEmpty() && height[stack.peek()] < height[i]) {
            int pop = stack.pop();
            if (stack.isEmpty()) {
                break;
            }

            int width = i - stack.peek() - 1;
            int h = Math.min(height[i], height[stack.peek()]) - height[pop];
            res += width * h;
        }

        stack.push(i);
    }

    return res;
}
C++
int trap(vector<int>& height) {
    int res = 0;
    stack<int> stack;
    for (int i=0; i<height.size(); i++) {
        while (!stack.empty() && height[stack.top()] < height[i]) {
            int top = stack.top();
            stack.pop();
            if (stack.empty()) {
                break;
            }
            int width = i - stack.top() - 1;
            int h = min(height[i], height[stack.top()]) - height[top];
            res += width * h;
        }
        stack.push(i);
    }
    return res;
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),每个元素最多入栈一次出栈一次
  • 空间复杂度: O ( n ) O(n) O(n),借助了一个大小为n的栈

如果觉得本篇文章对你有所帮助,请帮我点一个免费的赞和关注,这对我非常重要,谢谢!(手动鞠躬)

欢迎关注公众号:程序员冻豆腐,里面有我所有的原创文章

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/317370.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

每日算法打卡:蚂蚁感冒 day 13

文章目录 原题链接题目描述输入格式输出格式数据范围输入样例1&#xff1a;输出样例1&#xff1a;输入样例2&#xff1a;输出样例2&#xff1a; 题目分析示例代码 原题链接 1211. 蚂蚁感冒 题目难度&#xff1a;简单 题目来源&#xff1a;第五届蓝桥杯省赛C A/B组 题目描述…

纯 JavaScript 生成UUID和随机MD5值

在开发中&#xff0c;我们经常需要生成唯一的标识符或随机的哈希值。在这篇博客中&#xff0c;我将介绍如何使用纯 JavaScript 生成 UUID&#xff08;通用唯一标识符&#xff09;和随机 MD5 值的方法。这些方法适用于前端和后端开发&#xff0c;让我们一起深入浅出地了解吧。 前…

this.setState的注意事项

目录 1、this.setState的注意事项 2、是什么造成了this.setState()的不同步&#xff1f; 3、 那this.setState()什么时候同步&#xff0c;什么时候不同步&#xff1f; 3.1 经过React包装的onClick点击事件&#xff08;&#xff09; 3.2 没经过React包装的 原生点击事件 …

浅析Linux进程管理:current宏实现

本文基于Linux 5.10.186版本内核源码进行分析。 文章目录 current概述早期内核版本实现最新版本内核实现x86体系下的current宏实现ARMv8体系下的current实现 相关参考 current概述 Linux内核在运行时经常需要访问当前运行进程的task_struct指针&#xff0c;于是&#xff0c;系…

ptaR7-6/zzuli2106 有去有回

题目 输入n个整数&#xff0c;第一趟按从左到右间隔k个数取数据&#xff0c;然后第二趟再从右到左间隔k-1个数取余下的数&#xff0c;如果数据没有取完&#xff0c;下一趟再间隔k-2个从左到右取数据&#xff0c;如此反复&#xff0c;直到所有的数据取完为止。注意&#xff1a;…

【2023年度回顾】让我们在新的一年继续努力前行

每当我们在努力的时候都会想&#xff1a;为什么我要努力&#xff1f;躺着不舒服吗&#xff1f; 大家好&#xff01;我是命运之光&#xff0c;一名普普通通的计算机科学与技术专业的大三学生。 &#x1f4d5;回顾一下整个2023年 因为我有每天发朋友圈的习惯&#xff0c;所以这一…

QToolBar、QStatusBar和QDockWidget的使用

1. 工具栏 QToolBar 1.1 创建工具栏 1.1.1 工具栏的基本函数 设置工具栏的停靠区域 参数 Qt::LeftToolBarArea //左边 Qt::RightToolBarArea //右边 Qt::TopToolBarArea //顶部 Qt::BottomToolBarArea //底部 Qt::AllToolBarAreas //所有区域 Qt::NoToolBarArea //没有QMa…

数据结构与算法之美学习笔记:47 | 向量空间:如何实现一个简单的音乐推荐系统?

这里写自定义目录标题 前言算法解析总结引申 前言 本节课程思维导图&#xff1a; 很多人都喜爱听歌&#xff0c;以前我们用 MP3 听歌&#xff0c;现在直接通过音乐 App 在线就能听歌。而且&#xff0c;各种音乐 App 的功能越来越强大&#xff0c;不仅可以自己选歌听&#xff0…

HBase 复制、备份、迁移

行业分享 HBase金融大数据乾坤大挪移 https://www.jianshu.com/p/cb4a645dd66a HBase跨机房迁移技术分享总结 https://www.jianshu.com/p/defc787b2704 dbaplus181期&#xff1a;腾讯金融HBase跨机房迁移实战 https://m.qlchat.com/topic/details?topicId2000003847589595 ht…

原生Jdbc获取库、表、字段;驼峰与下划线转换

1、获取catalog 1&#xff09;代码如下&#xff1a; /*** 获取catalog** param jdbcdriver 驱动类(DriverClass)(com.mysql.cj.jdbc.Driver)* param url 地址(jdbc:mysql://10.20.30.40:3306)* param username 用户名* param password 密码*/public static List&l…

[acm算法学习] 后缀数组SA

学习自B站up主 kouylan 定义 后缀是包含最后个字母的子串 把字符串 str 的所有后缀按字典排序&#xff0c;sa[i]表示排名为 i 的后缀的开头下标 如何求解SA 倍增的方法 先把每个位置开始的长度为1的子串排序&#xff0c;在此基础上再把长度为2的子串排序&#xff08;长度…

[足式机器人]Part2 Dr. CAN学习笔记-Advanced控制理论 Ch04-7 LQR控制器 Linear Quadratic Regulator

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-Advanced控制理论 Ch04-7 LQR控制器 Linear Quadratic Regulator 线性控制器设计-轨迹跟踪&#xff08;Fellow a Desired Path&#xff09;

软件测试|如何实现字典的键值互换,你会了吗?

简介 在Python中&#xff0c;字典是一种非常有用的数据结构&#xff0c;它将数据存储为键值对&#xff0c;并且键必须是唯一的。有时候&#xff0c;我们可能需要将字典的键和值互换&#xff0c;以便查找或操作数据更加方便。本文将详细介绍如何在Python中实现字典键值的互换操…

【Effective Objective - C】—— 熟悉Objective-C

【Effective Objective - C】—— 熟悉Objective-C 熟悉Objective-C1.oc的起源消息和函数的区别运行期组件和内存管理要点&#xff1a; 2.在类的头文件中尽量少引入其他头文件向前声明要点&#xff1a; 3.多使用字面量语法&#xff0c;少用与之等价的方法字符串字面量字面数值字…

AntDesignBlazor示例——暗黑模式

本示例是AntDesign Blazor的入门示例&#xff0c;在学习的同时分享出来&#xff0c;以供新手参考。 示例代码仓库&#xff1a;https://gitee.com/known/BlazorDemo 1. 学习目标 暗黑模式切换查找组件样式覆写组件样式 2. 添加暗黑模式切换组件 1&#xff09;双击打开MainL…

在CMake中自定义宏 add_definitions(-DDEBUG)

hehedalinux:~/Linux/loveDBTeacher-v6$ tree . ├── CMakeLists.txt └── test.c0 directories, 2 files hehedalinux:~/Linux/loveDBTeacher-v6$ test.c #include <stdio.h> #define NUMBER 3int main() {int a 10; #ifdef DEBUGprintf("我是一个程序猿,我…

驾驭未来:从传统运维到智能化运维的转型之路

随着科技的飞速发展&#xff0c;企业的业务需求也在不断变化。为了满足这些需求&#xff0c;企业的IT架构逐渐向云原生、容器化和微服务化演进。作为支撑企业业务发展的运维人员&#xff0c;我们需要紧跟时代步伐&#xff0c;不断提升自己的技能和认知水平。 在2023年全球运维大…

评估LLM在细胞数据上的实用性(3)-基因层面的评估

目录 定义基因功能预测扰动预测基因网络分析 基因层面的评估基因功能预测扰动预测基因网络分析 定义 基因功能预测 基因功能预测对于识别基因在不同条件下的特性非常重要。因为人类大约有20,000个蛋白质编码基因&#xff0c;只有一些被标注了功能。对基因功能的准确预测可以帮…

CMake在静态库中链接静态库

hehedalinux:~/Linux/multi-v2$ tree . ├── calc │ ├── add.cpp │ ├── CMakeLists.txt │ ├── div.cpp │ ├── mult.cpp │ └── sub.cpp ├── CMakeLists.txt ├── include │ ├── calc.h │ └── sort.h ├── lib │ ├── l…

【WEB API自动化测试】接口文档与在线测试

这一篇我们主要介绍如何做API帮助文档&#xff0c;给API的调用人员介绍各个 API的功能, 输入参数&#xff0c;输出参数, 以及在线测试 API功能(这个也是方便我们自己开发调试) 我们先来看看我们的API最终帮助文档及在线测试最终达到的效果: 概要图 GET API 添加产品API: 删除…