力扣刷题----42. 接雨水

给定 n 个非负整数表示每个宽度为 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 个单位的雨水(蓝色部分表示雨水)。 
复制代码
输入:height = [4,2,0,3,2,5]
输出:9

自己写的

public class Solution

{

public int Trap(int[] height)

{

int[] Theleft=new int[height.Length];

int[] Therightside=new int[height.Length];

int[] res=new int[height.Length];

int result = 0;

int left=Theleft[0];

int right = Therightside[Therightside.Length - 1];

for (int i = 0; i < height.Length; i++)

{

if (left <= height[i])

{

Theleft[i] = 0;

left = height[i];

}

else

{

Theleft[i] = left - height[i] ;

}

}

for (int i = height.Length-1; i >= 0; i--)

{

if (right <= height[i])

{

Therightside[i] = 0;

right = height[i];

}

else

{

Therightside[i] = right - height[i];

}

}

for (int i = 0; i < height.Length; i++)

{

res[i]= Math.Min(Theleft[i], Therightside[i]);

}

for (int i = 0; i < res.Length; i++)

{

result=result + res[i];

}

return result;

}

}

空间复杂度比较高 和官方的第一个题解一样

方法一:动态规划

对于下标 i,下雨后水能到达的最大高度等于下标 i 两边的最大高度的最小值,下标 i 处能接的雨水量等于下标 i 处的水能到达的最大高度减去 height[i]。

朴素的做法是对于数组 height 中的每个元素,分别向左和向右扫描并记录左边和右边的最大高度,然后计算每个下标位置能接的雨水量。假设数组 height 的长度为 n,该做法需要对每个下标位置使用 O(n) 的时间向两边扫描并得到最大高度,因此总时间复杂度是 O(n

2

)。

上述做法的时间复杂度较高是因为需要对每个下标位置都向两边扫描。如果已经知道每个位置两边的最大高度,则可以在 O(n) 的时间内得到能接的雨水总量。使用动态规划的方法,可以在 O(n) 的时间内预处理得到每个位置两边的最大高度。

创建两个长度为 n 的数组 leftMax 和 rightMax。对于 0≤i<n,leftMax[i] 表示下标 i 及其左边的位置中,height 的最大高度,rightMax[i] 表示下标 i 及其右边的位置中,height 的最大高度。

显然,leftMax[0]=height[0],rightMax[n−1]=height[n−1]。两个数组的其余元素的计算如下:

当 1≤i≤n−1 时,leftMax[i]=max(leftMax[i−1],height[i]);

当 0≤i≤n−2 时,rightMax[i]=max(rightMax[i+1],height[i])。

因此可以正向遍历数组 height 得到数组 leftMax 的每个元素值,反向遍历数组 height 得到数组 rightMax 的每个元素值。

在得到数组 leftMax 和 rightMax 的每个元素值之后,对于 0≤i<n,下标 i 处能接的雨水量等于 min(leftMax[i],rightMax[i])−height[i]。遍历每个下标位置即可得到能接的雨水总量。

但是官方的双指针更优化

方法三:双指针

动态规划的做法中,需要维护两个数组 leftMax 和 rightMax,因此空间复杂度是 O(n)。是否可以将空间复杂度降到 O(1)?

注意到下标 i 处能接的雨水量由 leftMax[i] 和 rightMax[i] 中的最小值决定。由于数组 leftMax 是从左往右计算,数组 rightMax 是从右往左计算,因此可以使用双指针和两个变量代替两个数组。

维护两个指针 left 和 right,以及两个变量 leftMax 和 rightMax,初始时 left=0,right=n−1,leftMax=0,rightMax=0。指针 left 只会向右移动,指针 right 只会向左移动,在移动指针的过程中维护两个变量 leftMax 和 rightMax 的值。

当两个指针没有相遇时,进行如下操作:

使用 height[left] 和 height[right] 的值更新 leftMax 和 rightMax 的值;

如果 height[left]<height[right],则必有 leftMax<rightMax,下标 left 处能接的雨水量等于 leftMax−height[left],将下标 left 处能接的雨水量加到能接的雨水总量,然后将 left 加 1(即向右移动一位);

如果 height[left]≥height[right],则必有 leftMax≥rightMax,下标 right 处能接的雨水量等于 rightMax−height[right],将下标 right 处能接的雨水量加到能接的雨水总量,然后将 right 减 1(即向左移动一位)。

当两个指针相遇时,即可得到能接的雨水总量。

下面用一个例子 height=[0,1,0,2,1,0,1,3,2,1,2,1] 来帮助读者理解双指针的做法。

class Solution {
    public int trap(int[] height) {
        int ans = 0;
        int left = 0, right = height.length - 1;
        int leftMax = 0, rightMax = 0;
        while (left < right) {
            leftMax = Math.max(leftMax, height[left]);
            rightMax = Math.max(rightMax, height[right]);
            if (height[left] < height[right]) {
                ans += leftMax - height[left];
                ++left;
            } else {
                ans += rightMax - height[right];
                --right;
            }
        }
        return ans;
    }
}

关于动态规划的介绍

动态规划(Dynamic Programming,简称DP)是一种在数学、管理科学、计算机科学、经济学和生物信息学等领域中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。

动态规划经常用于求解具有重叠子问题和最优子结构性质的问题。这里的"重叠子问题"是指在递归算法中反复出现的问题,而"最优子结构"是指问题的最优解包含其子问题的最优解。

动态规划的关键步骤:

  1. 识别子问题:将问题分解为小的子问题,这些子问题往往是原问题的规模较小的版本。

  2. 确定状态 :定义问题的解状态,通常用变量表示,例如 dp[i]

  3. 确定状态转移方程:找出状态之间的关系,即当前状态是如何由之前的一个或多个状态推导出来的。

  4. 确定初始状态和边界条件:确定状态转移的起点,即基本情况或边界条件。

  5. 确定求解策略:是自底向上(从最小的子问题开始解决)还是自顶向下(递归地解决)。

  6. 构造最优解:根据子问题的解构造原问题的解。

动态规划的常见应用:

  • 斐波那契数列:计算第n个斐波那契数。
  • 背包问题:在不超过背包容量的前提下,选择物品以最大化价值。
  • 最长公共子序列:找出两个序列的最长公共子序列。
  • 最短路径问题:如贝尔曼-福特算法解决带负权边的最短路径问题。
  • 矩阵链乘问题:计算矩阵乘法的最少操作次数。
  • 编辑距离问题:计算两个字符串之间的最小编辑距离。

示例:斐波那契数列的动态规划解法

斐波那契数列是一个典型的动态规划问题,其递归解法存在大量重复计算。动态规划解法如下:

  1. 状态定义dp[i] 表示斐波那契数列的第 i 个数。

  2. 状态转移方程dp[i] = dp[i-1] + dp[i-2]

  3. 初始状态dp[0] = 0dp[1] = 1

  4. 循环计算 :从 2n,依次计算 dp[i]

    using System;

    class Program
    {
    // 动态规划计算斐波那契数列的第n项
    static long Fibonacci(int n)
    {
    if (n <= 1)
    return n;

         long[] dp = new long[n + 1];
         dp[0] = 0;
         dp[1] = 1;
    
         for (int i = 2; i <= n; i++)
         {
             dp[i] = dp[i - 1] + dp[i - 2];
         }
    
         return dp[n];
     }
    
     static void Main()
     {
         int n = 10; // 计算斐波那契数列的第10项
         long result = Fibonacci(n);
         Console.WriteLine($"Fibonacci of {n} is {result}");
     }
    

    }

动态规划是一种非常强大的方法,可以显著提高算法的效率,特别是在解决具有重复子问题和最优子结构的问题时。

相关推荐
武子康7 分钟前
大数据-212 数据挖掘 机器学习理论 - 无监督学习算法 KMeans 基本原理 簇内误差平方和
大数据·人工智能·学习·算法·机器学习·数据挖掘
passer__jw76736 分钟前
【LeetCode】【算法】283. 移动零
数据结构·算法·leetcode
Ocean☾42 分钟前
前端基础-html-注册界面
前端·算法·html
顶呱呱程序1 小时前
2-143 基于matlab-GUI的脉冲响应不变法实现音频滤波功能
算法·matlab·音视频·matlab-gui·音频滤波·脉冲响应不变法
爱吃生蚝的于勒1 小时前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~1 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
王哈哈^_^2 小时前
【数据集】【YOLO】【VOC】目标检测数据集,查找数据集,yolo目标检测算法详细实战训练步骤!
人工智能·深度学习·算法·yolo·目标检测·计算机视觉·pyqt
星沁城2 小时前
240. 搜索二维矩阵 II
java·线性代数·算法·leetcode·矩阵
脉牛杂德2 小时前
多项式加法——C语言
数据结构·c++·算法
legend_jz2 小时前
STL--哈希
c++·算法·哈希算法