目录
14. 最长公共前缀https://leetcode.cn/problems/longest-common-prefix/
20. 有效的括号https://leetcode.cn/problems/valid-parentheses/
217. 存在重复元素https://leetcode.cn/problems/contains-duplicate/
15. 三数之和https://leetcode.cn/problems/3sum/
2810. 故障键盘https://leetcode.cn/problems/faulty-keyboard/
21. 合并两个有序链表https://leetcode.cn/problems/merge-two-sorted-lists/
14. 最长公共前缀
https://leetcode.cn/problems/longest-common-prefix/
题目
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
。
示例 1:
输入:strs = ["flower","flow","flight"] 输出:"fl"
示例 2:
输入:strs = ["dog","racecar","car"] 输出:"" 解释:输入不存在公共前缀。
提示:
1 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i]
仅由小写英文字母组成
思路
- 首先找到最短字符串的长度,遍历长度以最短字符串的长度为准。逐字符比较所有字符串,直至找到最长公共前缀或遍历完所有字符(最短),最后返回截取后的结果。
完整代码
char* longestCommonPrefix(char** strs, int strsSize) {
int i,j;
int len = strlen(strs[0]);
for (i = 1; i < strsSize; i++)
{
if (strlen(strs[i]) < len)
{
len = strlen(strs[i]);
}
}
for (j = 0; j < len; j++)
{
for (i = 1; i < strsSize; i++)//从第二个字符串开始,依次与前一个字符串在相同位置上进行比较
{
if (strs[i][j] != strs[i - 1][j])//如果发现不相等,说明当前位置不再是公共前缀,立即跳出循环
{
break;
}
}
if (i != strsSize)
{
break;
}
}
strs[0][j] = '\0';
return strs[0];
}
20. 有效的括号
https://leetcode.cn/problems/valid-parentheses/
题目
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()" 输出:true
示例 2:
输入:s = "()[]{}" 输出:true
示例 3:
输入:s = "(]" 输出:false
提示:
1 <= s.length <= 104
s
仅由括号'()[]{}'
组成
思路
- 符合true的字符串一定是偶数
- 根据题目,可以知道最里边的左括号最先遇到匹配的右括号,这里与栈的思想显示,后入栈的先取出
- 根据这个思想,使用辅助数组模拟栈,用于存储右边的括号
- 遇到左括号,就将对应的右括号压入栈中,同时栈顶数加1
- 遇到右括号,如果栈中没有对应的左括号与之匹配,返回false,否则,弹出栈顶元素并与当前右括号进行比较,如果两者不匹配,返回false,若匹配,则继续遍历字符串以检查剩余括号
- 遍历完整个字符串后,如果栈中已经为空,说明使用左括号已经找到对应的右括号,此时返回true,否则返回false
GIF图演示
完整代码
bool isValid(char * s){
int len = strlen(s);
if(len % 2 != 0)
{
return false;
}
char str[len];
int top = 0;
for(int i=0 ;i < n ; i++)
{
if(s[i])
{
if(s[i] == '{')
{
str[top++] = '}';
}
else if(s[i] == '[')
{
str[top++] = ']';
}
else if(s[i] =='(')
{
str[top++] = ')';
}
else
{
if(top == 0 || str[--top] != ch)
{
return false;
}
}
}
}
if(top == 0)
{
return true;
}
return false;
}
LCR 086. 分割回文串
https://leetcode.cn/problems/M99OJA/
题目
给定一个字符串 s
,请将 s
分割成一些子串,使每个子串都是 回文串 ,返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = "google" 输出:[["g","o","o","g","l","e"],["g","oo","g","l","e"],["goog","l","e"]]
示例 2:
输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]
示例 3:
输入:s = "a" 输出:[["a"]]
提示:
1 <= s.length <= 16
s
仅由小写英文字母组成
接口解读
- int* returnSize:函数执行完毕后,该指针所指向的整数值应被设置为实际生成的子字符串个数。
- int** returnColumnSizes:这个二级指针将被函数填充,指向一个包含所有子字符串长度的数组。每个元素对应一个子字符串的长度,按顺序与返回的子字符串数组对应。
- 返回值类型是一个指向指针的指针的指针,即 char***。这意味着 partition函数将返回一个三维字符数组(或者更准确地说,是一个二维字符指针数组)。
- 每一层指针分别代表:
- - 最外层指针:指向一组子字符串数组(即第二维)的指针。
- - 第二层指针:每个元素是一个指向一个子字符串(即第一维)的指针。
- - 第一层(最内层):实际的字符数组,构成单个子字符串。
- 该 partition 函数执行以下操作:
- 分配并填充一个二维字符数组(通过多级指针表示),其中每个元素是一段连续的字符序列,代表一个子字符串。
- 更新 returnSize 指针以存储子字符串的数量。
- 分配并填充一个一维整数数组(通过 returnColumnSizes 指针访问),其中每个元素记录相应子字符串的长度。
- 返回指向这个二维字符数组的指针,以及通过 returnColumnSizes提供的子字符串长度数组。
- 调用者需要包括返回的二维字符数组及其内部的一维子字符串数组,以及returnColumnSizes指向的长度数组。
算法
dfs(深度优先算法)+DP
思路
通过深度优先算法,结合动态规划预计算回文子串信息,实现对给定字符串进行回文子串分割的功能。最终能够返回一个包含所有有效分割方案的二维字符数组机器相关尺寸信息
- df函数首先检查是否所有字符都已经被处理过,如果是,则将当前的分割结果复制到结果数组中,并更新retSize和retColSize。
- 如果还有字符未处理,dfs会遍历剩余的字符,寻找可以分割的位置。如果二位布尔数组中该位置元素为真,表示从位置i到位置j的子串可以被分割,然后创建一个子串,将其添加到ans数组中,并递归调用dfs。
- 递归调用`dfs`后,由于要回溯,所以需要减少ansSize,释放掉刚刚添加的子串所占用的内存。
完整代码
void dfs(char* s, int n, int i, int** f, char*** ret, int* retSize, int* retColSize, char** ans, int* ansSize) {
if (i == n) {
char** tmp = malloc(sizeof(char*) * (*ansSize));//对于元素数组采取初始化操作(malloc)
for (int j = 0; j < (*ansSize); j++) {
int ansColSize = strlen(ans[j]);
tmp[j] = malloc(sizeof(char) * (ansColSize + 1));
strcpy(tmp[j], ans[j]);
}
ret[*retSize] = tmp;
retColSize[(*retSize)++] = *ansSize;
return;
}
for (int j = i; j < n; ++j) {
if (f[i][j]) {
char* sub = malloc(sizeof(char) * (j - i + 2));
for (int k = i; k <= j; k++) {
sub[k - i] = s[k];
}
sub[j - i + 1] = '\0';
ans[(*ansSize)++] = sub;
dfs(s, n, j + 1, f, ret, retSize, retColSize, ans, ansSize);
--(*ansSize);
}
}
}
char*** partition(char* s, int* returnSize, int** returnColumnSizes) {
int n = strlen(s);//测量s的长度
int retMaxLen = n * (1 << n);//进行左移计算的操作
char*** ret = malloc(sizeof(char**) * retMaxLen);//采取malloc的初始化
*returnSize = 0;//初始化为0填充
*returnColumnSizes = malloc(sizeof(int) * retMaxLen);//同样的malloc的初始化内存空间
int* f[n];//建立数组f(n长度)
for (int i = 0; i < n; i++) {
f[i] = malloc(sizeof(int) * n);//进行内存分配
for (int j = 0; j < n; j++) {
f[i][j] = 1;//所有元素进行1填充
}
}
for (int i = n - 1; i >= 0; --i) {
for (int j = i + 1; j < n; ++j) {
f[i][j] = (s[i] == s[j]) && f[i + 1][j - 1];
}
}
char* ans[n];//建立ans数组
int ansSize = 0;//初始化ansSize
dfs(s, n, 0, f, ret, returnSize, *returnColumnSizes, ans, &ansSize);//执行dfs函数操作
return ret;
}
217. 存在重复元素
https://leetcode.cn/problems/contains-duplicate/
题目
给你一个整数数组 nums
。如果任一值在数组中出现 至少两次 ,返回 true
;如果数组中每个元素互不相同,返回 false
。
示例 1:
输入:nums = [1,2,3,1] 输出:true
示例 2:
输入:nums = [1,2,3,4] 输出:false
示例 3:
输入:nums = [1,1,1,3,3,4,3,2,4,2] 输出:true
提示:
1 <= nums.length <= 105
-109 <= nums[i] <= 109
思路
普通的暴力解法消耗的时间过大,为了降低时间复杂度,可以先使相同元素挨在一起,声所以可以采用排序,使数组变成有序数组,为时代码简洁,采用qsort函数,则数组变成有序数组
- 经历过升序排序后,如果有重复元素一定挨在元素旁边,所以只需要判断,该元素第一次出现的时候,右边的一位元素是否与之相等即可,不相等,那么只指向下一位继续比较,相等则返回true,循环结束,都没有返回true那么就没有重复元素,则返回false
完整代码
int cmp(const void* a, const void* b) {
return *(int*)a - *(int*)b;
}
bool containsDuplicate(int* nums, int numsSize) {
qsort(nums, numsSize, sizeof(nums[0]), cmp);
for (int i = 0; i < numsSize - 1; ++i) {
if (nums[i] == nums[i + 1])
return true;
}
return false;
}
15. 三数之和
https://leetcode.cn/problems/3sum/
题目
提示
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]] 解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1] 输出:[] 解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0] 输出:[[0,0,0]] 解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
接口解读
这道题主要思路和方法并不难,但却很麻烦,需要考虑的情况很多,以及不熟悉力扣的话,对接口上面的形参无法理解,以下是对这道题接口的解读。
在leetcode的很多题目中,都会出现int* returnSize之类的参数,这个其实是要求我们以传引用的方式返回最终答案数组的大小
int** returnColSize 的用途:除了需要知道二维数组的行数,每一行的数组的长度也是需要返回的
为什么returnColSize是二级指针:是从外部传进来的一个指针参数,要保证在函数中对该指针的改变对外部产生影响,那么在外部传入的时候,应按照传引用的方式传入一个指针的地址,反映到函数中就是一个指向指针的指针
算法
双指针+排序
思路
- 首先考虑数组会出现没有元素或者是小于三个元素,这两种情况无效,所以返回空指针。
- 根据题目,方便正负数的寻找,以及相加==0需要考虑的大小写,对数组进行升序排序,为了让代码更加简洁,这里使用qsort()函数。
- 所需的内存应根据实际找到的三元组数量动态分配。可以先分配较小的初始空间,随着找到更多的三元组,若空间不足再进行动态扩容。这样可以避免不必要的内存浪费。
- 我们需要以一个元素为基准(索引),然后进行遍历数组找到另外两个元素匹配组成和为0的三元组(内层循环),然后要找到所有的三元组,该基准也要遍历,还需要考虑两种情况:(下面块引用中)
- 然后需要双指针进行遍历匹配找到符合和为0的三元组
- 首元素如果大于零,或者末元素小于零的话,则不可能组成和为0三元组,就需要终止循环。
- 根据题目得结果不可包含重复的三元组,所以遍历当前的元素与前一个元素如果相等的话,就要跳过本次循环。
5.最外层循环索引 i = 0 :
1)如果当前元素大于0,终止循环
2)如果当前元素==前一个元素,跳过本次循环
6.内层循环:
1)计算三元组和
2)判断当前sum是否==0
3)如果==0
由于为了优化空间采取动态内存分配,所以需要先判断此时已有内存是否足够,不够还需要进行扩容
然后为当前三元组分配内存,并将这三个元素放入ret[*returnsize][0],...[1],...[2]中
在 `*returnColumnSizes` 中记录当前子数组大小为3,增加 `*returnSize` 表示找到了一个新的三元组
使用两个 `while` 循环跳过左侧和右侧与当前元素相等的部分,以避免重复三元组。
4) 若 sum < 0,将左指针 `left` 向右移动一位。
若 sum >0,将右指针 `right` 向左移动一位。
7.最后返回得到的二维数组。
完整代码
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
int cmp(const void* a,const void* b){
return (*(int*)a-*(int*)b);//如果想达到降序的效果在后面*(-1)
}
int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
*returnSize = 0;
if (nums == NULL || numsSize < 3) {
return NULL;
}
qsort(nums, numsSize, sizeof(int), cmp);
int initialCapacity = 100; // 初始容量(可调整)
int** ret = (int**)malloc(initialCapacity * sizeof(int*));
*returnColumnSizes = (int*)malloc(initialCapacity * sizeof(int));
for (int i = 0; i < numsSize; i++) {
if (nums[i] > 0) {
break;
}
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1, right = numsSize - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
if (*returnSize == initialCapacity) { // 动态扩容
initialCapacity *= 2;
ret = (int**)realloc(ret, initialCapacity * sizeof(int*));
*returnColumnSizes = (int*)realloc(*returnColumnSizes, initialCapacity * sizeof(int));
}
ret[*returnSize] = (int*)malloc(sizeof(int) * 3);
ret[*returnSize][0] = nums[i];
ret[*returnSize][1] = nums[left];
ret[*returnSize][2] = nums[right];
(*returnColumnSizes)[*returnSize] = 3;//下面对此有详细解释这里先知道,这表示在*returnColumnSizes中记录当前子数组大小为3
(*returnSize)++;//增加*returnSize表示找到了一个新的三元组。
while (left < right && nums[left] == nums[++left]);
while (left < right && nums[right] == nums[--right]);
}
else if (sum < 0) {
left++;
}
else {
right--;
}
}
}
return ret;
}
(*returnColumnSizes)[*returnSize] = 3;
(*returnSize)++;
详解
- *returnColumnSizes:这是一个指向整数的指针,可以理解为一个数组的长度数组。在二维数组的上下文中,它用于存储每一列(子数组)的元素个数。
- *returnSize:这是一个指向整数的指针,表示整个二维数组(即包含所有子数组的大数组)的大小,即子数组的数量。
(*returnColumnSizes)[*returnSize] = 3;
```这行代码做了以下几件事:
- 先访问`*returnSize`所指向的整数值,得到当前二维数组中子数组的个数(索引)。
- 使用这个索引来访问*returnColumnSizes所指向的长度数组,即设置对应索引处的元素值。
- 将该元素值设为3,意味着新添加的子数组(列)有3个元素。
(*returnSize)++;
```这行代码对*returnSize所指向的整数值进行自增操作意味着:
- 更新整个二维数组(大数组)的大小,表明已经添加了一个新的子数组。
- 新增的子数组就是前一行代码中设置长度为3的那个子数组。
2810. 故障键盘
https://leetcode.cn/problems/faulty-keyboard/
题目
你的笔记本键盘存在故障,每当你在上面输入字符 'i'
时,它会反转你所写的字符串。而输入其他字符则可以正常工作。
给你一个下标从 0 开始的字符串 s
,请你用故障键盘依次输入每个字符。
返回最终笔记本屏幕上输出的字符串。
示例 1:
输入:s = "string" 输出:"rtsng" 解释: 输入第 1 个字符后,屏幕上的文本是:"s" 。 输入第 2 个字符后,屏幕上的文本是:"st" 。 输入第 3 个字符后,屏幕上的文本是:"str" 。 因为第 4 个字符是 'i' ,屏幕上的文本被反转,变成 "rts" 。 输入第 5 个字符后,屏幕上的文本是:"rtsn" 。 输入第 6 个字符后,屏幕上的文本是: "rtsng" 。 因此,返回 "rtsng" 。
示例 2:
输入:s = "poiinter" 输出:"ponter" 解释: 输入第 1 个字符后,屏幕上的文本是:"p" 。 输入第 2 个字符后,屏幕上的文本是:"po" 。 因为第 3 个字符是 'i' ,屏幕上的文本被反转,变成 "op" 。 因为第 4 个字符是 'i' ,屏幕上的文本被反转,变成 "po" 。 输入第 5 个字符后,屏幕上的文本是:"pon" 。 输入第 6 个字符后,屏幕上的文本是:"pont" 。 输入第 7 个字符后,屏幕上的文本是:"ponte" 。 输入第 8 个字符后,屏幕上的文本是:"ponter" 。 因此,返回 "ponter" 。
提示:
1 <= s.length <= 100
s
由小写英文字母组成s[0] != 'i'
思路
- 根据题目,可以得出三个主要核心代码:找到 i 的位置;反转 i 以前的字符串;删除 i并覆盖
- 根据题目可以先判断i是否连续,若有连续如果是偶数效果抵消,直接删除,如果是奇数就要将前面的翻转一次,这样省略了反复逆转
- 所以需要先找到i的位置,然后统计连续 i 的个数,不管奇数还是偶数,都需要进行覆盖i,如果连续 i 的个数是奇数,那么还需要反转 i 以前的字符串,由于覆盖的操作,字符串的长度会发生变化,所以需要一个变量lastlen记录字符串每次变化后的长度,以免越界访问等等的问题
- 最后字符串的长度发生变化,需要设置\0标志字符串的结束
完整代码
void researveString(char* s,int i){
int l = 0;
int r = i - 1;
while(l<r){
char temp = s[l];
s[l] = s[r];
s[r] = temp;
l++;
r--;
}
}
char* finalString(char* s) {
int len = strlen(s);
int count;
int i = 0;
int lastlen = len;
while (i < lastlen) {
count = 0;
for (; i < lastlen; i++) {
if (s[i] == 'i') {
count++;
break;
}
}//找到了第一个i的位置
for (int j = i+1; j < lastlen; j++) {
if (s[j] == 'i') {
count++;
}
else
break;
}//统计几个连续i
for (int j = i; j + count < lastlen; j++) {
s[j] = s[j + count];
}
if (count % 2 != 0) {
researveString(s, i);
}
i++;
lastlen -=count;
}
s[lastlen] = '\0';
return s;
}
21. 合并两个有序链表
https://leetcode.cn/problems/merge-two-sorted-lists/
题目
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 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
均按 非递减顺序 排列
思路
- 采用高效的递归方法,通过递归比较并连接两个链表的节点
- 将这个问题拆分成更小的相同小问题,那就是判断链表是否为空然后进行比较连接
- 首先要检查输入链表的list1和list2是否为空,如果为空直接返回非空链表结束递归
- 非空的话,需要比较当前节点的值,为了确定以哪个节点应该作为合并新链表的当前节点,保持链表的有序性,将已选节点的下一个节点与另一个链表剩余部分进行合并,每次递归都会处理链表的一部分,直到其中一个链表耗尽。
完整代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
if (list1 == NULL) {
return list2;
}
if (list2 == NULL) {
return list1;
}
struct ListNode* LIST;
if (list1->val <= list2->val) {
LIST = list1;
LIST->next = mergeTwoLists(list1->next, list2);
}
else {
LIST = list2;
LIST->next = mergeTwoLists(list1, list2->next);
}
return LIST;
}