大一考核题解

4.15考核

73. 矩阵置零

给定一个 *m* x *n* 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法**。**

示例 1:

img

输入: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 ,例如不会出现像 3a2[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. 用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 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
  • 最多调用 100pushpoppeekempty
  • 假设所有操作都是有效的 (例如,一个空的队列不会调用 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:

img

输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]

示例 2:

img

输入: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. 判断子序列

给定字符串 st ,判断 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
  • l1l2 均按 非递减顺序 排列

合并两个有序链表,直接同时遍历这两个链表,将每次遇到的较小值连接到答案链表当中,然后向后移动一位,一次操作即可

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. 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
  2. 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
  3. 重复直到所有输入数据插入完为止。

下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。

对链表进行插入排序。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

示例 1:

img

输入: 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值