问题转换 + dp + 单调栈
前言
递减、递增和单调栈紧密相关,单调栈是单调问题的基础。该题可练习预先的问题转换 + 单调栈的使用,除此之外,还可以感受动态规划的本质,记录信息并提供信息。
一、使数组按非递减顺序排序
二、单调栈+问题转换
1、思路版
/*
单调栈解:保持nums剩余元素递增,维持一个栈从右到左添加nums元素,呈递减。
问题转换:消除多少趟使得nums递增,消除的本质:元素x会被左边大于他的元素y消除,毕竟y的右边不允许比它小的。
到底元素x会被y第几次消除?nums中还存在很多像x 与 y关系的元素对。所以问题就变成了这些元素对中谁的y消除x的时间最长,成为了总步骤。
所以维持一个单调栈保持从右到左都是非递增的,如果不递减,就消除栈中所有x元素比当前y元素小的值,记录最大消除时间。
*/
public int totalSteps(int[] nums) {
// 用于存储从右到左的递减元素的下表,这个下标为了配合--消除时间数组,记录每个位置曾经消除了多少次。
Stack<Integer> stack = new Stack<>();
// ArrayDeque真的很好用。
// ArrayDeque<Integer> deque = new ArrayDeque<>();
int n = nums.length;
// 消除时间的数组,记录每个位置曾经消除多少右边的小元素x。
int[] dp = new int[n];
// 记录最多消除右边小元素x的数量。
int max = 0;
for (int i = n - 1; i >= 0; i--) {
// 左边的当前元素y需要消除栈中比它小的(右边的)元素x。
while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
// dp[i]表示它曾经消除了多少右边小元素x;+1表示要把当前栈顶这个比它小的消除掉;
// max()表示nums[i]既然消除了nums[pop],那么nums[i]也能消除nums[pop]曾经消除的所有小元素。
// 把两个消除区间合并一起,取消除最多元素的值。
dp[i] = Math.max(dp[i] + 1, dp[stack.pop()]);
// 记录当前为了递减而消除是否为最大消除数。
if (max < dp[i]) max = dp[i];
}
// 递减,直接加入单调递减栈中。
stack.push(i);
}
return max;
}
2、简洁版
// 简化版。
public int totalSteps2(int[] nums) {
Stack<Integer> stack = new Stack<>();
int dp[] = new int[nums.length], max = 0;
for (int i = nums.length - 1; i >= 0; stack.push(i--)) {
while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
max = Math.max(max, dp[i] = Math.max(dp[i] + 1, dp[stack.pop()]));
}
}
return max;
}
总结
1)脑筋急转弯、找规律并问题转换、信息预处理等是很多算法的预处理步骤。
2)单调栈练习。
3)dp本质记录有用的信息。