【Leetcode】算法与数据结构 C语言

字符串:

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 美元的钞票,一种是 35 美元的钞票,如果两种组合方式都没有,则无法正确找零。当可以正确找零时,两种找零的方式中我们更倾向于第一种,即如果存在 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/
【Leetcode】算法与数据结构 C语言_第1张图片

在二分查找的每一步中,左边界为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];
}

你可能感兴趣的:(算法与数据结构,C语言进阶,string,函数,leetcode,math,stack)