4.15考核
73. 矩阵置零
给定一个 *m* x *n*
的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法**。**
示例 1:
输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]
示例 2:
输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]]
提示:
m == matrix.length
n == matrix[0].length
1 <= m, n <= 200
-231 <= matrix[i][j] <= 231 - 1
进阶:
- 一个直观的解决方案是使用
O(*m**n*)
的额外空间,但这并不是一个好的解决方案。 - 一个简单的改进方案是使用
O(*m* + *n*)
的额外空间,但这仍然不是最好的解决方案。 - 你能想出一个仅使用常量空间的解决方案吗?
这道题目就是遍历二维数组,遇到0就将行列置0,但是直接置为0会影响后面的判断,所以用到两次遍历,第一次将行列标记为特殊值,第二遍再将特殊值变为0
void setZeroes(int** matrix, int matrixSize, int* matrixColSize) {
int col = matrixColSize[0];
for(int i = 0; i < matrixSize; i++) {
for(int j = 0; j < col; j++) {
if(matrix[i][j] == 0) {
matrix[i][i] == INT_MIN + 677;
for(int y = 0; y < matrixSize; y++) {
if(matrix[y][j] == 0) {
continue;
}
matrix[y][j] = INT_MIN + 677; //这里设为特殊值INT_MIN + 677
}
for(int x = 0; x < col; x++) {
if(matrix[i][x] == 0) {
continue;
}
matrix[i][x] = INT_MIN + 677;
}
}
}
}
for(int i = 0; i < matrixSize; i++) { //第二次遍历将特殊值置0
for(int j = 0; j < col; j++) {
if(matrix[i][j] == INT_MIN + 677) {
matrix[i][j] = 0;
}
}
}
}
23. 合并 K 个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i]
按 升序 排列lists[i].length
的总和不超过10^4
这里的基础是合并两个链表,之后再遍历链表数组,依次将链表合并
struct ListNode* merge(struct ListNode* l1, struct ListNode* l2) { //这是合并两个链表的函数
struct ListNode* newhead = (struct ListNode*)malloc(sizeof(struct ListNode));
newhead->next = NULL;
struct ListNode* cur = newhead;
while(l1 && l2) {
if(l1->val < l2->val) {
newhead->next = l1;
l1 = l1->next;
}else {
newhead->next = l2;
l2 = l2->next;
}
newhead = newhead->next;
}
if(l1) {
newhead->next = l1;
}else {
newhead->next = l2;
}
return cur->next;
}
struct ListNode* mergeKLists(struct ListNode** lists, int listsSize) {
if(listsSize == 0) {
return NULL;
}
struct ListNode* newhead = lists[0];
for(int i = 1; i < listsSize; i++) { //遍历链表数组,连接两个链表
newhead = merge(newhead, lists[i]);
}
return newhead;
}
394. 字符串解码
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string]
,表示其中方括号内部的 encoded_string
正好重复 k
次。注意 k
保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k
,例如不会出现像 3a
或 2[4]
的输入。
示例 1:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
示例 2:
输入:s = "3[a2[c]]"
输出:"accaccacc"
示例 3:
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
示例 4:
输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"
提示:
1 <= s.length <= 30
s
由小写英文字母、数字和方括号'[]'
组成s
保证是一个 有效 的输入。s
中所有整数的取值范围为[1, 300]
这里考虑到中括号里面会有其他中括号,所以每一个左右括号要相互对应,这里用到栈来保存每一个字符,然后处理括号里面的字符
char* decodeString(char* s) {
int len = strlen(s);
char* ans =(char*)malloc(sizeof(char) * 10000); //这是答案字符串
int anstop = 0;
int* stack = (int*)malloc(sizeof(int) * 10000); //这是栈
int stacktop = 0;
int* left = (int*)malloc(sizeof(int) * 10000); //这个用于记录每个左括号的位置
int lefttop = 0;
for(int i = 0; i < len; i++) {
if(s[i] >= '0' && s[i] <= '9') { //首先,如果遍历到了数字,这里就需要将字符串准换为数字,入栈,便于之后处理字符串
int sum = 0;
while(s[i] >= '0' && s[i] <= '9') {
sum = sum * 10 + (s[i] - '0');
i++;
}
stack[stacktop++] = sum;
}
if(s[i] == '[') { //遇到了左括号,就将左括号的位置记录下来
left[lefttop++] = stacktop;
}else if(s[i] == ']') { //遇到右括号就是正式处理字符串的时候了
int i;
int temp[12000];
int temptop = 0; //这里要将左右括号之间的字符串提取出来,存储在temp数组中
for(i = left[lefttop - 1]; i < stacktop; i++) {
temp[temptop++] = stack[i];
}
stacktop = left[lefttop - 1] - 1; //将栈顶指针回退到左括号的位置,根据左括号前一位记录的数字,将字符串重复几遍
int k = stack[stacktop];
for(i = 0; i < k; i++) {
for(int j = 0; j < temptop; j++) {
stack[stacktop++] = temp[j];
}
}
lefttop--;
}else {
stack[stacktop++] = s[i] - 'a';
}
}
int i = 0;
for(i = 0; i < stacktop; i++) { //将栈里面记录的答案转存到答案数组里面
ans[i] = stack[i] + 'a';
}
ans[i] = '\0';
return ans;
}
238. 除自身以外数组的乘积
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。
题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 **不要使用除法,**且在 O(*n*)
时间复杂度内完成此题。
示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
提示:
2 <= nums.length <= 105
-30 <= nums[i] <= 30
- 保证 数组
nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内
这里用到的方法是先将所有非零的数字乘起来,前提是遍历一遍数组,若是有两个以上的0,则其他位置都将是0,若是只有一个0,则只有0的位置需要特殊处理。
int* productExceptSelf(int* nums, int numsSize, int* returnSize) {
int* ans = (int*)malloc(sizeof(int) * numsSize);
*returnSize = numsSize;
int top = 0;
int count = 0;
int sum = 1;
for(int i = 0; i < numsSize; i++) {
if(nums[i] == 0) {
count++;
}else {
sum *= nums[i]; //得到所有数字的乘积
}
}
if(count > 1) {
for(int i = 0; i < numsSize; i++) {
ans[i] = 0;
}
}else if(count == 1) {
for(int i = 0; i < numsSize; i++) {
if(nums[i] == 0) {
ans[i] = sum;
}else {
ans[i] = 0;
}
}
}else {
for(int i = 0; i < numsSize; i++) {
if(nums[i] == 0) {
ans[i] = sum;
}else {
ans[i] = sum / nums[i]; //遍历到每一个位置,该位置的值就是总积和该值的商
}
}
}
return ans;
}
232. 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
):
实现 MyQueue
类:
void push(int x)
将元素 x 推到队列的末尾int pop()
从队列的开头移除并返回元素int peek()
返回队列开头的元素boolean empty()
如果队列为空,返回true
;否则,返回false
说明:
- 你 只能 使用标准的栈操作 —— 也就是只有
push to top
,peek/pop from top
,size
, 和is empty
操作是合法的。 - 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
示例 1:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
1 <= x <= 9
- 最多调用
100
次push
、pop
、peek
和empty
- 假设所有操作都是有效的 (例如,一个空的队列不会调用
pop
或者peek
操作)
进阶:
- 你能否实现每个操作均摊时间复杂度为
O(1)
的队列?换句话说,执行n
个操作的总时间复杂度为O(n)
,即使其中一个操作可能花费较长时间。
用栈实现队列,需要用到两个栈,一个用于入队,一个用于出队
typedef struct {
int stackintop, stackouttop;
int stackin[100], stackout[100];
} MyQueue;
MyQueue* myQueueCreate() { //创建队列就是分配内存,再将两个栈顶指针置为0
MyQueue* queue = (MyQueue*)malloc(sizeof(MyQueue));
queue->stackintop = 0;
queue->stackouttop = 0;
return queue;
}
void myQueuePush(MyQueue* obj, int x) {
obj->stackin[obj->stackintop++] = x; //入队就是将值放进负责入队的栈里面
}
int myQueuePop(MyQueue* obj) { //出队需要将负责入队的栈的值存储到负责出队的栈当中,调转顺序之后输出就是正常的顺序了
if (obj->stackouttop == 0) {
while (obj->stackintop > 0) {
obj->stackout[obj->stackouttop++] = obj->stackin[--obj->stackintop];
}
}
return obj->stackout[--obj->stackouttop];
}
int myQueuePeek(MyQueue* obj) { //获取队首值是同样的道理,这里只需要输出值,不用将出栈指针回退一
if (obj->stackouttop == 0) {
while (obj->stackintop > 0) {
obj->stackout[obj->stackouttop++] = obj->stackin[--obj->stackintop];
}
}
return obj->stackout[obj->stackouttop - 1];
}
bool myQueueEmpty(MyQueue* obj) {
return obj->stackintop == 0 && obj->stackouttop == 0; //两个栈顶指针为零则队空
}
void myQueueFree(MyQueue* obj) {
free(obj); //释放队列空间
}
61. 旋转链表
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]
示例 2:
输入:head = [0,1,2], k = 4
输出:[2,0,1]
提示:
- 链表中节点的数目在范围
[0, 500]
内 -100 <= Node.val <= 100
0 <= k <= 2 * 109
这道题目的关键是计算好新的头节点是哪一个,然后将链表尾部和头部相连接就可以了
int count(struct ListNode* head) {
int ans = 0;
while(head) {
head = head->next;
ans++;
}
return ans;
}
struct ListNode* rotateRight(struct ListNode* head, int k) {
if(head == NULL || head->next == NULL || k == 0) {
return head;
}
int len = count(head);
k = k % len;
if(k == 0) {
return head;
}
int n = len - k;
if(n == 0) {
return head; //计算出链表长度之后,就能计算出新的头节点的位置,然后将上一个节点指向NULL,再将头尾相接就好了
}
struct ListNode* newhead = NULL;
struct ListNode* cur = head;
for(int i = 0; i < n - 1; i++) {
cur = cur->next;
}
newhead = cur->next;
cur->next = NULL;
cur = newhead;
while(cur->next != NULL) {
cur = cur->next;
}
cur->next = head;
return newhead;
}
392. 判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"
是"abcde"
的一个子序列,而"aec"
不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
致谢:
特别感谢 @pbrother 添加此问题并且创建所有测试用例。
示例 1:
输入:s = "abc", t = "ahbgdc"
输出:true
示例 2:
输入:s = "axc", t = "ahbgdc"
输出:false
提示:
0 <= s.length <= 100
0 <= t.length <= 10^4
- 两个字符串都只由小写字符组成。
这里用双指针,依次判断原字符串是否能构成目标字符串
bool isSubsequence(char* s, char* t) {
int i = 0, j = 0;
int l1 = strlen(s);
int l2 = strlen(t);
if(l2 < l1) {
return false;
}
for(j = 0; j < l2 && i < l1; j++) {
if(t[j] == s[i]) { //如果字符相等,则移动两个指针,否则就移动一个
i++;
}
}
if(i == l1) { //若是目标字符串的指针移动到了末尾,就说明能够由原字符串构成
return true;
}else {
return false;
}
}
125. 验证回文串
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。
字母和数字都属于字母数字字符。
给你一个字符串 s
,如果它是 回文串 ,返回 true
;否则,返回 false
。
示例 1:
输入: s = "A man, a plan, a canal: Panama"
输出:true
解释:"amanaplanacanalpanama" 是回文串。
示例 2:
输入:s = "race a car"
输出:false
解释:"raceacar" 不是回文串。
示例 3:
输入:s = " "
输出:true
解释:在移除非字母数字字符之后,s 是一个空字符串 "" 。
由于空字符串正着反着读都一样,所以是回文串。
提示:
1 <= s.length <= 2 * 105
s
仅由可打印的 ASCII 字符组成
这里用双指针,从两侧向中间判断,跳过无效值,然后比较字母是否回文
bool isAlphanumeric(char c) { //判断是否是有效字母
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
}
char toLower(char c) { //将大写字母转换为小写字母
if (c >= 'A' && c <= 'Z') {
return c + ('a' - 'A');
}
return c;
}
bool isPalindrome(char* s) {
int left = 0;
int right = strlen(s) - 1;
while (left < right) {
if (!isAlphanumeric(s[left])) {
left++;
continue;
}
if (!isAlphanumeric(s[right])) {
right--;
continue;
}
if (toLower(s[left]) != toLower(s[right])) {
return false;
}
left++;
right--;
}
return true;
}
21. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
提示:
- 两个链表的节点数目范围是
[0, 50]
-100 <= Node.val <= 100
l1
和l2
均按 非递减顺序 排列
合并两个有序链表,直接同时遍历这两个链表,将每次遇到的较小值连接到答案链表当中,然后向后移动一位,一次操作即可
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
struct ListNode* newhead = (struct ListNode*)malloc(sizeof(struct ListNode));
newhead->next = NULL;
struct ListNode* cur = newhead;
while(list1 != NULL || list2 != NULL){
if(list1 == NULL){
cur->next = list2;
break;
}
if(list2 == NULL){
cur->next = list1;
break;
}
if(list1->val < list2->val){
cur->next = list1;
list1 = list1->next;
}else{
cur->next = list2;
list2 = list2->next;
}
cur = cur->next;
}
return newhead->next;
}
147. 对链表进行插入排序
给定单个链表的头 head
,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。
插入排序 算法的步骤:
- 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
- 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
- 重复直到所有输入数据插入完为止。
下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。
对链表进行插入排序。
示例 1:
输入: head = [4,2,1,3]
输出: [1,2,3,4]
示例 2:
输入: head = [-1,5,3,4,0]
输出: [-1,0,3,4,5]
提示:
- 列表中的节点数在
[1, 5000]
范围内 -5000 <= Node.val <= 5000
这里的思路是每次遍历链表,将找到的最小值插入到链表的头部,这样就实现了排序
struct ListNode* insertionSortList(struct ListNode* head) {
struct ListNode* newhead = (struct ListNode*)malloc(sizeof(struct ListNode));
newhead->val = 0;
newhead->next = head;
struct ListNode* tail = newhead;
struct ListNode* prev = newhead;
struct ListNode* cur = prev->next;
while(tail != NULL && tail->next !=NULL && tail->next->next != NULL) {
struct ListNode* min = tail->next;
struct ListNode* min_prev = tail;
for(prev = tail, cur = tail->next; cur != NULL;cur = prev->next) { //这里就是插入到头部的操作
if(cur->val < min->val) {
min = cur;
min_prev = prev;
}
prev = cur;
}
min_prev->next = min->next;
min->next = tail->next;
tail->next = min; //及时更新头部节点
tail = min;
}
return newhead->next;
}
309. 买卖股票的最佳时机含冷冻期
给定一个整数数组prices
,其中第 prices[i]
表示第 *i*
天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: prices = [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
示例 2:
输入: prices = [1]
输出: 0
提示:
1 <= prices.length <= 5000
0 <= prices[i] <= 1000
首先确定dp数组以及下标的含义:
dp[i][j]
中i
表示第i
天,dp[i][j]
表示第i
天状态j
所剩最大现金。找出不同的状态:
冷冻期
持有股票状态
保持卖出股票状态
卖出股票
初始化:
dp[0][0]:买入股票,所以是 -prices[0]。 dp[0][1]、dp[0][2]、dp[0][3]:分别是0,因为第一天不可能处于冷冻期或不持有股票。
接下来遍历:
dp[i][0]:可以从前一天的持有股票状态(dp[i - 1][0])转换而来,也可以在冷冻期结束后购买股票(dp[i - 1][1] - prices[i])或者在新冷冻期后购买股票(dp[i - 1][3] - prices[i])。 dp[i][1]:保持卖出股票的状态,或者保持前一天的冷冻期状态。 dp[i][2]:今天卖出股票,所以是 dp[i - 1][0] + prices[i]。 dp[i][3]:冷冻期状态,即前一天的卖出股票状态。
int maxProfit(int* prices, int pricesSize) {
if(pricesSize == 0){
return 0;
}
int dp[pricesSize][4];
memset(dp, 0, sizeof (int ) * pricesSize * 4);
dp[0][0] = -prices[0];
for (int i = 1; i < pricesSize; ++i) {
dp[i][0] = fmax(dp[i - 1][0], fmax(dp[i - 1][1] - prices[i], dp[i - 1][3] - prices[i]));
// 持有股票状态
dp[i][1] = fmax(dp[i - 1][1], dp[i - 1][3]);
// 保持卖出股票的状态
dp[i][2] = dp[i - 1][0] + prices[i];
// 今天卖出股票
dp[i][3] = dp[i - 1][2];
// 冷冻期状态
}
return fmax(dp[pricesSize - 1][1], fmax(dp[pricesSize - 1][2], dp[pricesSize - 1][3]));
}
187. 重复的DNA序列
DNA序列 由一系列核苷酸组成,缩写为 'A'
, 'C'
, 'G'
和 'T'
.。
- 例如,
"ACGAATTCCG"
是一个 DNA序列 。
在研究 DNA 时,识别 DNA 中的重复序列非常有用。
给定一个表示 DNA序列 的字符串 s
,返回所有在 DNA 分子中出现不止一次的 长度为 10
的序列(子字符串)。你可以按 任意顺序 返回答案。
示例 1:
输入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
输出:["AAAAACCCCC","CCCCCAAAAA"]
示例 2:
输入:s = "AAAAAAAAAAAAA"
输出:["AAAAAAAAAA"]
提示:
0 <= s.length <= 105
s[i]``==``'A'
、'C'
、'G'
or'T'
对于这道题目,暴力的思想就是遇到一个新的字符串,就遍历所有字符串去寻找,但是这样会超时。
我们发现,暴力的方法需要对前面已经查找过的串再遍历一遍,所以我们可以将找过的字符串用哈希表存储起来,所以这里的关键是建立哈希表和处理哈希映射
char **findRepeatedDnaSequences(char *s, int *returnSize) {
int n = strlen(s); // 计算输入字符串的长度
*returnSize = 0; // 初始化返回数组的大小为0
// 定义哈希表节点结构体
struct HashMap {
char *key; // 存储DNA子串
int value; // 存储子串出现的次数
struct HashMap *next; // 指向下一个节点的指针,用于处理哈希冲突
};
// 分配内存给哈希表数组,初始大小为1024
struct HashMap **map = (struct HashMap **)malloc(1024 * sizeof(struct HashMap *));
memset(map, 0, 1024 * sizeof(struct HashMap *));
// 分配内存给存储结果的指针数组,初始大小为字符串s的长度
char **ans = (char **)malloc(n * sizeof(char *));
// 遍历字符串s中的所有可能的DNA子串
for (int i = 0; i <= n - 10; ++i) {
// 提取当前位置起始的长度为10的DNA子串
char *sub = (char *)malloc((10 + 1) * sizeof(char));
strncpy(sub, &s[i], 10);
sub[10] = '\0';
// 计算DNA子串的哈希值
unsigned int hash = 0;
for (int j = 0; j < 10; ++j) {
hash = (hash << 3) | (sub[j] & 7); // 将每个字符的ASCII码与7进行按位与运算,获得3位的哈希值
}
// 计算哈希值对应的哈希表索引
int index = hash % 1024;
// 查找哈希表中是否存在当前DNA子串
struct HashMap *temp = map[index];
while (temp) {
if (strcmp(temp->key, sub) == 0) {
// 如果找到了相同的DNA子串,更新其出现次数
if (++temp->value == 2) {
// 如果出现次数为2,将该DNA子串加入结果数组
ans[(*returnSize)++] = strdup(sub);
}
break;
}
temp = temp->next;
}
// 如果哈希表中不存在当前DNA子串,将其添加到哈希表中
if (!temp) {
struct HashMap *newNode = (struct HashMap *)malloc(sizeof(struct HashMap));
newNode->key = strdup(sub);
newNode->value = 1;
newNode->next = map[index];
map[index] = newNode;
}
free(sub);
}
// 释放哈希表中的内存
for (int i = 0; i < 1024; ++i) {
struct HashMap *temp = map[i];
while (temp) {
struct HashMap *next = temp->next;
free(temp->key);
free(temp);
temp = next;
}
}
free(map);
return ans; // 返回重复的DNA序列数组
}
2517. 礼盒的最大甜蜜度
给你一个正整数数组 price
,其中 price[i]
表示第 i
类糖果的价格,另给你一个正整数 k
。
商店组合 k
类 不同 糖果打包成礼盒出售。礼盒的 甜蜜度 是礼盒中任意两种糖果 价格 绝对差的最小值。
返回礼盒的 最大 甜蜜度*。*
示例 1:
输入:price = [13,5,1,8,21,2], k = 3
输出:8
解释:选出价格分别为 [13,5,21] 的三类糖果。
礼盒的甜蜜度为 min(|13 - 5|, |13 - 21|, |5 - 21|) = min(8, 8, 16) = 8 。
可以证明能够取得的最大甜蜜度就是 8 。
示例 2:
输入:price = [1,3,1], k = 2
输出:2
解释:选出价格分别为 [1,3] 的两类糖果。
礼盒的甜蜜度为 min(|1 - 3|) = min(2) = 2 。
可以证明能够取得的最大甜蜜度就是 2 。
示例 3:
输入:price = [7,7,7,7], k = 2
输出:0
解释:从现有的糖果中任选两类糖果,甜蜜度都会是 0 。
提示:
2 <= k <= price.length <= 105
1 <= price[i] <= 109
首先我们要理解题目的意思:
礼盒的 甜蜜度 是礼盒中任意两种糖果 价格 绝对差的最小值,这里说到任意两种,所以最后的答案的顺序没关系,这里为了方便后面查找,我们可以先将数组排好序,然后通过计算相邻两个数的差,这样就能算出每k种的最大甜蜜度了
int compare(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
int find(int* price, int priceSize, int k, int x) { //这个函数用于寻找是否有比x大的甜蜜度组合
int count = 1;
int prev = price[0];
for (int i = 1; i < priceSize; i++) { //计算相邻数的差值
if (price[i] - prev >= x) {
count++;
prev = price[i];
if (count == k) //达到了k的长度就返回
return 1;
}
}
return 0;
}
int maximumTastiness(int* price, int priceSize, int k) {
qsort(price, priceSize, sizeof(int), compare); //先用快速排序将数组排好序
int left = 0; //数组有序,这里用二分查找提高效率,指向的值代表最大甜蜜度
int right = price[priceSize - 1] - price[0];
int ans = 0;
while (left <= right) {
int mid = left + (right - left) / 2;
if (find(price, priceSize, k, mid)) { //如果找到了比x大的,就在右边继续查找,否则就在左边查找
ans = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return ans;
}