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

目录

  • 1 拥有最多糖果的孩子
  • 2 求1+2+…+n
    • 递归
    • 快速乘
  • 3 新21点
    • 递归 超时
    • 动态规划
  • 4 除自身以外数组的乘积
  • 5 顺时针打印矩阵
  • 6 最长连续序列
    • 哈希
    • 并查集
  • 7 等式方程的可满足性
    • 并查集
  • 8 把数字翻译成字符串
    • 动态规划
  • 9 回文数
    • 转化为数组
    • 反转一半数字 厉害
  • 10 回文链表
    • 翻转链表+快慢指针
    • 翻转链表
  • 11 每日温度
    • 暴力超时
    • 暴力不超时
    • 单调栈
  • 12 四数之和
    • 双指针
  • 13 使用最小花费爬楼梯
    • 动态规划
  • 14 最长公共前缀
  • 15 二叉树的序列化与反序列化
    • 递归 前序遍历
    • 序列化 层序遍历 非递归 队列
  • 16 最长公共前缀
  • 17 最长公共前缀
    • 迭代 栈
  • 18 验证回文串
    • 双指针
  • 19 二叉树中的最大路径和
    • 递归
  • 20 二进制加法
    • 模拟加法
    • 位运算
  • 21 最接近的三数之和
    • 双指针
  • 22 缺失的第一个正数
    • 变相哈希
    • 置换
  • 23 长度最小的子数组
    • 暴力
    • 双指针
    • 前缀和+二分查找
  • 24 数组中的第K个最大元素
    • 暴力冒泡排序
    • 暴力快排超时
    • 快速选择排序
    • 快速排序随机化版本
    • 堆排序
  • 25 用两个栈实现队列
  • 基础知识
    • sprintf
    • strcat 字符串连接

1 拥有最多糖果的孩子

2020-6-1

给你一个数组 candies 和一个整数 extraCandies ,其中 candies[i] 代表第 i 个孩子拥有的糖果数目。对每一个孩子,检查是否存在一种方案,将额外的 extraCandies 个糖果分配给孩子们之后,此孩子有 最多 的糖果。注意,允许有多个孩子同时拥有 最多 的糖果数目。

示例 1:
输入:candies = [2,3,5,1,3], extraCandies = 3
输出:[true,true,true,false,true]
解释:
孩子 1 有 2 个糖果,如果他得到所有额外的糖果(3个),那么他总共有 5 个糖果,他将成为拥有最多糖果的孩子。
孩子 2 有 3 个糖果,如果他得到至少 2 个额外糖果,那么他将成为拥有最多糖果的孩子。
孩子 3 有 5 个糖果,他已经是拥有最多糖果的孩子。
孩子 4 有 1 个糖果,即使他得到所有额外的糖果,他也只有 4 个糖果,无法成为拥有糖果最多的孩子。
孩子 5 有 3 个糖果,如果他得到至少 2 个额外糖果,那么他将成为拥有最多糖果的孩子。

示例 2:
输入:candies = [4,2,1,1,2], extraCandies = 1
输出:[true,false,false,false,false]
解释:只有 1 个额外糖果,所以不管额外糖果给谁,只有孩子 1 可以成为拥有糖果最多的孩子。

示例 3:
输入:candies = [12,1,12], extraCandies = 10
输出:[true,false,true]

提示:

2 <= candies.length <= 100
1 <= candies[i] <= 100
1 <= extraCandies <= 50

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
bool* kidsWithCandies(int* candies, int candiesSize, int extraCandies, int* returnSize){

    bool* ans=(bool*)malloc(candiesSize*sizeof(bool));
    *returnSize=0;
    int max=0;
    for(int i=0;i<candiesSize;i++){
        if(candies[i]>max)max=candies[i];
    }
    for(int i=0;i<candiesSize;i++){
        if(candies[i]+extraCandies>=max)ans[*returnSize]=true;
        else ans[*returnSize]=false;
        (*returnSize)++;
    }
    return ans;
}

儿童节快乐☺ ♪ ☑

2 求1+2+…+n

2020-6-2

求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)

示例 :
输入: n = 9
输出: 45

限制:
1 <= n <= 10000

可以使用的工具:加减法,赋值,位运算符以及逻辑运算符

递归

int sumNums(int n){
    if(n==0)return 0;
    return n+sumNums(n-1);
}

被限制的方向:递归的条件判断

逻辑运算符的短路性质

以逻辑运算符 & & \&\& && 为例,对于 A & & B A\&\&B A&&B 这个表达式,如果 A 表达式返回 F a l s e False False,那么 A & & B A \&\& B A&&B 已经确定为 F a l s e False False ,此时不会去执行表达式 B。同理,对于逻辑运算符 ||, 对于 A || B 这个表达式,如果 A 表达式返回 T r u e True True ,那么 A || B 已经确定为 T r u e True True ,此时不会去执行表达式 B。

int sumNums(int n){
    n&&(n+=sumNums(n-1));
    return n;
}

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

函数指针+两次反运算

typedef unsigned int (*fun)(unsigned int);
unsigned int Teminator(unsigned int n){
    return 0;
}

int sumNums(int n){
    static fun f[2]={Teminator,sumNums};
    return n+f[!!n](n-1);  //连续做两次反运算,非零转换为true,0转换为false

}

快速乘

使用加法和移位运算
乘以2的幂
遍历二进制展开

俄罗斯农民乘法

int quickMulti(int A, int B) {
    int ans = 0;
    for ( ; B; B >>= 1) {
        if (B & 1) {
            ans += A;
        }
        A <<= 1;
    }
    return ans;
}

手动展开 1414 层代替循环

int sumNums(int n){

    int ans = 0, A = n, B = n + 1;

    (B & 1) && (ans += A);
    A <<= 1;  //右移一位 乘2
    B >>= 1;  //左移一位 除2

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    (B & 1) && (ans += A);
    A <<= 1;
    B >>= 1;

    return ans >> 1;
}

回顾

  • 递归函数的空间复杂度取决于递归调用栈的深度

3 新21点

2020-6-3

爱丽丝参与一个大致基于纸牌游戏 “21点” 规则的游戏,描述如下:
爱丽丝以 0 分开始,并在她的得分少于 K 分时抽取数字。 抽取时,她从 [1, W] 的范围中随机获得一个整数作为分数进行累计,其中 W 是整数。 每次抽取都是独立的,其结果具有相同的概率。当爱丽丝获得不少于 K 分时,她就停止抽取数字。 爱丽丝的分数不超过 N 的概率是多少?

示例 1:
输入:N = 10, K = 1, W = 10
输出:1.00000

示例 2:
输入:N = 6, K = 1, W = 10
输出:0.60000

示例 3:
输入:N = 21, K = 17, W = 10
输出:0.73278

提示:
0 <= K <= N <= 10000
1 <= W <= 10000
如果答案与正确答案的误差不超过 10^-5,则该答案将被视为正确答案通过。

21点:荷官发牌,你的目的是拿到手上的牌总点数尽可能大。不高于某个值(K)的时候你可以一直叫牌,但万一牌的总点数超过某个值(N)你就爆牌了,此局判负。

那么本题就是在求赢的概率

递归 超时

double new21Game(int N, int K, int W){
    double result = 0;
    if(K == 0) {
        return 1.0;
    }
    for(int i=1; i<W+1; i++) {
        if(i < K) {
            double sub = new21Game(N-i, K-i, W);
            result += (1/(double)W) * sub;
        } else {
            if(i <= N) {
                result += 1/(double)W;
            }
        }
    }
    return result;
}

动态规划

d p [ x ] dp[x] dp[x]表示从得分为 x x x 的情况开始游戏并且获胜的概率,目标是求 d p [ 0 ] dp[0] dp[0] 的值
状态转移方程:
d p [ x ] = d p [ x + 1 ] + d p [ x + 2 ] + ⋯ + d p [ x + W ] W dp[x]=\frac{dp[x+1]+dp[x+2]+⋯+dp[x+W]}{W} dp[x]=Wdp[x+1]+dp[x+2]++dp[x+W]

停止抽牌时,最大牌面为 K + W − 1 K+W-1 K+W1

double new21Game(int N, int K, int W){
    double s=0.0;
    double *dp=(double*)malloc((K+W)*sizeof(double));

    for(int i = K;i < K + W;i++){
        if(i <= N){
            dp[i]=1;
        }else{
            dp[i]=0;
        }  
        s=s+dp[i];   
    }
    for(int i = K-1;i >= 0;i--){
        dp[i]=s/W;
        s = s-dp[i + W]+dp[i];
    }
    return dp[0];
}

时间复杂度: O ( min ⁡ ( N , K + W ) ) O(\min(N, K+W)) O(min(N,K+W))
空间复杂度: O ( K + W ) O(K+W) O(K+W)

4 除自身以外数组的乘积

2020-6-4

给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

示例:
输入: [1,2,3,4]
输出: [24,12,8,6]

提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法

当前数左边乘积*当前数右边乘积

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* productExceptSelf(int* nums, int numsSize, int* returnSize){
    *returnSize=0;
    int *L=(int *)malloc(numsSize*sizeof(int));
    int *R=(int *)malloc(numsSize*sizeof(int));
    int *ans=(int *)malloc(numsSize*sizeof(int));
    L[0]=1;
    R[numsSize-1]=1;
    for(int i=1;i<numsSize;i++){
        L[i]=L[i-1]*nums[i-1];
    }
    for(int i=numsSize-2;i>=0;i--){
        R[i]=R[i+1]*nums[i+1];
    }
    for(int i=0;i<numsSize;i++){
        ans[i]=L[i]*R[i];
        (*returnSize)++;
    }
    return ans;
}

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* productExceptSelf(int* nums, int numsSize, int* returnSize){
    *returnSize=0;
    int *L=(int *)malloc(numsSize*sizeof(int));
    L[0]=1;
    int R=1;
    for(int i=1;i<numsSize;i++){
        L[i]=L[i-1]*nums[i-1];
    }
    for(int i=numsSize-1;i>=0;i--){   
        L[i]=L[i]*R;
        R=R*nums[i];
        (*returnSize)++;
    }
    return L;
}

5 顺时针打印矩阵

2020-6-5

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

示例 :
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

限制:
0 <= matrix.length <= 100
0 <= matrix[i].length <= 100

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* spiralOrder(int** matrix, int matrixSize, int* matrixColSize, int* returnSize){
    
    int m=matrixSize;   //行
    *returnSize=0;
    if(m==0)return NULL;
    int n=matrixColSize[0];   //列
    int *ans=(int *)malloc((m*n)*sizeof(int));
 
    int top=0,bottom=m-1,left=0,right=n-1;
    while(left<=right&&top<=bottom){
        for (int column = left; column <= right; column++) {  //上
            ans[(*returnSize)++] = matrix[top][column];
        }
        for (int row = top + 1; row <= bottom; row++) {   //右
            ans[(*returnSize)++] = matrix[row][right];
        }
        if (left < right && top < bottom) {   
            for (int column = right - 1; column > left; column--) {   //下
                ans[(*returnSize)++]= matrix[bottom][column];
            }
            for (int row = bottom; row > top; row--) {   //左
                ans[(*returnSize)++] = matrix[row][left];
            }
        }
        left++;
        right--;
        top++;
        bottom--;
    }
    return ans;
}

时间复杂度: O ( m n ) O(mn) O(mn)
空间复杂度: O ( 1 ) O(1) O(1)

6 最长连续序列

2020-6-6

给定一个未排序的整数数组,找出最长连续序列的长,要求算法的时间复杂度为 O(n)。

示例:
输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。

哈希

  1. 用哈希表查找,查找的时间复杂度为 O ( 1 ) O(1) O(1)
  2. 链表哈希,修改插入函数使之不插入重复数字
  3. 哈希表直接存入数组中的数字
  4. 为减少枚举,加入跳过判断条件,即:对于当前数 x x x,若 x − 1 x-1 x1不存在,则此 x x x为子序列的第一个数,进入循环,进行对 x + k x+k x+k的判断。由此,数组中的每个数只会进入内层循环一次,则整体的时间复杂度为 O ( n ) O(n) O(n)
struct hashNode {
    int key;
    struct hashNode *next;//冲突时,用指针连接
};

struct hashMap {
    int size;
    struct hashNode *data;
};

struct hashMap * hashCreat(int size)
{
    //新建一个哈希表
    struct hashMap * hashMapInfo;
    //为哈希表分配地址
    hashMapInfo = (struct hashMap *)malloc(sizeof(struct hashMap));
    //设置哈希表的大小
    hashMapInfo->size = size;
    //为哈希表的data部分分配地址
    hashMapInfo->data = (struct hashNode *)malloc(sizeof(struct hashNode) * size);
    //初始化哈希表每个哈希节点的对象
    for (int i = 0; i < size; i++) {
        hashMapInfo->data[i].key = INT_MIN;
        hashMapInfo->data[i].next = NULL;
    }
    return hashMapInfo;
}

//将键为key的节点放入到哈希表中
void Put(int key, struct hashMap * hashMapInfo)
{
    //计算节点的位置
    int pos = abs(key) % hashMapInfo->size;
    //定义哈希节点,其值为 key所在位置的数据的链表地址
    struct hashNode *data = &hashMapInfo->data[pos];
    //定义一个新的哈希节点,为空
    struct hashNode *newNode = NULL;

    if (data->key == INT_MIN) {  //链表未占用
        data->key = key;
        return;
    }
    
    while (data != NULL) {//while循环要考虑当前节点,不能从data->next开始
        if (data->key == key) { //当前链表已存在该key,直接返回不插入
            return;
        } 
        if (data->next == NULL) { //为后续添加Node做准备
            break;
        }
        data = data->next; //链表next节点
    }

    /* 添加节点 */
    newNode = (struct hashNode *)malloc(sizeof(struct hashNode));
    newNode->key = key;
    newNode->next = NULL;
    data->next = newNode;

    return;
}

//查找
bool Contain(long key, struct hashMap * hashMapInfo)
{
    //计算节点的位置
    int pos;
    if(key==INT_MIN)pos = abs(INT_MAX) % hashMapInfo->size;
    else pos = abs(key) % hashMapInfo->size;
    //定义哈希节点,其值为 key所在位置的数据的链表地址
    struct hashNode *data = &hashMapInfo->data[pos];

    if (data->key == INT_MIN) {  //链表未占用
        return false;
    }

    while (data != NULL) {//while循环要考虑当前节点,不能从data->next开始
        if (data->key == key) { //当前链表已存在该key
            return true;
        } 
        data = data->next; //链表next节点
    }
    return false;
}


int longestConsecutive(int* nums, int numsSize){
    struct hashMap *hashMapInfo;
    hashMapInfo=hashCreat(numsSize);
    for(int i=0;i < numsSize;i++) {
        //printf("TTTT\n");
        Put(nums[i], hashMapInfo);
    }

    int longestStreak = 0;

    for (int i=0;i<numsSize;i++) {
        if (!Contain(nums[i]-1,hashMapInfo)) { //前一个数不存在,那我此数即为序列第一个
            int currentNum = nums[i];
            int currentStreak = 1;

            while (Contain(currentNum + 1,hashMapInfo)) {
                currentNum += 1;
                currentStreak += 1;
            }
            longestStreak = fmax(longestStreak, currentStreak);
        }
    }

    return longestStreak;
}

并查集

https://leetcode-cn.com/problems/longest-consecutive-sequence/solution/bing-cha-ji-ting-qi-lai-nan-shi-ji-yi-dian-du-bu-n/

下次一定

回顾

  1. 使用了空指针
Line 35: Char 37: runtime error: member access within null pointer of type 'struct hashMap' (solution.c)
  1. 取绝对值越界
Line 72: Char 15: runtime error: negation of -2147483648 cannot be represented in type 'int'; cast to an unsigned type to negate this value to itself (solution.c)

7 等式方程的可满足性

2020-6-8

给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:“a==b” 或 “a!=b”。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false。

示例 1:
输入:[“a==b”,“b!=a”]
输出:false
解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。没有办法分配变量同时满足这两个方程。

示例 2:
输出:[“b==a”,“a==b”]
输入:true
解释:我们可以指定 a = 1 且 b = 1 以满足满足这两个方程。

示例 3:
输入:[“a==b”,“b==c”,“a==c”]
输出:true

示例 4:
输入:[“a==b”,“b!=c”,“c==a”]
输出:false

示例 5:
输入:[“c==c”,“b==d”,“x!=z”]
输出:true

提示:
1 <= equations.length <= 500
equations[i].length == 4
equations[i][0] 和 equations[i][3] 是小写字母
equations[i][1] 要么是 ‘=’,要么是 ‘!’
equations[i][2] 是 ‘=’

并查集

并查集

#define MAXN 26
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 equationsPossible(char ** equations, int equationsSize){
    
    init(MAXN);

    for(int i=0;i<equationsSize;i++){
        if(equations[i][1]=='='){
            int index1=equations[i][0]-'a';
            int index2=equations[i][3]-'a';
            merge(index1,index2);
        }
    }

    for(int i=0;i<equationsSize;i++){
        if(equations[i][1]=='!'){
            int index1=equations[i][0]-'a';
            int index2=equations[i][3]-'a';
            if (find(index1) == find(index2)) {
                return false;
            }
        }
    }
    
    return true;
}

8 把数字翻译成字符串

2020-6-9

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", “bwfi”, “bczi”, “mcfi"和"mzi”

提示:
0 < = n u m < 2 31 0 <= num < 2^{31} 0<=num<231

动态规划

d p [ i ] dp[i] dp[i] x i x_i xi为开头的数字的翻译方案数量
d p [ i ] = d p [ i + 1 ] + g ( i , i + 1 ) ∗ d p [ i + 2 ] dp[i]=dp[i+1]+g(i,i+1)*dp[i+2] dp[i]=dp[i+1]+g(i,i+1)dp[i+2]

从后往前计算

int translateNum(int num){
    if(num<0)return 0;
    if(num==0)return 1;
    int count[11];  
    int n=0;  //位数

    //从低位到高位存储数字中每一位数
    int number[11];
    int temp=num,i=0;
    while(temp){
        int complement=temp%10;
        temp=temp/10;
        n++;
        number[i++]=complement;
    }
    //翻转,从高位到低位
    int Number[11];
    for(int i=0;i<n;i++){
        Number[i]=number[n-i-1];
    }

    if(n==1)return 1;  //个位数直接输出

    //计算后两位
    count[n-1]=1;
    int two=Number[n-2]*10+Number[n-1];
    if((two>=10)&&(two<=25))count[n-2]=2;
    else count[n-2]=1;

    for(int i=n-3;i>=0;i--){
        two=Number[i]*10+Number[i+1];
        int g;
        if((two>=10)&&(two<=25))g=1;
        else g=0;
        if(i<n-1){
            count[i]=count[i+1]+g*count[i+2];
        }    
    }
    return count[0];
}

d p [ i ] dp[i] dp[i] x i x_i xi为结尾的数字的翻译方案数量

d p [ i ] = d p [ i − 1 ] + g ( i , i − 1 ) ∗ d p [ i − 2 ] dp[i] = dp[i - 1] + g(i,i-1)*dp[i - 2] dp[i]=dp[i1]+g(i,i1)dp[i2]

初始状态: d p [ 0 ] = d p [ 1 ] = 1 dp[0] = dp[1] = 1 dp[0]=dp[1]=1

返回值: d p [ n ] dp[n] dp[n]

int translateNum(int num){
    if(num<0)return 0;
    if(num==0)return 1;
    int count[11];  
    int n=0;  //位数

    //从低位到高位存储数字中每一位数
    int number[11];
    int temp=num,i=0;
    while(temp){
        int complement=temp%10;
        temp=temp/10;
        n++;
        number[i++]=complement;
    }
    //翻转,从高位到低位
    int Number[11];
    for(int i=0;i<n;i++){
        Number[i]=number[n-i-1];
    }

    if(n==1)return 1;  //个位数直接输出

    count[0]=1;
    int two=Number[0]*10+Number[1];
    if((two>=10)&&(two<=25))count[1]=2;
    else count[1]=1;
    
    for(int i=2;i<n;i++){
        int two=Number[i]+Number[i-1]*10;
        int g;
        if((two>=10)&&(two<=25))g=1;
        else g=0;
        count[i]=count[i-1]+g*count[i-2];  
    }
    return count[n-1];
}

边取余边计算
d p [ i ] = d p [ i + 1 ] + g ( i , i + 1 ) ∗ d p [ i + 2 ] dp[i]=dp[i+1]+g(i,i+1)*dp[i+2] dp[i]=dp[i+1]+g(i,i+1)dp[i+2]

d p [ i ] dp[i] dp[i] x i x_i xi开头的数字的翻译方案数量,从后往前计算

int translateNum(int num){
    if(num<0)return 0;
    if(num==0)return 1;

    int dp0=1;  //dp[i]
    int dp1=1;  //dp[i+1]
    int dp2=1;   //dp[i+2]
 
    int n1;  //第i位
    int n2=num%10;  //第i+1位
    
    while(num){
        num=num/10;
        n1=num%10;
        int two=10*n1+n2;
        if((two>=10)&&(two<=25))dp0=dp1+dp2;
        else dp0=dp1;
        //向左移动
        dp2=dp1;
        dp1=dp0;
        n2=n1;  
    }
    return dp0;  
}

9 回文数

2020-6-10

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

示例 1:
输入: 121
输出: true

示例 2:
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。

转化为数组

bool isPalindrome(int x){
    if(x==0)return true;
    if(x<0)return false;
    int num[12],n=0,i=0;
    while(x){
        num[i]=x%10;
        x=x/10;
        n++;
        i++;
    }
    for(int j=0,k=n-1;j<=k;j++,k--){
        if(num[j]!=num[k])return false;
    }
    return true;
}

反转一半数字 厉害

bool isPalindrome(int x){
    if(x == 0)return true;
    if (x < 0 || (x % 10 == 0 && x != 0)) {
        return false;
    }

    int revertedNumber = 0;
    while (x > revertedNumber) {
        revertedNumber = revertedNumber * 10 + x % 10;
        x = x / 10;
    }

    // 当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。
    // 例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,revertedNumber = 123,
    // 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
    return x == revertedNumber || x == revertedNumber / 10;
}

10 回文链表

2020-6-10

请判断一个链表是否为回文链表,用 O(n) 时间复杂度和 O(1) 空间复杂度

翻转链表+快慢指针

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

bool isPalindrome(struct ListNode* head){
    if(!head || !head->next)
        return true;
    struct ListNode *fast = head, *slow = head;
    struct ListNode *p, *pre = NULL;
    while(fast && fast->next){
        p = slow;
        slow = slow->next;    //快慢遍历
        fast = fast->next->next;
        p->next = pre;  //翻转
        pre = p;
        //printf("pre=%d,p=%d,slow=%d,fast=%d\n",pre->val,p->val,slow->val,fast->val);
    }
    if(fast)  //奇数个节点时跳过中间节点
        slow = slow->next;

    while(p){       //前半部分和后半部分比较
        if(p->val != slow->val)
            return false;
        p = p->next;
        slow = slow->next;
    }
    return true;
}

翻转链表

void reverse(struct ListNode* head){
    struct ListNode *h=head;
    while(h!=NULL){
        printf("%d  ",h->val);
        h=h->next;
    }
    printf("\n");
    struct ListNode *temp = head;
    struct ListNode *p, *pre = NULL;
    while(temp!=NULL){
        if(pre!=NULL)printf("pre=%d ",pre->val);
        if(p!=NULL)printf("p=%d ",p->val);
        if(temp!=NULL)printf("temp=%d ",temp->val);
        printf("\n");
        p = temp;
        temp=temp->next;
        p->next = pre;  //翻转
        pre = p; 
        
    }
    while(p!=NULL){
        printf("%d  ",p->val);
        p=p->next;
    }
    while(pre!=NULL){
        printf("%d  ",pre->val);
        pre=pre->next;
    }
}

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

11 每日温度

2020-6-11

根据每日气温列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

暴力超时

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* dailyTemperatures(int* T, int TSize, int* returnSize){
    *returnSize=0;
    int *ans=(int *)malloc(TSize*sizeof(int));
    for(int i=0;i<TSize;i++){
        int num=0;
        for(int j=i+1;j<TSize;j++){
            if(T[j]>T[i]){
                num=j-i;
                break;
            }
        }
        ans[i]=num;
        (*returnSize)++;
    }
    return ans;
}

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)

暴力不超时

  • 反向遍历:从后往前依次将各个温度的下标存入next数组,对于每个温度,查找next数组中比他大的温度中角标最小的,相减即为结果
  • 因为是从后往前,所以查找的一定是当前温度后面的温度,√
  • 结果数组初始化为零,当没有找到比该温度大的温度,使用初始值
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* dailyTemperatures(int* T, int TSize, int* returnSize){
    *returnSize=0;
    
    int *ans=(int *)malloc(TSize*sizeof(int));
    for(int s=0;s<TSize;s++){
        ans[s]=0;
    }
    int next[101]={0};
    for(int i=0;i<101;i++){
        next[i]+=INT_MAX;
    }

    for (int i = TSize - 1; i >= 0; --i) {
        int warmerIndex = INT_MAX;
        for (int t = T[i] + 1; t <= 100; ++t) {
            warmerIndex = fmin(warmerIndex, next[t]);
        }
        if (warmerIndex != INT_MAX) {
            ans[i] = warmerIndex - i;
        }
        next[T[i]] = i;
        (*returnSize)++;
    }
    return ans;
}

时间复杂度: O ( n m ) O(nm) O(nm)
空间复杂度: O ( m ) O(m) O(m)

单调栈

  • 栈内存下标
  • 从栈底到栈顶的下标对应的温度列表中的温度依次递减
  • 栈为空或者当前元素小于栈顶元素,进栈
  • 当前元素大于栈顶元素,出栈,计算栈顶元素与当前元素之差即为所求,一直出栈知道栈顶元素大于当前元素或者栈为空,进栈
#define MaxSize 30000
typedef struct Stack{
    int top;
    int num[MaxSize];
}Stack;

Stack S;

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

void push(int a){
    S.top++;
    S.num[S.top]=a;
}

int pop(){
    if(S.top==0)return 0;
    S.top--;
    return S.num[S.top+1];
}

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

int empty(){
    if(S.top==0)return 1;
    else return 0;
}
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* dailyTemperatures(int* T, int TSize, int* returnSize){
    init();
    *returnSize=0;
    int *ans=(int *)malloc(TSize*sizeof(int));
    for(int s=0;s<TSize;s++){
        ans[s]=0;
    }
    for(int i=0;i<TSize;i++){
        while(!empty()&&T[Top()]<T[i]){
            int index=Top();
            ans[index]=i-index;
            pop();
        }
        push(i);
        (*returnSize)++;
    }
    return ans;
}

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

回顾

我初始化数组时像个人工智障 稍后再看

//数组初始化实在是头疼
    int *ans=(int *)malloc(TSize*sizeof(int));
    for(int s=0;s<TSize;s++){
        ans[s]=0;
    }
    int next[101]={0};
    for(int i=0;i<101;i++){
        next[i]+=INT_MAX;
    }

12 四数之和

2020-6-12

三数之和之前搞过了,整个类似的四数之和回忆一下:D

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等,找出所有满足条件且不重复的四元组。答案中不可以包含重复的四元组。

示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[[-1, 0, 0, 1],[-2, -1, 1, 2],[-2, 0, 0, 2]]

双指针

/**
 * 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 compInc(const void *a, const void *b)   //递增
{
    return *(int *)a - *(int *)b;
}
int** fourSum(int* nums, int numsSize, int target, int* returnSize, int** returnColumnSizes){
    *returnSize = 0;   // 参数returnSize用来作为二维数组行数下标的指针
    if (numsSize < 4)
    {
        return NULL;
    }
    qsort(nums, numsSize, sizeof(int), compInc);
    int maxSize=numsSize*10;
    int** returnArray = (int**)malloc(sizeof(int*) * maxSize);
    *returnColumnSizes = (int*)malloc(sizeof(int) * maxSize);

    for(int i=0;i<numsSize-3;i++){
        for(int j=i+1;j<numsSize-2;j++){          
            int L=j+1,R=numsSize-1;
            while(L<R){
                int sum=nums[i]+nums[j]+nums[L]+nums[R];
                if(sum==target){
                    returnArray[*returnSize] = (int*)malloc(sizeof(int) * 4); 
                    (*returnColumnSizes)[*returnSize] = 4;
                    returnArray[*returnSize][0] = nums[i];
                    returnArray[*returnSize][1] = nums[j];
                    returnArray[*returnSize][2] = nums[L];
                    returnArray[*returnSize][3] = nums[R];
                    (*returnSize)++;
                    while (nums[L + 1] == nums[L] &&(L+1)< R)
                    {
                        L++;
                    }
                    while (nums[R - 1] == nums[R] && L < (R-1))
                    {
                        R--;
                    }
                    L++;
                    R--;
                }
                else if(sum<target){
                    L++;
                }
                else if(sum>target){
                    R--;
                }
            }
            while(nums[j]==nums[j+1]&&j<numsSize-2){
            j++;
        }
        }
        while(nums[i]==nums[i+1]&&i<numsSize-3){
            i++;
        }
    }
    return returnArray;
}

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

13 使用最小花费爬楼梯

2020-6-13

爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] dp[i]=dp[i-1]+dp[i-2] dp[i]=dp[i1]+dp[i2]

int climbStairs(int n){
    if(n==1)return 1;
    if(n==2)return 2;
    int dp1=1,dp2=2,dp=0;
    for(int i=3;i<=n;i++){
        //printf("dp1= %d dp2= %d dp= %d\n",dp1,dp2,dp);
        dp=dp1+dp2;
        dp1=dp2;
        dp2=dp;
    }
    return dp;
}

使用最小花费爬楼梯

数组的每个索引作为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 c o s t [ i ] cost[i] cost[i](索引从0开始)。每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。

示例 1:
输入: cost = [10, 15, 20]
输出: 15
解释: 最低花费是从cost[1]开始,然后走两步即可到阶梯顶,一共花费15。

示例 2:
输入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出: 6
解释: 最低花费方式是从cost[0]开始,逐个经过那些1,跳过cost[3],一共花费6。

注意:
cost 的长度将会在 [2, 1000]。
每一个 cost[i] 将会是一个Integer类型,范围为 [0, 999]。

动态规划

d p [ i ] = c o s t [ i ] + m i n ( d p [ i + 1 ] , d p [ i + 2 ] ) dp[i] = cost[i] + min(dp[i+1], dp[i+2]) dp[i]=cost[i]+min(dp[i+1],dp[i+2])

d p [ i ] dp[i] dp[i]表示从第 i i i个阶梯开始爬并经过第 i i i个阶梯,需要的最小花费

int minCostClimbingStairs(int* cost, int costSize){
    int dp1 = 0, dp2 = 0;
    for (int i = costSize - 1; i >= 0; --i) {
        int dp0 = cost[i] + fmin(dp1, dp2);
        dp2 = dp1;
        dp1 = dp0;
    }
    return fmin(dp1, dp2);
}

d p [ i ] = c o s t [ i ] + m i n ( d p [ i − 1 ] , d p [ i − 2 ] ) dp[i] = cost[i] + min( dp[i-1] , dp[i-2] ) dp[i]=cost[i]+min(dp[i1],dp[i2])

d p [ i ] dp[i] dp[i]表示第 i i i 级阶梯的总花费 = 第 i i i 级的 c o s t cost cost + 前两级阶梯的总花费的较小者

//dp[i] = cost[i] + min(dp[i-1], dp[i-2])
int minCostClimbingStairs(int* cost, int costSize){
    int dp1 = 0, dp2 = 0;
    for (int i = 0; i < costSize; i++) {
        int dp0 = cost[i] + fmin(dp1, dp2);
        dp2 = dp1;
        dp1 = dp0;
    }
    return fmin(dp1, dp2);
}

d p [ n ] = m i n ( d p [ n − 1 ] + c o s t [ n − 1 ] , d p [ n − 2 ] + c o s t [ n − 2 ] ) dp[n] = min(dp[n-1] + cost[n-1], dp[n-2] + cost[n-2]) dp[n]=min(dp[n1]+cost[n1],dp[n2]+cost[n2])

初始可以选择第0或第1个阶梯,所以 d p [ 0 ] = 0 , d p [ 1 ] = 0 dp[0]=0,dp[1]=0 dp[0]=0dp[1]=0,题目就是要求出 d p [ c o s t S i z e ] dp[costSize] dp[costSize]

int minCostClimbingStairs(int* cost, int costSize){
    int *dp=(int *)malloc((costSize+1)*sizeof(int));
    dp[0]=0;
    dp[1]=0;
    for (int i = 2; i < costSize+1; i++) {
        dp[i]= fmin(dp[i-2]+ cost[i-2], dp[i-1] + cost[i-1]);
    }
    return dp[costSize];
}

14 最长公共前缀

2020-6-15

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。

示例 1:
输入: [“flower”,“flow”,“flight”]
输出: “fl”

示例 2:
输入: [“dog”,“racecar”,“car”]
输出: “”
解释: 输入不存在公共前缀。

char * longestCommonPrefix(char ** strs, int strsSize){
    
    int n=10000;
    for(int i=0;i<strsSize;i++){
        if(n>strlen(strs[i]))n=strlen(strs[i]);
    }
    char *ans=(char *)malloc((n+1)*sizeof(char));
    int s=0;
    if(strsSize==0)
    {
        ans[s]='\0';
        return ans;
    }
    for(int j=0;j<n;j++){
        for(int k=0;k<strsSize-1;k++){
            if(strs[k][j]!=strs[k+1][j]){
                ans[s]='\0';
                return ans;
            }
        }
        ans[s]=strs[0][j];
        s++;
    }
    ans[s]='\0';
    return ans;
}

官方题解给了四种方法:纵向、横向、分治、二分,但从解题角度来讲,纵向就挺好,又快。还有一个字典树,下次一定。

15 二叉树的序列化与反序列化

2020-6-16

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

  • 节点数值之间用逗号隔开

递归 前序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
/** Encodes a tree to a single string. */
#define MAX_SIZE 100000
#define STR_SIZE 10

void pre_order(struct TreeNode *root, char * data){  //前序遍历
    if(root == NULL){
        strcat(data, "#");
        strcat(data, ",");
        return;
    }
    char tmp[STR_SIZE] = "";
    sprintf(tmp, "%d", root->val); // 把结果输出到字符串tmp中
    strcat(data, tmp);
    strcat(data, ",");
    pre_order(root->left, data);
    pre_order(root->right, data);
}

char* serialize(struct TreeNode* root) {
    char * ans = malloc(sizeof(char) * MAX_SIZE);
    memset(ans, '\0', sizeof(char) * MAX_SIZE);
    pre_order(root, ans);
    return ans;  
}

struct TreeNode * createTree(char *data, int *index){
    if(data[*index] == '#'){
        (*index)++; // ‘#’
        (*index)++; // ‘,’
        return NULL;
    }
    struct TreeNode * root = malloc(sizeof(struct TreeNode));
    char tmp[STR_SIZE] = "";
    int k = 0;
    while(data[*index] != ','){
        tmp[k++] = data[*index];
        (*index)++;
    }
    (*index)++; // 因为此时data[*index] = ','
    root->val = atoi(tmp); // 获取数值
    root->left = createTree(data, index); // 递归左子树
    root->right = createTree(data, index); // 递归右子树
    return root;
}

/** Decodes your encoded data to tree. */
struct TreeNode* deserialize(char* data) {
    int index = 0; // index表示数组下标,不需要回溯
    return createTree(data, &index);  
}

// Your functions will be called as such:
// char* data = serialize(root);
// deserialize(data);

序列化 层序遍历 非递归 队列

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

#define MaxSize 100000
typedef struct queue {
    struct TreeNode* data[MaxSize];
    int front;
    int rear;
}Queue;


void initilize(Queue *Q) { 
    Q->front = 0;
    Q->rear = 0;
}

void Push(struct TreeNode* root,Queue *Q) { 
    Q->data[++Q->rear] = root;
}

struct TreeNode* Pop(Queue *Q) { 
    return Q->data[++Q->front];
}

struct TreeNode* Top(Queue *Q) { 
    return Q->data[Q->front];
}

int empty(Queue *Q) { 
    return Q->rear == Q->front;
}

char* serialize(struct TreeNode* root) {

    Queue * Q=(Queue*)malloc(sizeof(Queue));
    initilize(Q);
    struct TreeNode *cur= (struct TreeNode*) calloc (1, sizeof(struct TreeNode));
    char *ans=(char *)malloc(MaxSize*sizeof(char));
    ans[0]='\0';  为方便使用strcat
    char tmp[10] = "";

    if (root == NULL) return 0;  //空树

    Push(root,Q);
    sprintf(tmp, "%d", root->val);
    strcat(ans, tmp);
    strcat(ans, ",");

    while (!empty(Q)) {
        cur = Pop(Q);
        if(cur->left == NULL){
            strcat(ans, "*");
            strcat(ans, ",");
        }else{
            sprintf(tmp, "%d", cur->left->val);
            strcat(ans, tmp);
            strcat(ans, ",");
            Push(cur->left,Q);
        }
        if(cur->right == NULL){
            strcat(ans, "*");
            strcat(ans, ",");
        }else{
            sprintf(tmp, "%d", cur->right->val);
            strcat(ans, tmp);
            strcat(ans, ",");
            Push(cur->right,Q);
        } 
    }
    //输出转换后的序列
    int t=0;
    while(ans[t]!='\0'){
        printf("%c ",ans[t]);
        t++;
    }
    
    return ans;
}

16 最长公共前缀

2020-6-17

给定正整数数组 A A A A [ i ] A[i] A[i]表示第 i i i个观光景点的评分,并且两个景点$ i $和 j j j 之间的距离为 j − i j - i ji。一对景点 ( i < j ) (i < j) (i<j)组成的观光组合的得分为 ( A [ i ] + A [ j ] + i − j ) (A[i] + A[j] + i - j) (A[i]+A[j]+ij):景点的评分之和减去它们两者之间的距离。返回一对观光景点能取得的最高分。

示例:
输入:[8,1,5,2,6]
输出:11
解释:i = 0, j = 2, A[i] + A[j] + i - j = 8 + 5 + 0 - 2 = 11

提示:
2 <= A.length <= 50000
1 <= A[i] <= 1000

A [ i ] + A [ j ] + i − j → ( A [ i ] + i ) + ( A [ j ] − j ) A[i] + A[j] + i - j\rightarrow (A[i] + i)+(A[j] - j) A[i]+A[j]+ij(A[i]+i)+(A[j]j)

  • 次序问题, i i i一定在 j j j之前,则对 i i i的计算可以与 j j j同时进行
  • 对于每一个 A [ j ] − j A[j] - j A[j]j,其对应的 A [ i ] + i A[i] + i A[i]+i中的 i i i一定是在 j j j之前,因此可以在迭代 A [ j ] − j A[j] - j A[j]j的同时计算最大的 A [ i ] + i A[i] + i A[i]+i,可以保证对于每一个 A [ j ] − j A[j] - j A[j]j,最大的 A [ i ] + i A[i] + i A[i]+i都是在 j j j之前的数
  • 有点前缀和的意思
int maxScoreSightseeingPair(int* A, int ASize){
    int ans = 0, mx = A[0] + 0;
    for (int j = 1; j < ASize; ++j) {
        ans = fmax(ans, mx + A[j] - j);
        mx = fmax(mx, A[j] + j);
    }
    return ans;
}

17 最长公共前缀

2020-6-18

我们从二叉树的根节点 root 开始进行深度优先搜索。
在遍历中的每个节点处,我们输出 D 条短划线(其中 D 是该节点的深度),然后输出该节点的值。(如果节点的深度为 D,则其直接子节点的深度为 D + 1。根节点的深度为 0)。
如果节点只有一个子节点,那么保证该子节点为左子节点。
给出遍历输出 S,还原树并返回其根节点 root。

迭代 栈

  • 从前向后依次遍历
  • 计算当前节点的层数与数值
  • 当节点的层数等于当前栈的长度,则当前节点为栈顶元素的左孩子
  • 如果节点的层数不等于当前栈的长度,出栈直到等于,且当前节点为栈顶元素的右孩子
  • 每个节点都在最后入栈
  • 最后出栈直到只剩第一个元素,即为根节点,返回即可

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

循环 level value length pop action push stack
1 0 1 0 - - 1 1
2 1 2 1 - 1 − > l e f t = 2 1->left=2 1>left=2 2 1 2
3 2 3 2 - 2 − > l e f t = 3 2->left=3 2>left=3 3 1 2 3
4 2 4 3 3 2 − > r i g h t = 4 2->right=4 2>right=4 4 1 2 4
5 1 5 3 4 2 1 − > r i g h t = 5 1->right=5 1>right=5 5 1 5
6 2 6 2 - 5 − > l e f t = 6 5->left=6 5>left=6 6 1 5 6
7 2 7 1 6 5 − > r i g h t = 7 5->right=7 5>right=7 7 1 5 7
8 - - - 7 5 - - 1
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

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

Stack S;

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

void Push(struct TreeNode* root) { 
    S.top++;
    S.data[S.top]=root;
}

struct TreeNode* Pop() { 
    S.top--;
    return S.data[S.top+1];
}

struct TreeNode* Top() { 
    return S.data[S.top];
}

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

int Length(){
    return S.top;
}

struct TreeNode* recoverFromPreorder(char * S){
    int n=strlen(S);

    Initilize();
    for(int i=0;i<n;){
        int level=0;
        while(S[i]=='-'){  //计算每一个节点的深度
            i++;
            level++;
        }
       // printf("level: %d   ",level);
        int value=0;
        while(i<n&&isdigit(S[i])){  //计算节点的数字,需要逐位翻译
            value=value*10+(int)(S[i]-'0');
            i++;
        }
        //printf("value: %d   ",value);
        struct TreeNode* node=malloc(sizeof(struct TreeNode));
        node->val=value;
        node->left=NULL;
        node->right=NULL;
        //printf("length: %d  ",Length());
        if(level==Length()){
            if(!Empty()){
                Top()->left=node;
            }
        }else{
            while(level!=Length()){
                Pop();
            }
            Top()->right=node;
        }
        Push(node);
    }
    while(Length()>1){
        Pop();
    }
    return Top();
}

18 验证回文串

2020-6-19

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

示例 1:
输入: “A man, a plan, a canal: Panama”
输出: true

示例 2:
输入: “race a car”
输出: false

双指针

bool isPalindrome(char * s){
    int n=strlen(s);
    //printf("%d",n);
    if(n==0)return true;
    for(int i=0;i<n;i++){
        if(s[i]>='A'&&s[i]<='Z'){
            s[i]=s[i]+32;
            printf("%c ",s[i]);
        }
    }
    for(int left=0,right=n-1;left<right;left++,right--){
        //printf("left: %d,right: %d \n",left,right);
        while(!((s[left]>='0'&&s[left]<='9')||(s[left]>='a'&&s[left]<='z'))&&left<right){
            left++;
        }
        while(!((s[right]>='0'&&s[right]<='9')||(s[right]>='a'&&s[right]<='z'))&&left<right){
            right--;
        }
        if(s[left]!=s[right]){
            return false;
        }
    }
    return true;
}

特殊的测试用例

""     //空字符串
",.,"    //不含有数字字母的字符串

回顾

  • 官方题解说:“本题考查的是语言中常用字符(串)相关 API 的使用”,使用C语言的我留下不学无术的泪水
  • 回文的常见的3种做法:双指针,栈,reverse

19 二叉树中的最大路径和

2020-6-21

给定一个非空二叉树,返回其最大路径和。
本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。

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

递归

  • 只有当前节点
  • 当前节点+左节点
  • 当前节点+由节点
  • 当前节点+左节点+右节点
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

int maxGain(struct TreeNode *root,int *maxValue){
    if(root==NULL){
        return 0;
    }
    int leftGain=fmax(maxGain(root->left,maxValue),0);
    int rightGain=fmax(maxGain(root->right,maxValue),0);
    int curValue=root->val+leftGain+rightGain;
    //printf("curValue: %d maxValue: %d\n",curValue,*maxValue);
    *maxValue=fmax(*maxValue,curValue);
    return root->val+fmax(leftGain,rightGain);
}
int maxPathSum(struct TreeNode* root)
{
    int maxValue = INT_MIN; // 最小值
    maxGain(root, &maxValue);
    return maxValue;
}

20 二进制加法

2020-6-23

给你两个二进制字符串,返回它们的和(用二进制表示)。
输入为 非空 字符串且只包含数字 1 和 0。
示例 1:
输入: a = “11”, b = “1”
输出: “100”

示例 2:
输入: a = “1010”, b = “1011”
输出: “10101”

模拟加法

  • 翻转字符串
  • n = max ⁡ { ∣ a ∣ , ∣ b ∣ } n = \max\{ |a|, |b| \} n=max{a,b},循环 n n n 次,从最低位开始遍历。
  • c a r r y carry carry 表示上一个位置的进位,初始值为 0。
  • 记当前位置对其的两个位为 a i a_i ai b i b_i bi
  • 每一位的答案为 ( c a r r y + a i + b i )   m o d   2 ({\rm carry} + a_i + b_i) \bmod{2} (carry+ai+bi)mod2,下一位的进位为 ⌊ c a r r y + a i + b i 2 ] \lfloor \frac{{\rm carry} + a_i + b_i}{2}] 2carry+ai+bi]
  • 循环计算直到数字 a和 b 的每一位计算完毕
  • c a r r y carry carry 的不为0,则添加1到末尾。
  • 翻转字符串
//翻转字符串
void reserve(char* s) {
    int len = strlen(s);
    for (int i = 0; i < len / 2; i++) {
        char t = s[i];
        s[i] = s[len - i - 1], s[len - i - 1] = t;
    }
}

char* addBinary(char* a, char* b) {
    reserve(a);
    reserve(b);

    int len_a = strlen(a), len_b = strlen(b);
    int n = fmax(len_a, len_b), carry = 0, len = 0;
    char* ans = (char*)malloc(sizeof(char) * (n + 2));
    for (int i = 0; i < n; ++i) {
        carry += i < len_a ? (a[i] == '1') : 0;
        carry += i < len_b ? (b[i] == '1') : 0;
        ans[len++] = carry % 2 + '0';
        carry /= 2;
    }

    if (carry) {
        ans[len++] = '1';
    }
    ans[len] = '\0'; //重要操作
    reserve(ans);

    return ans;
}

位运算

  • ans = x ^ y:计算当前 x 和 y 的无进位相加结果
  • carry = (x & y) << 1:计算当前 x 和 y 的进位
  • 循环直到进位为零

半成品代码

void reserve(char* s) {
    int len = strlen(s);
    printf("len: %d ",len);
    for (int i = 0; i < len / 2; i++) {
        char t = s[i];
        s[i] = s[len - i - 1];
        s[len - i - 1] = t;
    }
}

char * addBinary(char * a, char * b){
    int n1=strlen(a);
    int n2=strlen(b);
    int A=0;
    int B=0;
    int ans,carry;

    //字符串转化为整数
    for(int i=0;i<n1;i++){
        A=A*10+a[ i ]-'0';
    }
    for(int i=0;i<n2;i++){
        B=B*10+b[ i ]-'0';
    }
    while(B){
        ans=(A ^ B);
        carry=(A & B) << 1;
        A=ans;
        B=carry;
    }

    //整数转化为字符串
    int i=0;
    char* Ans=malloc((fmax(n1,n2)+2)*sizeof(char));
    while(ans != 0){
       Ans[ i ]=ans % 10+'0';
       i++;
       ans=ans/10;
    }
    Ans[i]='\0'; //重要操作
    reserve(Ans);
    return Ans;
}

21 最接近的三数之和

2020-6-24

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

提示:
3 < = n u m s . l e n g t h < = 1 0 3 3 <= nums.length <= 10^3 3<=nums.length<=103
− 1 0 3 < = n u m s [ i ] < = 1 0 3 -10^3 <= nums[i] <= 10^3 103<=nums[i]<=103
− 1 0 4 < = t a r g e t < = 1 0 4 -10^4 <= target <= 10^4 104<=target<=104

双指针

与三数之和相同

int compInc(const void *a, const void *b)   //递增
{
    return *(int *)a - *(int *)b;
}
int threeSumClosest(int* nums, int numsSize, int target){
    int error=INT_MAX,ans;
    qsort(nums, numsSize, sizeof(int), compInc);
    for(int i=0;i<numsSize;i++){
        int L=i+1;
        int R=numsSize-1;
        while(L<R){
            int sum=nums[i]+nums[L]+nums[R];
            if(sum==target){
                return target;
            }
            if(fabs(sum-target)<error){
                error=fabs(sum-target);
                ans=sum;
            }
            if(sum<target){
                while(nums[L]==nums[L+1]&&(L+1)<R){
                    L++;
                }
                L++;
            }else{
                while(nums[R]==nums[R-1]&&L<(R-1)){
                    R--;
                }
                R--;
            }
        }
    }
    return ans;
}

22 缺失的第一个正数

2020-6-27

给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。

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

示例 2: 输入: [3,4,-1,1] 输出: 2

示例 3: 输入: [7,8,9,11,12] 输出: 1

提示:
你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。

关键思路
对于一个长度为 N N N 的数组,其中没有出现的最小正整数只能在 [ 1 , N + 1 ] [1, N+1] [1,N+1] 中。这是因为如果 [ 1 , N ] [1, N] [1,N] 都出现了,那么答案是 N + 1 N+1 N+1,否则答案是 [ 1 , N ] [1, N] [1,N] 中没有出现的最小正整数

变相哈希

  1. 数据处理,负数不予考虑,转换为一个较大的数,比如n+1
  2. 建立一个长度为N的哈希数组分别存放1到N
  3. 遍历原数组,若值小于等于N,则将哈希数组中的此值划掉(取负)
  4. 只取一次负
  5. 遍历哈希数组,出现的第一个没有被划掉的数即为所求,若所有的都被划掉,则结果为N+1

我的抄答案版本

int firstMissingPositive(int* nums, int numsSize){
    int* NUM=malloc(numsSize*sizeof(int));
    for(int i=0;i<numsSize;i++){
        NUM[i]=i+1;
    }
    for(int i=0;i<numsSize;i++){
        if(nums[i]<=0){
            nums[i]=numsSize+1;
        }
    }
    for(int j=0;j<numsSize;j++){
        if(nums[j]<=numsSize&&NUM[nums[j]-1]>0){
            NUM[nums[j]-1]=-NUM[nums[j]-1];
        }
    }
    for(int k=0;k<numsSize;k++){
        if(NUM[k]>0)return NUM[k];
    }
    return numsSize+1;
}

官方题解版本,不新建数组

  1. 利用角标的关系:数组中的数小于N时,将当前的数作为角标,此角标对应的数取负,则最后的结果数组中没有被取负的数对应的角标即为所求,都取负为N+1
  2. 在比较当前数与N的大小关系时需要先取绝对值,因为可能会在之前的操作中已经被取负
int firstMissingPositive(int* nums, int numsSize) {
    for (int i = 0; i < numsSize; ++i) {
        if (nums[i] <= 0) {
            nums[i] = numsSize + 1;
        }
    }
    for (int i = 0; i < numsSize; ++i) {
        int num = abs(nums[i]);
        if (num <= numsSize) {
            nums[num - 1] = -abs(nums[num - 1]);
        }
    }
    for (int i = 0; i < numsSize; ++i) {
        if (nums[i] > 0) {
            return i + 1;
        }
    }
    return numsSize + 1;
}

置换

  • 对数组进行一次遍历,对于遍历到的数 x = n u m s [ i ] x=nums[i] x=nums[i],如果 x ∈ [ 1 , N ] x∈[1,N] x[1,N],我们就知道 x x x 应当出现在数组中的 x − 1 x−1 x1 的位置,因此交换 n u m s [ i ] nums[i] nums[i] n u m s [ x − 1 ] nums[x−1] nums[x1],这样 x x x 就出现在了正确的位置。在完成交换后,新的 n u m s [ i ] nums[i] nums[i] 可能还在 [ 1 , N ] [1, N] [1,N] 的范围内,我们需要继续进行交换操作,直到 x ∉ [ 1 , N ] x \notin [1, N] x/[1,N]

  • 如果 n u m s [ i ] nums[i] nums[i] 恰好与 n u m s [ x − 1 ] nums[x−1] nums[x1] 相等,那么就会无限交换下去。此时我们有 n u m s [ i ] = x = n u m s [ x − 1 ] nums[i]=x=nums[x−1] nums[i]=x=nums[x1],说明 x x x 已经出现在了正确的位置。因此我们可以跳出循环,开始遍历下一个数。

  • 由于每次的交换操作都会使得某一个数交换到正确的位置,因此交换的次数最多为 N N N,整个方法的时间复杂度为 O ( N ) O(N) O(N)

int firstMissingPositive(int* nums, int numsSize) {
    for (int i = 0; i < numsSize; ++i) {
        while (nums[i] > 0 && nums[i] <= numsSize &&
               nums[nums[i] - 1] != nums[i]) {
            int t = nums[nums[i] - 1];
            nums[nums[i] - 1] = nums[i], nums[i] = t;
        }
    }
    for (int i = 0; i < numsSize; ++i) {
        if (nums[i] != i + 1) {
            return i + 1;
        }
    }
    return numsSize + 1;
}

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的连续子数组,返回 0。

示例:
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。

23 长度最小的子数组

2020-6-28

暴力

int minSubArrayLen(int s, int* nums, int numsSize){
    if(numsSize==0)return 0;
    int ans=INT_MAX;
    for(int i=0;i<numsSize;i++){
        int sum=0;
        for(int j=i;j<numsSize;j++){
            sum=sum+nums[j];
            int count=j-i+1;
            printf("sum= %d  count= %d\n",sum,count);
            if(sum>=s&&count<ans){
                ans=count;
                break;
            }
        }
    }
    if(ans==INT_MAX)ans=0;
    return ans;
}

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)

双指针

int minSubArrayLen(int s, int* nums, int numsSize) {
    if (numsSize == 0) {
        return 0;
    }
    int ans = INT_MAX;
    int L=0,R=0,sum=0;
    while(R<numsSize){
        sum+=nums[R];
        while(sum>=s){
            ans=fmin(ans,R-L+1);
            sum-=nums[L];
            L++;
        }
        R++;
    }
    return ans == INT_MAX ? 0 : ans;
}

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

前缀和+二分查找

  • 确定每个子数组的开始下标后,使用二分查找,找到长度最小的子数组需要 O ( l o g n ) O(logn) O(logn) 的时间
  • 数组中每个元素都为正,所以前缀和一定是递增的,这一点保证了二分的正确性
int lower_bound(int *a, int l, int r, int q) {
    if (a[r] < q) return -1;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (a[mid] >= q) {
            r = mid;
        } else {
            l = mid + 1;
        }
    }
    return l;
}
int minSubArrayLen(int s, int *nums, int numsSize) {
    if (numsSize == 0) {
        return 0;
    }
    int ans = INT_MAX;
    int *sums = (int *)malloc(sizeof(int) * (numsSize + 1));
    // 为了方便计算,令 size = n + 1
    // sums[0] = 0 意味着前 0 个元素的前缀和为 0
    // sums[1] = A[0] 前 1 个元素的前缀和为 A[0]
    // 以此类推
    for (int i = 1; i <= numsSize; i++) {
        sums[i] = sums[i - 1] + nums[i - 1];
    }
    for (int i = 1; i <= numsSize; i++) {
        int target = s + sums[i - 1];
        int bound = lower_bound(sums, 1, numsSize, target);
        if (bound != -1) {
            ans = fmin(ans, bound - (i - 1));
        }
    }
    return ans == INT_MAX ? 0 : ans;
}

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( n ) O(n) O(n)

24 数组中的第K个最大元素

2020-6-29

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

暴力冒泡排序

int findKthLargest(int* nums, int numsSize, int k){
    for(int i=numsSize-1;i>=numsSize-k;i--){
        for(int j=0;j<i;j++){
            if(nums[j]>nums[j+1]){
                int t=nums[j];
                nums[j]=nums[j+1];
                nums[j+1]=t;
            }
        }
    }
    return nums[numsSize-k];
}

暴力快排超时

//严奶奶版本快排
int Partition(int *A,int low,int high){
    int key=A[low];
    while(low < high){
        while(low < high && A[high] >= key){
            high--;
        }
        int t=A[low];
        A[low]=A[high];
        A[high]=t;
        while(low < high && A[low] <= key){
            low++;
        }
        t=A[low];
        A[low]=A[high];
        A[high]=t;
    }
    A[low]=key;
    return low;
}
void QSort(int* A,int low,int high){
    if(low<high){
        int piv=Partition(A,low,high);
        QSort(A,low,high-1);
        QSort(A,low+1,high);
    }
}

int findKthLargest(int* nums, int numsSize, int k){
    QSort(nums,0,numsSize-1);
    for(int i=0;i < numsSize;i++){
        printf("%d ",nums[i]);
    }
    return nums[numsSize-k];
}

快速选择排序

  • 对于快速排序,每一趟排序之后,作为枢轴的关键字就被交换到正确的位置k,在他左边是小于他的数,右边是大于他的数,此关键字就是第K大的数字
  • 因此可以在每一趟排序之后判断此关键字的角标k与K的大小关系,若k小于K,则快排右半部分,若k大于K,则快排左半部分
//算法导论版本快排
int Partition(int* a, int l, int r) {
    int x = a[r], i = l - 1;
    for (int j = l; j < r; ++j) {
        if (a[j] <= x) {
            int t = a[++i];
            a[i] = a[j], a[j] = t;
        }
    }
    int t = a[i + 1];
    a[i + 1] = a[r], a[r] = t;
    return i + 1;
}

int quickSelect(int* a, int l, int r, int index) {
    int q = Partition(a, l, r);
    if (q == index) {
        return a[q];
    } else {
        return q < index ? quickSelect(a, q + 1, r, index): quickSelect(a, l, q - 1, index);
    }
}

int findKthLargest(int* nums, int numsSize, int k) {
    return quickSelect(nums, 0, numsSize - 1, numsSize - k);
}

快速排序随机化版本

随机选择主元:随机选择一个元素与A[r]交换

inline int partition(int* a, int l, int r) {
    int x = a[r], i = l - 1;
    for (int j = l; j < r; ++j) {
        if (a[j] <= x) {
            int t = a[++i];
            a[i] = a[j], a[j] = t;
        }
    }
    int t = a[i + 1];
    a[i + 1] = a[r], a[r] = t;
    return i + 1;
}

inline int randomPartition(int* a, int l, int r) {
    int i = rand() % (r - l + 1) + l;
    int t = a[i];
    a[i] = a[r], a[r] = t;
    return partition(a, l, r);
}

int quickSelect(int* a, int l, int r, int index) {
    int q = randomPartition(a, l, r);
    if (q == index) {
        return a[q];
    } else {
        return q < index ? quickSelect(a, q + 1, r, index)
                         : quickSelect(a, l, q - 1, index);
    }
}

int findKthLargest(int* nums, int numsSize, int k) {
    srand(time(0));
    return quickSelect(nums, 0, numsSize - 1, numsSize - k);
}

堆排序

void maxHeapify(int* a, int i, int heapSize) {
    int l = i * 2 + 1, r = i * 2 + 2, largest = i;
    if (l < heapSize && a[l] > a[largest]) {
        largest = l;
    }
    if (r < heapSize && a[r] > a[largest]) {
        largest = r;
    }
    if (largest != i) {
        int t = a[i];
        a[i] = a[largest], a[largest] = t;
        maxHeapify(a, largest, heapSize);
    }
}

void buildMaxHeap(int* a, int heapSize) {
    for (int i = heapSize / 2; i >= 0; --i) {
        maxHeapify(a, i, heapSize);
    }
}

int findKthLargest(int* nums, int numsSize, int k) {
    int heapSize = numsSize;
    buildMaxHeap(nums, heapSize);
    for (int i = numsSize - 1; i >= numsSize - k + 1; --i) {
        int t = nums[0];
        nums[0] = nums[i], nums[i] = t;
        --heapSize;
        maxHeapify(nums, 0, heapSize);
    }
    return nums[0];
}
  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),建堆的时间代价是 O ( n ) O(n) O(n),删除的总代价是 O ( k log ⁡ n ) O(k \log n) O(klogn),因为 k < n k < n k<n,故渐进时间复杂为 O ( n + k log ⁡ n ) = O ( n log ⁡ n ) O(n + k \log n) = O(n \log n) O(n+klogn)=O(nlogn)
  • 空间复杂度: O ( log ⁡ n ) O(\log n) O(logn),即递归使用栈空间的空间代价。

25 用两个栈实现队列

2020-6-30

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

示例 1:
输入:
[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[3],[],[]]
输出:[null,null,3,-1]

示例 2:
输入:
[“CQueue”,“deleteHead”,“appendTail”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]

提示:
1 <= values <= 10000
最多会对 appendTail、deleteHead 进行 10000 次调用

#define MaxSize 10000

typedef struct {
    int data1[MaxSize];
    int data2[MaxSize];
    int top1;
    int top2;
} CQueue;


CQueue* cQueueCreate() {
    CQueue* obj=malloc(sizeof(CQueue));
    obj->top1=0;
    obj->top2=0;
    return obj;
}

void cQueueAppendTail(CQueue* obj, int value) {
    obj->top1++;
    obj->data1[obj->top1]=value;
}

int cQueueDeleteHead(CQueue* obj) {
    if(obj->top2!=0){
        obj->top2--;
        return obj->data2[obj->top2+1];
    }
    while(obj->top1!=0){
        //出栈
        int t=obj->data1[obj->top1];
        obj->top1--;
        //进栈
        obj->top2++;
        obj->data2[obj->top2]=t;
    }
    if(obj->top2==0)return -1;
    obj->top2--;
    return obj->data2[obj->top2+1];

}

void cQueueFree(CQueue* obj) {
	free(obj);
    obj = NULL;
}

/**
 * Your CQueue struct will be instantiated and called as such:
 * CQueue* obj = cQueueCreate();
 * cQueueAppendTail(obj, value);
 
 * int param_2 = cQueueDeleteHead(obj);
 
 * cQueueFree(obj);
*/

基础知识

sprintf

函数功能:格式化字符串,将格式化的数据写入字符串中。

函数原型:

int sprintf(char *buffer, const char *format, [argument]...)

// buffer:是char类型的指针,指向写入的字符串指针
// format:格式化字符串,即在程序中想要的格式
// argument:可选参数,可以为任意类型的数据

函数返回值:buffer指向的字符串的长度;

sprintf函数的使用

C语言中sprintf()函数的用法

strcat 字符串连接

头文件:

#include  
#include

声明:

char *strcat(char *dest, const char *src)

参数:

  • dest – 指向目标字符串,该数组包含了一个 C 字符串,且足够容纳追加后的字符串
  • src – 指向要追加的字符串,该字符串不会覆盖目标的字符串

功能:

把src所指字符串添加到dest结尾处(覆盖dest结尾处的’\0’)并添加’\0’

C字符串操作strcat/strcat_s详解

你可能感兴趣的:(2020年6月 leetcode每日一题 C语言版本)