2020年7月 leetcode每日一题 C语言版本

目录

  • 1 最长重复子数组
    • 暴力超时
    • 动态规划
    • 滑动窗口
  • 2 有序矩阵中第K小的元素
    • 暴力排序
    • 二分法 *
  • 3 将有序数组转换为二叉搜索树
  • 4 最长有效括号**
    • 动态规划
    • 双指针
  • 5 不同路径2
    • 动态规划
  • 6 路径总和
    • 递归*
    • 广度优先搜索
  • 7 !通配符匹配
  • 8 跳水板
    • 数学推导
  • 9 恢复空格
    • 字典树详解
    • 动态规划+字典树
  • 10 最佳买卖股票时机含冷冻期
    • 动态规划
  • 11 计算右侧小于当前元素的个数([未完待续](https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/))
  • 12 地下城游戏
    • 动态规划
  • 13 两个数组的交集 II
    • 排序+双指针
  • 14 !三角形最小路径和
  • 15 不同的二叉搜索树
    • 动态规划 **
    • 卡特兰数 **
  • 16 判断二分图
    • 并查集
    • 深度优先搜索
  • 28 二叉树的最深度
    • 递归 深度优先搜索
    • 广度优先搜索

1 最长重复子数组

2020-7-1

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例 1:
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出: 3
解释: 长度最长的公共子数组是 [3, 2, 1]。

说明:
1 <= len(A), len(B) <= 1000
0 <= A[i], B[i] < 100

暴力超时

int findLength(int* A, int ASize, int* B, int BSize){
    int max=0;
    for(int i=0;i<ASize;i++){     
        for(int j=0;j<BSize;j++){
            int k=0;
            while((i+k<ASize)&&(j+k<BSize)&&A[i+k]==B[j+k]){
                k++;
            }
            max=k>max?k:max;
        }   
    }
    return max;
}

时间复杂度: O ( n 3 ) O(n^3) O(n3)

动态规划

  • d p [ i ] [ j ] dp[i][j] dp[i][j]表示 以 A [ i ] 、 B [ j ] 以A[i]、B[j] A[i]B[j]开头的数组的最长重复子数组的数目
  • d p [ i ] [ j ] dp[i][j] dp[i][j]初始值为 0 0 0
  • 如果 A [ i ] = B [ j ] A[i]=B[j] A[i]=B[j] d p [ i ] [ j ] = d p [ i + 1 ] [ j + 1 ] + 1 dp[i][j]=dp[i+1][j+1]+1 dp[i][j]=dp[i+1][j+1]+1
int findLength(int* A, int ASize, int* B, int BSize){
    int **dp=(int**)malloc((ASize+1)*sizeof(int*));
    for(int i=0;i<ASize+1;i++){
        dp[i]=(int*)malloc((BSize+1)*sizeof(int));
    }
    for(int i=0;i<=ASize;i++){
        for(int j=0;j<=BSize;j++){
            dp[i][j]=0;
        }
    }
    int max=0;
    for(int i=ASize-1;i>=0;i--){
        for(int j=ASize-1;j>=0;j--){
            if(A[i]==B[j]){
                dp[i][j]=dp[i+1][j+1]+1;
            }else{
                dp[i][j]=0;
            }
            max=dp[i][j]>max?dp[i][j]:max;
        }
    }
    return max;
}

时间复杂度: O ( N × M ) O(N \times M) O(N×M)
空间复杂度: O ( N × M ) O(N \times M) O(N×M)

滑动窗口

  • 之所以两位置会比较多次,是因为重复子数组在两个数组中的位置可能不同。因此可以将 A 和 B 进行「对齐」,对这两个数组进行一次遍历,得到子数组的长度
  • 固定一个数组不变,移动另一个
//抄的答案
int maxLength(int* A, int* B, int addA, int addB, int len) {
    int ret = 0, k = 0;
    for (int i = 0; i < len; i++) {
        if (A[addA + i] == B[addB + i]) {
            k++;
        } else {
            k = 0;
        }
        ret = fmax(ret, k);
    }
    return ret;
}

int findLength(int* A, int ASize, int* B, int BSize) {
    int ret = 0;
    for (int i = 0; i < ASize; i++) {
        int len = fmin(BSize, ASize - i);
        int maxlen = maxLength(A, B, i, 0, len);
        ret = fmax(ret, maxlen);
    }
    for (int i = 0; i < BSize; i++) {
        int len = fmin(ASize, BSize - i);
        int maxlen = maxLength(A, B, 0, i, len);
        ret = fmax(ret, maxlen);
    }
    return ret;
}

时间复杂度: O ( ( N + M ) × min ⁡ ( N , M ) ) O((N + M) \times \min(N, M)) O((N+M)×min(N,M))
空间复杂度: O ( 1 ) O(1) O(1)

2 有序矩阵中第K小的元素

2020-7-2

给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是排序后的第 k 小元素,而不是第 k 个不同的元素。

示例:
matrix = [
[ 1, 5, 9],
[10, 11, 13],
[12, 13, 15]
],
k = 8,返回 13。

提示:
你可以假设 k 的值永远是有效的,1 ≤ k ≤ n2

暴力排序

//抄答案的
int cmp(const void *a, const void *b) { return (*(int *)a - *(int *)b); }

int kthSmallest(int **matrix, int matrixSize, int *matrixColSize, int k) {
    int *rec = (int *)malloc(matrixSize * matrixSize * sizeof(int));

    int num = 0;
    for (int i = 0; i < matrixSize; i++) {
        for (int j = 0; j < matrixColSize[i]; j++) {
            rec[num++] = matrix[i][j];
        }
    }
    qsort(rec, num, sizeof(int), cmp);

    return rec[k - 1];
}

二分法 *

  • k k k小元素,即小于此元素的有 k − 1 k-1 k1
  • 从左下向右上遍历,计算小于某数的个数
    2020年7月 leetcode每日一题 C语言版本_第1张图片
//抄答案的
bool check(int **matrix, int mid, int k, int n) {
    int i = n - 1;
    int j = 0;
    int num = 0;
    while (i >= 0 && j < n) {
        if (matrix[i][j] <= mid) {
            num += i + 1;
            j++;
        } else {
            i--;
        }
    }
    return num >= k;
}

int kthSmallest(int **matrix, int matrixSize, int *matrixColSize, int k) {
    int left = matrix[0][0];
    int right = matrix[matrixSize - 1][matrixSize - 1];
    while (left < right) {
        int mid = left + ((right - left) >> 1);
        if (check(matrix, mid, k, matrixSize)) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    return left;
}

3 将有序数组转换为二叉搜索树

2020-7-3

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:
2020年7月 leetcode每日一题 C语言版本_第2张图片
https://leetcode-cn.com/problems/balance-a-binary-search-tree/solution/jiang-er-cha-sou-suo-shu-bian-ping-heng-by-leetcod/

二叉搜索树的中序遍历是升序序列

选择中间数字作为二叉搜索树的根节点,这样分给左右子树的数字个数相同或只相差 1,可以使得树保持平衡。如果数组长度是奇数,则根节点的选择是唯一的,如果数组长度是偶数,则可以选择中间位置左边的数字作为根节点或者选择中间位置右边的数字作为根节点,选择不同的数字作为根节点则创建的平衡二叉搜索树也是不同的。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
//抄的
struct TreeNode* helper(int* nums, int left, int right) {
    if (left > right) {
        return NULL;
    }

    // 总是选择中间位置左边的数字作为根节点
    int mid = (left + right) / 2;
    // 总是选择中间位置右边的数字作为根节点
    //int mid = (left + right + 1) / 2;
    // 选择任意一个中间位置数字作为根节点
    //int mid = (left + right + rand.nextInt(2)) / 2;
    
    struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->val = nums[mid];
    root->left = helper(nums, left, mid - 1);
    root->right = helper(nums, mid + 1, right);
    return root;
}

struct TreeNode* sortedArrayToBST(int* nums, int numsSize) {
    return helper(nums, 0, numsSize - 1);
}

4 最长有效括号**

2020-7-4

#define MaxSize 100000
typedef struct stack {
    int data[MaxSize];
    int top;
}Stack;

Stack S;

void init() { 
    S.top = 0;
}

void push(int x) { 
    S.top=S.top+1;
    S.data[S.top] = x;
}

void pop() {
    S.top=S.top-1; 
}

int top() {
    return S.data[S.top];
}

int isEmpty() { 
    return S.top==0;
}

int longestValidParentheses(char * s){
    int ans=0;
    int len=strlen(s);
    init();
    push(-1);
    for(int i=0;i<len;i++){
        if(s[i]=='('){  //左括号 入栈
            push(i);
        }else if(s[i]==')'){ //右括号 出栈 
            pop();
            if(!isEmpty()){  //右括号 栈不为空
                int sum=i-top();
                ans=sum>ans?sum:ans;
            }else{  //右括号 栈为空
                push(i);
            }
        }
    }
    return ans;
}

简洁版栈

int longestValidParentheses(char * s){
    int ans=0;
    int len=strlen(s);
    int top=-1;
    int data[len+1];
    data[++top]=-1;  //-1入栈

    for(int i=0;i<len;i++){
        if(s[i]=='('){  //左括号 入栈
            data[++top]=i;
        }else if(s[i]==')'){ //右括号 出栈 
            top--;
            if(top!=-1){  //右括号 栈不为空
                int sum=i-data[top];
                ans=sum>ans?sum:ans;
            }else{  //右括号 栈为空,-1已经被pop了
                data[++top]=i;
            }
        }
    }
    return ans;
}

动态规划

2020年7月 leetcode每日一题 C语言版本_第3张图片

//抄的
int longestValidParentheses(char* s) {
    int maxans = 0, n = strlen(s);
    if (n == 0) return 0;
    int dp[n];
    memset(dp, 0, sizeof(dp));
    for (int i = 1; i < n; i++) {
        if (s[i] == ')') {
            if (s[i - 1] == '(') {
                dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
            } else if (i - dp[i - 1] > 0 && s[i - dp[i - 1] - 1] == '(') {
                dp[i] = dp[i - 1] +
                        ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
            }
            maxans = fmax(maxans, dp[i]);
        }
    }
    return maxans;
}

双指针

妙哉
https://leetcode-cn.com/problems/longest-valid-parentheses/solution/zui-chang-you-xiao-gua-hao-by-leetcode-solution/

5 不同路径2

2020-7-6

一个机器人位于一个 m x n 网格的左上角 。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
说明:m 和 n 的值均不超过 100。

示例 1:

输入:
[ [0,0,0],
[0,1,0],
[0,0,0]]
输出: 2

解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1.向右 -> 向右 -> 向下 -> 向下
2.向下 -> 向下 -> 向右 -> 向右

动态规划

d p [ i ] [ j ] dp[i][j] dp[i][j]表示到达当前位置的路径数

  • 当前无障碍:
    d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j]=dp[i-1][j]+dp[i][j-1] dp[i][j]=dp[i1][j]+dp[i][j1]
  • 当前有障碍:
    d p [ i ] [ j ] = 0 dp[i][j]=0 dp[i][j]=0
//我滴
int uniquePathsWithObstacles(int** obstacleGrid, int obstacleGridSize, int* obstacleGridColSize){
    if(obstacleGridSize==0)return 0;
    int **dp=(int**)malloc(obstacleGridSize*sizeof(int*));
    for(int i=0;i<obstacleGridSize;i++){
        dp[i]=(int*)malloc((*obstacleGridColSize)*sizeof(int));
    }
    
    if(obstacleGrid[0][0]==1){
        dp[0][0]=0;
    }else{
        dp[0][0]=1;
    }
    
    for(int i=1;i<obstacleGridSize;i++){   //第一列
        if(obstacleGrid[i][0]==0&&dp[i-1][0]==1){  //当前是零,且前一个可通过
            dp[i][0]=1;
        }else{
            dp[i][0]=0;
        }  
    }
    for(int i=1;i<obstacleGridColSize[0];i++){   //第一行
        if(obstacleGrid[0][i]==0&&dp[0][i-1]==1){  //当前是零,且前一个可通过
            dp[0][i]=1;
        }else{
            dp[0][i]=0;
        }  
    }

    for(int i=1;i<obstacleGridSize;i++){
        for(int j=1;j<obstacleGridColSize[0];j++){
            if(obstacleGrid[i][j]==1){
                dp[i][j]=0;
            }else{
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
    }
    /*for(int i=0;i
    return dp[obstacleGridSize-1][obstacleGridColSize[0]-1];
}

空间复杂度优化

  • 使用长度为列数的一维数组
  • 初始值设为0
  • 若当前的位置为1,将dp值设为0,并且停止继续判断(后面过不去,均为默认值0)
  • 对于非第一列的位置,当前为零且前一列也为零, d p [ j ] = d p [ j ] + d p [ j − 1 ] dp[j]=dp[j]+dp[j-1] dp[j]=dp[j]+dp[j1],其中 d p [ j ] dp[j] dp[j]是上一列的数,即上边的, d p [ j − 1 ] dp[j-1] dp[j1]是当前列的,即左边的
int uniquePathsWithObstacles(int** obstacleGrid, int obstacleGridSize,int* obstacleGridColSize) {
    int n = obstacleGridSize, m = obstacleGridColSize[0];
    int dp[m];
    memset(dp, 0, sizeof(dp));  //初始值设为零
    dp[0] = (obstacleGrid[0][0] == 0);
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            if (obstacleGrid[i][j] == 1) {
                dp[j] = 0;
                continue;
            }
            if (j - 1 >= 0 && obstacleGrid[i][j - 1] == 0) {
                dp[j] += dp[j - 1];
            }
        }
    }

    return dp[m - 1];
}

回顾

  1. 名字太长可以用简单的替代以简化代码
  2. 滚动数组要学会,尤其是这种涉及到左边和上边的元素的
  3. 多用+=,显得高级^_^
  4. 我从思路到代码还是太笨拙,活到老学到老

6 路径总和

2020-7-7

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。

示例:
给定如下二叉树,以及目标和 sum = 22,
2020年7月 leetcode每日一题 C语言版本_第4张图片
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。

递归*

  • 是否存在从当前节点 r o o t root root 到叶子节点的路径,满足其路径和为 s u m sum sum
  • 子问题:假定从根节点到当前节点的值之和为 v a l val val,是否存在从当前节点的子节点到叶子的路径,满足其路径和为 s u m − v a l sum - val sumval
bool hasPathSum(struct TreeNode *root, int sum) {
    if (root == NULL) {
        return false;
    }
    if (root->left == NULL && root->right == NULL) {
        return sum == root->val;
    }
    return hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val);
}

时间复杂度: O ( N ) O(N) O(N)
空间复杂度: O ( l o g N ) O(logN) O(logN)

广度优先搜索

下次一定

7 !通配符匹配

2020-7-7

8 跳水板

2020-7-8

你正在使用一堆木板建造跳水板。有两种类型的木板,其中长度较短的木板长度为shorter,长度较长的木板长度为longer。你必须正好使用k块木板。编写一个方法,生成跳水板所有可能的长度。返回的长度需要从小到大排列。

示例:
输入:
shorter = 1
longer = 2
k = 3
输出: {3,4,5,6}

提示:
0 < shorter <= longer
0 <= k <= 100000

数学推导

k + 1 k+1 k+1种不同的长度,对于 0 ≤ i ≤ k 0≤i≤k 0ik,令 l e n g t h s [ i ] = s h o r t e r ∗ ( k − i ) + l o n g e r ∗ i lengths[i]=shorter∗(k−i)+longer∗i lengths[i]=shorter(ki)+longeri,则 l e n g t h s lengths lengths包含跳水板所有可能的长度,且长度为升序排序

我的代码

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* divingBoard(int shorter, int longer, int k, int* returnSize){
    int *ans=malloc((k+1)*sizeof(int));
    *returnSize=0;
    if(k==0){
        return ans;
    }
    if(shorter==longer){
        (*returnSize)++;
        ans[0]=shorter*k;
        return ans;
    }
    for(int i=0;i<=k;i++){
        ans[i]=shorter*(k-i)+longer*i;
        (*returnSize)++;
    }
    return ans;
}

看答案修改之后的版本

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* divingBoard(int shorter, int longer, int k, int* returnSize){
    if(k==0){
        *returnSize=0;
        return NULL;
    }
    if(shorter==longer){
        int *ans0=malloc(sizeof(int));
        *returnSize=1;
        ans0[0]=shorter*k;
        return ans0;
    }
    *returnSize=k+1;
    int *ans=malloc((k+1)*sizeof(int));
    for(int i=0;i<=k;i++){
        ans[i]=shorter*(k-i)+longer*i;
    }
    return ans;
}

等差数列
首项为: s h o r t e r ∗ k shorter*k shorterk
公差为: l o n g e r − s h o r t e r longer-shorter longershorter

ans[0]=k*shorter;
    int tolerance=longer-shorter;
    for(int i=1;i<=k;i++){
        ans[i]=ans[i-1]+tolerance;
    }

时间复杂度: O ( k ) O(k) O(k)
空间复杂度: O ( 1 ) O(1) O(1)

9 恢复空格

2020-7-9

哦,不!你不小心把一个长篇文章中的空格、标点都删掉了,并且大写也弄成了小写。像句子"I reset the computer. It still didn’t boot!“已经变成了"iresetthecomputeritstilldidntboot”。在处理标点符号和大小写之前,你得先把它断成词语。当然了,你有一本厚厚的词典dictionary,不过,有些词没在词典里。假设文章用sentence表示,设计一个算法,把文章断开,要求未识别的字符最少,返回未识别的字符数。

示例:
输入:
dictionary = [“looked”,“just”,“like”,“her”,“brother”]
sentence = “jesslookedjustliketimherbrother”
输出: 7
解释: 断句后为"jess looked just like tim her brother",共7个未识别字符。

提示:
0 <= len(sentence) <= 1000
dictionary中总字符数不超过 150000。
你可以认为dictionary和sentence中只包含小写字母。

字典树详解

  • ID:trie树、前缀树、单词查找树、键树,哈希树的变种
  • 功能:快速检索的多叉树结构,判断一个字符串是否是一个字符串集合中某个字符串的前缀
  • 思想:空间换时间,利用字符串的公共前缀降低查询的时间开销
  • 应用:统计和排序大量的字符串,搜索引擎系统的文本词频统计
  • 结构:每条边代表一个字符;节点保存当前字符串及是否是结尾标志;从根节点到某一节点,路径上经过的字符连接起来为该节点对应的字符串;一个节点的所有孩子都有相同的前缀
  • 英文字母的字典树是一个26叉树,数字的字典树是一个10叉树
    2020年7月 leetcode每日一题 C语言版本_第5张图片

基本性质

(1) 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
(2) 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
(3) 每个节点的所有子节点包含的字符都不相同。

数据结构

typedef struct Trie {
	//每个结点有26个指针,指向下一层子结点,每个指针代表下一个不同的字母
    struct Trie* next[26];  //指向各个子树的指针
    bool isEnd;  //标记字符串是否结束
} Trie;

初始化

Trie* initNode(Trie** p)
{
	Trie* p =(Trie*)malloc(sizeof(Trie));
	(*p)->isEnd = false;
	memset((*p)->next, NULL, sizeof((*p)->next));
	return p;
}

插入操作

逐个插入字符串的每个字符,若存在当前字母直接指向当前字母的 n e x t next next节点,否则新建当前字母的节点

  • 伪代码
Insert(root,s):
	p=root
	for i=1...s.len: //分别插入字符串的每个字符
		if p->next[s[i]]==NULL)  //字符对应的子节点为空
			p.addChild(s[i],new Node())  //为S[i]新建节点
		p=p.next[s[i]]   //指向当前节点的下一层节点
	p.isEnd = true;   //标记p为终结点
  • C语言代码
void insert(Trie* curPos, char* s) {
    int len = strlen(s);
    for (int i = 0; i < len; i++) {
        int t = s[i] - 'a';
        if (curPos->next[t] == NULL) {
            init(&curPos->next[t]);
        }
        curPos = curPos->next[t];
    }
    curPos->isEnd = true;
}

查询操作

  • 伪代码
Search(root,S):
	for i=1...S.len:
	 	if p.thru(S[i])==NULL:
	 		return false
	 	p=p.thru(S[i])
	 return true
  • C语言代码
int Search(Trie* root,const char* s)
{
	Trie *p=root;
	while(*s!='\0')
	{
		p=p->next[*s-'a'];
		if(p==NULL)
		return 0;
	}
	return 1;
}

用二维数组实现

#include
#include
#include
using namespace std;
const int MAX_NODE = 1000000 + 10;
const int CHARSET = 26;
int trie[MAX_NODE][CHARSET] = {0};
int color[MAX_NODE] = {0};
int k = 1;

void insert(char *w){
    int len = strlen(w);
    int p = 0;
    for(int i=0; i<len; i++){
        int c = w[i] - 'a';
        if(!trie[p][c]){
            trie[p][c] = k;
            k++;
        }
        p = trie[p][c];
    }
    color[p] = 1;
}

int search(char *s){
    int len = strlen(s);
    int p = 0;
    for(int i=0; i<len; i++){
        int c = s[i] - 'a';
        if(!trie[p][c]) return 0;
        p = trie[p][c];
    }
    return color[p] == 1;
}

int main(){
    int t,q;
    char s[20];
    scanf("%d%d", &t,&q);
    while(t--){
        scanf("%s", s);
        insert(s);
    }
    while(q--){
        scanf("%s", s);
        if(search(s)) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

Trie树的常见应用大总结(面试+附代码实现)
剑指Offer——Trie树(字典树)
字典树(前缀树)

动态规划+字典树

d p [ i ] dp[i] dp[i]表示第 i i i个字符以前最少未识别的字符串
对于子字符串 s [ j − 1 , j , . . . i ] s[j-1,j,...i] s[j1,j,...i],若没找到, d p [ i ] = d p [ i − 1 ] + 1 dp[i]=dp[i-1]+1 dp[i]=dp[i1]+1,否则 d p [ i ] = m i n ( d p [ i ] , d p [ j − 1 ] ) dp[i]=min(dp[i],dp[j-1]) dp[i]=min(dp[i],dp[j1])

typedef struct Trie {
    struct Trie* next[26];
    bool isEnd;
} Trie;

void init(Trie** p) {
    *p = (Trie*)malloc(sizeof(Trie));
    (*p)->isEnd = false;
    memset((*p)->next, NULL, sizeof((*p)->next));
}

void insert(Trie* curPos, char* s) {
    int len = strlen(s);
    for (int i = len - 1; i >= 0; --i) {
        int t = s[i] - 'a';
        if (curPos->next[t] == NULL) {
            init(&curPos->next[t]);
        }
        curPos = curPos->next[t];
    }
    curPos->isEnd = true;
}

int respace(char** dictionary, int dictionarySize, char* sentence) {
    int n = strlen(sentence), inf = 0x3f3f3f3f;

    Trie* root;
    init(&root);
    for (int i = 0; i < dictionarySize; i++) {  
        insert(root, dictionary[i]);
    }
    int dp[n + 1];
    memset(dp, 0x3f, sizeof(dp));
    dp[0] = 0;
    for (int i = 1; i <= n; ++i) {
        dp[i] = dp[i - 1] + 1;

        Trie* curPos = root;
        for (int j = i; j >= 1; --j) {
            int t = sentence[j - 1] - 'a';
            if (curPos->next[t] == NULL) {  //不存在,结束当前循环
                break;
            } else if (curPos->next[t]->isEnd) {  //存在于字典中
                dp[i] = fmin(dp[i], dp[j - 1]);
            }
            if (dp[i] == 0) {
                break;
            }
            curPos = curPos->next[t];
        }
    }
    return dp[n];
}

10 最佳买卖股票时机含冷冻期

2020-7-10

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

动态规划

i i i天持有股票 d p [ i ] [ 0 ] dp[i][0] dp[i][0]

  • 昨天冷冻,今天买入: d p [ i − 1 ] [ 2 ] − p r i c e s [ i ] dp[i-1][2]-prices[i] dp[i1][2]prices[i]
  • 昨天持,今天持: d p [ i − 1 ] [ 0 ] dp[i-1][0] dp[i1][0]

i i i天不持有股票 d p [ i ] [ 1 ] dp[i][1] dp[i][1]

  • 昨天持,今天卖: d p [ i − 1 ] [ 0 ] + p r i c e s [ i ] dp[i-1][0]+prices[i] dp[i1][0]+prices[i]
  • 昨天不持,昨天不持: d p [ i − 1 ] [ 1 ] dp[i-1][1] dp[i1][1]

i i i天冻结 d p [ i ] [ 2 ] dp[i][2] dp[i][2]

  • 昨天卖,今冷冻: d p [ i ] [ 2 ] = d p [ i − 1 ] [ 1 ] dp[i][2]=dp[i-1][1] dp[i][2]=dp[i1][1]

状态转移方程:
d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 2 ] − p r i c e s [ i ] , d p [ i − 1 ] [ 0 ] ) dp[i][0]=max(dp[i-1][2]-prices[i],dp[i-1][0]) dp[i][0]=max(dp[i1][2]prices[i],dp[i1][0])

d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] + p r i c e s [ i ] ) dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]) dp[i][1]=max(dp[i1][1],dp[i1][0]+prices[i])

d p [ i ] [ 2 ] = d p [ i − 1 ] [ 1 ] dp[i][2]=dp[i-1][1] dp[i][2]=dp[i1][1]

初始值:
d p [ 0 ] [ 0 ] = − p r i c e s [ 0 ] 、 d p [ 0 ] [ 1 ] = 0 、 d p [ 0 ] [ 2 ] = 0 dp[0][0]=-prices[0] 、 dp[0][1]=0、 dp[0][2]=0 dp[0][0]=prices[0]dp[0][1]=0dp[0][2]=0

int maxProfit(int* prices, int pricesSize){
    if(pricesSize==0)return 0;
    int dp[pricesSize][3];
    dp[0][0]=-prices[0];
    dp[0][1]=0;
    dp[0][2]=0;
    for(int i=1;i<pricesSize;i++){
        dp[i][0]=fmax(dp[i-1][2]-prices[i],dp[i-1][0]);
        dp[i][1]=fmax(dp[i-1][1],dp[i-1][0]+prices[i]);
        dp[i][2]=dp[i-1][1];
       // printf("%d %d %d\n",dp[i][0],dp[i][1],dp[i][2]);
    }
    return dp[pricesSize-1][1];
}

空间优化

int maxProfit(int* prices, int pricesSize){
    if(pricesSize==0)return 0;

    int dp0=-prices[0];
    int dp1=0;
    int dp2=0;
    
    for(int i=1;i<pricesSize;i++){
        int dp0_temp=fmax(dp2-prices[i],dp0);
        int dp1_temp=fmax(dp1,dp0+prices[i]);
        int dp2_temp=dp1;
        dp0=dp0_temp;
        dp1=dp1_temp;
        dp2=dp2_temp;
    }
    return dp1;
}

11 计算右侧小于当前元素的个数(未完待续)

2020-7-11

给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

示例:
输入:[5,2,6,1]
输出:[2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int insert(int *A,int size,int a){
    int l=0,r=size-1,mid=0;
    while(l<=r){
        mid=(l+r)/2;
        if(x==A[mid])return mid;
        if(x<A[mid]){
            r=mid-1;
        }else{
            l=mid+1;
        }
    }
    while(size >= 0 && A[size] >= a){
        A[size+1]=A[size];
        size--;
    }
    A[size+1]=a;
    return size+1;
}

int* countSmaller(int* nums, int numsSize, int* returnSize){
    *returnSize=numsSize;
    if(numsSize==0)return NULL;
    int sort_nums[numsSize];
    int sort_nums_size=0;
    int *ans=malloc(numsSize*sizeof(int));
    ans[numsSize-1]=0;
    sort_nums[0]=nums[numsSize-1];

    for(int i=numsSize-2;i>=0;i--){
        //ans[i]=biSearch(sort_nums,sort_nums_size,nums[i]);
        ans[i]=insert(sort_nums,sort_nums_size,nums[i]);
        sort_nums_size++;
    }
    return ans;
}

12 地下城游戏

2020-7-12

一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。

骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。

有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。为了尽快到达公主,骑士决定每次只向右或向下移动一步。 编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。

例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。

2020年7月 leetcode每日一题 C语言版本_第6张图片

说明:

  • 骑士的健康点数没有上限。
  • 任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。

动态规划

从右下向左上dp

int calculateMinimumHP(int** dungeon, int dungeonSize, int* dungeonColSize) {
    int n = dungeonSize, m = dungeonColSize[0];
    int dp[n + 1][m + 1];
    memset(dp, 0x3f, sizeof(dp));
    dp[n][m - 1] = dp[n - 1][m] = 1;
    for (int i = n - 1; i >= 0; --i) {
        for (int j = m - 1; j >= 0; --j) {
            int minn = fmin(dp[i + 1][j], dp[i][j + 1]);
            dp[i][j] = fmax(minn - dungeon[i][j], 1);
        }
    }
    return dp[0][0];
}

13 两个数组的交集 II

2020-7-13

给定两个数组,编写一个函数来计算它们的交集。

示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]

排序+双指针

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
void swap(int arr[], int low, int high)
{
    int temp;
    temp = arr[low];
    arr[low] = arr[high];
    arr[high] = temp;
}

int Partition(int array[], int low, int high) {
    int base = array[low];
    while (low < high) {
        while (low < high && array[high] >= base) {
            high--;
        }
        swap(array, low, high);//array[low] = array[high];
        while (low < high && array[low] <= base) {
            low++;
        }
        swap(array, low, high);//array[high] = array[low];
    }
    array[low] = base;
    return low;
}

void QuickSort(int array[], int low, int high) {
    if (low < high) {
        int base = Partition(array, low, high);
        QuickSort(array, low, base - 1);
        QuickSort(array, base + 1, high);
    }
}

int* intersect(int* nums1, int nums1Size, int* nums2, int nums2Size, int* returnSize){
    QuickSort(nums1, 0 ,nums1Size-1);
    QuickSort(nums2, 0 ,nums2Size-1);

    int n=fmin(nums1Size,nums2Size);
    int *ans=malloc(n*sizeof(int));
    int i=0,j=0,k=0;
    
    while(i < nums1Size && j < nums2Size){
        if(nums1[i] < nums2[j]){
            i++;
        }else if(nums1[i] > nums2[j]){
            j++;
        }else{
            ans[k]=nums2[j];
            i++;
            j++;
            k++;
        }
    }
    *returnSize=k;
    return ans;
}

使用qsort库函数进行排序时,回调函数使用(*(int *)a - *(int *)b),会出现整数溢出

int compInc(const void *a, const void *b)   //递增
{
    return *(int *)a - *(int *)b;
}
qsort(nums1, nums1Size, sizeof(int), compInc);
qsort(nums2, nums2Size, sizeof(int), compInc);

测试用例:

[-2147483648,1,2,3]
[1,-2147483648,-2147483648]

报错:

Line 6: Char 22: runtime error: signed integer overflow: -2147483648 - 1 cannot be represented in type 'int' (solution.c)

更正后

int compInc(const void *a, const void *b){
    return (*(int *)a > *(int *)b); 
}

int* intersect(int* nums1, int nums1Size, int* nums2, int nums2Size, int* returnSize){
  
    qsort(nums1, nums1Size, sizeof(int), compInc);
    qsort(nums2, nums2Size, sizeof(int), compInc);

    int n=fmax(nums1Size,nums2Size);
    int *ans=malloc(n*sizeof(int));
  
    int i=0,j=0,k=0;
    while(i < nums1Size && j < nums2Size){
        if(nums1[i] < nums2[j]){
            i++;
        }else if(nums1[i] > nums2[j]){
            j++;
        }else{
            ans[k]=nums2[j];
            i++;
            j++;
            k++;
        }
    }
    *returnSize=k;
    return ans;
}

时间复杂度: O ( m log ⁡ m + n log ⁡ n ) O(m \log m+n \log n) O(mlogm+nlogn)
空间复杂度: O ( min ⁡ ( m , n ) ) O(\min(m,n)) O(min(m,n))

14 !三角形最小路径和

2020-7-14

15 不同的二叉搜索树

2020-7-15

给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

二叉搜索树
它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树、

动态规划 **

给定一个有序序列 1 ⋯ n 1⋯n 1n,为了构建出一棵二叉搜索树,我们可以遍历每个数字 i i i,将该数字 i i i作为树根,将 1 ⋯ ( i − 1 ) 1⋯(i−1) 1(i1) 序列作为左子树,将 n ( i + 1 ) ⋯ n n(i+1)⋯n n(i+1)n 序列作为右子树。

假设 n n n个节点存在二叉搜索树的个数是 G ( n ) G(n) G(n),令 f ( i ) f(i) f(i)为以 i i i为根的二叉搜索树的个数,则
G ( n ) = f ( 1 ) + f ( 2 ) + f ( 3 ) + f ( 4 ) + . . . + f ( n ) G(n) = f(1) + f(2) + f(3) + f(4) + ... + f(n) G(n)=f(1)+f(2)+f(3)+f(4)+...+f(n)

i i i为根节点时,其左子树节点个数为 i − 1 i-1 i1个,右子树节点为 n − i n-i ni,则
f ( i ) = G ( i − 1 ) ∗ G ( n − i ) f(i) = G(i-1)*G(n-i) f(i)=G(i1)G(ni)

综合两个公式可以得到卡特兰数公式
G ( n ) = G ( 0 ) ∗ G ( n − 1 ) + G ( 1 ) ∗ ( n − 2 ) + . . . + G ( n − 1 ) ∗ G ( 0 ) G(n) = G(0)*G(n-1)+G(1)*(n-2)+...+G(n-1)*G(0) G(n)=G(0)G(n1)+G(1)(n2)+...+G(n1)G(0)

int numTrees(int n) {
    int G[n + 1];
    memset(G, 0, sizeof(G));
    G[0] = G[1] = 1;
    for (int i = 2; i <= n; ++i) {
        for (int j = 1; j <= i; ++j) {
            G[i] += G[j - 1] * G[i - j];
        }
    }
    return G[n];
}

卡特兰数 **

G ( n + 1 ) = 4 n + 2 n + 2 G ( n ) G(n+1)=\frac{4n+2}{n+2}G(n) G(n+1)=n+24n+2G(n)

int numTrees(int n) {
    long long C = 1;
    for (int i = 0; i < n; ++i) {
        C = C * 2 * (2 * i + 1) / (i + 2);
    }
    return (int)C;
}

16 判断二分图

2020-7-16

给定一个无向图graph,当这个图为二分图时返回true。如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图。graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点。每个节点都是一个在0到graph.length-1之间的整数。这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值。

示例 1:
输入: [[1,3], [0,2], [1,3], [0,2]]
输出: true
解释:
我们可以将节点分成两组: {0, 2} 和 {1, 3}。

示例 2:
输入: [[1,2,3], [0,2], [0,1,3], [0,2]]
输出: false

注意:
graph 的长度范围为 [1, 100]。
graph[i] 中的元素的范围为 [0, graph.length - 1]。
graph[i] 不会包含 i 或者有重复的值。
图是无向的: 如果j 在 graph[i]里边, 那么 i 也会在 graph[j]

并查集

  • 初始设置父节点为本身
  • 对第 i i i行的数据,分别比较 i i i的父节点与第 j j j个数据的父节点是否相同,若相同则表示线段两端的节点在同一集合,返回Flase
  • 对第 i i i行的数据,将其合并到同一个集合中
#define MAXN 100
int fa[MAXN];

//初始化
void init(int n)
{
    for (int i = 0; i < n; ++i){
        fa[i] = i;
    }     
}

//查询
int find(int x)
{
    if(fa[x] == x)
        return x;
    else
        return find(fa[x]);
}

//合并
void merge(int i, int j)
{
    fa[find(i)] = find(j);
}

bool isBipartite(int** graph, int graphSize, int* graphColSize){
    init(graphSize);
    for(int i=0;i<graphSize;i++){
        for(int j=0;j<graphColSize[i];j++){
            if(find(i)==find(graph[i][j])){
                return false;
            }
        }
        for(int j=1;j<graphColSize[i];j++){
            merge(graph[i][j-1],graph[i][j]);
        }
    }
    return true;
}

深度优先搜索

// 0:未染色
// 1:颜色1
// 2:颜色2

bool dfs(int node, int c, int* color, int** graph, int* graphColSize) {
    color[node] = c;
    int cNei = (c == 1 ? 2 : 1);  //表示邻结点应有的颜色,当前为1邻应为2,当前为2邻应为1
    for (int i = 0; i < graphColSize[node]; ++i) {  //遍历每一行
        int neighbor = graph[node][i]; 
        if (color[neighbor] == 0) {
            if (!dfs(neighbor, cNei, color, graph, graphColSize)) {
                return false;
            }
        } else if (color[neighbor] != cNei) {
            return false;
        }
    }
    return true;
}

bool isBipartite(int** graph, int graphSize, int* graphColSize) {
    int* color = (int*)malloc(sizeof(int) * graphSize);
    memset(color, 0, sizeof(int) * graphSize);
    for (int i = 0; i < graphSize; ++i) {
        if (color[i] == 0) {
            if (!dfs(i, 1, color, graph, graphColSize)) {
                free(color);
                return false;
            }
        }
    }
    free(color);
    return true;
}

28 二叉树的最深度

2020-7-29

给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。

递归 深度优先搜索

如果我们知道了左子树和右子树的最大深度 l l l r r r,那么该二叉树的最大深度即为 m a x ( l , r ) + 1 max(l,r)+1 max(l,r)+1,而左子树和右子树的最大深度又可以以同样的方式进行计算。因此我们在计算当前二叉树的最大深度时,可以先递归计算出其左子树和右子树的最大深度,然后在 O ( 1 ) O(1) O(1)时间内计算出当前二叉树的最大深度,递归在访问到空节点时退出。

我的代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

int DFS(struct TreeNode* root,int depth){
    if(root==NULL)return depth;
    depth++;
    return fmax(DBS(root->left,depth),DBS(root->right,depth));
}
int maxDepth(struct TreeNode* root){
    if(root==NULL)return 0;
    int ans=0;
    ans=DFS(root,ans);
    return ans;

}

答案

int maxDepth(struct TreeNode *root) {
    if (root == NULL) return 0;
    return fmax(maxDepth(root->left), maxDepth(root->right)) + 1;
}

另外的题解

int maxDepth(struct TreeNode *root) {
    int leftDepth = 0;
    int rightDepth = 0;
    if (NULL == root)
        return 0;

    if (root->left)
        leftDepth = maxDepth(root->left);

    if (root->right)
        rightDepth = maxDepth(root->right);

    return 1 + ((leftDepth > rightDepth) ? leftDepth : rightDepth);
}

广度优先搜索

一个题解

//队列节点的的数据结构
typedef struct QueueNode {
    struct TreeNode *data;
    struct QueueNode *next;
} QueueNode_t;

//用链表表示队列的数据结构
typedef struct LinkQueue {
    int count;
    QueueNode_t *front;
    QueueNode_t *rear;
} LinkQueue_t;

int initQueue(LinkQueue_t *queue) {
    queue->front = queue->rear = malloc(sizeof(QueueNode_t));
    if (NULL == queue->front)
        return -1;
    queue->front->next = NULL;
    queue->count = 0;
    return 0;
}

//入队
int enqueue(LinkQueue_t *queue, struct TreeNode *data) {
    QueueNode_t *newNode = malloc(sizeof(QueueNode_t));
    if (NULL == newNode) {
        return -1;
    }

    newNode->data = data;
    newNode->next = NULL;
    queue->rear->next = newNode;
    queue->rear = newNode;
    queue->count++;

    return 0;
}
//出队
int dequeue(LinkQueue_t *queue, struct TreeNode **data) {
    if (queue->front == queue->rear) {
        return -1;
    }

    QueueNode_t *denode = queue->front->next;
    *data = denode->data;
    queue->front->next = denode->next;

    if (denode == queue->rear) {
        queue->rear = queue->front;
    }

    free(denode);
    queue->count--;
    return 0;
}
//删除队
void destroyQueue(LinkQueue_t *queue) {
    /*q->front 指向头node, queue->rear指向下一个节点,这里当临时指针用*/
    while (queue->front) {
        queue->rear = queue->front->next;
        free(queue->front);
        queue->front = queue->rear;
    }
}
//判断队是否为空
int emptyQueue(LinkQueue_t *queue) {
    return queue->front == queue->rear ? 1 : 0;
}

int maxDepth(struct TreeNode *root) {
    if (NULL == root)
        return 0;

    int max = 0, i = 0, cnt = 0;
    struct TreeNode *data = NULL;
    LinkQueue_t queue;
    initQueue(&queue);

    enqueue(&queue, root);  //根节点入队
    while (!emptyQueue(&queue)) {
        cnt = queue.count; //队列的长度 
        for (i = 0; i < cnt; i++) {   //当前层出队  下一层入队
            dequeue(&queue, &data);
            if (data->left) {
                enqueue(&queue, data->left);
            }
            if (data->right) {
                enqueue(&queue, data->right);
            }
        }
        max++;
    }
    destroyQueue(&queue);
    return max;
}

答案

struct QueNode {
    struct TreeNode *p;
    struct QueNode *next;
};

void init(struct QueNode **p, struct TreeNode *t) {
    (*p) = (struct QueNode *)malloc(sizeof(struct QueNode));
    (*p)->p = t;
    (*p)->next = NULL;
}

int maxDepth(struct TreeNode *root) {
    if (root == NULL) return 0;
    struct QueNode *left, *right;
    init(&left, root);
    right = left;
    int ans = 0, sz = 1, tmp = 0;
    while (left != NULL) {
        tmp = 0;
        while (sz > 0) {
            if (left->p->left != NULL) {
                init(&right->next, left->p->left);
                right = right->next;
                tmp++;
            }
            if (left->p->right != NULL) {
                init(&right->next, left->p->right);
                right = right->next;
                tmp++;
            }
            left = left->next;
            sz--;
        }
        sz += tmp;
        ans++;
    }
    return ans;
}

你可能感兴趣的:(LeetCode)