LeetCode刷题计划--C语言

编程基础0到1–简单题

题目一: 1768. 交替合并字符串

题解

char* mergeAlternately(char* word1, char* word2) {
    int len1 = strlen(word1);
    int len2 = strlen(word2);

    int len = len1 + len2;
    char* result = malloc(len * sizeof(char) + 1); // 申请字符串空间

    int i = 0;
    while (i < len1 && i < len2) {
        result[i * 2] = word1[i];
        result[i * 2 + 1] = word2[i];
        i = i + 1;
    }

    if (len1 > i) {
        for (int j = 0; j < len1 - i; j++) {
            result[i * 2 + j] = word1[i + j];
        }
    }

    if (len2 > i) {
        for (int j = 0; j < len2 - i; j++) {
            result[i * 2 + j] = word2[i + j];
        }
    }

    result[len] = '\0'; // 注意'\0'是占位置的,result字符串以'\0'结尾
    return result;
}

题目二: 389. 找不同

题解

  • 方法一:计数。首先遍历字符串 s,对其中的每个字符都将计数值加1;然后遍历字符串t,对其中的每个字符都将计数值减1。当发现某个字符计数值为负数时,说明该字符在字符串 t中出现的次数大于在字符串 s中出现的次数,因此该字符为被添加的字符。
  • 方法二:求和。 将字符串 s 中每个字符的 ASCII 码的值求和,得到 As,对字符串t同样的方法得At。两者的差值At−As​即代表了被添加的字符。
  • 方法三:位运算。如果将两个字符串拼接成一个字符串,则问题转换成求字符串中出现奇数次的字符。
char findTheDifference(char* s, char* t) {
    
    int len1 = strlen(s);
    int len2 = strlen(t);

    int As = 0;
    int At = 0;

    for (int i = 0; i < len1; i++) {
        As += s[i];
    }

    for (int i = 0; i < len2; i++) {
        At += t[i];
    }

    return At - As;
}

题目三: 28. 找出字符串中第一个匹配项的下标

题解

  • 方法一: 暴力匹配。 我们可以让字符串 needle 与字符串 haystack 的所有长度为 mmm 的子串均匹配一次。
    为了减少不必要的匹配,我们每次匹配失败即立刻停止当前子串的匹配,对下一个子串继续匹配。如果当前子串匹配成功,我们返回当前子串的开始位置即可。如果所有子串都匹配失败,则返回−1。

  • 方法二:Knuth-Morris-Pratt 算法,简称KMP 算法

int strStr(char* haystack, char* needle) {
    int len1 = strlen(haystack);
    int len2 = strlen(needle);

    for (int i = 0; i + len2 <= len1; i++) {  //注意i + len2
        bool flag = true;
        for (int j = 0; j < len2; j++) {
            if (haystack[i + j] != needle[j]) {
                flag = false;
                break;
            }
        }
        if (flag) {
            return i;
        }
    }
    return -1;
}

题目四:242. 有效的字母异位词

题解:

  • 方法一:排序。t 是 s 的异位词等价于两个字符串排序后相等。因此我们可以对字符串 s 和 t分别排序,看排序后的字符串是否相等即可判断。此外,如果 s 和 t 的长度不同,t 必然不是 s 的异位词。
  • 方法二:哈希表。 从另一个角度考虑,t 是 s 的异位词等价于两个字符串中字符出现的种类和次数均相等。由于字符串只包含 26个小写字母,因此我们可以维护一个长度为 26 的频次数组 table,先遍历记录字符串 s 中字符出现的频次,然后遍历字符串 t,减去table 中对应的频次,如果出现 table[i]<0,则说明 t 包含一个不在 s 中的额外字符,返回 false 即可。
bool isAnagram(char* s, char* t) {
    int len1 = strlen(s);
    int len2 = strlen(t);

    if (len1 != len2) {
        return false;
    }

    int table[26] = {0};
    for (int i = 0; i < len1; i++) {
        table[s[i] - 'a']++;
    }

    for (int i = 0; i < len2; i++) {
        table[t[i] - 'a']--;
        if (table[t[i] - 'a'] < 0) { //在长度相等的情况下,若不互为字母异位词,一定会存在某个字母数量使table[i] < 0;若用table[i] > 0则无法判断;
            return false;
        }
    }

    return true;
}

题目五:459. 重复的子字符串

题解:
方法一:枚举
在这里插入图片描述

bool repeatedSubstringPattern(char* s) {
    int len = strlen(s);
    for (int i = 1; i * 2 <= len; i++) {
        if (len % i == 0) {  //条件一,i为分母,不能为0
            bool match = true;
            for (int j = i; j < len;j++) {
                if (s[j] != s[j - i]) {  //条件三,i相当于一个周期
                    match = false;
                    break;
                }
            }
            if (match) {
                return true;
            }
        }
    }
    return false;
}

方法二:字符串匹配
在这里插入图片描述

bool repeatedSubstringPattern(char* s) {
    int len = strlen(s);
    char k[2 * len + 1]; // 计算拼接后的字符数另外需再加1才够空间存放末尾的空字符
    k[0] = 0;
    strcat(k, s); // char *strcat(char *dest, const char *src) 把 src所指向的字符串追加到 dest 所指向的字符串的结尾。
    strcat(k, s); // 将两个 s 连在一起
    return strstr(k + 1, s) - k !=len; // strstr()函数搜索一个字符串在另一个字符串中的第一次出现,返回的是匹配成功的字符串以及后面的字符串
    // k + 1:k字符串舍去前面1个字符
}

疑问:strstr(k + 1, s) - k是什么用法?

题目六:283. 移动零

题解
方法一:双指针
使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。

右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。

注意到以下性质:

  • 左指针左边均为非零数;
  • 右指针左边直到左指针处均为零。

因此每次交换,都是将左指针的零与右指针的非零数交换,且非零数的相对顺序并未改变。

注意:函数传参不同,解决方法不同!

void swap(int *a,int *b){
    int t = *a;
    *a = *b;
    *b = t;
}
//很像冒泡哎,把0往后冒
void moveZeroes(int* nums, int numsSize) {
    int left = 0,right = 0;
    while(right < numsSize){
        if(nums[right] != 0){
            swap(nums + left,nums + right); //传的是地址
            left++;
        }
        right++;
    }
}
class Solution {
    public void moveZeroes(int[] nums) {
        int n = nums.length, left = 0, right = 0;
        while (right < n) {
            if (nums[right] != 0) {
                swap(nums, left, right);
                left++;
            }
            right++;
        }
    }

    public void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
}

题目七: 66. 加一

题解
当我们对数组 digits 加一时,我们只需要关注 digits 的末尾出现了多少个 9 即可。我们可以考虑如下的三种情况:

  • 如果 digits 的末尾没有 9,例如[1,2,3],那么我们直接将末尾的数加一,得到 [1,2,4] 并返回;
  • 如果 digits 的末尾有若干个9,例如 [1,2,3,9,9],那么我们只需要找出从末尾开始的第一个不为 9的元素,即3,将该元素加一,得到 [1,2,4,9,9]。随后将末尾的 9 全部置零,得到[1,2,4,0,0] 并返回。
  • 如果 digits 的所有元素都是9,例如[9,9,9,9,9],那么答案为 [1,0,0,0,0,0]。我们只需要构造一个长度比digits 多 1 的新数组,将首元素置为 1,其余元素置为 0 即可。

只需要对数组 digits 进行一次逆序遍历,找出第一个不为 9 的元素,将其加一并将后续所有元素置零即可。如果 digits 中所有的元素均为 9,那么对应着「思路」部分的第三种情况,我们需要返回一个新的数组。

int* plusOne(int* digits, int digitsSize, int* returnSize) {
    //包括了情况1,情况2,情况3的置0部分
    for(int i = digitsSize - 1;i >= 0;i--){//倒着进行检查
        if(digits[i] != 9){
            digits[i]++;
            break;
        }else{
            digits[i] = 0;
        }
    } 

    if(digits[0] == 0){//当数字全为9时
        int *temp;
        int size = digitsSize + 1;
        temp = (int *)malloc(size * sizeof(int));

        temp[0] = 1;
        for(int i = 1;i < size;i++){
            temp[i] = 0;
        }

        *returnSize = size;
        return temp;
    }

    *returnSize = digitsSize;
    return digits;
}

题目八:1822. 数组元素积的符号

题解
如果数组中有一个元素 0,那么所有元素值的乘积肯定为 0,我们直接返回 0。使用 sign 记录元素值乘积的符号,1为表示正,−1 表示为负,初始时 sign=1。遍历整个数组,如果元素为正,那么 sign 不变,否则令 sign=−sign,最后返回 sign。
’卖家秀‘

int arraySign(int* nums, int numsSize) {
    int sign = 1;
    for (int i = 0; i < numsSize; i++) {
        if (nums[i] == 0) {
            return 0;
        }
        if (nums[i] < 0) {
            sign = -sign;
        }
    }
    return sign;
}

‘买家秀’

int arraySign(int* nums, int numsSize) {
    int product = 1;
    for(int i = 0;i < numsSize;i++){
        if(nums[i] < 0){
            product *= -1;
        }else if(nums[i] > 0){
            product *= 1;
        }else{
            product = 0;
        }
    }
    if(product < 0){
        return -1;
    }else if(product > 0){
        return 1;
    }else{
        return 0;
    }
}

题目九:1502. 判断能否形成等差数列

题解
首先我们对原序列排序,假设排序之后序列为 {a0,a1,⋯an},如果对 i∈[1,n−1]中的每个数都有 a[i] × 2=a[i - 1] + a[i + 1]​成立,那么这个数列就是等差数列。
小小qsort()函数,还未拿捏!

int cmp(const void *a,const void *b){
    return *(int *)a - *(int *)b;
}

bool canMakeArithmeticProgression(int* arr, int arrSize) {
    //等差数列最少三项
    if(arrSize <= 2){
        return true;
    }
    //快速排序
    qsort(arr,arrSize,sizeof(int),cmp);
    //判断是否符合等差数列特征
    for(int i = 1;i < arrSize - 1;i++){
        if((arr[i] - arr[i - 1]) !=  (arr[i + 1] - arr[i])){
            return false;
        }
    }
    return true;
}

题目十:896. 单调数列

题解
方法一:两次遍历。遍历两次数组,分别判断其是否为单调递增或单调递减。
**方法二:一次遍历。**遍历数组 nums,若既遇到了 nums[i]>nums[i + 1] 又遇到了 nums[i ′]<nums[i ′ +1],则说明 nums 既不是单调递增的,也不是单调递减的。
卖家秀

bool isMonotonic(int* nums, int numsSize) {
    bool in = false,de = false;
    for (int i = 0; i < numsSize - 1; i++) {
        if (nums[i] > nums[i + 1]) {
            de = true;
        }
        if (nums[i] < nums[i + 1]) {
            in = true;
        }
    }
    if(de == true && in == true){
        return false;
    }
    return true;
}

买家秀
解题思路存在问题,总是在连续存在相同值时判断失误,没有抓住单调数列的核心特征。

bool isMonotonic(int* nums, int numsSize) {
    int flag = 0;
    int b = 1;
    if (nums[0] > nums[1]) {
        flag = -1; // 通过a1,a2初步判断,数列为递减
    } else if(nums[0] < nums[1]){
        flag = 1; // 通过a1,a2初步判断,数列为递增
    }else{
        flag = 0;
    }
    for (int i = 1; i < numsSize - 1; i++) {
        if (nums[i] >= nums[i + 1] && flag != 1) {
            continue; // 通过a1,a2初步判断,数列为递减
        } else if (nums[i] <= nums[i + 1] && flag != -1) {
            continue;
        }else{
            return false;
        }
    }
    return true;
}

题目十一:13. 罗马数字转整数

题解
方法一:模拟。
通常情况下,罗马数字中小的数字在大的数字的右边。若输入的字符串满足该情况,那么可以将每个字符视作一个单独的值,累加每个字符对应的数值即可。
例如 XXVII 可视作X+X+V+I+I=10+10+5+1+1=27。

若存在小的数字在大的数字的左边的情况,根据规则需要减去小的数字。对于这种情况,我们也可以将每个字符视作一个单独的值,若一个数字右侧的数字比它大,则将该数字的符号取反。例如 XIV 可视作 X−I+V=10−1+5=14。
卖家秀

int romanToInt(char* s) {
    int symbolValues[26];
    symbolValues['I' - 'A'] = 1;
    symbolValues['V' - 'A'] = 5;
    symbolValues['X' - 'A'] = 10;
    symbolValues['L' - 'A'] = 50;
    symbolValues['C' - 'A'] = 100;
    symbolValues['D' - 'A'] = 500;
    symbolValues['M' - 'A'] = 1000;
    //以上处理妙不可言
    int ans = 0;
    int n = strlen(s);
    for (int i = 0; i < n; ++i) {
        int value = symbolValues[s[i] - 'A'];
        if (i < n - 1 && value < symbolValues[s[i + 1] - 'A']) {//i < n - 1 防止越界
            ans -= value;
        } else {
            ans += value;
        }
    }
    return ans;
}

买家秀

int romanToInt(char* s) {
    int len = strlen(s);
    int sum = 0;
    for (int i = 0; i < len; i++) {
        if (s[i] == 'I' && (s[i + 1] == 'I' || s[i + 1] == '\0')) {
            sum = sum + 1;
        }else if (s[i] == 'V' && (s[i + 1] == 'I' || s[i + 1] == 'V' || s[i + 1] == '\0')) {
            sum = sum + 5;
        }else if (s[i] == 'X' && (s[i + 1] != 'L' && s[i + 1] != 'C' &&
                            s[i + 1] != 'D' && s[i + 1] != 'M' || s[i + 1] == '\0')) {
            sum = sum + 10;
            
        }else if (s[i] == 'L' &&
            (s[i + 1] != 'C' && s[i + 1] != 'D' && s[i + 1] != 'M'|| s[i + 1] == '\0')) {
            sum = sum + 50;
            
        }else if (s[i] == 'C' && (s[i + 1] != 'D' && s[i + 1] != 'M' || s[i + 1] == '\0')) {
            sum = sum + 100;
            
        }else if (s[i] == 'D' && (s[i + 1] != 'M' || s[i + 1] == '\0')) {
            sum = sum + 500;
            
        }else if (s[i] == 'M') {
            sum = sum + 1000;    
        }else if(s[i] == 'I'){
            sum = sum - 1;
        }else if(s[i] == 'V'){
            sum = sum - 5;
        }else if(s[i] == 'X'){
            sum = sum - 10;
        }else if(s[i] == 'L'){
            sum = sum - 50;
        }else if(s[i] == 'C'){
            sum = sum - 100;
        }else if(s[i] == 'D'){
            sum = sum - 500;
        }else if(s[i] == 'M'){
            sum = sum - 1000;
        }
        printf("%d ",sum);
    }
    return sum;
}

题目十二:58. 最后一个单词的长度

题解

int lengthOfLastWord(char* s) {
    int len = strlen(s);
    int j = 0;
    for(int i = len - 1;i >= 0;i--){
        if(s[i] != ' '){
            j++;
        }else if(j == 0){
            continue;
        }else{
            break;
        }
    }
    return j;
}

注意:此题若只要求返回最后一个单词的长度比较容易。现在思考一下,若是也要返回最后一个单词呢?

int lengthOfLastWord(char* s) {
    int len = strlen(s);
    char word[10];  //用于存储最后一个单词
    int j = 0;
    for(int i = len - 1;i >= 0;i--){
        if(s[i] != ' '){
            word[j] = s[i];
            j++;
        }else if(j == 0){
            continue;
        }else{
            break;
        }
    }
    for(int i = j - 1;i >= 0;i--){
        printf("%c",word[i]);
    }
    return j;
}

题目十三:709. 转换成小写字母

题解
方法一: 使用语言 API
我们可以使用语言自带的大写字母转小写字母的 API。
方法二:自行实现该 API
我们可以想到的最简单的方法是使用一个哈希映射,哈希映射中包含 26 个键值对 (A,a),(B,b),⋯,(Z,z)。对于每个待转换的字符 ch,如果它出现在是哈希映射中(即 ch 是哈希映射中的一个键),那么 ch 是大写字母,我们获取 ch 在哈希映射中的值即可得到对应的小写字母;如果它没有出现在哈希映射中,那么 ch 是其它字符,我们无需进行转换。

然而这种方法需要一定量的辅助空间,不够简洁。一种更好的方法是观察小写字母和大写字母的 ASCII 码表示:
大写字母 A - Z 的 ASCII 码范围为 [65,90]:
小写字母 a - z 的 ASCII 码范围为[97,122]。

因此,如果我们发现 ch 的 ASCII 码在 [65,90] 的范围内,那么我们将它的 ASCII 码增加 32,即可得到对应的小写字母。

近而我们可以发现,由于 [65,90] 对应的二进制表示为 [(01000001) 2,(01011010) 2 ],32 对应的二进制表示为 (00100000) 2,而对于 [(01000001) 2,(01011010) 2] 内的所有数,表示 32 的那个二进制位都是 0,因此可以对 ch 的 ASCII 码与 32 做按位或运算,替代与 32 的加法运算。

char * toLowerCase(char * s){
    for(int i = 0; i < strlen(s); i++){
        if(s[i] >= 'A' && s[i] <= 'Z'){
            s[i] = s[i] + 32; //s[i] |= 32;
        } 
    }
    return s;
}

题目十四:682. 棒球比赛

题解
本题难点在于如何将数字字符转变为数字,例如'4' 👉 4,进行计算?非也非也,无法保证ops[i] 之前的元素是数字。

int calPoints(char ** ops, int opsSize){
    int ret = 0;
    int *points = (int *)malloc(sizeof(int) * opsSize);//申请栈
    int pos = 0;
    for (int i = 0; i < opsSize; i++) {
        switch (ops[i][0]) { //ops数组有opsSize个元素,第i + 1个元素,第1个元素,一直是0为本题特点
            case '+':
                ret += points[pos - 1] + points[pos - 2]; //规则
                points[pos++] = points[pos - 1] + points[pos - 2]; //本轮成绩入栈
                break;
            case 'D':
                ret += 2 * points[pos - 1];
                points[pos++] = 2 * points[pos - 1];
                break;
            case 'C':
                ret -= points[pos - 1];
                pos--;
                break;
            default:
                ret += atoi(ops[i]); //atoi()函数将字符串里的数字字符转化为整形数
                points[pos++] = atoi(ops[i]);
                break;
        }
    }
    free(points);
    return ret;
}

题目十五:657. 机器人能否返回原点

题解
So easy! 没有一点弯弯绕绕

bool judgeCircle(char* moves) {
    int len = strlen(moves);
    int x = 0;
    int y = 0;
    for(int i = 0;i < len;i++){
        if(moves[i] == 'U'){
            y = y + 1;
        }else if(moves[i] == 'D'){
            y = y - 1;
        }else if(moves[i] == 'L'){
            x = x - 1;
        }else if(moves[i] == 'R'){
            x = x + 1;
        }
    }
    if(x == 0 && y == 0){
        return true;
    }else{
        return false;
    }
}

题目十六:1275. 找出井字棋的获胜者

题解
int** moves VS moves[i][j]?

char* tictactoe(int** moves, int movesSize, int* movesColSize) {
    int temp[3][3] = {0};
    for (int i = 0; i < movesSize; i++) {
        int x = moves[i][0];
        int y = moves[i][1];
        if (i % 2 == 0) {
            temp[x][y]++;
        } else {
            temp[x][y]--;
        }
    }
    int sum1 = temp[0][0] + temp[1][1] + temp[2][2];
    if (sum1 == 3) {
        return "A";
    } else if (sum1 == -3) {
        return "B";
    }
    //检查对角线
    int sum2 = temp[0][2] + temp[1][1] + temp[2][0];
    if (sum2 == 3) {
        return "A";
    } else if (sum2 == -3) {
        return "B";
    }
    //检查行
    for (int i = 0; i < 3; i++) {
        int sum3 = temp[i][0] + temp[i][1] + temp[i][2];
        if (sum3 == 3) {
            return "A";
        } else if (sum3 == -3) {
            return "B";
        }
    }
    //检查列
    for (int i = 0; i < 3; i++) {
        int sum4 = temp[0][i] + temp[1][i] + temp[2][i];
        if (sum4 == 3) {
            return "A";
        } else if (sum4 == -3) {
            return "B";
        }
    }
    //排除以上情况之后
    if (movesSize == 9) {
        return "Draw";
    }
    return "Pending";
}

题目十七:1672. 最富有客户的资产总量

题解
申请一个数组maxs[accountsSize],维护每个客户的总金额,并对数组进行初始化

int maximumWealth(int** accounts, int accountsSize, int* accountsColSize) {
    int maxs[accountsSize];
    int max = 0;
    memset(maxs,0, sizeof(maxs));//初始化数组
    for (int i = 0; i < accountsSize; i++) {
        for (int j = 0; j < accountsColSize[0]; j++) {
            maxs[i] += accounts[i][j];
        }
    }
    for (int i = 0; i < accountsSize; i++) {
        if (maxs[i] > max) {
            max = maxs[i];
        }
    }
    return max;
}

题目十七:1572. 矩阵对角线元素的和

题解

int diagonalSum(int** mat, int matSize, int* matColSize){
    int sum = 0;
    //主对角线
    for(int i = 0;i <  matSize;i++){
        sum += mat[i][i];
    }
    //副对角线
    for(int i = 0;i < matSize;i++){
        sum += mat[i][matSize - 1 - i];
    }
    //判断是否要去掉重复值
    if(matSize % 2 == 1){
        int x = (matSize - 1) / 2;
        sum = sum - mat[x][x];
    }
    return sum;
}

题目十八:1523. 在区间范围内统计奇数数目

题解
如果我们暴力枚举 [low,high] 中的所有元素会超出时间限制。

int countOdds(int low, int high){
    int count = 0;
    if(low % 2 == 1 && high % 2 == 1){
        count = (high - low) / 2 + 1; 
    }else if(low % 2 != 1 && high % 2 != 1){
        count = (high - low) /2;
    }else{
        count = (high - low + 1) / 2;
    }
    return count;
}

题目十九:1491. 去掉最低工资和最高工资后的工资平均值

题解

double average(int* salary, int salarySize) {
    double min = salary[0];
    double max = salary[0];
    double sum = 0;
    double avg = 0.0;
    for (int i = 0; i < salarySize; i++) {
        if (salary[i] < min) {
            min = salary[i];
        }
    }
    for (int i = 0; i < salarySize; i++) {
        if (salary[i] > max) {
            max = salary[i];
        }
    }
    for (int i = 0; i < salarySize; i++) {
        sum += salary[i];
    }
    avg = (sum - min - max) / (salarySize - 2);
    return avg;
}

题目二十:860. 柠檬水找零

题解
方法一:贪心
由于顾客只可能给你三个面值的钞票,而且我们一开始没有任何钞票,因此我们拥有的钞票面值只可能是 5 美元,10 美元和 20 美元三种。基于此,我们可以进行如下的分类讨论。

5 美元,由于柠檬水的价格也为 5 美元,因此我们直接收下即可。

10 美元,我们需要找回 5 美元,如果没有 5 美元面值的钞票,则无法正确找零。

20 美元,我们需要找回 15 美元,此时有两种组合方式,一种是一张 10 美元和 5 美元的钞票,一种是 3 张 5 美元的钞票,如果两种组合方式都没有,则无法正确找零。当可以正确找零时,两种找零的方式中我们更倾向于第一种,即如果存在 5 美元和 10 美元,我们就按第一种方式找零,否则按第二种方式找零,因为需要使用 5 美元的找零场景会比需要使用 10 美元的找零场景多,我们需要尽可能保留 5 美元的钞票。

基于此,我们维护两个变量 count 和 count1 表示当前手中拥有的 5 美元和 10 美元钞票的张数,从前往后遍历数组分类讨论即可。

bool lemonadeChange(int* bills, int billsSize) {
    int count = 0;//记录5元的个数
    int count1 = 0;//记录10元的个数
    for(int i = 0;i < billsSize;i++){
        if(bills[i] == 5){
            count++;
        }else if(bills[i] == 10 && count > 0){
            count--;
            count1++;
        }else if(bills[i] == 20 && count > 0 && count1 > 0){
            count--;
            count1--;
        }else if(bills[i] == 20 && count >= 3){
            count = count - 3;
        }else{
            return false;
        }
    }
    return true;
}

题目二十一:976. 三角形的最大周长

题解
方法一:排序+贪心
不失一般性,我们假设三角形的边长 a,b,c 满足 a≤b≤c,那么这三条边组成面积不为零的三角形的充分必要条件为 a+b>c。

基于此,我们可以选择枚举三角形的最长边 c,而从贪心的角度考虑,我们一定是选「小于 c 的最大的两个数」作为边长 a 和 b,此时最有可能满足 a+b>c,使得三条边能够组成一个三角形,且此时的三角形的周长是最大的。

因此,我们先对整个数组排序,倒序枚举第 iii 个数作为最长边,那么我们只要看其前两个数 A[i−2] 和 A[i−1],判断 A[i−2]+A[i−1]] 是否大于 A[i] 即可,如果能组成三角形我们就找到了最大周长的三角形,返回答案 A[i−2]+A[i−1]+A[i] 即可。如果对于任何数作为最长边都不存在面积不为零的三角形,则返回答案 0。

int cmp(const void *a,const void *b){
    return *(int *)a - *(int *)b;
}
int sum = 0;
int largestPerimeter(int* nums, int numsSize) {
    qsort(nums,numsSize,sizeof(int),cmp);
    for(int i = 0;i < numsSize;i++){
        printf("%d ",nums[i]);
    }
    for(int i = numsSize - 1;i > 1;i--){
        if(nums[i] < (nums[i - 1] + nums[i - 2]) && (nums[i] - nums[i - 2]) < nums[i - 1]){
            sum = nums[i] + nums[i - 1] + nums[i - 2];
            return sum;
        }
    }
    return 0;
}

题目二十二:1232. 缀点成线

题解
方法一:将直线分为三种情况考虑
斜率不一定是整数,需要用double表示;浮点数不能直接比较相等,需要比较差值;
另外浮点数取绝对值需要用fabs,abs是整数的绝对值函数。

bool checkStraightLine(int** coordinates, int coordinatesSize,
                       int* coordinatesColSize) {
    int x = 0;
    int y = 0;
    double k = 0;
    double b = 0.0;
    //垂直于y轴的直线
    if (coordinates[0][0] == coordinates[1][0]) {
        x = coordinates[0][0];
        for (int i = 1; i < coordinatesSize; i++) {
            if (coordinates[i][0] != x) {
                return false;
            }
        }
        return true;
    } else if (coordinates[0][1] == coordinates[1][1]) { //垂直于x轴的直线
        y = coordinates[0][1];
        for (int i = 1; i < coordinatesSize; i++) {
            if (coordinates[i][1] != y) {
                return false;
            }
        }
        return true;
    } else {  //满足y = kx + b形式的直线
        k = (double)(coordinates[1][1] - coordinates[0][1]) //必须加(double),否则精度不满足?
            (coordinates[1][0] - coordinates[0][0]);
        b = coordinates[0][1] - coordinates[0][0] * k;
        printf("%lf ,%lf ",k,b);
        for (int i = 2; i < coordinatesSize; i++) {
            if ((coordinates[i][0] * k + b) != coordinates[i][1]) {
                return false;
            }
        }
        return true;
    }
}

方法二:平移+直线函数
记数组 coordinates 中的点为 P0,P1,…,Pn−1。为方便后续计算,将所有点向 (−P0x,−P0y)方向平移。记平移后的点为 P0′,P1′,…,Pn−1′,其中

Pi′=(Pix−P0x,Piy−P0y)

位于坐标系原点 O 上。

由于经过两点的直线有且仅有一条,我们以 P0′P1′来确定这条直线。

因为 P0′位于坐标系原点 O 上,直线过原点,故设其方程为 Ax+By=0,将 P1′坐标代入可得 A=P1y′,B=−P1x′,B=−P1x′ .

然后依次判断 P2′,…,Pn−1′是否在这条直线上,将其坐标代入直线方程即可。

bool checkStraightLine(int** coordinates, int coordinatesSize, int* coordinatesColSize) {
    int deltaX = coordinates[0][0], deltaY = coordinates[0][1];
    //所有点全部移动,使得直线过原点,移动后的数组的第一个点为原点
    for (int i = 0; i < coordinatesSize; ++i) {
        coordinates[i][0] -= deltaX;
        coordinates[i][1] -= deltaY;
    }
    //将方程的形式设为Ax + By = 0;
    int A = coordinates[1][1], B = -coordinates[1][0];
    //检查coordinates[i](i > 1)的所有点是否符合方程
    for (int i = 2; i < coordinatesSize; ++i) {
        int x = coordinates[i][0], y = coordinates[i][1];
        if (A * x + B * y != 0) {
            return false;
        }
    }
    return true;
}

题目二十三:67. 二进制求和

题解
方法一:模拟
我们可以借鉴「列竖式」的方法,末尾对齐,逐位相加。在十进制的计算中「逢十进一」,二进制中我们需要「逢二进一」。

具体的,我们可以取 n=max⁡{∣a∣,∣b∣},循环 n 次,从最低位开始遍历。我们使用一个变量 carry 表示上一个位置的进位,初始值为 0。记当前位置对其的两个位为 ai和 bi,则每一位的答案为 (carry+ai+bi) mod 2,下一位的进位为 ⌊(carry+ai+bi)/ 2⌋。重复上述步骤,直到数字 a 和 b 的每一位计算完毕。最后如果 carry 的最高位不为 0,则将最高位添加到计算结果的末尾。

注意,为了让各个位置对齐,你可以先反转这个代表二进制数字的字符串,然后低下标对应低位,高下标对应高位。当然你也可以直接把 a 和 b 中短的那一个补 0 直到和长的那个一样长,然后从高位向低位遍历,对应位置的答案按照顺序存入答案字符串内,最终将答案串反转。这里的代码给出第一种的实现。

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);
    int flag = 0, len = 0;
    // 用来存答案,n + 2:可能会进一位
    char* ans = (char*)malloc(sizeof(char) * (n + 2));

    for (int i = 0; i < n; ++i) {
        int temp = 0;
        if (i < len_a && i < len_b) { //a,b字符串都没超过本身长度
            temp = (a[i] - '0') + (b[i] - '0') + flag;
            ans[i] = temp % 2 + '0';
            flag = temp / 2;
        } else if (i < len_a) { //b字符串超出本身长度
            temp = (a[i] - '0') + flag;
            ans[i] = temp % 2 + '0';
            flag = temp / 2;
        } else {  //a字符串超出本身长度
            temp = (b[i] - '0') + flag;
            ans[i] = temp % 2 + '0';
            flag = temp / 2;
        }
        len = i + 1; //记录字符串最后一位之后,ans的长度
    }

    if (flag) { //最后一位是'2'?
        ans[len++] = '1';
    }
    ans[len] = '\0'; //结尾
    reserve(ans);   //翻转答案

    return ans;
}

方法二位运算???
如果不允许使用加减乘除,则可以使用位运算替代上述运算中的一些加减乘除的操作。
我们可以设计这样的算法来计算:

  • 把 a 和 b 转换成整型数字 x 和 y,在接下来的过程中,x 保存结果,y 保存进位。
  • 当进位不为 0 时
    • 计算当前 x 和 y 的无进位相加结果:answer = x ^ y
    • 计算当前 x 和 y 的进位:carry = (x & y) << 1
    • 完成本次循环,更新 x = answery = carry 返回 x 的二进制形式

为什么这个方法是可行的呢?在第一轮计算中,answer 的最后一位是 x 和 y 相加之后的结果,carry 的倒数第二位是 x 和 y 最后一位相加的进位。接着每一轮中,由于 carry 是由 x 和 y 按位与并且左移得到的,那么最后会补零,所以在下面计算的过程中后面的数位不受影响,而每一轮都可以得到一个低 i 位的答案和它向低 i+1 位的进位,也就模拟了加法的过程。

题目二十四:21. 合并两个有序链表

题解


typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if (list1 == NULL) {
        return list2;
    }
    if (list2 == NULL) {
        return list1;
    }
    ListNode *p, *q;
    ListNode *result,*resulthead;
    resulthead = result  = (ListNode*)malloc(sizeof(ListNode));
    //resulthead记录头节点地址
    p = list1;
    q = list2;

    while (p && q) {
        if (p->val >= q->val) {
            result->next = q;
            result = result->next;
            q = q->next;
            
        } else {
            result->next = p;
            result = result->next;
            p = p->next;
        }
    }
    if(p){
        result->next = p;
    }
    if(q){
        result->next = q;
    }
    return resulthead->next;
}

题目二十五:206. 反转链表

题解
方法一:迭代
假设链表为 1→2→3→∅ ,我们想要把它改成 ∅←1←2←3

在遍历链表时,将当前节点的 next指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {

    ListNode *result,*p,*next;
    p = head;
    result = NULL;

    while (p) {
        next = p->next;//相当于一个temp
        p->next = result;
        result = p;//result指向了head头节点
        p = next;
    }

    return result;
}

方法二:递归

递归版本稍微复杂一些,其关键在于反向工作。假设链表的其余部分已经被反转,现在应该如何反转它前面的部分?

假设链表为:

n1→…→nk−1→nk→nk+1→…→nm→∅

若从节点 nk+1nm已经被反转,而我们正处于 nk

n1→…→nk−1→nk→nk+1←…←nm我们希望 nk+1的下一个节点指向 nk

所以,nk.next.next=nk

需要注意的是 n1的下一个节点必须指向 。如果忽略了这一点,链表中可能会产生环。

  • 10
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值