- https://leetcode-cn.com/problems/path-with-minimum-effort/solution/dfs-er-fen-cha-zhao-mo-ban-ti-by-kobe24o/">https://leetcode-cn.com/problems/path-with-minimum-effort/solution/dfs-er-fen-cha-zhao-mo-ban-ti-by-kobe24o/
- ☆☆☆1631. 最小体力消耗路径">☆☆☆1631. 最小体力消耗路径
- ☆☆☆410. 分割数组的最大值">☆☆☆410. 分割数组的最大值
- 题目标题难度划分
- 算法掌握难度程度划分
https://leetcode-cn.com/problems/path-with-minimum-effort/solution/dfs-er-fen-cha-zhao-mo-ban-ti-by-kobe24o/
- 「使……最大值尽可能小」是二分搜索题目常见的问法。
- 「将数组分割为 m 段,求……」是动态规划题目常见的问法。
☆☆☆1631. 最小体力消耗路径
你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。
一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。
请你返回从左上角走到右下角的最小 体力消耗值 。 示例 1:
输入:heights = [[1,2,2],[3,8,2],[5,3,5]] 输出:2 解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。 这条路径比路径 [1,2,2,2,5] 更优,因为另一条路劲差值最大值为 3 。
二分法+dfs
思路
「使……最大值尽可能小」是二分搜索题目常见的问法。
本题中,我们注意到当选取一个mid时,若矩阵中存在一条路径的体力<=mid时,便设置右边界为mid-1,保存mid;若不存在,则说明矩阵中不存在任何一条路径存在该值,故设置左边界为mid+1;
对于矩阵搜索中是否存在一条路径满足mid,可采用DFS,对于某点(i,j),在其满足条件的方向上,若不存在体力值<mid,则返回
;若存在一条,且最终该点可以走到(m-1,n-1),则返回true;
算法
本题的难度在与如何剪枝,方法如下:
- 每次二分查找维护一个二维数组vis,保证所有探索过的路径不会经过第二次。
若在探索途中发现体力值>mid,则无须继续探索该方向,选取下一个方向(因为只要找到一条路径即可)
实现
class Solution {vector<vector<int>> dir={{0,1},{0,-1},{1,0},{-1,0}};int m,n;public:int minimumEffortPath(vector<vector<int>>& heights) {m=heights.size();n=heights[0].size();int l=0,r=1e6,ans=0;while(l<=r){int mid=l+((r-l)>>1);vector<vector<bool>> vis(m,vector<bool>(n,false));if(dfs(heights,vis,0,0,mid)){// if x existed,x<=mid。r=mid-1;ans=mid;}else{//all x >midl=mid+1;}}return ans;}bool dfs(vector<vector<int>>& heights,vector<vector<bool>>& vis,int x,int y,int tar){if(x==m-1 && y==n-1){return true;}vis[x][y]=true;int i,j,k;for(k=0;k<4;k++){int i=x+dir[k][0];int j=y+dir[k][1];if(i>=0 && i<m && j>=0 && j<n&& !vis[i][j]&& abs(heights[i][j]-heights[x][y])<=tar){if(dfs(heights,vis,i,j,tar)){return true;}}}return false;}};
复杂度分析
时间复杂度:
- 空间复杂度:
,vis所占用的空间。
☆☆☆410. 分割数组的最大值
给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。
注意: 数组长度 n 满足以下条件:
1 ≤ n ≤ 10001 ≤ m ≤ min(50, n)示例:
输入:
nums = [7,2,5,10,8]
m = 2
输出:
18
解释:
一共有四种方法将nums分割为2个子数组。
其中最好的方式是将其分为[7,2,5] 和 [10,8],
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。
贪心+二分法
思路
「使……最大值尽可能小」是二分搜索题目常见的问法。
本题中,我们注意到:当我们选定一个值 mid,我们可以线性地验证是否存在一种分割方案,满足其最大分割子数组和不超过mid。策略如下:
针对mid,遍历数组,最大化每个子数组内元素的个数,保证sum(subArray)<=x;
- 若最大化子数组个数cnt<=m,说明依靠mid来分组,组数可能不满足,需要减少mid,故令右边界为mid
-
算法
设置左右边界: l=max(nums);r=sum(nums)
-
实现
class Solution {public:int splitArray(vector<int>& nums, int m) {int l=0,r=0,ans=0;for(auto val:nums){if(l<val) l=val;r+=val;}return binary(nums,m,l,r);}int binary(vector<int>& nums,int m,int l,int r){int n=nums.size();while(l<r){int mid=l+((r-l)>>1);int curSum=0,cnt=1;for(int i=0;i<n;i++){curSum+=nums[i];if(curSum>mid){cnt++;curSum=nums[i];}}if(cnt<=m){r=mid;}else{l=mid+1;}}return l;}};
复杂度分析
时间复杂度:
-
动态规划
思路
「将数组分割为 m 段,求……」是动态规划题目常见的问法。
设置f[i][j]为前i个数分割为j段的和的最大值的最小值。现在考虑状态转移方式:
对于f[i][j],考虑j-1段的问题,其为前k个元素,则f[i][``j]的值就取决于f[k][j-1]与sub(k,i),其中sub(k,i)为第j段的和,故状态转移方程如下:
由于不能分出空的数组,限制条件为,对于
i<j的情况,由于要求出最小值,故将其设为一个很大的值。算法
求解状态转移方程
- 对状态转移方程进行分析
-
实现
class Solution {public:int splitArray(vector<int>& nums, int m) {int n=nums.size();vector<vector<long long>> dp(n+1,vector<long long>(m+1,INT_MAX));dp[0][0]=0;vector<long long> segSum(n+1,0);for(int i=0;i<n;i++){segSum[i+1]=segSum[i]+nums[i];}for(int i=1;i<n+1;i++){//i>=jfor(int j=1;j<=min(i,m);j++){//k>=j-1for(int k=j-1;k<i;k++){dp[i][j]=min(dp[i][j],max(dp[k][j-1],segSum[i]-segSum[k]));}}}return dp[n][m];}};
复杂度分析
时间复杂度:
- 空间复杂度:
题目标题难度划分
| 星级 | 题目难度 |
|---|---|
| ☆ | 简单 |
| ☆☆ | 中等 |
| ☆☆☆ | 困难 |
算法掌握难度程度划分
| 星级 | 掌握难度 |
|---|---|
| 无 | 普通 |
| ❤ | 经典,易掌握 |
| ❤❤ | 经典,略难掌握 |
| ❤❤❤ | 困难 |

