贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
贪心算法基本思路
- 建立数学模型来描述问题
- 把求解的问题分成若干个子问题
- 对每个子问题求解,得到子问题的局部最优解
- 把子问题的解局部最优解合成原来问题的一个解
1221. 分割平衡字符串
在一个「平衡字符串」中,'L' 和 'R' 字符的数量是相同的。
给出一个平衡字符串 s,请你将它分割成尽可能多的平衡字符串。
返回可以通过分割得到的平衡字符串的最大数量。
示例 1:
输入:s = "RLRRLLRLRL"
输出:4
解释:s 可以分割为 "RL", "RRLL", "RL", "RL", 每个子字符串中都包含相同数量的 'L' 和 'R'。
题解:
class Solution {
public int balancedStringSplit(String s) {
String restStr = s;
ArrayList list = new ArrayList<>();
while (restStr.length()>0) {
restStr = getPinghengStr(restStr)[1]; //获取剩余字符串循环处理
list.add(getPinghengStr(restStr)[0]); //存储各个生成的平衡字符串
}
return list.size();
}
/**
* 传入一个字符串,截取平衡字符串,同时返回剩余字符串
* @param str
* @return
*/
private String[] getPinghengStr(String str) {
String jugeStr;
for (int i=1;i
944. 删列造序
给定由 N 个小写字母字符串组成的数组 A,其中每个字符串长度相等。
你需要选出一组要删掉的列 D,对 A 执行删除操作,使 A 中剩余的每一列都是 非降序 排列的,然后请你返回 D.length 的最小可能值。
删除 操作的定义是:选出一组要删掉的列,删去 A 中对应列中的所有字符,形式上,第 n 列为 [A[0][n], A[1][n], ..., A[A.length-1][n]])。(可以参见 删除操作范例)
示例 1:
输入:["cba", "daf", "ghi"]
输出:1
解释:
当选择 D = {1},删除后 A 的列为:["c","d","g"] 和 ["a","f","i"],均为非降序排列。
若选择 D = {},那么 A 的列 ["b","a","h"] 就不是非降序排列了。
题解:
class Solution {
/**
* 思路:
* 将字符串数组排成字符矩阵,
* 需要满足的条件是每一列从上到下需要按照字母顺序排列,如果不是字母顺序,就记录该列
* 返回记录的列数
* @param A
* @return
*/
public int minDeletionSize(String[] A) {
int count = 0;
a: for (int c=0;c
1403. 非递增顺序的最小子序列
给你一个数组 nums,请你从中抽取一个子序列,满足该子序列的元素之和 严格 大于未包含在该子序列中的各元素之和。
如果存在多个解决方案,只需返回 长度最小 的子序列。如果仍然有多个解决方案,则返回 元素之和最大 的子序列。
与子数组不同的地方在于,「数组的子序列」不强调元素在原数组中的连续性,也就是说,它可以通过从数组中分离一些(也可能不分离)元素得到。
注意,题目数据保证满足所有约束条件的解决方案是 唯一 的。同时,返回的答案应当按 非递增顺序 排列。
示例 1:
输入:nums = [4,3,10,9,8]
输出:[10,9]
解释:子序列 [10,9] 和 [10,8] 是最小的、满足元素之和大于其他各元素之和的子序列。但是 [10,9] 的元素之和最大。
题解:
class Solution {
/**
* 思路:
* 1.对nums数组进行从小到大排序
* 2.遍历从nums中从尾端依次取出数到sublist集合中,如果sublist中数的和大于剩余数的和,则返回该sublist,否则继续取数
* @param nums
* @return
*/
public List minSubsequence(int[] nums) {
List subList = new ArrayList<>(); //抽出来的子序列
Arrays.sort(nums); //对nums排序
int total = 0; //计算nums数组的总大小
int subNum = 0; //计算抽出来数的和的值
for (int num : nums) {
total += num;
}
for (int i=nums.length-1;i>=0;i--) {
//从最后一个数开始抽取
subList.add(nums[i]);
subNum += nums[i];
if (subNum > total/2) {
//判断抽出的数的和是否比剩下的数的和大
return subList;
}
}
return subList;
}
}
1518. 换酒问题
小区便利店正在促销,用 numExchange 个空酒瓶可以兑换一瓶新酒。你购入了 numBottles 瓶酒。
如果喝掉了酒瓶中的酒,那么酒瓶就会变成空的。
请你计算 最多 能喝到多少瓶酒。
示例 1:
输入:numBottles = 9, numExchange = 3
输出:13
解释:你可以用 3 个空酒瓶兑换 1 瓶酒。
所以最多能喝到 9 + 3 + 1 = 13 瓶酒。
题解:
class Solution {
public int numWaterBottles(int numBottles, int numExchange) {
//把现有买来的酒喝完
int drinkCount = numBottles; //现有喝酒数量
int bottleCount = numBottles; //手里现有酒瓶数量
//开始兑换,判断空瓶数量是否大于等于numExchange
while (bottleCount >= numExchange) {
int curExchangeCount = bottleCount/numExchange; //本次换酒数量
//计算是否有剩余换不了的几个酒瓶 restBottleCount
分发饼干
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
题解:
class Solution {
/**
* 思路:先将胃口和饼干从小到大排序
* 优先满足小胃口的孩子,给当前这个小胃口孩子一个最小尺寸的饼干,且能满足该孩子
* 用两个下标记录各个孩子和各个饼干
*/
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int gPos = 0;
int sPos = 0;
int count = 0;
while(gPos=g[gPos]) {
count++;
//该孩子得到满足
sPos++;
gPos++;
} else {
sPos++; //仅饼干往后移
}
}
return count;
}
}
数组拆分
给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。
返回该 最大总和 。
题解:
class Solution {
/**
* 假设排完序的结果为a1<=b1<=a2<=b2<=...<=an<=bn
* 那么a1应该跟谁一组呢?
*
* a1作为全局最小值,无论谁跟a1搭档都是无效的,都是炮灰,
* 那倒不如取一个最小的,把大的留给后面做搭档,才能保证后面的累计和尽可能地大,即给a1找一个“最小的搭档”b1。
*
* 当a1、b1被处理之后,a2同a1同理分析,给a2找个最小的b2
* 所以,最终选择a1,a2,...,an会得到最好的结果。
*/
public int arrayPairSum(int[] nums) {
Arrays.sort(nums);
int total = 0;
for (int i=0;i
柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
题解:
class Solution {
/**
* 思路:只管5、10元够不够找零,20元不用管,因为20元不能找
* 那么记录5、10元的张数,如果是5,则直接存储,如果是10,则5元-1张,10元+1张
* 如果是20元,那么先找5、10元各减一张,如果不能,则5元减3张,如果还不行,则返回false
*/
public boolean lemonadeChange(int[] bills) {
int fiveNum = 0, tenNum = 0;
for (int i=0;i=1) {
fiveNum--;
tenNum++;
} else {
return false;
}
break;
case 20:
if (fiveNum>=1 && tenNum>=1) {
//5、10元各减一张
fiveNum--;
tenNum--;
} else if (fiveNum >=3) {
fiveNum -= 3;
} else {
return false;
}
break;
}
}
return true;
}
}
去除重复字母
给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
题解:
class Solution {
/**
* 解题思路:
* 1.第一个条件:去重字母
* 2.第二个条件:保证字典序从小到大
* 3.第三个条件:保证字母存在至少一个
* @param s
* @return
*/
public String removeDuplicateLetters(String s) {
//第三个条件:保证字母存在至少一个
//做法:给每一种字母维护一个计数器,先遍历获取其数量,如果数量为1,则在第二个条件中不要弹出栈,否则可以弹出栈
int[] charCount = new int[256];
for (int i=0;i stk = new Stack<>();
boolean[] inStack = new boolean[256]; //用于存储这个字符是否已经记录过
// 第一个条件:去重字母
for(char c : s.toCharArray()) {
// 每遍历过一个字符,都将对应的计数减一
charCount[c]--;
if (inStack[c]) {
continue;
}
//第二个条件:保证字典序从小到大,在插入字母之前,判断与前一个字母的字典序,
// 如果比前一个小,则循环将字符弹出栈顶,清除在栈中的记录
while(!stk.empty() && stk.peek() > c) {
if (charCount[stk.peek()] == 0) {
break;
}
inStack[stk.pop()] = false;
}
stk.push(c);
inStack[c] = true;
}
StringBuilder sb = new StringBuilder();
while (!stk.empty()) {
sb.append(stk.pop());
}
return sb.reverse().toString();
}
}