从头再来!社招找工作——算法题复习八:贪心
贪心
贪心思想是一种很常见的算法题思想。贪心思想说起来非常简单,就是在眼前有多个选项,哪个选项最有利我就选哪个,根本不去考虑选了这个选项之后后面的路有多难走。如果当前选项和接下来可能会遇到的选项的困难程度都差不多,那么贪心思想就是一种极佳的方法了。
比如普通时间段在一座交通不拥挤的小城市里开车,从东南角开到西北角,所有马路的限速都是60码,经过的红绿灯的等待时长、通行时长都差不多,在我们每到一个十字路口、选择向西还是向北行驶的时候,其实哪个方向绿灯就选择哪个方向即可。这就是贪心思想的最好体现。
但是在大城市的早晚高峰,这种贪心思想就不好用了!有些道路限速,有些路口红灯时间超级长,甚至还会有些地方限牌让你无路可走(这种此路不通的情况是贪心最大的障碍)!这就是贪心思想不能应用的场景了。
所以我们还是要慎重使用贪心思想,要时常想想如果贪心了会不会有的路是走不通的。不过在题目难度不是很大时,贪心思想也够包打天下了。在以后讲解图论的时候,我们会学到很多算法,包括大名鼎鼎的用来求解有向权重图的单源最短路径的迪杰斯特拉算法,就是采用了贪心思想。
接下来我们来聊聊一些可以应用贪心思想的算法题,由易到难逐渐深入学会使用贪心思想。
有效括号序列2
Leetcode
给定一个只包含三种字符 ( , ) 和 * 的字符串,请判断这个字符串是否为有效字符串括号字符串。
有效 字符串符合如下规则:
- 任何左括号 ( 必须有相应的右括号 )
- 任何右括号 ) 必须有相应的左括号 (
- 左括号 ( 必须在对应的右括号之前 )
- * 可以被视为单个右括号 ) ,或单个左括号 ( ,或一个空字符。
这道题不存在大括号、小括号之间的嵌套关系(上一篇栈的博客的有效括号序列题目就涉及到嵌套关系),所以我们甚至都不需要用栈来存储括号字符。如果不考虑 * 的话,我们可以直接设置一个整数变量cnt,如果遇到左括号则加1,遇到右括号就减1并判断cnt是否仍然大于等于0。
现在再来考虑 * 的事情。由于它既能看作左括号,又能看作右括号,我们可以记录两个cnt变量,一个cntLeft变量用来记录’*‘全部看作左括号的情况,一个cntRight变量用来记录’*'全部看作右括号的情况(但是必须始终大于等于0,如果某一次小于0了说明有一个 * 不能变成右括号而是应该变成空字符,或者不能变成空字符而是应该变成左括号)。由于cntRight一定比cntLeft小,且最小是0,因此在字符串遍历完成之后,只需要比较cntRight是否等于0,即是否能嵌套完所有括号对。
public boolean checkValidString(String s) {
char[] cs = s.toCharArray();
int cntLeft = 0;
int cntRight = 0;
for (char c : cs) {
if (c == '(') {
cntLeft++;
cntRight++;
}
else if (c == ')') {
cntLeft--;
if (cntLeft < 0) {
return false; // 如果'*'全部看成左括号都不足够嵌套的,那么字符串一定不合法
}
cntRight = cntRight == 0 ? 0 : cntRight-1; // 这里等于0是可以商量的,我们把前面某个变成右括号的'*'回退成空字符
}
else {
cntLeft++;
cntRight = cntRight == 0 ? 0 : cntRight-1;
}
}
return cntRight == 0; // cntRight一定比cntLeft小,且最小是0,所以只需要比较cntRight是否等于0,即是否能嵌套完所有括号对
}
最大数
Leetcode
给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。
这道题可以用贪心思想来解,因为按照一定存在某种方法对数字进行排序,拼成一个最大整数。大家如果手写了几个例子,可能会找出对数字逐位比较的排序方法,但实际上有更简单的排序法:对于两个数字字符串A和B,直接比较A+B和B+A的字典序,就可以知道应该谁前谁后了。
我们重写数组的排序方法,按上述规则进行排序(可以参考我的一篇旧文),最后拼起来即可,代码如下:
public String largestNumber(int[] nums) {
int len = nums.length;
String[] strs = new String[len];
for (int i = 0;i