122.买卖股票的最佳时机 II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
- 输入: [7,1,5,3,6,4]
- 输出: 7
- 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
我的算法:
分析:分类讨论所有情况!
int maxProfit(int* prices, int pricesSize) {
int flag=0;//表示手上有没有股票
int comeinprice;//表示成本
int profit=0;
if(pricesSize==1) return 0;
for (int i=1;i<pricesSize;i++){
int line=prices[i]-prices[i-1];
if(line>=0 && flag==0){//下一阶段股票涨了,且手上没有股票——可以买
comeinprice=prices[i-1];
flag=1;
}
//下一阶段涨了,且手上有股票——不需要操作
//下一阶段跌了,手上有股票——卖
else if(line <0 && flag==1 ){
flag=0;
profit+= prices[i-1] - comeinprice;
}
//下一阶段跌了,手上没股票——不需要操作
if ( i==pricesSize-1 && flag==1){//已经到最后一天了++手上有股票必须卖掉
profit+=prices[i]-comeinprice;
}
}
return profit;
}
算法2:
使用贪心的时候 尽量从最最最最局部的开始考虑
因为实际上可以分成每一天为一段!只考虑收集正的段,就不用考虑我的方法中对于flag和最后一天情况的讨论了!
int maxProfit(int* prices, int pricesSize) {
int profit=0;
if(pricesSize==1) return 0;
for (int i=1;i<pricesSize;i++){
if(prices[i]-prices[i-1]>0 ){
profit+=prices[i]-prices[i-1];
}
}
return profit;
}
55. 跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
- 输入: [2,3,1,1,4]
- 输出: true
- 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
分析:
找最大的覆盖范围
bool canJump(int* nums, int numsSize) {
int max=0;//记录能跳到的最大位置——也就是反映能跳到的范围
int i=0;
while(i<=max && i<=numsSize-2){//在能跳到的范围内进行遍历
max=fmax(max, nums[i]+i);
i++;
}
if(max>=numsSize-1 )return true;
return false;
}
45.跳跃游戏 II
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
- 输入: [2,3,1,1,4]
- 输出: 2
- 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
-
说明: 假设你总是可以到达数组的最后一个位置。
分析:
直觉来说需要往最远的地方跳——为什么不行呢?——往最远的地方跳了之后,可能中间路途上跳过了一个非常大的值,而后面的地方反而值非常的小——需要维护最远可到达位置
这轮遍历——找到下一轮最远可到达位置
从这轮终点——再到刚刚找到的最远可到达位置(直到最远可到达位置超过终点)
……
**注意:记录的内容是什么,各个循环遍历等号到底是怎么取
int jump(int* nums, int numsSize) {
if(numsSize==1) return 0;
//遍历当前范围,找当前范围内最大的下一个范围
int start=0;//记录这一轮的开始结点
int cur=nums[0];//当前最远可以到哪个结点
int max_next_move=0;//下一次最远可以到哪个结点
int ans=1;
while (true){//因为必然可以跳到,所以可以while true
int i;
for (i=start;i<=cur;i++){
max_next_move=fmax(max_next_move,i+nums[i]);
if(i>=numsSize-1) return ans;
}
//这轮没有能够达到最后一个位置,去下一轮
cur=max_next_move;
start=i;//从上一个失败的i往后,到max next move
ans++;
if(cur>= numsSize -1) return ans;
}
return ans;
}
1005.K次取反后最大化的数组
给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)
以这种方式修改数组后,返回数组可能的最大和。
示例 1:
- 输入:A = [4,2,3], K = 1
- 输出:5
- 解释:选择索引 (1) ,然后 A 变为 [4,-2,3]。
- 输入:A = [3,-1,0,2], K = 3
- 输出:6
- 解释:选择索引 (1, 2, 2) ,然后 A 变为 [3,1,0,2]。
分析:
贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。
局部最优可以推出全局最优。
那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。
那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。
虽然这道题目大家做的时候,可能都不会去想什么贪心算法,一鼓作气,就AC了。
那么本题的解题步骤为:
- 第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小(很妙!)
- 第二步:从前向后遍历,遇到负数将其变为正数,同时K--
- 第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
- 第四步:求和
int cmp(const void *a,const void *b)
{
return abs(*(int *)b)-abs(*(int *)a);
}
int largestSumAfterKNegations(int* nums, int numsSize, int k) {
qsort(nums, numsSize, sizeof(int),cmp);
int sum=0;
for (int i=0;i<numsSize;i++){
if(k>0 && nums[i]<0) {
nums[i]=-nums[i];
k--;
}
while(k>0 && i==numsSize-1 ){
nums[i]=-nums[i];
k--;
}
sum=sum+nums[i];
}
return sum;
}
34. 加油站
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
说明:
- 如果题目有解,该答案即为唯一答案。
- 输入数组均为非空数组,且长度相同。
- 输入数组中的元素均为非负数。
示例 1: 输入:
- gas = [1,2,3,4,5]
- cost = [3,4,5,1,2]
输出: 3 解释:
- 从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
- 开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
- 开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
- 开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
- 开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
- 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
- 因此,3 可为起始索引。
分析:
尝试用累积的方法求解
如果总油量减去总消耗大于等于零那么一定可以跑完一圈。——sum
说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。
i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。
int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize) {
int sum=0;//求差值,如果sum<0,肯定不能跑完一圈
int record_start;
int zonal_sum=-1;//通过zonal_sum找到能累积起最大值的起点
for(int i=0;i<gasSize;i++){
if(zonal_sum<0){
zonal_sum=0;
record_start=i;
}
gas[i]=gas[i]-cost[i];
sum+=gas[i];
zonal_sum+=gas[i];
}
if(sum<0) return -1;
return record_start;
}
135. 分发糖果
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
- 相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
示例 1:
- 输入: [1,0,2]
- 输出: 5
- 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。
分析:
采用了两次贪心的策略:
- 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
- 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。
这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。
这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。
先确定右边评分大于左边的情况(也就是从前向后遍历)
此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
局部最优可以推出全局最优。
**注意对相等情况的处理
再确定左孩子大于右孩子的情况(从后向前遍历)
如果 ratings[i] > ratings[i + 1],此时candyVec[i](第i个小孩的糖果数量)就有两个选择了,一个是candyVec[i + 1] + 1(从右边这个加1得到的糖果数量),一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。
那么又要贪心了,局部最优:取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量既大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。
局部最优可以推出全局最优。
代码:
int candy(int* ratings, int ratingsSize) {
int *c=malloc(sizeof(int)*ratingsSize);
c[0]=1;
for (int i=0;i<ratingsSize-1;i++){
int l=ratings[i];
int r=ratings[i+1];
if(r>l) c[i+1]=c[i]+1;
else c[i+1]=1;
}
int sum=c[ratingsSize-1];
for (int i=ratingsSize-1;i>=1;i--){
int l=ratings[i-1];
int r=ratings[i];
if(l>r) c[i-1]=fmax(c[i]+1,c[i-1]);
sum+=c[i-1];
}
return sum;
}