
最大子序和,贪心的做法在于:如果nums[i-j]范围的和为正,则继续往j+1探索,记录范围和是否大于max,是就记录下来;如此探索,直到范围和为负,就清零,重新确定i和新的范围和。
贪心贪在,只考虑范围和为正的部分。
java
class Solution {
public int maxSubArray(int[] nums) {
int simu_sum = 0;
int res = Integer.MIN_VALUE;
for(int i = 0; i< nums.length; i++){
simu_sum += nums[i];
res = Math.max(simu_sum, res);
if(simu_sum <= 0){ // 只考虑和为正数的情况
simu_sum = 0;
}
}
return res;
}
}

贪心的做法是,计算每一天的股票价格波动,prices[i-1] - prices[i]。然后的话,只要一天过去,股票有涨价的趋势,就把这部分的波动价格加进来,相当于有人开了上帝之眼,只要看着第二天价格比第一天高,就不抛售,一旦价格掉了,不管掉多少,就在掉之前收掉。
java
class Solution {
public int maxProfit(int[] prices) {
// // 动态规划
// int pre_have = -prices[0];
// int pre_not_have = 0;
// int cur_have, cur_not_have;
// int res = 0;
// for(int i = 1; i< prices.length; i++){
// cur_have = Math.max(pre_have, pre_not_have - prices[i]);
// cur_not_have = Math.max(pre_have + prices[i], pre_not_have);
// res = Math.max(res,Math.max(cur_have, cur_not_have));
// pre_have = cur_have;
// pre_not_have = cur_not_have;
// }
// 贪心
int res = 0;
for(int i = 1; i< prices.length; i++){
int delta = prices[i] - prices[i-1];
if(delta > 0) res += delta;
}
return res;
}
}

跳跃游戏使用贪心思想的话,关注的是每个节点可以允许跳跃的最大覆盖范围,只要有点可以跳跃到达n-1的位置,或者超过这个位置,都判定为成功;
但如果一旦中间有某个节点卡住了,也就是说[0~i]范围的点最远只能跳到i,那就说明这条路会被i这个节点卡住,一直跳不过。
java
class Solution {
public boolean canJump(int[] nums) {
if(nums.length == 1) return true;
int fatherest_index = 0;
for(int i = 0; i< nums.length - 1; i++){
fatherest_index = Math.max(nums[i] + i, fatherest_index);
if(fatherest_index >= nums.length - 1){
return true;
}
else if(fatherest_index <= i){ // 到达i位置,还是无法跨越i,则视为永远无法跨越i,因此是无法跳过的
return false;
}
}
return false;
}
}

这里求跳跃的最少步数,从最后一个节点开始遍历研究,看看可以跳到n-1节点的最远的起点是哪个,比如是j,下一步就研究可以跳到j的最远起点是哪个,如此迭代下去,直到起点为0时候,就停止,计算跳了多少步。
java
class Solution {
public int jump(int[] nums) {
int cnt = 0;
int start_index = nums.length-1;
int target_index = nums.length - 1;
int[] available = new int[nums.length];
for(int i = 0; i< nums.length; i++){
available[i] = i + nums[i];
}
while(start_index!=0){
start_index = getMinIndexOverTarget(available, target_index);
cnt++;
target_index = start_index;
}
return cnt;
}
int getMinIndexOverTarget(int[] available, int end_index){
// 从nums的0-end_inedx范围内找出,第一个可以抵达end_index位置的index
int i =0;
for(i = 0; i<=end_index; i++){
if(available[i] >= end_index){
break;
}
}
return i;
}
}

这里的贪心,我自己能感觉出来大致的方向,但是提炼不出来,原来是看数的绝对值。按照绝对值大小对nums排序,然后优先对绝对值大的负数取相反数。如果处理完所有的负数后,取反的机会次数k还没用完,那就把这个取反的机会全部用在绝对值最小的数上面。最后求个和。
java
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
for(int i = 0; i < nums.length - 1; i++){
for(int j = 0; j < nums.length - i - 1; j++){
if(Math.abs(nums[j +1]) < Math.abs(nums[j])){
int swap = nums[j];
nums[j] = nums[j+1];
nums[j+1] = swap;
}
}
}
int i = nums.length - 1;
int cnt = 0;
while(k > 0){
while(i>=0 && nums[i] >= 0 ) i--;
if(i >=0){
nums[i] = -nums[i];
k--;
}
else{
nums[0] = k%2 ==1? -nums[0]:nums[0];
k=0;
}
}
int sum = 0;
for(i = 0; i< nums.length; i++){
sum += nums[i];
}
return sum;
}
}

这道题,对于返回-1的情况好确定,也就是gas_sum > cost_sum,所有加油站的汽油都不够绕这个环兜一圈消耗的汽油。
但是对于找出汽车出发的加油站,就有点需要好好思考一下了。
假设一个净存油量remain数组,remain[i] = gas[i] - cost[i] 表示仅仅依靠在i加油站加的油跑,最后还剩多少油。有个说法是,如果一旦remain[i-j]_sum 小于0了,那么i-j的加油站就不适合选择作为起点,因为不管选哪个值,只要走到了第j个加油站,油都会不够用。
这样的话,只需要遍历remain数组,看看从哪个起点开始(这个起点要么是0,要么就是在某个导致一段reamain_sum为负的j的下一个索引),可以使得remain_sum到终点都是非负数。
java
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int[] remain = new int[gas.length];
int sum = 0;
for(int i = 0; i<gas.length; i++){
remain[i]= gas[i] - cost[i];
sum += remain[i];
}
if(sum <0) return -1;
int part_sum = 0;
int part_sum_start_id = 0;
for(int i = 0; i< remain.length; i++){
part_sum += remain[i];
if(part_sum < 0){
part_sum_start_id = i+1;
part_sum = 0;
}
}
return part_sum_start_id;
}
}

分发🍬,要贪心的话,很容易想到的点是,能给1个就给1个,要多给的话,就多给1个,只要符合【相邻】【rating高的糖果多】就ok,没说rating一样有什么规则,自己差点也要一碗水端平了。
相邻 - rating高, 这其实描述了两种情况:rating[i]比rating[i-1]高,rating[i]比rating[i+1]高。也就是说邻居有两个,要同时统筹两个方向的糖果分发。
这里的贪心就是分步贪心了。先是从左往右的贪心,然后是从右往左贪心。
java
class Solution {
public int candy(int[] ratings) {
if(ratings.length ==1) return 1;
int[] res = new int[ratings.length];
Arrays.fill(res,1);
for(int i = 1; i<ratings.length; i++){ //从左往右贪心
if(ratings[i] > ratings[i-1]){
res[i] = res[i-1] +1;
}
else{
res[i] =1;
}
}
for(int i = ratings.length -2; i >=0 ;i--){ // 从右往左贪心
if(ratings[i] > ratings[i +1]){
res[i] = Math.max(res[i], res[i+1]+1); // 取最大值,是为了兼顾两方向的贪心结果(贪心,已经是尽可能少给了)
}
}
int sum = 0;
for(int i = 0; i< ratings.length; i++){
sum += res[i];
}
return sum;
}
}

这道题,我做题之处,还考虑赚了多少钱,但实际上,这道题,重点是你得到的钞票类型和数目是否可以让你保证找零钱时候找得开。
贪心的话,贪在,面对一个20d的顾客,我优先用10d(如果有的话) + 5d找钱,如果没有10d,再考虑用3*5d找钱。因为10d可以用的场景比较少,只能给20d的顾客找钱时候用得上。但是5d是10d, 20d的顾客找钱都可以用,是比较"万能"的"重要物资"。
java
class Solution {
public boolean lemonadeChange(int[] bills) {
int five = 0;
int ten = 0;
for(int i = 0; i< bills.length; i++){
if(bills[i] == 5) five ++;
if(bills[i] == 10){
five --;
ten ++;
}
else if(bills[i] == 20){
if(ten > 0){
five --;
ten --;
}
else{
five -= 3;
}
}
if(five <0 || ten <0) return false;
}
return true;
}
}

这个题目,贪心和分糖果类似,在考虑一个序列的两个维度要达到要求时候,需要分维度贪心。以及分维度时候,还要注意分维度的次序。
这道题,是先对身高进行排序,然后是对k_i进行排序。只能说java有这些现成的sort,LinkedList工具是极大减少了代码量。但是会用工具又显得很重要了。
java
class Solution {
public int[][] reconstructQueue(int[][] people) {
Arrays.sort(people,(a,b) -> {
if (a[0] == b[0]) return a[1] - b[1]; // 身高一样时候,k_i降序排列
return b[0] - a[0]; // 身高降序排列
});
LinkedList<int[]> que = new LinkedList<>();
for(int[] p: people){
que.add(p[1],p); // 把p插入到p的k_i位置处
}
return que.toArray(new int[people.length][]);
}
}