【刷题之路】LeetCode 2073. 买票需要的时间

news/2024/7/2 23:37:29

【刷题之路】LeetCode 2073. 买票需要的时间

  • 一、题目描述
  • 二、解题
    • 1、方法1——记录每个人需要的时间
      • 1.1、思路分析
      • 1.2、代码实现
    • 2、方法2——队列+记录下标
      • 2.1、思路分析
      • 2.2、先将队列实现一下
      • 2.3、代码实现

一、题目描述

原题连接: 2073. 买票需要的时间
题目描述:
有 n 个人前来排队买票,其中第 0 人站在队伍 最前方 ,第 (n - 1) 人站在队伍 最后方 。
给你一个下标从 0 开始的整数数组 tickets ,数组长度为 n ,其中第 i 人想要购买的票数为 tickets[i] 。
每个人买票都需要用掉 恰好 1 秒 。一个人 一次只能买一张票 ,如果需要购买更多票,他必须走到 队尾 重新排队(瞬间 发生,不计时间)。如果一个人没有剩下需要买的票,那他将会 离开 队伍。
返回位于位置 k(下标从 0 开始)的人完成买票需要的时间(以秒为单位)。

示例 1:

输入: tickets = [2,3,2], k = 2
输出: 6
解释:

  • 第一轮,队伍中的每个人都买到一张票,队伍变为 [1, 2, 1] 。
  • 第二轮,队伍中的每个都又都买到一张票,队伍变为 [0, 1, 0] 。
    位置 2 的人成功买到 2 张票,用掉 3 + 3 = 6 秒。

示例 2:

输入: tickets = [5,1,1,1], k = 0
输出: 8
解释:

  • 第一轮,队伍中的每个人都买到一张票,队伍变为 [4, 0, 0, 0] 。
  • 接下来的 4 轮,只有位置 0 的人在买票。
    位置 0 的人成功买到 5 张票,用掉 4 + 1 + 1 + 1 + 1 = 8 秒。

提示:
n == tickets.length
1 <= n <= 100
1 <= tickets[i] <= 100
0 <= k < n

二、解题

1、方法1——记录每个人需要的时间

1.1、思路分析

很显然,我们的程序是要在第k个人买完票的时候停止。那么在第k个人买完票的时候,其他的所有人能买到的票数,也即买票的时间也就已经确定了(包括买完票和未买完票)。
具体的:
1、对于排在第k个人前面的或者第k个人(即i <= k),因为他们的买票顺序一定先于第k个人(即使是重排到队尾也还是前面的人先重排)。
在这里插入图片描述
如果他们其中一人要买的票数小于第k个人要买的票数,那么他在第k个人买完票之前就能先买完票。而如果他要买的票数大于或等于第k个人需要买的票数,那么在第k个人买完票时他能买到的票数也和第k个人的相同。
所以对于位置小于或等于k的人,他在第k个人买完票时能买到的票数是min(tickets[i], tickets[k])。
2、而对于排在第k个人后面的人,如上面所说的,他们买票的顺序一定是在第k个人之后。所以当第k个人买完票时,如果他们中的每一个最多能买到的票数为tickets[k] - 1,而如果小于tickets[k] - 1,那就能买完。
所以对于位置大于k的人,他在第k个人买完票时能买到的票数是min(tickets[i], tickets[k] - 1)。

有题目所述我们得知,每个人买到的票数即是每个人所花的时间,所以我们可以先统计完每个人所花的时间,然后再算出这些事件的总和,最后返回这个总和即可。

1.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

int min(int x, int y) {
    return x < y ? x : y;
}
int timeRequiredToBuy(int* tickets, int ticketsSize, int k) {
    assert(tickets);
    int *time = (int*)malloc(ticketsSize * sizeof(int));
    if (NULL == time) {
        perror("malloc fail!\n");
        exit(-1);
    }
    int i = 0;
    for (i = 0; i < ticketsSize; i++) {
        if (i <= k) {
            time[i] = min(tickets[i], tickets[k]);
        } else {
            time[i] = min(tickets[i], tickets[k] - 1);
        }
    }
    int totalTime = 0; // 统计总时间
    for (i = 0; i < ticketsSize; i++) {
        totalTime += time[i];
    }
    free(time);
    return totalTime;
}

2、方法2——队列+记录下标

可能方法一的思路会有点儿抽象,有的朋友可能会看不懂。
但没关系,我这里还准备了第二种思路更简单易懂的方法,保证你能听得懂,只不过代码比方法一要稍微复杂“一点儿”。

2.1、思路分析

其实题目中描述的未买完票的人继续回到队尾排理解起来并不难,用队列实现也非常简单。但最主要的问题是当队列重排了之后我们并不能知道之后买完票的人是最初始队列中的第几个。
这个问题其实很容易解决,我们可以采用类似键值对的方式,创建一个结构体,里面保存好最初始队列中每个人对应的位置(下标)和票数(值),就像下面这样:

typedef struct keyVal {
    int index;
    int val;
} keyVal;

然后我们再将队列保存的数据改成keyVal,再将初始队列中每个人对应的keyVal入队。这样在任何时候当队列中有人买完票,我们就知道是初始队列中的第几个人买完票了,那么这一题也就迎刃而解了。

2.2、先将队列实现一下

因为我是用的是C语言,所以没办法还是得先自己造轮子,先将队列实现一下。
(没想到吧,这年头还有人用纯C刷这么多的题)

// 创建一个结构体,保存数组中对应的之和下标
typedef struct keyVal {
    int index;
    int val;
} keyVal;

// 先将队列实现一下
// 重定义数据类型
typedef keyVal QDataType;
// 定义节点类型
typedef struct QueueNode {
	struct QueueNode* next;
	QDataType data;
} QueueNode;
// 定义队列类型
typedef struct Queue {
	QueueNode* head;
	QueueNode* tail;
} Queue;
// 队列的初始化
void QueueInit(Queue* pq);
// 队列的入队
void QueuePush(Queue* pq, QDataType x);
// 队列的出队
void QueuePop(Queue* pq);
// 返回队列的对头元素
QDataType QueueFront(Queue* pq);
// 返回队列的队尾元素
QDataType QueueBack(Queue* pq);
// 返回队列中的节点个数
int QueueSize(Queue* pq);
// 判断队列是否为空
bool QueueEmpty(Queue* pq);
// 销毁队列
void QueueDestroy(Queue* pq);
// 队列的初始化
void QueueInit(Queue* pq) {
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}
// 队列的入队
void QueuePush(Queue* pq, QDataType x) {
	assert(pq);
	// 创建一个新节点
	QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
	if (NULL == newNode) {
		perror("malloc fail!\n");
		exit(-1);
	}
	newNode->data = x;
	if (NULL == pq->head) {
		pq->head = newNode;
		pq->tail = newNode;
		pq->tail->next = NULL;
	}
	else {
		pq->tail->next = newNode;
		pq->tail = pq->tail->next;
		pq->tail->next = NULL;
	}
}
// 队列的出队
void QueuePop(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	QueueNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
	// 如果对头为空了,我们也要把队尾也给置空,避免野指针
	if (NULL == pq->head) {
		pq->tail = NULL;
	}
}
// 返回队列的对头元素
QDataType QueueFront(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}
// 返回队列的队尾元素
QDataType QueueBack(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}
// 返回队列中的节点个数
int QueueSize(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	QueueNode* cur = pq->head;
	int size = 0;
	while (cur) {
		size++;
		cur = cur->next;
	}
	return size;
}
// 判断队列是否为空
bool QueueEmpty(Queue* pq) {
	assert(pq);
	return pq->head == NULL;
}
// 销毁队列
void QueueDestroy(Queue* pq) {
	assert(pq);
	if (!QueueEmpty(pq)) {
        QueueNode* cur = pq->head;
        QueueNode* next = cur->next;
        while (cur) {
            next = cur->next;
            free(cur);
            cur = next;
        }
    }
	
	pq->head = NULL;
	pq->tail = NULL;
}

其实这里也就是将以前写过的队列CV大法了一遍,也就只改了个数据类型。并没有什么复杂或难的。
熟悉队列这个数据结构的朋友完全可以不看这里的实现啦。

2.3、代码实现

有了以上的思路和队列的实现,那我们写起代码来也就水到渠成了:

int timeRequiredToBuy(int* tickets, int ticketsSize, int k) {
    assert(tickets);
    int time = 0;
    Queue queue;
    QueueInit(&queue);
    int i = 0;
    keyVal *keyArray = (keyVal*)malloc(ticketsSize *sizeof(keyVal));
    if (NULL == keyArray) {
        perror("malloc fail!\n");
        exit(-1);
    }
    // 先完善好keyArray
    for (i = 0; i < ticketsSize; i++) {
        keyArray[i].index = i;
        keyArray[i].val = tickets[i];
    }
    // 再将keyArray中的元素入队列
    for (i = 0; i < ticketsSize; i++) {
        QueuePush(&queue, keyArray[i]);
    }
    
    while (1) {
        keyVal front = QueueFront(&queue);
        QueuePop(&queue);
        front.val--;
        if (front.val > 0) {
            QueuePush(&queue, front);
        }
        time++;
        if (front.index == k && front.val == 0) {
            break;
        }
    }
    QueueDestroy(&queue);
    free(keyArray);
    return time;
}


http://lihuaxi.xjx100.cn/news/1178908.html

相关文章

序列化_原理与应用

关键字&#xff1a;序列化,java,proto,json,字节序列,字节数组,byte array,serialize序列化简介 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。 如在内存中的java对象&#xff08;本是方便JVM使用的格式&#xff09;序列化为硬盘或是网络传输…

关于linux系统can收发,以及jetson系列can收发的说明,以及SN65HVD230 CAN board和MCP2515和TJA1050的区别是什么?

1&#xff0c;jetson orin、Tx2有can处理器&#xff0c;没有收发器 所以官方推荐用SN65HVD230 CAN board就可以了。理论上单独的TJA1050也是可以的。。。 2&#xff0c;如果本身没有can像jetson nano、香橙派这样&#xff0c;就需要使用MCP2515和TJA1050的结合体了 SN65HVD230…

HCIA-MSTP替代技术之链路捆绑(手工模式)

目录 1&#xff0c;网络的可靠性需求 2&#xff0c;链路聚合原理 链路聚合&#xff1a; 聚合组(Link Aggregation Group&#xff0c;LAG)&#xff1a; 成员接口和成员链路&#xff1a; 活动接口和活动链路&#xff1a; 非活动接口和非活动链路&#xff1a; 聚合模式&…

termux的一些问题

我的电脑过安检的时候&#xff0c;竟然被卡住&#xff0c;压坏了。没办法&#xff0c;有需要电脑工作。我就用家里的平板电脑工作。我首先安装了termux&#xff0c;但是遇到这些问题。 (1)我在平板电脑上安装了termux后&#xff0c;我想通过手机登陆到平板电脑&#xff0c;但是…

基于Open3D的点云处理4-数据结构Kdtree和Octree

Kdtree Kdtree是一种划分k维数据空间的数据结构&#xff0c;本质也是一颗二叉树&#xff0c;只不过每个节点的数据都是k维&#xff0c;当k1时&#xff0c;就是普通二叉树。 建立Kdtree实际上是一个不断划分的过程&#xff0c;首先选择最sparse的维度&#xff08;一般通过计算…

内网渗透(八十五)之ADCS证书服务攻击

ADCS证书服务攻击 漏洞背景 2021年6月17日,国外安全研究员 Will Schroeder 和 Lee Christensen 共同发布了针对ADCS(Active Directory Certificate Service, 活动目录证书服务)的攻击手法。同年8月5日,在Black Hat 2021上 Will Schroeder 和 Lee CHristensen 对该攻击手法进…

【JVM】11. 垃圾回收及回收算法算法

文章目录 11.1. 垃圾回收概述11.1.1. 什么是垃圾&#xff1f;什么是垃圾&#xff1f; 11.1.2. 为什么需要GC11.1.3. 早期垃圾回收11.1.4. Java垃圾回收机制担忧GC主要关注的区域 11.2. 垃圾回收相关算法11.2.1. 标记阶段&#xff1a;引用计数算法方式一&#xff1a;引用计数算法…

分治入门+例题

目录 &#x1f947;2.3.2 合并排序 &#x1f947;2.3.3 快速排序 &#x1f33c;P1010 [NOIP1998 普及组] 幂次方 &#x1f333;总结 形象点&#xff0c;分治正如“凡治众如治寡&#xff0c;分数是也”&#xff0c;管理少数几个人&#xff0c;即可统领全军 本质&#xff…