https://leetcode-cn.com/problems/reverse-string/
void swap(char *a, char *b) {
char t = *a;
*a = *b, *b = t;
}
void reverseString(char *s, int sSize) {
for (int left = 0, right = sSize - 1; left < right; ++left, --right) {
swap(s + left, s + right);
}
}
时间复杂度:O(N),其中 N 为字符数组的长度。一共执行了 N/2 次的交换。
空间复杂度:O(1),只使用了常数空间来存放若干变量
https://leetcode-cn.com/problems/reverse-string-ii/
void reverse(char* l, char* r) {
while (l < r) {
swap(l++, --r);
}
}
int min(int a, int b) {
return a < b ? a : b;
}
char* reverseStr(char* s, int k) {
int n = strlen(s);
for (int i = 0; i < n; i += 2 * k) {
reverse(&s[i], &s[min(i + k, n)]);
}
return s;
}
https://leetcode-cn.com/problems/daily-temperatures/
https://leetcode-cn.com/problems/online-stock-span/
https://leetcode-cn.com/problems/number-of-islands/
DFS,使用深度优先遍历。
遍历每一个元素,从每个元素开始向其上下左右四个元素进行深度度优先遍历
找出每一个‘1’元素并将每一个‘1’元素置为‘0’
直到遍历不到‘1’为止,即为一个岛屿
void dfs(char** grid, int x, int y, int m, int n){ //深度优先遍历函数
if(x < 0 || x >= m || y < 0 || y >= n || grid[x][y] == '0') //遇到边界或‘0’直接返回
return ;
grid[x][y] = '0'; //遍历的元素都置为‘0’
dfs(grid, x-1, y, m, n); //下面的元素
dfs(grid, x+1, y, m, n); //上面的元素
dfs(grid, x, y-1, m, n); //左边的元素
dfs(grid, x, y+1, m, n); //右边的元素
}
int numIslands(char** grid, int gridSize, int* gridColSize){
if(grid == NULL) //网格为空
return 0;
int count = 0; //记数
for(int i=0; i < gridSize; ++i){ //遍历网格所以元素
for(int j=0; j < *gridColSize; ++j){
if(grid[i][j] == '1'){
++count;
dfs(grid, i, j, gridSize, *gridColSize); //深度优先遍历
}
}
}
return count;
}
时间复杂度:O(mn)。
空间复杂度:O(mn)。
https://leetcode-cn.com/problems/container-with-most-water/
两个指针left,right开始分别指向头、尾,往中间移动;
当left < right时,每次计算盛水的值,与最大值比较,取最大值;
数小的一侧往中间移动;如果两侧数值相等,同时移动;
最后返回最大值。
int maxArea(int* height, int heightSize){
int max = 0,left = 0,right = heightSize - 1;
while(left < right){
max = fmax(max,(right - left) * fmin(height[left],height[right]));
if( height[left] > height[right])
right--;
else if(height[left] < height[right])
left++;
else{
left++;
right--;
}
}
return max;
}
https://leetcode-cn.com/problems/3sum/
解题思路
如果numsSize < 3,返回空值;
利用qsort函数和比较函数给数组‘从小到大’排序;
定义i,j,k,sum,k,j表示两端的数,i = k + 1,sum表示nums[i] + nums[j] +nums[k],寻找满足条件的i,j,k;
如果k > 0 && nums[k] == nums[k-1]表示有重复数,跳过;
循环遍历整个数组,直到nums出现大于0的数nums[k](说明所有组合已遍历完);
给i,j赋值,i指向k+1,j指向数组最后一位,当i < j时:
如果sum == 0,申请空间,将数组加入;如果sum > 0,j--;如果sum < 0,i++;
其中去重:while(i < j && nums[i] == nums[++i]);while(i < j && nums[j] == nums[--j]);
最后返回ans。
注意*的用法表示;
注意要有 *returnSize = 0,这样为空时不会出错,返回[]而不是[[]].
int cmp(const void *a,const void *b){
return *(int*)a - *(int*)b;
}
int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
*returnSize = 0;
if(numsSize < 3)
return NULL;
qsort(nums,numsSize,sizeof(int),cmp);
int **ans = (int **)malloc(sizeof(int *) * numsSize *numsSize);
*returnColumnSizes = (int *)malloc(sizeof(int) * numsSize * numsSize);
int i,j,k,sum;
for(k = 0;k < numsSize - 2;k++){
if(nums[k] > 0)
return ans;
if(k > 0 && nums[k] == nums[k-1])
continue;
i = k + 1;
j = numsSize - 1;
while(i < j){
sum = nums[i] + nums[j] + nums[k];
if(sum == 0){
ans[*returnSize] = (int*)malloc(sizeof(int)*3);
(*returnColumnSizes)[*returnSize] = 3;
ans[*returnSize][0] = nums[k];
ans[*returnSize][1] = nums[i];
ans[*returnSize][2] = nums[j];
*returnSize += 1;
while(i < j && nums[i] == nums[++i]);
while(i < j && nums[j] == nums[--j]);
}else if(sum > 0)
j--;
else
i++;
}
}
return ans;
}
https://leetcode-cn.com/problems/squares-of-a-sorted-array/
https://leetcode-cn.com/problems/remove-linked-list-elements/
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val){
struct ListNode *shead = (struct ListNode *)malloc(sizeof(struct ListNode));
struct ListNode *cur = shead;
struct ListNode *tmp;
shead->next = head;
while(cur->next != NULL){
if (cur->next->val == val){
tmp = cur->next;
cur->next = cur->next->next;
free(tmp);
}
else{
cur = cur->next;
}
}
head = shead->next;
free(shead);
return head;
}
https://leetcode-cn.com/problems/design-linked-list/
https://leetcode-cn.com/problems/minimum-size-subarray-sum/
定义两个指针start 和 end 分别表示子数组(滑动窗口窗口)的开始位置和结束位置,维护变量 sum 存储子数组中的元素和(即从 nums[start] 到nums[end] 的元素和)。
初始状态下,start 和 end 都指向下标 0,sum 的值为 0。
每一轮迭代,将 nums[end] 加到sum,如果sum≥s,则更新子数组的最小长度(此时子数组的长度是 end−start+1),
然后将 nums[start] 从 sum 中减去并将start 右移,直到 sum<s,在此过程中同样更新子数组的最小长度。在每一轮迭代的最后,将 end 右移。
int minSubArrayLen(int s, int *nums, int numsSize) {
if (numsSize == 0) {
return 0;
}
int ans = INT_MAX;
int start = 0, end = 0;
int sum = 0;
while (end < numsSize) {
sum += nums[end];
while (sum >= s) {
ans = fmin(ans, end - start + 1);
sum -= nums[start];
start++;
}
end++;
}
return ans == INT_MAX ? 0 : ans;
}
https://leetcode-cn.com/problems/get-equal-substrings-within-budget/
假定字符串 s 和 t 的长度均为 nn,对于任意 0≤i<n,将s[i] 变成 t[i] 的开销是s[i]−t[i]
因此可以创建一个长度为 n 的数组diff,其中 diff[i]= s[i]−t[i]
创建数组 diff 之后,问题转化成计算数组 diff 的元素和不超过 maxCost 的最长子数组的长度。可以用滑动窗口。
int equalSubstring(char* s, char* t, int maxCost) {
int n = strlen(s);
int diff[n];
memset(diff, 0, sizeof(diff));
for (int i = 0; i < n; i++) {
diff[i] = fabs(s[i] - t[i]);
}
int maxLength = 0;
int start = 0, end = 0;
int sum = 0;
while (end < n) {
sum += diff[end];
while (sum > maxCost) {
sum -= diff[start];
start++;
}
maxLength = fmax(maxLength, end - start + 1);
end++;
}
return maxLength;
}
https://leetcode-cn.com/problems/number-of-substrings-containing-all-three-characters/
1、初步方向:连续子串个数,首先想到就是划窗。
2、转换思路:只要找到首个abc同时存在,后面追加新字母后组成的窗口还是满足题意,题目转换为找出abc同时存在的最短子串!
4、最终题解:划窗内只保存abc同时存在临界范围,不满足abc同时存在挪动右指针,满足同时存在连续挪动左指针,直至不满足,并累加更新结果(右指针及之后字母个数即为以当前划窗为起始符合题意的所有子串)。
5、小技巧:因关键数据量不大,只有abc,因此不用hash保存abc计数,直接用个数组即可。
int numberOfSubstrings(char * s){
if (s == NULL) {
return 0;
}
if (strlen(s) < 3) {
return 0;
}
int start = 0;
int end = 0;
int cnt = 0;
int len = strlen(s);
int recode[3] = {0};
while (end < len) {
recode[s[end] - 'a']++; // 对应字母计数
while ((recode[0] > 0) && (recode[1] > 0) && (recode[2] > 0)) {
cnt += (len - end); // 满足条件更新结果
recode[s[start] - 'a']--; //对应数组位置归0
start++;
}
end++;
}
return cnt;
}
动态规划:
https://leetcode-cn.com/problems/gaM7Ch/
https://leetcode-cn.com/problems/lemonade-change/
由于顾客只可能给你三个面值的钞票,而且我们一开始没有任何钞票,因此我们拥有的钞票面值只可能是 5 美元,10 美元和 20 美元三种。基于此,我们可以进行如下的分类讨论。
55 美元,由于柠檬水的价格也为 55 美元,因此我们直接收下即可。
10 美元,我们需要找回 5 美元,如果没有 5 美元面值的钞票,则无法正确找零。
20 美元,我们需要找回 15 美元,此时有两种组合方式,一种是一张 10 美元和 5 美元的钞票,一种是 3 张 5 美元的钞票,如果两种组合方式都没有,则无法正确找零。当可以正确找零时,两种找零的方式中我们更倾向于第一种,即如果存在 5 美元和 10 美元,我们就按第一种方式找零,否则按第二种方式找零,因为需要使用 5 美元的找零场景会比需要使用 10 美元的找零场景多,我们需要尽可能保留 5 美元的钞票。
基于此,我们维护两个变量five 和 ten 表示当前手中拥有的 5 美元和 10 美元钞票的张数,从前往后遍历数组分类讨论即可。
bool lemonadeChange(int* bills, int billsSize) {
int five = 0, ten = 0;
for (int i = 0; i < billsSize; i++) {
if (bills[i] == 5) {
five++;
} else if (bills[i] == 10) {
if (five == 0) {
return false;
}
five--;
ten++;
} else {
if (five > 0 && ten > 0) {
five--;
ten--;
} else if (five >= 3) {
five -= 3;
} else {
return false;
}
}
}
return true;
}
https://leetcode-cn.com/problems/assign-cookies/
首先对数组 g 和 s 排序,然后从小到大遍历 g 中的每个元素,对于每个元素找到能满足该元素的 s 中的最小的元素。
对于每个元素 g[i],找到未被使用的最小的 j 使得 g[i]≤s[j],则 s[j]s[j] 可以满足 g[i]。由于 g 和 s 已经排好序,因此整个过程只需要对数组 g 和 s 各遍历一次。当两个数组之一遍历结束时,说明所有的孩子都被分配到了饼干,或者所有的饼干都已经被分配或被尝试分配(可能有些饼干无法分配给任何孩子),此时被分配到饼干的孩子数量即为可以满足的最多数量
二叉树遍历:
https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
https://leetcode-cn.com/problems/binary-tree-postorder-traversal/
递归
思路与算法
首先我们需要了解什么是二叉树的后序遍历:按照访问左子树——右子树——根节点的方式遍历这棵树,而在访问左子树或者右子树的时候,我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。
定义 postorder(root) 表示当前遍历到 root 节点的答案。按照定义,我们只要递归调用 postorder(root->left) 来遍历 root 节点的左子树,然后递归调用 postorder(root->right) 来遍历 root 节点的右子树,最后将 root 节点的值加入答案即可,递归终止的条件为碰到空节点。
void postorder(struct TreeNode *root, int *res, int *resSize) {
if (root == NULL) {
return;
}
postorder(root->left, res, resSize);
postorder(root->right, res, resSize);
res[(*resSize)++] = root->val;
}
int *postorderTraversal(struct TreeNode *root, int *returnSize) {
int *res = malloc(sizeof(int) * 2001);
*returnSize = 0;
postorder(root, res, returnSize);
return res;
}
https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/
https://leetcode-cn.com/problems/capacity-to-ship-packages-within-d-days/
我们将「最少需要运送的天数」与 days 进行比较,就可以解决这个判定问题。
当其小于等于 days 时,我们就忽略二分的右半部分区间;
当其大于days 时,我们就忽略二分的左半部分区间。
二分查找的初始左右边界应当如何计算呢?
对于左边界而言,由于我们不能「拆分」一个包裹,因此船的运载能力不能小于所有包裹中最重的那个的重量,即左边界为数组weights 中元素的最大值。
对于右边界而言,船的运载能力也不会大于所有包裹的重量之和,即右边界为数组 weights 中元素的和。
我们从上述左右边界开始进行二分查找,就可以保证找到最终的答案。
int shipWithinDays(int* weights, int weightsSize, int days) {
// 确定二分查找左右边界
int left = 0, right = 0;
for (int i = 0; i < weightsSize; i++) {
left = fmax(left, weights[i]);
right += weights[i];
}
while (left < right) {
int mid = (left + right) / 2;
// need 为需要运送的天数
// cur 为当前这一天已经运送的包裹重量之和
int need = 1, cur = 0;
for (int i = 0; i < weightsSize; i++) {
if (cur + weights[i] > mid) {
++need;
cur = 0;
}
cur += weights[i];
}
if (need <= days) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/
在二分查找的每一步中,左边界为low,右边界为high,区间的中点为 pivot,最小值就在该区间内。
我们将中轴元素[pivot] 与右边界元素[high] 进行比较,可能会有以下的三种情况:
第一种情况: nums[pivot]<nums[high]。可以忽略二分查找区间的右半部分
第二种情况: nums[pivot]>nums[high]。可以忽略二分查找区间的左半部分
第二种情况:由于数组不包含重复元素,并且只要当前的区间长度不为 1,pivot 就不会与 high 重合;而如果当前的区间长度为 1,这说明我们已经可以结束二分查找了。因此不会存在 nums[pivot]=nums[high] 的情
int findMin(int* nums, int numsSize) {
int low = 0;
int high = numsSize - 1;
while (low < high) {
int pivot = low + (high - low) / 2;
if (nums[pivot] < nums[high]) {
high = pivot;
} else {
low = pivot + 1;
}
}
return nums[low];
}