738 单调递增的数字
思路
题目要求小于等于N的最大单调递增的整数,那么拿一个两位的数字来举例。
例如:98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]--(首位如果不减1,所得的数 递增条件 与 小于等于该数 两全不得),然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数。
这一点如果想清楚了,这道题就好办了。
此时是从前向后遍历还是从后向前遍历呢?都试试
从前向后遍历的话,遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]减一,但此时如果strNum[i - 1]减一了,可能又小于strNum[i - 2]。因为第 i - 1 递减后,前面的第 i - 2没有考虑到,只考虑到后面的元素。
这么说有点抽象,举个例子,数字:332,从前向后遍历的话,那么就把变成了329,此时2又小于了第一位的3了,真正的结果应该是299。如下图:
那么从后向前遍历,就可以重复利用上次比较得出的结果了,从后向前遍历332的数值变化为:332 -> 329 -> 299
确定了遍历顺序之后,那么此时局部最优就可以推出全局,找不出反例,试试贪心。
整体代码如下:
public int monotoneIncreasingDigits(int n) {
char[] chars = String.valueOf(n).toCharArray();
// 在数组外
int flag = chars.length;
// 从后往前遍历
for (int i = chars.length - 1; i > 0; i--) {
if (chars[i] < chars[i - 1]) {
chars[i - 1]--;
flag = i;
}
}
// flag之后的数字变成9
for (int i = flag; i <= chars.length - 1; i++) {
chars[i] = '9';
}
return Integer.parseInt(String.valueOf(chars));
}
- 时间复杂度:O(n),n 为数字长度
- 空间复杂度:O(n),需要一个字符串,转化为字符串操作更方便
❗注意此处基本功,【字符串 数组 整型 数据类型之间互相转换的方法】 如:Integer.parseInt(String.valueOf(chars)); // 将数组字符串化,没有[],然后使用Integer转整型 String.valueOf(n).toCharArray(); // 将整型字符串化,转换成数组 字符串是互相转换不错的“媒婆”~
总结
本题只要想清楚个例,例如98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]减一,strNum[i]赋值9,这样这个整数就是89。就可以很自然想到对应的贪心解法了。
想到了贪心,还要考虑遍历顺序,只有从后向前遍历才能重复利用上次比较的结果。
最后代码实现的时候,也需要一些技巧,例如用一个flag来标记从哪里开始赋值9。
968.监控二叉树
一刷了解思路,详见这里👈
贪心算法总结
每个章节有每个章节的特点。正如理论篇提到的,贪心没有套路。 因此和之前二叉树、回溯等章节循序渐进的难度以及前后题有联系的刷题体验不同,贪心章节刷题每天难与易的题都有。 接下来按难易程度以及题目类型大体归个类。
贪心理论基础
在贪心系列理论篇中,大体了解贪心算法题目特点以及要如何去想。
- 贪心很简单,就是常识?
题目巧妙,代码精简,难度不低。
- 贪心有没有固定的套路?
贪心无套路,也没有框架之类的,需要多看多练培养感觉才能想到贪心的思路。
- 究竟什么题目是贪心呢?
如果找出局部最优并可以推出全局最优,以实现结果收益最"值"化的,就是贪心。
其实不太需要给贪心下定义,太学术了没必要。只要有一个感觉,会解题就好。
- 如何知道局部最优推出全局最优,有数学证明么?
同样,没必要。最好的方法是,有没有反例?如果列举不了反例,就不妨用用贪心的思想。
以上就是贪心一些基本的认识。
贪心简单题
几乎每天的第一题都是简单题。以下三道题目就是简单题,会发现贪心完全可以靠常识。是的,如下三道题目,就是靠常识,
但试着分析一下局部最优是什么,全局最优是什么,有助于锻炼贪心的思维。(贪心也要贪的有理有据~)
贪心中等题
贪心中等题,靠常识开始有点吃力了。开始初现贪心算法的难度与巧妙之处。
- 376 摆动序列
- 738 单调递增的数字(今天的第一题)
贪心解决股票问题
股票问题在动态规划出现比较多(可以说专场了),但这道在贪心比较典型
两个维度权衡问题
在出现两个维度相互影响的情况时,两边一起考虑一定会顾此失彼,要先确定一个维度,再确定另一个一个维度。
在讲解本题的过程中,还体现了数据结构的重要性。基于各个数据结构的底层实现,题中使用 LinkedList 链表形式 比 ArrayList 数组形式效率高
贪心难题
这里的题目如果没有接触过,其实是很难想到的,甚至接触过,也一时想不出来,所以题目不要做一遍,要多练!
贪心解决区间问题
关于区间问题,大家应该印象深刻,有一周我们专门讲解的区间问题,各种覆盖各种去重。
其他难题
53 最大子序和其实是动态规划的题目,但贪心性能更优,贪心也能比动态规划效率高。
134 加油站可能以为是一道模拟题,但就算模拟其实也不简单。但其实是可以使用贪心给时间复杂度降低一个数量级。难点就在[start,i]之前的任意一点还能不能作为start
最后贪心系列压轴题目贪心算法:我要监控二叉树!,不仅贪心的思路不好想,而且需要对二叉树的操作特别娴熟,也有二叉树典型解法——分情况讨论。这就是典型的交叉类难题了。
总结
在这十八道贪心经典题目中,在刷每一道题目时,把什么是局部最优,和什么是全局最优说清楚,有助于进一步理解贪心思想。
这也是判断这是一道贪心题目的依据,如果找不出局部最优,那可能就是一道模拟题。
学习资料: