局部最优可以退出整体最优,并且找不出反例,则可以考虑贪心算法。
代码随想录
leetcode 455 分发饼干
class Solution {
public void sort(int[] nums){
for(int i = 0; i < nums.length - 1; i++){
for(int j = i + 1; j < nums.length; j++){
int temp;
if(nums[j] < nums[i]){
temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
}
}
public int findContentChildren(int[] g, int[] s) {
sort(g);
sort(s);
int count = 0;
int i = 0;//指向g的指针
int j = 0;//指向s的指针
while(i < g.length && j < s.length){
if(s[j] - g[i] >= 0){
count++;
i++;
j++;
}else{
j++;
}
}
return count;
}
}
leetcode 376 摆动序列
本题分为三种情况:
1.上下坡中有平坡;
2.只有首尾两个元素;
3.单调坡中有平坡;(prediff只是记录摆动出现的时候初始的坡的方向)
假设最右侧有坡度,故初始result = 1.
class Solution {
public int wiggleMaxLength(int[] nums) {
//只有一个元素时
if(nums.length == 1){
return 1;
}
//分为3种情况:上下有平坡;首位元素;单调有平坡
int result = 1;
int prediff = 0;//相当于在数列首部又添加了一个与其第一个元素等值的元素
int curdiff = 0;
for(int i = 0; i < nums.length - 1; i++){
curdiff = nums[i + 1] - nums[i];
if(prediff <= 0 && curdiff > 0 || prediff >= 0 && curdiff < 0){
result++;//满足上下有平坡
//处理单调有平坡的情况
prediff = curdiff;
}
}
return result;
}
}
leetcode 53 最大子数组和
若连续和为负数,则立刻抛弃,选择下一个数作为起始位置。
以下暴力解法超时:
class Solution {
public int maxSubArray(int[] nums) {
if(nums.length == 1){
return nums[0];
}
int max = nums[0];
int cur = 0;
for(int i = 0; i < nums.length; i++){
cur = nums[i];
if(cur > max){
max = cur;
}
for(int j = i + 1; j < nums.length; j++){
cur += nums[j];
if(cur > max){
max = cur;
}
}
}
return max;
}
}
利用贪心:
class Solution {
int result = -1000000;//将resulr设置成为最小值
public int maxSubArray(int[] nums) {
int count = 0;//记录当前遍历到的值
for(int i = 0; i < nums.length; i++){
count += nums[i];
if(count > result){
result = count;
}
if(count < 0){
count = 0;//相当于让count重新从下一个位置开始累加,体现了贪心的思想
}
}
return result;
}
}
leetcode 122 买卖股票的最佳时机
class Solution {
public int maxProfit(int[] prices) {
int i = 0;
int j = 1;
int sum = 0;
while(j < prices.length){
if(prices[j] >= prices[i]){
sum += prices[j] - prices[i];
i++;
j++;
}else{
i++;
j++;
}
}
return sum;
}
}
leetcode 55 跳跃游戏
利用每个元素的覆盖范围,随着遍历的递进,不断更新覆盖范围,判断其是否可以覆盖到最后一个元素。
class Solution {
//关键:利用每个元素所能够覆盖的范围,若其能够包括最后一个元素,则可以到达
public int max(int val1, int val2){
return val1 > val2 ? val1 : val2;
}
public boolean canJump(int[] nums) {
if(nums.length == 1){
return true;
}
int cover = 0;
for(int i = 0; i <= cover; i++){
cover = max(cover, nums[i] + i);
if(cover >= nums.length - 1){
//说明可以覆盖到最后一个元素
return true;
}
}
return false;
}
}
leetcode 45 跳跃游戏(2)
class Solution {
public int max(int val1, int val2){
return val1 > val2 ? val1 : val2;
}
public int jump(int[] nums) {
int cur = 0;
int next = 0;
int result = 0;
for(int i = 0; i < nums.length; i++){
next = max(next, nums[i] + i);
if(i == cur){
if(cur < nums.length - 1){
result++;
cur = next;
if(cur >= nums.length -1){
break;
}
}else{
break;
}
}
}
return result;
}
}
leetcode 1005 K次取反后最大化的数组和
本题总共使用了两次贪心策略。
class Solution {
public void sort(int[] nums){
for(int i = 0; i < nums.length - 1; i++){
for(int j = i + 1; j < nums.length; j++){
int temp;
if(nums[j] < nums[i]){
temp = nums[j];
nums[j] = nums[i];
nums[i] = temp;
}
}
}
}
public int largestSumAfterKNegations(int[] nums, int k) {
//第一次贪心策略:将最小的负数取反
int sum = 0;
sort(nums);
for(int i = 0; i < nums.length; i++){
if(k == 0){
break;
}
if(i > 0 && nums[i] > 0 && -nums[i] < -nums[i - 1]){
while(k != 0){
nums[i - 1] = -nums[i - 1];
k--;
}
}
if(nums[i] > 0){
while(k != 0){
nums[i] = -nums[i];
k--;
}
}
else if(nums[i] == 0){
k = 0;
break;
}else{
if(k != 0 ){
nums[i] = -nums[i];
k--;
}
}
}
while(k != 0){
nums[nums.length - 1] = - nums[nums.length - 1];
k--;
}
for(int i = 0; i < nums.length; i++){
sum += nums[i];
}
return sum;
}
}
leetcode 134 加油站
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
//将gas数组的每个元素与cost数组的每个元素相减,得到的数组表示走到下一站时剩余的油的点数
//如索引从0到i的油的总和即为走到i+1处还剩余的油的点数
int[] diff = new int[gas.length];
for(int i = 0; i < gas.length; i++){
diff[i] = gas[i] - cost[i];
}
int sum = 0;
int i = 0;
int start = 0;
Queue<Integer> queue = new LinkedList<>();
for(i = 0; i < diff.length; i++){
sum += diff[i];
if(sum < 0){
sum = 0;
while(queue.size() != 0){
queue.poll();
}
continue;
}
queue.offer(i);
}
int sum2 = 0;
if(queue.size() != 0){
start = queue.poll();
for(int j = start; j < diff.length; j++){
sum2 += diff[j];
}
for(int j = 0; j < start; j++){
sum2 += diff[j];
}
}else{
return -1;
}
if(sum2 < 0){
return -1;
}else{
return start;
}
}
}
leetcode 135 分发糖果
本题需要分两次比较。一次完整的遍历只针对一种比较方式。不要同时比较。
class Solution {
public int max(int val1, int val2){
return val1 > val2 ? val1 : val2;
}
public int candy(int[] ratings) {
int res = 0;
//每个孩子至少一个糖果
int[] temp = new int[ratings.length];
for(int i = 0; i < temp.length; i++){
temp[i] = 1;
}
//1.首先只考虑右边比左边大的情况:正向遍历
for(int i = 1; i < ratings.length; i++){
if(ratings[i] > ratings[i -1]){
temp[i] = temp[i - 1] + 1;
}
}
//2.再比较左边比右边大的情况:逆向遍历
for(int i = ratings.length - 2; i >= 0; i--){
if(ratings[i] > ratings[i + 1]){
temp[i] = max(temp[i + 1] + 1, temp[i]);
}
}
//3.将temp数组的值累加即可
for(int i = 0; i < temp.length; i++){
res += temp[i];
}
return res;
}
}
leetcode 860 柠檬水找零
贪心思路:尽可能地留下更多的5,因为5更加万能,其不仅可以对20找零还可以对10找零,而10只能对20找零。
class Solution {
public boolean lemonadeChange(int[] bills) {
int five = 0;
int ten = 0;
int twenty = 0;
for(int i = 0; i < bills.length; i++){
if(bills[i] == 5){
five++;
}
if(bills[i] == 10){
if(five == 0){
return false;
}else{
five--;
ten++;
}
}
if(bills[i] == 20){
if(ten > 0 && five > 0){
ten--;
five--;
twenty++;//这行代码可以省略
}else if(five >= 3){
five -= 3;
twenty++;//这行代码可以省略
}else{
return false;
}
}
}
return true;
}
}
leetcode 406 根据身高重建队列
与分发糖果的题目类似,本题仍旧采用分别比较两个维度的形式。注意要重新定义一个queue进行后续的插入元素操作,因为若在原始队列中进行插入操作则有可能导致重复操作同一个元素。
注意:利用Arrays.sort()可以自定义排序规则后再返回排序结果,sort函数中的第一个参数是待排序的数组,第二个参数是自定义的排序规则,b - a降序,a - b升序。利用LinkedList<>可以实现按照指定位置插入元素: LinkedList<> res.add(index, E)。
class Solution {
public LinkedList<int[]> res = new LinkedList<>();//注意:队列中存放的元素是int[]类型
// public void sort(int[][] people){
// //根据身高降序排序,若身高相同,则k值大的排在后面
// for(int i = 0; i < people.length - 1; i++){
// for(int j = i + 1; j < people.length; j++){
// int[] temp;
// if(people[i][0] < people[j][0]){
// temp = people[i];
// people[i] = people[j];
// people[j] = temp;
// }
// }
// }
// for(int i = 1; i < people.length; i++){
// if(people[i][0] == people[i - 1][0] && people[i][1] < people[i - 1][1]){
// int[] temp;
// temp = people[i];
// people[i] = people[i - 1];
// people[i - 1] = temp;
// }
// }
// }
public int[][] reconstructQueue(int[][] people) {
//先根据身高降序排列,若身高相同,则k值越大的位置越靠后
Arrays.sort(people, (a, b) -> {
if(a[0] == b[0]) return a[1] - b[1];
return b[0] - a[0];
});
//再根据k值将people插入到数组的对应的位置中去
for(int[] p : people){
res.add(p[1], p);
}
return res.toArray(new int[people.length][2]);
}
}
leetcode 452 用最少数量的箭引爆气球
注意使用:Arrays.sort(points, (a, b) ->
// 使用Integer内置比较方法,不会溢出
Integer.compare(a[1], b[1])
); 方法。
class Solution {
public int findMinArrowShots(int[][] points) {
if(points.length == 1){
return 1;
}
int res = 1;
//先按照end升序排列
Arrays.sort(points, (a, b) ->
// 使用Integer内置比较方法,不会溢出
Integer.compare(a[1], b[1])
);
int temp = points[0][1];
for(int i = 1; i < points.length; i++){
if(temp >= points[i][0]){
continue;
}
temp = points[i][1];
res++;
}
return res;
}
}
leetcode 435 无重叠区间
本题思路:若i位置的左值小于i - 1位置的右值,说明这两个区间有重叠部分,此时result加1,并且将i位置的右值更新为i与i - 1的右值的较小值,再继续遍历完整个数组。
class Solution {
public int min(int val1, int val2){
return val1 < val2 ? val1 : val2;
}
public int eraseOverlapIntervals(int[][] intervals) {
//首先根据start进行升序排序
Arrays.sort(intervals, (a, b) -> {
if(a[0] == b[0]) return a[1] - b[1];
return a[0] - b[0];
});
int res = 0;
for(int i = 1; i < intervals.length; i++){
if(intervals[i][0] < intervals[i - 1][1]){
res++;
intervals[i][1] = min(intervals[i][1], intervals[i - 1][1]);
}
}
return res;
}
}
leetcode 763 划分字母区间
首先找到每个字母元素在字符串中的最远出现位置。
class Solution {
public int max(int val1, int val2){
return val1 > val2 ? val1 : val2;
}
public List<Integer> partitionLabels(String s) {
//首先找到每个字母的最远出现位置并记录在hash数组中
int[] hash = new int[27];//因为只有26个字母
for(int i = 0; i < s.length(); i++){
hash[s.charAt(i) - 'a'] = i;
}
List<Integer> res = new ArrayList<>();
int left = 0;//左区间的位置
int right = 0;//右区间的位置
for(int i = 0; i < s.length(); i++){
right = max(right, hash[s.charAt(i) - 'a']);
if(right == i){
res.add(right - left + 1);//将找到的区间的长度记录到res中
left = right + 1;
}
}
return res;
}
}
leetcode 56 合并区间
先按照start升序排序再遍历。(固定套路)
class Solution {
public int min(int val1, int val2){
return val1 < val2 ? val1 : val2;
}
public int max(int val1, int val2){
return val1 > val2 ? val1: val2;
}
public LinkedList<int[]> res = new LinkedList<>();
public int[][] merge(int[][] intervals) {
if(intervals == null){
return res.toArray(new int[intervals.length][]);
}
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
for(int i = 1; i < intervals.length; i++){
if(intervals[i][0] <= intervals[i - 1][1]){
intervals[i][0] = min(intervals[i][0], intervals[i - 1][0]);
intervals[i][1] = max(intervals[i][1], intervals[i - 1][1]);
intervals[i - 1][0] = -100000;
intervals[i - 1][1] = -100000;
}
}
for(int[] interval : intervals){
if(interval[0] == -100000 && interval[1] == -100000){
continue;
}else{
res.add(interval);
}
}
return res.toArray(new int[res.size()][2]);
}
}
leetcode 738 单调递增的数字
注意先将输入的n转换成字符串,再将字符串转换成字符数组,最后输出结果的时候再依照上述顺序逆向操作一次。int类型可通过强制类型转换(char)转换成字符。
字符char也可以像int这样比较大小。
更改字符串指定位置的字符:
String str = “abc”;
StringBuilder sb = new StringBuilder(str);
sb.setCharAt(0, ‘d’);
str = sb.toString();
System.out.print(str);
// “dbc”
上述代码来源
class Solution {
public int monotoneIncreasingDigits(int n) {
//首先将n转换成String
String str = String.valueOf(n);
//再将str转换成字符数组
char[] chars = str.toCharArray();
int flag = str.length();//记录变成9的起始位置
for(int i = str.length() - 1; i > 0; i--){
if(chars[i] < chars[i - 1]){
chars[i - 1] = (char)(Integer.valueOf(chars[i - 1]) - 1);
flag = i;
}
}
//将从flag开始的所有位置都变成9
for(int i = flag; i < str.length(); i++){
chars[i] = '9';
}
//将String转换成int
str = new String(chars);
return Integer.valueOf(str);
}
}
leetcode 968 监控二叉树
注意:以叶子节点为主导(因为二叉树中叶子节点的数量最多)。采用后序遍历的方式。
总共有3中状态:0.无覆盖 1.有摄像头 2.有覆盖
遇见空节点,认为空节点是有覆盖的状态,向上返回2。
根据左右孩子的状态来确定父节点的状态。
递归函数中return的结果被上一层接受。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
//分3种状态:0.无覆盖 1.有摄像头 2.有覆盖
//讨论4种情况:1.左右节点都有覆盖,则其父节点一定无覆盖 2.左右节点至少一个无覆盖则其父节点一定有摄像头
//3.左右节点至少有一个摄像头,则父节点一定有覆盖 4.二叉树的根节点无覆盖
class Solution {
public int res = 0;
public int travesal(TreeNode root){
//采用后序遍历
//递归结束条件
TreeNode cur = root;
if(cur == null){
return 2;
}
//处理单层递归逻辑
int left = travesal(root.left);
int right = travesal(root.right);
//处理情况1
if(left == 2 && right == 2){
return 0;
}
//处理情况2
if(left == 0 || right == 0){
res++;
return 1;
}
//处理情况3
if(left == 1 || right == 1){
return 2;
}
return -1;//其实根本不会走到改行代码的位置处
}
public int minCameraCover(TreeNode root) {
//4.二叉树的根节点无覆盖
if(travesal(root) == 0){
res++;//相当于根节点再多装一个摄像头
}
return res;
}
}