练习
合并相似物品
给你两个二维整数数组 items1 和 items2 ,表示两个物品集合。每个数组 items 有以下特质:
items[i] = [valuei, weighti] 其中 valuei 表示第 i 件物品的 价值 ,weighti 表示第 i 件物品的 重量 。
items 中每件物品的价值都是 唯一的 。
请你返回一个二维数组 ret,其中 ret[i] = [valuei, weighti], weighti 是所有价值为 valuei 物品的 重量之和 。
注意:ret 应该按价值 升序 排序后返回。
示例 1:
输入:items1 = [[1,1],[4,5],[3,8]], items2 = [[3,1],[1,5]]
输出:[[1,6],[3,9],[4,5]]
解释:
value = 1 的物品在 items1 中 weight = 1 ,在 items2 中 weight = 5 ,总重量为 1 + 5 = 6 。
value = 3 的物品再 items1 中 weight = 8 ,在 items2 中 weight = 1 ,总重量为 8 + 1 = 9 。
value = 4 的物品在 items1 中 weight = 5 ,总重量为 5 。
所以,我们返回 [[1,6],[3,9],[4,5]] 。
示例 2:
输入:items1 = [[1,1],[3,2],[2,3]], items2 = [[2,1],[3,2],[1,3]]
输出:[[1,4],[2,4],[3,4]]
解释:
value = 1 的物品在 items1 中 weight = 1 ,在 items2 中 weight = 3 ,总重量为 1 + 3 = 4 。
value = 2 的物品在 items1 中 weight = 3 ,在 items2 中 weight = 1 ,总重量为 3 + 1 = 4 。
value = 3 的物品在 items1 中 weight = 2 ,在 items2 中 weight = 2 ,总重量为 2 + 2 = 4 。
所以,我们返回 [[1,4],[2,4],[3,4]] 。
示例 3:
输入:items1 = [[1,3],[2,2]], items2 = [[7,1],[2,2],[1,4]]
输出:[[1,7],[2,4],[7,1]]
解释:
value = 1 的物品在 items1 中 weight = 3 ,在 items2 中 weight = 4 ,总重量为 3 + 4 = 7 。
value = 2 的物品在 items1 中 weight = 2 ,在 items2 中 weight = 2 ,总重量为 2 + 2 = 4 。
value = 7 的物品在 items2 中 weight = 1 ,总重量为 1 。
所以,我们返回 [[1,7],[2,4],[7,1]] 。
提示:
1 <= items1.length, items2.length <= 1000
items1[i].length == items2[i].length == 2
1 <= valuei, weighti <= 1000
items1 中每个 valuei 都是 唯一的 。
items2 中每个 valuei 都是 唯一的 。
大神代码
class Solution {
public List<List<Integer>> mergeSimilarItems(int[][] items1, int[][] items2) {
List<List<Integer>> temp = new ArrayList<>();
HashMap<Integer, Integer> map = new HashMap<>();
for (int[] i :items1) {//让得到items1的键和值
//后面+i[1]是为了键值相同情况下 让其值相加
map.put(i[0], map.getOrDefault(i[0],0)+i[1]);//看下面ps
}
for (int[] i :items2) {
//后面+i[1]是为了键值相同情况下 让其值相加
map.put(i[0], map.getOrDefault(i[0],0)+i[1]);
}
for (Map.Entry<Integer,Integer>entry:map.entrySet()){
int value=entry.getKey();//获取键
int weight=entry.getValue();//获取值
ArrayList<Integer> item = new ArrayList<>();//一位数组
item.add(value);//[value,]
item.add(weight);//[value,weight]
temp.add(item);//二维添加一维数组
}
//为了顺序遍历 因为哈希数组是无序的
Collections.sort(temp, Comparator.comparingInt(a -> a.get(0)));
return temp;
}
}
PS
getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
eg:map.getOrDefault(i[0],0) 默认返回0
entrySet获取 key和value的映射
不粘自己的了 自己用的数组 因为题规定了最多1000的长度 我就用数组遍历写的,感觉投机取巧了
没大神写的大气 还是得多学哈希数组 这样以后也用得上
另外
Collections.sort(temp, Comparator.comparingInt(a -> a.get(0)));
第一次见 长见识了
K 次调整数组大小浪费的最小总空间
你正在设计一个动态数组。给你一个下标从 0 开始的整数数组 nums ,其中 nums[i] 是 i 时刻数组中的元素数目。除此以外,你还有一个整数 k ,表示你可以 调整 数组大小的 最多 次数(每次都可以调整成 任意 大小)。
t 时刻数组的大小 sizet 必须大于等于 nums[t] ,因为数组需要有足够的空间容纳所有元素。t 时刻 浪费的空间 为 sizet - nums[t] ,总 浪费空间为满足 0 <= t < nums.length 的每一个时刻 t 浪费的空间 之和 。
在调整数组大小不超过 k 次的前提下,请你返回 最小总浪费空间 。
注意:数组最开始时可以为 任意大小 ,且 不计入 调整大小的操作次数。
示例 1:
输入:nums = [10,20], k = 0
输出:10
解释:size = [20,20].
我们可以让数组初始大小为 20 。
总浪费空间为 (20 - 10) + (20 - 20) = 10 。
示例 2:
输入:nums = [10,20,30], k = 1
输出:10
解释:size = [20,20,30].
我们可以让数组初始大小为 20 ,然后时刻 2 调整大小为 30 。
总浪费空间为 (20 - 10) + (20 - 20) + (30 - 30) = 10 。
示例 3:
输入:nums = [10,20,15,30,20], k = 2
输出:15
解释:size = [10,20,20,30,30].
我们可以让数组初始大小为 10 ,时刻 1 调整大小为 20 ,时刻 3 调整大小为 30 。
总浪费空间为 (10 - 10) + (20 - 20) + (20 - 15) + (30 - 30) + (30 - 20) = 15 。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 106
0 <= k <= nums.length - 1
大神代码
class Solution {
public int minSpaceWastedKResizing(int[] nums, int k) {
int n=nums.length;//获得nums数组长度
int[][] m=new int[n][n];
int sum=0;//因为题目每个区间都要求和(区间最大值-每个值)
for (int i = 0; i < n; i++) {//遍历n
int max=0;//为了后面的nums[j]值比较
for (int j = i; j < n; j++) {
max=Math.max(max,nums[j]);//每一项最大的边界
sum=sum+nums[j];//区间和
m[i][j]=max*(j-i+1)-sum;//区间最大值*区间长度-区间和
}
}
int[][] dp=new int[n][k+2];// k次机会可以形成k+1段
// dp[i][j] 表示[0, i] 划分为j段的浪费数量
// 即每段过度时,都会消耗一次调整机会将容量调整到下一段的最大值
for (int i = 0; i < n; i++) {
//Arrays类的fill() 方法是用来输入给定数组中元素值的
Arrays.fill(dp[i],Integer.MAX_VALUE/2);
}
//dp算法(迭代)
//第一次循环 象征从后往前循环i个数
//第二次循环 循环遍历nums值
//第三次循环 判断最小操作次数 nums从l开始 到i结束
for (int i = 0; i < n; i++) {// 前i个数
for (int j = i; j <= k+1; j++) {// 调整j-1次
for (int l = 0; l <= i ; l++) {// 最后一段nums[l...i]
dp[i][j] = Math.min(dp[i][j], (l == 0 ? 0 : dp[l-1][j-1]) + m[l][i]);
}
}
}
return dp[n-1][k+1];
}
}
直接去看的代码 不会写
cpu炸了 愣在原地 去看了dp算法也是一脸懵逼 到现在也没理解,只知道一开始是递归算,后面变成带有哈希表的递归 再后面根据算法优化用空间换时间 变成迭代
dp前面的我还能懂一下,dp就直接人没了,哪怕看着写着注释,也理解不能
又是一道别人眼中的中等题,我眼中的高级题
八股
如何决定使用 HashMap 还是 TreeMap?
无序使用hashmap 有序使用treemap
如果是使用插入,删除和定位元素,hashmap比treemap更有效率
如果要有序集合,用treemap
说一下 HashMap 的实现原理
hashmap底层是数组和链表,通过哈希表的map接口进行非同步实现
可以映射键值对,允许null的键和值,但映射排列是无序的
说一下 HashSet 的实现原理?
hashset底层是hashmap
hashset的值存放在hashmap的key
hashmap的value是present
ArrayList 和 LinkedList 的区别是什么?
arraylist是数组,支持随机访问
linkedlist是双向链表,不支持随机访问
rabbitmq第四天..
死信队列
某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有
后续的处理,就变成了死信,有死信自然就有了死信队列
EG:
应用场景:为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息
消费发生异常时,将消息投入死信队列中.还有比如说: 用户在商城下单成功并点击去支付后在指定时
间未支付时自动失效
死信来源
消息 TTL 过期
队列达到最大长度(队列满了,无法再添加数据到 mq 中)
消息被拒绝(basic.reject 或 basic.nack)(nack否定或者reject拒绝应答)
并且 requeue=false.(不放回队列)
架构图
消费者1--消息时间过期
/**
*死信队列
*
* 消费者1
*/
public class Consumer01 {
//普通交换机名称
private static final String NORMAL_EXCHANGE = "normal_exchange";
//死信交换机名称
private static final String DEAD_EXCHANGE = "dead_exchange";
//声明普通队列名称
private static final String NORMAL_QUEUE = "normal-queue";
//声明死信队列名称
private static final String DEAD_QUEUE = "dead-queue";
public static void main(String[] argv) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//声明死信和普通交换机 类型为 direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
//正常队列绑定死信队列信息
Map<String, Object> params = new HashMap<>();
//过期时间 10s=10000ms
//可以在消费者设置 也可以在生产方设置(更多在生产方)
//params.put("x-message-tt1",10000);
//正常队列设置死信交换机 参数 key 是固定值
params.put("x-dead-letter-exchange", DEAD_EXCHANGE);
//正常队列设置死信 routing-key 参数 key 是固定值
params.put("x-dead-letter-routing-key", "lisi");
//普通队列
//用到了最后的其他参数
channel.queueDeclare(NORMAL_QUEUE, false, false, false, params);
//绑定普通的交换机与普通队列
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
//死信队列
channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
//绑定死信的交换机与死信队列 routingkey
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
System.out.println("等待接收消息.....");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("Consumer01 接收到消息"+message);
};
channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, consumerTag -> {
});
}
}
生产者
public class Producer {
//普通交换机名称
private static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] argv) throws Exception {
try (Channel channel = RabbitMqUtils.getChannel()) {
//死信消息
//设置死信消息的 TTL 时间
//构建 过期时间 构建 10000ms=10s
AMQP.BasicProperties properties = new
AMQP.BasicProperties().builder().expiration("10000").build();
//该信息是用作演示队列个数限制
for (int i = 1; i <11 ; i++) {
String message="info"+i;
channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", properties,
message.getBytes());
System.out.println("生产者发送消息:"+message);
}
}
}
}
为了模拟演示
消费者1开启后创建完队列 手动关闭
可以发现 10s后信息进入死信队列
消费者2
public class Consumer02 {
//声明死信队列名称
private static final String DEAD_QUEUE = "dead-queue";
public static void main(String[] argv) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
System.out.println("等待接收消息.....");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("Consumer02 接收到消息"+message);
};
channel.basicConsume(DEAD_QUEUE, true, deliverCallback, consumerTag -> {
});
}
}
创建消费者2接收死信队列 发现信息接收
队列达到最大长度
消费者1
//正常队列绑定死信队列信息
Map<String, Object> params = new HashMap<>();
//过期时间 10s=10000ms
//可以在消费者设置 也可以在生产方设置(更多在生产方)
//params.put("x-message-tt1",10000);
//正常队列设置死信交换机 参数 key 是固定值
params.put("x-dead-letter-exchange", DEAD_EXCHANGE);
//正常队列设置死信 routing-key 参数 key 是固定值
params.put("x-dead-letter-routing-key", "lisi");
新增
//设置正常队列长度限制-最多积压6条信息
params.put("x-max-length",6);
依旧是假死 死信队列出现四条
消息被拒绝
消费者1
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
//拒绝info5
if(message.equals("info5")){
System.out.println("Consumer01 接收到消息"+message+":此消息是C1拒绝的");
//不放回普通队列 false 放回就是true
channel.basicReject(delivery.getEnvelope().getDeliveryTag(),false);
}else {
System.out.println("Consumer01 接收到消息" + message);
//批量应答吗? false不批量
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}
};
//开启手动应答 不然不存在拒绝消息 false
channel.basicConsume(NORMAL_QUEUE, false, deliverCallback, consumerTag -> {
});
延迟队列
延迟队列概念及使用场景
延时队列,队列内部是有序的,最重要的特性就体现在它的延时属性上,延时队列中的元素是希望
在指定时间到了以后或之前取出和处理,简单来说,延时队列就是用来存放需要在指定时间被处理的
元素的队列
eg:
1.订单在十分钟之内未支付则自动取消
2.新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
3.用户注册成功后,如果三天内没有登陆则进行短信提醒。
4.用户发起退款,如果三天内没有得到处理则通知相关运营人员。
5.预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议
这些场景都有一个特点,需要在某个事件发生之后或者之前的指定时间点完成某一项任务
整合Springboot
队列TTL
配置类代码
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
*TTL队列 配置文件类代码
*/
@Configuration//配置
public class TtlQueueConfig {
//普通交换机名称
public static final String X_EXCHANGE = "X";
//普通队列名称
public static final String QUEUE_A = "QA";
public static final String QUEUE_B = "QB";
//死信交换机名称
public static final String Y_DEAD_LETTER_EXCHANGE = "Y";
//死信队列名称
public static final String DEAD_LETTER_QUEUE = "QD";
// 声明 xExchange 别名
@Bean("xExchange")
public DirectExchange xExchange(){
return new DirectExchange(X_EXCHANGE);
}
// 声明 yExchange 别名
@Bean("yExchange")
public DirectExchange yExchange(){
return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
}
//声明普通队列 A TTL 为 10s 并绑定到对应的死信交换机
@Bean("queueA")
public Queue queueA(){
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
//声明当前队列的死信路由 routing key
args.put("x-dead-letter-routing-key", "YD");
//声明队列的 TTL 单位ms
args.put("x-message-ttl", 10000);
return QueueBuilder.durable(QUEUE_A).withArguments(args).build();
}
// 声明队列 A 绑定 X 交换机
@Bean
public Binding queueaBindingX(@Qualifier("queueA") Queue queueA, @Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueA).to(xExchange).with("XA");
}
//声明普通队列 B TTL 为 40s 并绑定到对应的死信交换机
@Bean("queueB")
public Queue queueB(){
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
//声明当前队列的死信路由 key
args.put("x-dead-letter-routing-key", "YD");
//声明队列的 TTL
args.put("x-message-ttl", 40000);
return QueueBuilder.durable(QUEUE_B).withArguments(args).build();
}
//声明队列 B 绑定 X 交换机
@Bean
public Binding queuebBindingX(@Qualifier("queueB") Queue queue1B, @Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queue1B).to(xExchange).with("XB");
}
//声明死信队列 QD
@Bean("queueD")
public Queue queueD(){
return new Queue(DEAD_LETTER_QUEUE);
}
//声明死信队列 QD 绑定关系
@Bean
public Binding deadLetterBindingQAD(@Qualifier("queueD") Queue queueD, @Qualifier("yExchange") DirectExchange yExchange){
return BindingBuilder.bind(queueD).to(yExchange).with("YD");
}
}
生产者
/**
*发送延迟消息
*
* localhost:8080/ttl/sendMsg/xxx
*/
@Slf4j
@RequestMapping("ttl")
@RestController
public class SendMsgController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("sendMsg/{message}")
public void sendMsg(@PathVariable String message){
log.info("当前时间:{},发送一条信息给两个 TTL 队列:{}", new Date(), message);
rabbitTemplate.convertAndSend("X", "XA", "消息来自 ttl 为 10S 的队列: "+message);
rabbitTemplate.convertAndSend("X", "XB", "消息来自 ttl 为 40S 的队列: "+message);
}
}
消费者
/**
* 队列ttl 消费者
* */
@Slf4j
@Component
public class DeadLetterQueueConsumer {
//接收消息
@RabbitListener(queues = "QD")
public void receiveD(Message message, Channel channel) throws IOException {
//{}占位符 时间和消息 会替换到相应的位置
String msg = new String(message.getBody());
log.info("当前时间:{},收到死信队列信息{}", new Date().toString(), msg);
}
}
配置
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
*TTL队列 配置文件类代码
*/
@Configuration//配置
public class TtlQueueConfig {
//普通交换机名称
public static final String X_EXCHANGE = "X";
//普通队列名称
public static final String QUEUE_A = "QA";
public static final String QUEUE_B = "QB";
//死信交换机名称
public static final String Y_DEAD_LETTER_EXCHANGE = "Y";
//死信队列名称
public static final String DEAD_LETTER_QUEUE = "QD";
// 声明 xExchange 别名
@Bean("xExchange")
public DirectExchange xExchange(){
return new DirectExchange(X_EXCHANGE);
}
// 声明 yExchange 别名
@Bean("yExchange")
public DirectExchange yExchange(){
return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
}
//声明普通队列 A TTL 为 10s 并绑定到对应的死信交换机
@Bean("queueA")
public Queue queueA(){
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
//声明当前队列的死信路由 routing key
args.put("x-dead-letter-routing-key", "YD");
//声明队列的 TTL 单位ms
args.put("x-message-ttl", 10000);
return QueueBuilder.durable(QUEUE_A).withArguments(args).build();
}
// 声明队列 A 绑定 X 交换机
@Bean
public Binding queueaBindingX(@Qualifier("queueA") Queue queueA, @Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queueA).to(xExchange).with("XA");
}
//声明普通队列 B TTL 为 40s 并绑定到对应的死信交换机
@Bean("queueB")
public Queue queueB(){
Map<String, Object> args = new HashMap<>(3);
//声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
//声明当前队列的死信路由 key
args.put("x-dead-letter-routing-key", "YD");
//声明队列的 TTL
args.put("x-message-ttl", 40000);
return QueueBuilder.durable(QUEUE_B).withArguments(args).build();
}
//声明队列 B 绑定 X 交换机
@Bean
public Binding queuebBindingX(@Qualifier("queueB") Queue queue1B, @Qualifier("xExchange") DirectExchange xExchange){
return BindingBuilder.bind(queue1B).to(xExchange).with("XB");
}
//声明死信队列 QD
@Bean("queueD")
public Queue queueD(){
return new Queue(DEAD_LETTER_QUEUE);
}
//声明死信队列 QD 绑定关系
@Bean
public Binding deadLetterBindingQAD(@Qualifier("queueD") Queue queueD, @Qualifier("yExchange") DirectExchange yExchange){
return BindingBuilder.bind(queueD).to(yExchange).with("YD");
}
}
依赖
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--RabbitMQ 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!--RabbitMQ 测试依赖-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
出现了一个问题,我学的jdk版本是1.8 但我执意用的17 结果一直找不到bean 离谱
因为这个破问题找了一个半小时 本来今天和明天就能刷完这门课 悬了