【动态规划】【C++算法】741摘樱桃

简介: 【动态规划】【C++算法】741摘樱桃

LeetCode741 摘樱桃

给你一个 n x n 的网格 grid ,代表一块樱桃地,每个格子由以下三种数字的一种来表示:

0 表示这个格子是空的,所以你可以穿过它。

1 表示这个格子里装着一个樱桃,你可以摘到樱桃然后穿过它。

-1 表示这个格子里有荆棘,挡着你的路。

请你统计并返回:在遵守下列规则的情况下,能摘到的最多樱桃数:

从位置 (0, 0) 出发,最后到达 (n - 1, n - 1) ,只能向下或向右走,并且只能穿越有效的格子(即只可以穿过值为 0 或者 1 的格子);

当到达 (n - 1, n - 1) 后,你要继续走,直到返回到 (0, 0) ,只能向上或向左走,并且只能穿越有效的格子;

当你经过一个格子且这个格子包含一个樱桃时,你将摘到樱桃并且这个格子会变成空的(值变为 0 );

如果在 (0, 0) 和 (n - 1, n - 1) 之间不存在一条可经过的路径,则无法摘到任何一个樱桃。

示例 1:

输入:grid = [[0,1,-1],[1,0,-1],[1,1,1]]

输出:5

解释:玩家从 (0, 0) 出发:向下、向下、向右、向右移动至 (2, 2) 。

在这一次行程中捡到 4 个樱桃,矩阵变成 [[0,1,-1],[0,0,-1],[0,0,0]] 。

然后,玩家向左、向上、向上、向左返回起点,再捡到 1 个樱桃。

总共捡到 5 个樱桃,这是最大可能值。

示例 2:

输入:grid = [[1,1,-1],[1,-1,1],[-1,1,1]]

输出:0

参数范围

n == grid.length

n == grid[i].length

1 <= n <= 50

grid[i][j] 为 -1、0 或 1

grid[0][0] != -1

grid[n - 1][n - 1] != -1

动态规划

起程和返程可能进过同一地点,此处的樱桃只能摘一次。转换一下思路:假定有两个机器人,第一个机器按起程路线走,第二个机器人按返程路线反向走。由于只能向右下,所以不会存在环,也不会往回走。假定移动step次后,第一个机器人在r1行,第二个机器人在r2行,则第一个机器人在第step-c1列,第二个机器人在step-c2列。同一格行列号相同 ==> 行列号之和(step)相同 ==> 不同step,一定不是同一格子。

小技巧: setp相同时,只需要判断行号或列号是否相同。

动态规划的填表顺序

时间复杂度:O((n*2)nn) 三层循环,第一层从小到大枚举step;第二层枚举第一个机器人所在行,第二层枚举第二个机器人所在行。

动态规划的状态表示

pre[r1][r2]表示 step步后,机器人一在r1,机器人二在r2 已经收获的最大樱桃数。

dp[nr1][nr2]表示 step+1步后,机器人一在nr1,机器人二在nr2 已经收获的最大樱桃数。

动态规划的转移方程

int i2 = (nr1 == nr2) ? 0 : grid[nr2][nc2];//如果两个机器人 在同一位置不重复计算
              dp[nr1][nr2] = max(dp[nr1][nr2],pre[r1][r2] + grid[nr1][nc1] + i2 );

动态规划的初始状态

pre[0][0] = grid[0][0];

动态规划的返回值

return (-1==pre.back().back())?0: pre.back().back();

代码

封装类

class CEnumGridEdge
{
public:
  void Init()
  {
    for (int r = 0; r < m_r; r++)
    {
      for (int c = 0; c < m_c; c++)
      {
        Move(r, c, r + 1, c);
        Move(r, c, r - 1, c);
        Move(r, c, r, c + 1);
        Move(r, c, r, c - 1);
      }
    }
  }
  const int m_r, m_c;
protected:
  CEnumGridEdge(int r, int c) :m_r(r), m_c(c)
  {
  }
  void Move(int preR, int preC, int r, int c)
  {
    if ((r < 0) || (r >= m_r))
    {
      return;
    }
    if ((c < 0) || (c >= m_c))
    {
      return;
    }
    OnEnumEdge(preR, preC, r, c);
  };
  virtual void OnEnumEdge(int preR, int preC, int r, int c) = 0;
};

复制类

class TNeiBoForGrid :  public CEnumGridEdge
{
public: 
  TNeiBoForGrid(const vector<vector<int>>& grid):m_grid(grid),
    CEnumGridEdge(grid.size(),grid.front().size())
  {
    m_vNext.assign(m_r, vector < vector<pair<int, int>>>(m_c));
    Init();
  }
  virtual void OnEnumEdge(int preR, int preC, int r, int c)
  {
    if ((-1 == m_grid[preR][preC]) || (-1 == m_grid[r][c]))
    {
      return;
    }
    if ((preR > r) || (preC > c))
    {
      return;
    }
    m_vNext[preR][preC].emplace_back(r, c);
  }
  const vector<vector<int>>& m_grid;
  vector < vector < vector<pair<int, int>>>> m_vNext;
};

核心代码

class Solution {
public:
  int cherryPickup(vector<vector<int>>& grid) {
    TNeiBoForGrid neiBo(grid);
    vector<vector<int>> pre(neiBo.m_r, vector<int>(neiBo.m_r, -1));
    pre[0][0] = grid[0][0];
    for (int step = 0; step < neiBo.m_r + neiBo.m_c - 2; step++)
    {
      vector<vector<int>> dp(neiBo.m_r, vector<int>(neiBo.m_r, -1));
      for(int r1 = max(0,step-(neiBo.m_c-1)) ; r1 <= min(step, neiBo.m_r-1);r1++ )
        for (int r2 = max(0, step - (neiBo.m_c - 1)); r2 <= min(step, neiBo.m_r-1); r2++)
        {
          if (-1 == pre[r1][r2])
          {
            continue;
          }
          const int c1 = step - r1;
          const int c2 = step - r2;
          for (const auto& [nr1, nc1] : neiBo.m_vNext[r1][c1])
          {
            for (const auto& [nr2, nc2] : neiBo.m_vNext[r2][c2])
            {
              int i2 = (nr1 == nr2) ? 0 : grid[nr2][nc2];//如果两个机器人 在同一位置不重复计算
              dp[nr1][nr2] = max(dp[nr1][nr2],pre[r1][r2] + grid[nr1][nc1] + i2 );
            }
          }
        }
      pre.swap(dp);
    }
    return (-1==pre.back().back())?0: pre.back().back();
  }
};

测试用例

template<class T>
void Assert(const T& t1, const T& t2)
{
  assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
  if (v1.size() != v2.size())
  {
    assert(false);
    return;
  }
  for (int i = 0; i < v1.size(); i++)
  {
    Assert(v1[i], v2[i]);
  }
}
int main()
{
  vector<vector<int>> grid;
  {
    Solution sln;
    grid = { {-1} };
    auto res = sln.cherryPickup(grid);
    Assert(0, res);
  }
  {
    Solution sln;
    grid = { {0} };
    auto res = sln.cherryPickup(grid);
    Assert(0, res);
  }
  {
    Solution sln;
    grid = { {1} };
    auto res = sln.cherryPickup(grid);
    Assert(1, res);
  }
  {
    Solution sln;
    grid = { {0,1,-1},{1,0,-1},{1,1,1} };
    auto res = sln.cherryPickup(grid);
    Assert(5, res);
  }
  {
    Solution sln;
    grid = { {1,1,-1},{1,-1,1},{-1,1,1} };
    auto res = sln.cherryPickup(grid);
    Assert(0, res);
  }
  {
    Solution sln;
    grid = { {1, -1, 1, -1, 1, 1, 1, 1, 1, -1}, { -1,1,1,-1,-1,1,1,1,1,1 }, { 1,1,1,-1,1,1,1,1,1,1 }, { 1,1,1,1,1,1,1,1,1,1 }, { -1,1,1,1,1,1,1,1,1,1 }, { 1,-1,1,1,1,1,-1,1,1,1 }, { 1,1,1,-1,1,1,-1,1,1,1 }, { 1,-1,1,-1,-1,1,1,1,1,1 }, { 1,1,-1,-1,1,1,1,-1,1,-1 }, { 1,1,-1,1,1,1,1,1,1,1 } };
    auto res = sln.cherryPickup(grid);
    Assert(0, res);
  }
}

2023年1月

class Solution {
public:
int cherryPickup(vector& grid) {
m_c = grid.size();
m_preDp.assign(1, vector(1, grid[0][0]));
for (int len = 1; len <= m_c * 2 - 2; len++)
{
vector dp;
dp.assign(len + 1, vector(len + 1, -1));
for (int r1 = 0; r1 <= min(len,m_c-1); r1++)
{
for (int r2 = 0; r2 <= r1; r2++)
{
const int c1 = len - r1;
const int c2 = len - r2;
if ((c1 >= m_c) || (c2 >= m_c))
{
continue;
}
if ((grid[r1][c1] < 0) || (grid[r2][c2] < 0))
{
continue;
}
int tmp1 = grid[r1][c1];
if (r1 != r2)
{
tmp1 += grid[r2][c2];
}
int tmp2 = -1;
Test(tmp2, grid, r1, r2, len - 1);
Test(tmp2, grid, r1 - 1, r2, len - 1);
Test(tmp2, grid, r1, r2 - 1, len - 1);
Test(tmp2, grid, r1 - 1, r2 - 1, len - 1);
if (tmp2 < 0)
{
continue;
}
dp[r1][r2] = tmp1 + tmp2;
}
}
m_preDp.swap(dp);
}
return max(0,m_preDp[m_c-1][m_c-1]);
}
void Test(int& iNew,const vector& grid,int r1, int r2, int iPreLen)
{
if (r1 < 0 || r1 > iPreLen)
{
return;
}
if (r2 < 0 || r2 > iPreLen)
{
return;
}
if (r1 < r2)
{
int tmp = r1;
r1 = r2;
r2 = tmp;
}
if (m_preDp[r1][r2] < 0)
{
return;
}
iNew = max(iNew,m_preDp[r1][r2]);
}
int m_c;
vector m_preDp;
};


扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。

https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程

https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版

https://download.csdn.net/download/he_zhidan/88348653

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境: VS2022 C++17

如无特殊说明,本算法用**C++**实现。

相关文章
|
5天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
23 2
|
13天前
|
存储 算法 安全
基于红黑树的局域网上网行为控制C++ 算法解析
在当今网络环境中,局域网上网行为控制对企业和学校至关重要。本文探讨了一种基于红黑树数据结构的高效算法,用于管理用户的上网行为,如IP地址、上网时长、访问网站类别和流量使用情况。通过红黑树的自平衡特性,确保了高效的查找、插入和删除操作。文中提供了C++代码示例,展示了如何实现该算法,并强调其在网络管理中的应用价值。
|
11天前
|
存储 算法 安全
基于哈希表的文件共享平台 C++ 算法实现与分析
在数字化时代,文件共享平台不可或缺。本文探讨哈希表在文件共享中的应用,包括原理、优势及C++实现。哈希表通过键值对快速访问文件元数据(如文件名、大小、位置等),查找时间复杂度为O(1),显著提升查找速度和用户体验。代码示例展示了文件上传和搜索功能,实际应用中需解决哈希冲突、动态扩容和线程安全等问题,以优化性能。
|
18天前
|
算法 安全 C++
用 C++ 算法控制员工上网的软件,关键逻辑是啥?来深度解读下
在企业信息化管理中,控制员工上网的软件成为保障网络秩序与提升办公效率的关键工具。该软件基于C++语言,融合红黑树、令牌桶和滑动窗口等算法,实现网址精准过滤、流量均衡分配及异常连接监测。通过高效的数据结构与算法设计,确保企业网络资源优化配置与安全防护升级,同时尊重员工权益,助力企业数字化发展。
38 4
|
2月前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
70 2
|
3月前
|
算法
动态规划算法学习三:0-1背包问题
这篇文章是关于0-1背包问题的动态规划算法详解,包括问题描述、解决步骤、最优子结构性质、状态表示和递推方程、算法设计与分析、计算最优值、算法实现以及对算法缺点的思考。
132 2
动态规划算法学习三:0-1背包问题
|
3月前
|
算法
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
这篇文章介绍了动态规划算法中解决最大上升子序列问题(LIS)的方法,包括问题的描述、动态规划的步骤、状态表示、递推方程、计算最优值以及优化方法,如非动态规划的二分法。
87 0
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
|
3月前
|
算法 数据处理 C++
c++ STL划分算法;partition()、partition_copy()、stable_partition()、partition_point()详解
这些算法是C++ STL中处理和组织数据的强大工具,能够高效地实现复杂的数据处理逻辑。理解它们的差异和应用场景,将有助于编写更加高效和清晰的C++代码。
62 0
|
7天前
|
算法 数据安全/隐私保护
室内障碍物射线追踪算法matlab模拟仿真
### 简介 本项目展示了室内障碍物射线追踪算法在无线通信中的应用。通过Matlab 2022a实现,包含完整程序运行效果(无水印),支持增加发射点和室内墙壁设置。核心代码配有详细中文注释及操作视频。该算法基于几何光学原理,模拟信号在复杂室内环境中的传播路径与强度,涵盖场景建模、射线发射、传播及接收点场强计算等步骤,为无线网络规划提供重要依据。
|
8天前
|
机器学习/深度学习 数据采集 算法
基于GA遗传优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
本项目基于MATLAB2022a实现时间序列预测,采用CNN-GRU-SAM网络结构。卷积层提取局部特征,GRU层处理长期依赖,自注意力机制捕捉全局特征。完整代码含中文注释和操作视频,运行效果无水印展示。算法通过数据归一化、种群初始化、适应度计算、个体更新等步骤优化网络参数,最终输出预测结果。适用于金融市场、气象预报等领域。
基于GA遗传优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真