1.分发饼干
class Solution {
public int findContentChildren(int[] g, int[] s) {
//大饼干喂饱大胃口
int num=0;
int gLen=g.length-1;
int sLen=s.length-1;
Arrays.sort(g);
Arrays.sort(s);
int startIndex=sLen;
for(int i=gLen;i>=0;i--){
if(startIndex>=0&&g[i]<=s[startIndex]){
startIndex--;
num++;
}
}
return num;
}
}
2.摆动序列
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
- 输入: [1,7,4,9,2,5]
- 输出: 6
- 解释: 整个序列均为摆动序列。
class Solution {
public int wiggleMaxLength(int[] nums) {
int len=nums.length;
if(len<=1){
return len;
}
int curDiff=0;
int preDiff=0;
int count=1;//初始化有一个值
for(int i=1;i<len;i++){
curDiff=nums[i]-nums[i-1];
//考虑平坡
if((curDiff>0&&preDiff<=0)||(curDiff<0&&preDiff>=0)){
count++;
preDiff=curDiff; //记得移动
}
}
return count;
}
}
3.最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
- 输入: [-2,1,-3,4,-1,2,1,-5,4]
- 输出: 6
- 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
class Solution {
public int maxSubArray(int[] nums) {
int res = Integer.MIN_VALUE;
int sum=0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
res=Math.max(res,sum);
//如果总和变为负数会拖累后面的总和
if(sum<0){
sum=0;
}
}
return res;
}
}
4.买卖股票的最佳时机2
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
- 输入: [7,1,5,3,6,4]
- 输出: 7
- 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
class Solution {
public int maxProfit(int[] prices) {
if(prices==null||prices.length==0) return 0;
int len= prices.length;
int sum=0;
for(int i=1;i<len;i++){
if(prices[i]-prices[i-1]>0){ //只要比前一天盈利就可以
sum+=prices[i]-prices[i-1];
}
}
return sum;
}
}
5.跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
- 输入: [2,3,1,1,4]
- 输出: true
- 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
class Solution {
public boolean canJump(int[] nums) {
//应该用覆盖范围 而不是每次只取最远跳到哪里
if(nums.length==1){
return true;
}
int coverRange=0;//表示从第一个下标开始的覆盖范围
//!!!不是遍历nums数组的长度 是遍历覆盖范围
for(int i=0;i<=coverRange;i++){
coverRange=Math.max(coverRange,i+nums[i]);
if(coverRange>=nums.length-1){
return true;
}
}
return false;
}
}
6.跳跃游戏2
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
- 输入: [2,3,1,1,4]
- 输出: 2
- 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明: 假设你总是可以到达数组的最后一个位置。
class Solution {
//注意题目假设一定可以到达末尾
public int jump(int[] nums) {
if (nums == null || nums.length == 0 || nums.length == 1) {
return 0;
}
int curRange=0;//当前覆盖范围
int maxRange = 0;//最大的覆盖范围
int step=0;//初始化步数
for(int i=0;i<nums.length;i++){
maxRange=Math.max(maxRange,i+nums[i]);
if(maxRange>=nums.length-1){ //假设下一步就可以走到终点
step++;
break;
}
//注意是如果当前走到了覆盖范围的末尾才要走下一步
if(i==curRange){
curRange=maxRange;
step++;
}
}
return step;
}
}
7.K次取反后最大化的数组和
给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)
以这种方式修改数组后,返回数组可能的最大和。
思路:
让绝对值大的负数变为正数,当前数值达到最大。
那么如果将负数都转变为正数了,K依然大于0,只找数值最小的正整数进行反转
注意区分负数全为正数后,k是奇数还是偶数。
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
Arrays.sort(nums);
int sum = 0;
//尽量先把负数变成正数
for(int i=0;i<nums.length&&k>0;i++){
if(nums[i]<0){
nums[i]=-nums[i];
k--;
}
}
// 如果 k 还有剩余,检查 k 是否是奇数
Arrays.sort(nums); // 重新排序,因为负数可能变成正数,影响最小值
if (k % 2 == 1) {
nums[0] = -nums[0]; // 只翻转最小的数
}
// 计算最终的数组和
for (int num : nums) {
sum += num;
}
return sum;
}
}
##8.加油站
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
说明:
- 如果题目有解,该答案即为唯一答案。
- 输入数组均为非空数组,且长度相同。
- 输入数组中的元素均为非负数。
思路:
首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。
每个加油站的剩余量rest[i]为gas[i] - cost[i]。
i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。
那么为什么一旦[0,i] 区间和为负数,起始位置就可以是i+1呢,i+1后面就不会出现更大的负数?
如果出现更大的负数,就是更新i,那么起始位置又变成新的i+1了。
那有没有可能 [0,i] 区间 选某一个作为起点,累加到 i这里 curSum是不会小于零呢? 如图:
如果 curSum<0 说明 区间和1 + 区间和2 < 0, 那么 假设从上图中的位置开始计数curSum不会小于0的话,就是 区间和2>0。
区间和1 + 区间和2 < 0 同时 区间和2>0,只能说明区间和1 < 0, 那么就会从假设的箭头初就开始从新选择起始位置了。
为什么 curSum
没有涵盖所有路线也能保证结果正确?
1. curSum
只是局部的,totalSum
代表整体
curSum
负责局部计算,用来检查当前的起点是否可行。totalSum
负责整体判断,如果totalSum < 0
,则无解,直接返回-1
。
- 如果
curSum
变成负数,意味着之前的所有尝试的起点都不可能是解
- 如果从某个起点出发
curSum < 0
,说明无论在哪个中间站点出发,都会导致无油可走,所以必须换起点。 - 关键在于 如果
totalSum >= 0
,那一定有解,并且解一定在curSum
第一次变负数之后的i+1
位置!
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int curSum = 0; //当前路线上的剩余油量总和
int totalSum = 0;//所有加油站的剩余油量总和
int index = 0;//起点的坐标
for(int i=0;i<gas.length;i++){
curSum+=gas[i]-cost[i];
totalSum +=gas[i]-cost[i];
if(curSum<0){
index =(i+1)%gas.length;
curSum=0;
}
}
if(totalSum<0) return -1;
return index;
}
}
##9.分发糖果
师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
- 相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
示例 1:
- 输入: [1,0,2]
- 输出: 5
- 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。
示例 2:
- 输入: [1,2,2]
- 输出: 4
- 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
解析:
- 分两种情况遍历:一种是右孩子大于左孩子,从左往右遍历。一种是左孩子大于右孩子,从右往左遍历。
- 注意如果跟旁边的比较,相同没关系
class Solution {
public int candy(int[] ratings) {
int sum=0;
int[] candy= new int[ratings.length];
Arrays.fill(candy,1);
//右孩子比左孩子分高
for(int i=1;i<ratings.length;i++){
if(ratings[i]>ratings[i-1]){
candy[i]=candy[i-1]+1;
}
}
//左孩子比右孩子分高
for(int j=ratings.length-2;j>=0;j--){
if(ratings[j]>ratings[j+1]){
candy[j]=Math.max(candy[j],candy[j+1]+1);
}
}
for(int c:candy){
sum+=c;
}
return sum;
}
}
10.柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。
顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
示例 1:
- 输入:[5,5,5,10,20]
- 输出:true
- 解释:
- 前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
- 第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
- 第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
- 由于所有客户都得到了正确的找零,所以我们输出 true。
import java.util.HashMap;
import java.util.Map;
class Solution {
public boolean lemonadeChange(int[] bills) {
int five = 0, ten = 0; // 记录5美元和10美元的数量
for (int bill : bills) {
if (bill == 5) {
five++; // 收到5美元,不需要找零
} else if (bill == 10) {
if (five > 0) {
five--; // 找零一个5美元
ten++; // 收到一个10美元
} else {
return false; // 没有5美元找零,返回false
}
} else { // bill == 20
if (ten > 0 && five > 0) {
ten--; // 优先用一个10美元
five--; // 再用一个5美元
} else if (five >= 3) {
five -= 3; // 用三个5美元找零
} else {
return false; // 无法找零
}
}
}
return true;
}
}
11.根据升高重建队列
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
思路:
对于本题相信大家困惑的点是先确定k还是先确定h呢,也就是究竟先按h排序呢,还是先按照k排序呢?
如果按照k来从小到大排序,排完之后,会发现k的排列并不符合条件,身高也不符合条件,两个维度哪一个都没确定下来。
那么按照身高h来排序呢,身高一定是从大到小排(身高相同的话则k小的站前面),让高个子在前面。
此时我们可以确定一个维度了,就是身高,前面的节点一定都比本节点高!
那么只需要按照k为下标重新插入队列就可以了,为什么呢?
排序完的people: [[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]]
插入的过程:
- 插入[7,0]:[[7,0]]
- 插入[7,1]:[[7,0],[7,1]]
- 插入[6,1]:[[7,0],[6,1],[7,1]]
- 插入[5,0]:[[5,0],[7,0],[6,1],[7,1]]
- 插入[5,2]:[[5,0],[7,0],[5,2],[6,1],[7,1]]
- 插入[4,4]:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
class Solution {
public int[][] reconstructQueue(int[][] people) {
//先把身高按照从高到低排
//然后从高到底 按照k值 对每一个人从前往后开始排序
//这样后面的按照k进行排序 就是基于前面的人 前面的人都是固定站位的了
// 身高从大到小排(身高相同k小的站前面)
Arrays.sort(people, (a, b) -> {
if (a[0] == b[0]) return a[1] - b[1]; // a - b 是升序排列
return b[0] - a[0]; //b - a 是降序排列
});
LinkedList<int[]> que = new LinkedList<>();
for (int[] p : people) {
que.add(p[1],p); //Linkedlist.add(index, value)
}
return que.toArray(new int[people.length][]);
}
}