文章目录
区间问题
最大不相交区间数量
将所有区间按照右端点排序,如果目前区间左端与上一个区间不相交,则点数增加,更新右端点
import java.util.*;
import java.io.*;
class Seg{//保存区间
int l,r;
public Seg(int l,int r){//构造器
this.l=l;
this.r=r;
}
}
public class Main{
public static void main(String[] args)throws IOException {
Scanner sc=new Scanner(System.in);
int n=Integer.parseInt(sc.nextLine());//区间数
Seg[]segs=new Seg[n];//区间数组
//读取区间
for(int i=0;i<n;i++){
String []str=sc.nextLine().split(" ");
int l=Integer.parseInt(str[0]);
int r=Integer.parseInt(str[1]);
segs[i]=new Seg(l,r);
}
Arrays.sort(segs,new Comparator<Seg>(){
public int compare(Seg seg1,Seg seg2){
return seg1.r-seg2.r;//按照区间右端点排序
}
});
int res=0;
int end=Integer.MIN_VALUE;//初始化上一个区间的右端点
for(Seg s:segs){
if(s.l>end){//当前区间左端与前一个区间右端不相交
res++;
end=s.r;//更新下一个区间右端
}
}
System.out.println(res);
}
}
最少箭引爆气球
按照左端点排序,到达重叠起球最小边界就res++,否则更新当前重叠最小边界
class Solution {
public int findMinArrowShots(int[][] points) {
//按照左端点排序
Arrays.sort(points,(a,b)->Integer.compare(a[0],b[0]));
int res=1;
for(int i=1;i<points.length;i++){
if(points[i][0]>points[i-1][1]){//不相交了
res++;
}else{
//更新右边界最小值
points[i][1]=Math.min(points[i][1],points[i-1][1]);
}
}
return res;
}
}
无重叠区间
按照左端点排序,前后不相交时res++,最后移除的区间用总的区间减去不重叠区间的最大值(res)
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
int res=1;
for(int i=1;i<intervals.length;i++){
if(intervals[i][0]>=intervals[i-1][1]){
res++;
}else{
intervals[i][1]=Math.min(intervals[i][1],intervals[i-1][1]);
}
}
return intervals.length-res;
}
}
区间覆盖
import java.util.*;
class Range implements Comparable<Range>{
int l,r;
public int compareTo(Range o){
return Integer.compare(l,o.l);//按照左端点排序
}
public Range(int l,int r){
this.l=l;
this.r=r;
}
}
public class Main{
static int N=100010;
static Range[]range=new Range[N];
public static void main(String[]args){
Scanner sc=new Scanner(System.in);
int st=sc.nextInt();
int ed=sc.nextInt();
int n=sc.nextInt();
for(int i=0;i<n;i++){
int l=sc.nextInt();
int r=sc.nextInt();
range[i]=new Range(l,r);
}
Arrays.sort(range,0,n);//按照左端点排序
int res=0;//区间数
boolean pass=false;
for(int i=0;i<n;i++){
int j=i;
int end=(int)-(2e9);
while(j<n&&range[j].l<=st){//左端点包含了当前区间起点
end=Math.max(end,range[j].r);//更新最右端能伸到的位置
j++;
}
if(end<st){//不能包含,跳到下一次区间
break;
}
res++;//满足条件的区间数++
if(end>=ed){//结束
pass=true;
break;
}
st=end;//更新起点为最右的端点
i=j-1;//下一次从j走过的之后开始走
}
if(!pass)res=-1;
System.out.println(res);
}
}
划分字母区间
class Solution {
public List<Integer> partitionLabels(String s) {
List<Integer>list=new LinkedList<>();
char[]chars=s.toCharArray();
int []edge=new int [26];
for(int i=0;i<chars.length;i++){
edge[chars[i]-'a']=i;//记录每个字符出现的最远位置
}
int ed=0;
int st=-1;
for(int i=0;i<chars.length;i++){
ed=Math.max(ed,edge[chars[i]-'a']);//字符数组中距离最远的字符
if(ed==i){
list.add(i-st);//截取长度
st=i;
}
}
return list;
}
}
区间合并
按照左端点排序,如果前后区间存在重复,则更新当前区间的右端的最大值,不重复直接加入答案
class Solution {
public int[][] merge(int[][] intervals) {
LinkedList<int[]>res=new LinkedList<>();
Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));//左端点排序
res.add(intervals[0]);//加入第一个区间
for(int i=1;i<intervals.length;i++){
if(intervals[i][0]<=res.getLast()[1]){//重叠
int start=res.getLast()[0];
int end=Math.max(res.getLast()[1],intervals[i][1]);//取相邻两个区间右端最大值
res.removeLast();//移除前一个
res.add(new int[]{start,end});//加入合并后的区间
}else{
res.add(intervals[i]);
}
}
return res.toArray(new int[res.size()][]);
}
}
重置更新问题
最大连续子数组的和
class Solution {
public int maxSubArray(int[] nums) {
if(nums.length==1)return nums[0];
int res=Integer.MIN_VALUE; //更新区间和最大值
int count=0; //记录区间和
for(int i=0;i<nums.length;i++){
count+=nums[i];
res=Math.max(res,count);
if(count<=0){
count=0; //重计区间和,跳到下一个以正数开头的区间
}
}
return res;
}
}
加油站
记录连续的差的和,一旦小于0,就移动起点
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int curSum=0;
int totalSum=0;
int start=0;
for(int i=0;i<gas.length;i++){
curSum+=gas[i]-cost[i];//连续差的和
totalSum+=gas[i]-cost[i];
if(curSum<0){
curSum=0;//重置
start=i+1;//跳跃
}
}
if(totalSum<0)return -1;
return start;
}
}
股票问题
最大利润
//累计每一天的正利润,就保证了整体利润最大
class Solution {
public int maxProfit(int[] prices) {
int res=0;
for(int i=0;i<prices.length-1;i++){
if(prices[i+1]>prices[i]){
res+=Math.max(prices[i+1]-prices[i],0);
}
}
return res;
}
}
含手续费
class Solution {
public int maxProfit(int[] prices, int fee) {
int buy=prices[0]+fee;
int res=0;
for(int p:prices){
if(p+fee<buy){//低价格买入
buy=p+fee;
}else if(p>buy){//高价格卖出
res+=p-buy;
buy=p;
}
}
return res;
}
}
跳跃游戏
跳跃游戏|
class Solution {
public boolean canJump(int[] nums) {
if(nums.length==1)return true;
int cover=0;
for(int i=0;i<=cover;i++){
//跟新每一点的最大覆盖范围
cover=Math.max(cover,i+nums[i]);
if(cover>=nums.length-1){
return true;
}
}
return false;
}
}
跳跃游戏||
起点~倒数第二个点之间,每当到达最远点i==nextdistance,次数增加
class Solution {
public int jump(int[] nums) {
int curdistance=0;//当前点最大覆盖范围
int nextdistance=0;//下一个点最大覆盖范围
int ans=0;
for(int i=0;i<nums.length-1;i++){
nextdistance=Math.max(i+nums[i],nextdistance);
if(curdistance==i){//一到最远点就++
ans++;
curdistance=nextdistance;
}
}
return ans;
}
}
排序问题
分发糖果的最少数量
从左往右,右孩子大于左孩子,让右孩子糖果数比左孩子多一个
从右往左,左孩子大于右孩子,让左孩子糖果数要么比右孩子多一个,要么不变
class Solution {
public int candy(int[] ratings) {
int len=ratings.length;
int[]candy=new int[len];
for(int i=0;i<len;i++)candy[i]=1;//初始化为1个
for(int i=1;i<len;i++){//从左往右遍历
if(ratings[i]>ratings[i-1])
candy[i]=candy[i-1]+1;//右边大的统一比左边多一个
}
for(int i=len-2;i>=0;i--){//从右往左遍历
if(ratings[i]>ratings[i+1])
candy[i]=Math.max(candy[i],candy[i+1]+1);//取最值
}
int sum=0;
for(int c:candy){
sum+=c;
}
return sum;
}
}
根据身高重建队列
1)按照身高从高到低排序,身高一样按照k小的排前面
2)从前往后按照k值插入指定的下标位置
class Solution {
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];
});
LinkedList<int[]>que=new LinkedList<>();
for(int[]q:people){
que.add(q[1],q);//将q插入到q[1]的下标位置
}
return que.toArray(new int[people.length][]);
}
}
打水问题
最慢的人最后打水!,排序后求和即可
import java.util.*;
public class Main{
public static void main(String[]args)throws Exception{
Scanner sc=new Scanner(System.in);
int N=100010;
int[]t=new int[N];
int n=sc.nextInt();
for(int i=0;i<n;i++){
t[i]=sc.nextInt();
}
Arrays.sort(t);
long res=0;
for(int i=0;i<n;i++){
res+=t[i]*(n-i-1);
}
System.out.println(res);
}
}
哈夫曼树
有n堆果子,将果子两两合并,每次合并消耗的体力是两堆果子重量之和,求合并所有果子消耗的最少体力
每次选择质量最少的两堆果子合并,由局部最优可以推出全局最优解
import java.util.*;
public class Main{
public static void main(String[]args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
//小根堆
Queue<Integer>minHeap=new PriorityQueue<>();
for(int i=0;i<n;i++){
minHeap.add(sc.nextInt());
}
int res=0;
//每次取出最轻的两堆进行合并
while(minHeap.size()>1){
int a=minHeap.poll();
int b=minHeap.poll();
res+=a+b;
minHeap.add(a+b);
}
System.out.println(res);
}
}
绝对值不等式
如果是两个点a,b,则a,b到他们之间的点的距离最短,就是ab的距离,同理拓展到n个点,应选取中间的点即中位数
import java.util.*;
public class Main{
public static void main(String[]args){
Scanner sc=new Scanner(System.in);
int N=100010;
int[]a=new int[N];
int n=sc.nextInt();
for(int i=0;i<n;i++){
a[i]=sc.nextInt();
}
Arrays.sort(a,0,n);
long res=0;
for(int i=0;i<n;i++){
res+=Math.abs(a[i]-a[n/2]);
}
System.out.println(res);
}
}
其他
能否成功找零
1)收的5块,直接收下,不用找零
2)收的10块,找5块的
3)收的20块,优先找10和5块的,否则找3个5块的
class Solution {
public boolean lemonadeChange(int[] bills) {
int five=0,ten=0;
for(int bill:bills){
if(bill==5){
five++;
}
if(bill==10){
if(five<=0)return false;
five--;
ten++;
}
if(bill==20){
if(ten>=1&&five>=1){
ten--;
five--;
}else if(five>=3){
five-=3;
}else return false;
}
}
return true;
}
}
返回小于n的单增数字
从后往前遍历,如果相邻不满足单增关系,则前一个–,标记后一个要从9开始取
class Solution {
public int monotoneIncreasingDigits(int n) {
String s=String.valueOf(n);//转化为字符串
char[]chars=s.toCharArray();//转化为字符数组
int start=s.length();//标记赋值为9的位置
for(int i=s.length()-1;i>0;i--){
if(chars[i-1]>chars[i]){
chars[i-1]--;
start=i;
}
}
for(int i=start;i<s.length();i++){
chars[i]='9';//之后全部赋值为9
}
return Integer.parseInt(String.valueOf(chars));
}
}
满足孩子数量的最大值
class Solution {
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int count=0;
int index=s.length-1;
for(int i=g.length-1;i>=0;i--){//遍历饼干
if(index>=0&&g[i]<=s[index]){//先满足胃口大的
count++;
index--; //自减而不是双重循环
}
}
return count;
}
}
摆动序列个数
题目描述:
class Solution {
public int wiggleMaxLength(int[] nums) {
int res=1;
int prediff=0; //前一对儿的差值
int curdiff=0; //当前对儿的差值
for(int i=0;i<nums.length-1;i++){
curdiff=nums[i+1]-nums[i];
if((curdiff>0&&prediff<=0)||(curdiff<0&&prediff>=0)){
res++;
prediff=curdiff;
}
}
return res;
}
}
k次取反后的最大数组和
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
nums = IntStream.of(nums).boxed()
.sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1))
.mapToInt(Integer::intValue).toArray();
int len=nums.length;
for(int i=0;i<len;i++){
if(nums[i]<0&&k>0){//短路求值,k必须后判断!
nums[i]=-nums[i];
k--;
}
}
if(k%2==1){
nums[len-1]=-nums[len-1];
}
int sum=0;
for(int i=0;i<len;i++){
sum+=nums[i];
}
return sum;
}
}
监视所有结点的最少摄像头
/**
* 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;
* }
* }
*/
class Solution {
int res=0;
public int minCamera(TreeNode root){
/**
0表示未覆盖,1表示有摄像头,2表示已覆盖
*/
//空结点看作已经覆盖
if(root==null){
return 2;
}
//后序遍历
int left=minCamera(root.left);
int right=minCamera(root.right);
//左右儿子都覆盖,则父节点没覆盖
if(left==2&&right==2){
return 0;
}
//左右儿子至少有一个没有被覆盖,则必须放摄像头
else if(left==0||right==0){
res++;
return 1;
}
//左右儿子至少有一个放了摄像头,则已经覆盖
else {
return 2;
}
}
public int minCameraCover(TreeNode root) {
//先判断根节点是否已经覆盖,额外添加摄像头
if(minCamera(root)==0){
res++;
}
return res;
}
}