LeetCode刷题总结(C语言版)

编程总结

每每刷完一道题后,其思想和精妙之处没有地方记录,本篇博客用以记录刷题过程中的遇到的算法和技巧

001)两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的两个整数。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

typedef struct {
    int value; 
    int pos;
}num_data_t;

//use qsort to sort this nums array
int struct_cmp(const void *arr1, const void *arr2)
{
    num_data_t *arr1_tmp = (num_data_t *)arr1;
    num_data_t *arr2_tmp = (num_data_t *)arr2;
    return (arr1_tmp->value - arr2_tmp->value);
}


int* twoSum(int* nums, int numsSize, int target, int* returnSize){

    int *result = NULL;
    num_data_t *num_data = NULL;
    int left = 0;
    int right = 0;

    num_data = (num_data_t *)malloc(numsSize * sizeof(num_data_t));
    if(num_data == NULL) {
        return result;
    }

    for (int i = 0; i < numsSize; i++) {
        num_data[i].value = nums[i];
        num_data[i].pos   = i;
    }
    //0. due to need to return position in nums, so arrange a structure 

    //1. sort this nums array
    qsort(num_data, numsSize, sizeof(num_data_t), struct_cmp);

    left = 0; 
    right = numsSize - 1;
    //2. use two pointer, and move the left pointer if sums less than targets
    while (left < right) {
        if (num_data[left].value + num_data[right].value > target) {
            right--;
        }
        else if(num_data[left].value + num_data[right].value < target) {
            left++;
        }
        else if (num_data[left].value + num_data[right].value == target) {
            result = (int *)malloc(2 * sizeof(int));
            result[0] = num_data[left].pos;
            result[1] = num_data[right].pos;
            free(num_data);
            *returnSize = 2;
            return result;
        }
    }

    return result;
}

此题利用了qsort先排好顺序后,使用移动左右两个指针来进行查找,当前仅是找到一对满足条件的数组而已。算法思想在于,当和小于target时,移动left指针,当和大于target时,移动right指针,while循环条件是left < right(对撞指针)。

000)统计一个数中二进制表示0或者1出现的个数方法:
在这里插入图片描述
075)颜色分类法:
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色

本题由于是特殊 0 1 2 处理即可,可以不使用qsort来进行排序。具体做法是先使用桶排序对 0 1 2 的数进行计算,然后在nums上直接进行填数,时间复杂度是:O(n),需要遍历一次数组,空间复杂度为 O(K)级别,K为3(即需要一个桶来对整数0-2进行排序,本题使用count[2]这样一个数组)

void sortColors(int *nums, int numsSize){
	int count[3] = {0};
    int i = 0;
	int j = 0;

	//	借助桶排序,计算有多少个0/1/2
	for (i = 0; i < numsSize; i++) {
		if (nums[i] > 2) {
		  printf("inputs error");	
		}
		count[nums[i]]++;
	}
	//将计算好后的0-1-2的个数,回填至nums数组
	for(i = 0; i < count[0]; i++) {
		nums[j] = 0;
		j++;
	}
	for(i = 0; i < count[1]; i++) {
		nums[j] = 1;
		j++;
	}
	for(i = 0; i < count[2]; i++) {
		nums[j] = 2;
		j++;
	}
}

283)移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

//法一:将数组非零值拷贝至一个新的数组,时间复杂度:O(n),空间复杂度:O(n)
//法二:[0.......k)为存放非零的位置, [k,numsSize]为存放0
void moveZeroes(int *nums, int numsSize) {

    int k = 0; //[0.......k)为存放非零的位置, [k,numsSize]为存放0
    int i = 0;

    //遍历一次数组,将非零值都移动到[0,k)里
    for (i = 0; i < numsSize; i++) {
        if (nums[i] != 0) {
            nums[k] = nums[i];
            k++;
        }
    }

    //将超过k值的,赋值为0,[k, numsSize] 为0
    for (i = k; i < numsSize; i++) {
        nums[i] = 0;
    }

    return ;
}

125)验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。

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

解题思路,利用对撞指针,不过由于需要处理字符,可以先处理好大写字母转换为小写后和空格后,再利用对撞指针处理即可。

580)连续子区间和
题目描述
给一串含有c个正整数的数组, 求出有多少个下标的连续区间, 它们的和大于等于x。

解答要求
时间限制:1000ms, 内存限制:64MB
输入
第一行两个整数c x(0 < c <= 1000000, 0 <= x <= 100000000)
第二行有c个正整数(每个正整数小于等于100)。

输出
输出一个整数,表示所求的个数。

样例
输入样例 1 复制

3 6
2 4 7
输出样例 1
4
本题是利用双指针来实现,思路清晰, 但是 index1 和 index2 都只需要遍历一次即可,不需要回退,只要涵盖了回退操作,都不是最优解。另外,注意如果是 输出数字,有可能需要输出%ld的情况。

printf("%ld", result);
unsigned long calcSum1(unsigned int *num, unsigned int n, unsigned int m)
{
    unsigned int  index1 = 0;
    unsigned int  index2 = 0;
    unsigned long sum   = num[0];
    unsigned long count   = 0;
    unsigned long result  = 0;

    // 最直接能想到的方法:遍历两次数组 O(n^2),但是其中有很多可以优化的地方
    for (index1 = 0; index1 <= n; index1++) {
        count = 0;
        count = num[index1];
        for (index2 = index1 + 1; index2 <= n; index2++) {
            if (count >= m) {
                result++;
            }            
            count = count + num[index2];
        }
    }

    return result;
}
unsigned long calcSum2(unsigned int *num, unsigned int n, unsigned int m)
{
    unsigned int  index1 = 0;
    unsigned int  index2 = 0;
    unsigned long sum   = num[0];
    unsigned long count   = 0;
    unsigned long result  = 0;

    for (index1 = 0; index1 <= n; index1++) {
        count = 0;
        count = num[index1];
        for (index2 = index1 + 1; index2 <= n; index2++) {
            if (count >= m) {
                // 优化1:如果找到了超过m的index2 由于数组都是正数,后续数可以不用再遍历,直接退出即可
                result = result + n - index2 + 1;
                break;
            }            
            count = count + num[index2];
        }
    }

    return result;
}
unsigned long calcSum3(unsigned int *num, unsigned long n, unsigned long m)
{
    unsigned long  index1 = 0;
    unsigned long  index2 = 1;
    unsigned long sum   = num[0];
    unsigned long count   = 0;
    unsigned long result  = 0;

    count = num[index1];
    for (index1 = 0; index1 <= n; index1++) {
        for (index2; index2 <= n; index2++) { // 优化2:index2 在找到 index1 超过m时不用回到 index1+1 的位置,继续往下走即可
            if (count >= m) {
                // 优化1:如果找到了超过m的index2 由于数组都是正数,后续数可以不用再遍历,直接退出即可
                result = result + n - index2 + 1;
                count = count - num[index1];
                break;
            }
            count = count + num[index2];
        }
    }

    return result;
}

本题是利用双指针来实现,思路清晰, 但是 index1 和 index2 都只需要遍历一次即可,不需要回退,只要涵盖了回退操作,都不是最优解。下面再给出另外一种思路,index1 和 index2 都只遍历了一次,复杂度为O(n)。

unsigned long calcSum4(unsigned int *num, unsigned long n, unsigned long m)
{
    unsigned long  index1 = 0;
    unsigned long  index2 = 0;
    unsigned long count   = 0;
    unsigned long result  = 0;
    count = num[index1];

    while(index2 < n && index1 < n) {
    	//count < m 时,那就加 num[index2]
        if (count < m || index1 > index2) {
            index2++;
            count = count + num[index2];
        } else { //count >= n 时,那就减 num[index1]
            result = result + n - index2;
            count = count - num[index1];
            index1++;
        }
    }

    return result;
}

512. Word Maze
题目描述
Word Maze 是一个网络小游戏,你需要找到以字母标注的食物,但要求以给定单词字母的顺序吃掉。假设给定单词if,你必须先吃掉i然后才能吃掉f。
但现在你的任务可没有这么简单,你现在处于一个迷宫Maze(n×m的矩阵)当中,里面到处都是以字母标注的食物,但你只能吃掉能连成给定单词W的食物。

注意区分英文字母大小写,并且你只能上下左右行走。

5 5
SOLO
CPUCY
EKLQH
CRSOL
EKLQO
PGRBC
输出样例 1

YES
此题解题思路是利用DFS
1)先右后下,遍历整个输入矩阵,若找到满足条件的字符,则标记该点走过
2)接着依次遍历该点的上下左右四个点,函数入口处,判断当前已遍历点数和 target相等,认为已遍历完成,可以走通,输出Yes
3)如果该点在遍历上下左右四个点,均未走通
4)基于该位置点,将标志清空,查找下一个点
难点是出现下面这种情况的处理:

3 3
SOL     //这一行是 target ,需要找到的目标串
SOB     //maze的第一行
ABL     //maze的第二行
CSO     //maze的第三行

1)第一个’S’,字符就已经匹配上了,将‘S’标记为已访问,进入以下上右左的方式查找’O’
2)在向右的遍历中发现了’O’,匹配上了,将‘O’标记为已访问,接着再以下上左右的方式去查找‘L’,发现没有找到
3)这时将’O’,访问标记删除掉,回到‘S’的递归层次来,刚才查找‘O’是通过右查找到的,接着进入左方式查找’O’,无法找到,至此,下上右左的查找都已完成,都没有找到,此时将‘S’访问标记删除,当前递归循环完成,返回结论是以当前’S’点无法找到子串,所以退出,下面开始从’O’,查找target字符串 ‘SOLO’。

350. 两个数组的交集 II

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

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

示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]

法一:
当输入数据是有序的,推荐使用此方法。在这里,我们对两个数组进行排序,并且使用两个指针在一次扫面找出公共的数字。

int cmp(const void *a,const void *b){//编写比较函数, a > b 返回 1 ;是默认的从小到大的排序方法 
	if( *(int*)a > *(int*)b) return 1;
    else if(*(int*)a < *(int*)b) return -1;
    else if(*(int*)a == *(int*)b) return 0;
    return 0;
}

int* intersect(int *nums1, int nums1Size, int *nums2, int nums2Size, int* returnSize){
    qsort(nums1,nums1Size,sizeof(nums1[0]),cmp);
    qsort(nums2,nums2Size,sizeof(nums2[0]),cmp);
    int t = 0;
    for (int p = 0,q = 0; ((p < nums1Size) && (q < nums2Size)) ;){
        if (nums1[p] == nums2[q]) {
            nums1[t] = nums1[p];
            t++;
            p++;
            q++;
        }
        else if (nums1[p]>nums2[q]){
            q++;
        }
        else {
            p++;
        }
    }
    *returnSize = t;
    int *temp = (int *)malloc(t*sizeof(int));
    if (!temp) {
        printf("fail to malloc !");
        return  NULL;
    }
    for (int j = 0; j<t; j++){
        temp[j] = nums1[j];
    }
    return temp;
}

法二:
思路:此题可以看成是一道传统的映射题(map映射),为什么可以这样看呢,因为我们需找出两个数组的交集元素,同时应与两个数组中出现的次数一致。这样就导致了我们需要知道每个值出现的次数,所以映射关系就成了<元素,出现次数>,所以我们可以首先统计数组1中所有元素的出现次数。然后再遍历数组2,如果数组2中的元素在map中存在(出现次数大于0),该元素就是一个交集元素,我们就将其存入返回数组中并且将map中该元素的出现次数减一即可.

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        vector<int>rec;
        unordered_map<int,int>map;        
        for(int i =0;i<nums1.size();i++)  //首先统计数组1中所有元素的出现次数
            map[nums1[i]]+=1;
        for(int i =0;i<nums2.size();i++)  //遍历数组2
           if(map[nums2[i]]>0)
           {
               rec.push_back(nums2[i]);
               map[nums2[i]]-=1;          //将map中该元素的出现次数减一即可
           }
        return rec;
    }
};

454. 四数相加 II
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。

输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]

输出:
2

解释:
两个元组如下:

  1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
#include <stdio.h>
/*
 *  使用hash算法,O(n^2)
 */
typedef struct hlist {
    int nkey, pkey;
    int nNum, pNum;
    struct hlist *next;
}*hnode;

hnode newHashNode(int key)
{
    hnode n = (hnode)calloc(1, sizeof(struct hlist));
    if (key < 0){
        n->nkey = key;
        n->pkey = 0 - key;
        n->nNum = 1;
    }else{
        n->pkey = key;
        n->nkey = 0 - key;
        n->pNum = 1;      
        if (key == 0)
            n->nNum = 1;
    }
    return n;    
}

//键值即取余
int hashKey(int s)
{
    if (s < 0)
        return (0 - s) % 10000;
    else
        return s % 10000;
}

int fourSumCount(int* A, int ASize, int* B, int BSize, int* C, int CSize, int* D, int DSize){
    int i,j,n = 0,s, key;
    hnode hash[10000] = {0}, prev, curr, new;
    
    //特殊情况处理
    if (ASize == 0 || BSize == 0 ||CSize == 0 || DSize == 0)
        return 0;
    
    //遍历AB和,将其加入到hash桶中,和为key,值为次数
    for (i = 0; i < ASize; i++)
    {
        for (j = 0; j < BSize; j++)
        {
            s = A[i] + B[j];
            key = hashKey(s);
            prev = NULL;
            curr = hash[key];
            while (curr)
            {
                if (curr->nkey == s) {
                    curr->nNum++;
                    break;
                }
                else if (curr->pkey == s) {
                    curr->pNum++;
                    break;
                }
                else {
                    prev = curr;
                    curr = curr->next;
                }
            }
            if (!curr) {
                new = newHashNode(s);
                if (prev)
                    prev->next = new;
                else
                    hash[key] = new;
            }                        
        }
    }
    //遍历CD和s,在hash表中查找-s,统计次数。
    for (i = 0; i < CSize; i++)
    {
        for (j = 0; j < DSize; j++)
        {
            s = C[i] + D[j];
            s = 0 - s;
            key = hashKey(s);
            curr = hash[key];
            while (curr)
            {
                if (curr->nkey == s){
                    n += curr->nNum;
                    break;
                } else if (curr->pkey == s){
                    n += curr->pNum;
                    break;
                } else
                    curr = curr->next;
            }
        }
    }
    return n;
}

447. 回旋镖的数量
给定平面上 n 对不同的点,寻找有多少个这些点构成的三元组,表示的元组 (i, j, k) ,其中 i 和 j 之间的距离和 i 和 k 之间的距离相等(需要考虑元组的顺序)。

找到所有回旋镖的数量。你可以假设 n 最大为 500,所有点的坐标在闭区间 [-10000, 10000] 中。

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

输出:
2

解释:
两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]

int numberOfBoomerangs(vector<pair<int, int>>& points) {
    int res = 0;
        
     //遍历所有的点,建立以该i点为枢纽点,其他点到该点的距离为hash值    
    for (int i = 0; i != points.size(); i++){
        unordered_map<int, int> hashmap;
        for (int j = 0; j != points.size(); j++){
            if (i != j)
                hashmap[dis(points[i], points[j])]++;
        }
        //遍历该i点,如果相同距离的点超过了两个,则符合条件,记录到res里
        for (auto it = hashmap.begin(); it != hashmap.end(); it++){
            if (it -> second >= 2) 
                res += (it -> second) * (it -> second - 1); //Anm 排列组合的选择
        }
    }
    
    return res;
}
//由于距离需要开平方,这里不做该处理,直接用也可以反映真实距离的大小,不存在浮点误差的问题
int dis(const pair<int, int> &p1, const pair<int, int> &p2){
    return (p1.first -p2.first) * (p1.first -p2.first) +
        (p1.second - p2.second) * (p1.second - p2.second);
}

动态规划 – 自下而上的解决问题;记忆化搜索,自顶向下的解决问题。
动态规划
– 动态规划–将原问题拆解成若干子问题,同时保存子问题的答案,使每个子问题只求解一次,最终获得原问题的答案。

70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶
    示例 2:
    输入: 3
    输出: 3
    解释: 有三种方法可以爬到楼顶。
  3. 1 阶 + 1 阶 + 1 阶
  4. 1 阶 + 2 阶
  5. 2 阶 + 1 阶
// 递归 
int climbStairs(int n){
    if (n <= 2) {
        return n;
	}

    return climbStairs(n - 1) + climbStairs(n - 2);
}
// 记忆化搜索 -- 自顶往下
int memo[64] = {0};
int climbStairs(int n) {
    if (n <= 2) {
        return n;
	}
	// 如果满足条件则直接返回记忆数组里的值,减少递归次数
	if (n < 64 && memo[n] != 0) {
		return memo[n];
	}
    // 不满足条件才进行递归 O(n)
	memo[n] = climbStairs(n-1) + climbStairs(n-2);
	
	return memo[n];
}
// 动态规划 -- 自下往上
int climbStairs(int n){
    int i, a1, a2, res;
    if (n <= 2) {
        return n;
	}
    a1 = 1;
    a2 = 2;
    res = 0;
	// 自下而上的进行计算,动态规划
    for (i = 3; i <= n; i++) {
        res = a1 + a2;
        a1  = a2;
        a2  = res;
    }
    return res;
}

343. 整数拆分

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

思路是:1)先思考自顶往下的记忆化搜索的方式借助常规的递归的思路,但是依靠记忆化数组,减少递归次数,就有了记忆化搜索的方法。
2)再思考动态规划的方式,自底向上的方式,由小至多累加上去,直接想动态递归的思路较难。

// 记忆化搜索 -- 自顶往下
int memo[64] = {0}; // memo[i]表示将i分割后可以获得的最大乘积
int integerBreak(int n) {
	int res = -1;

	if (n == 1 || n == 2) {  //memo[1] || memo[2] == 1
		return 1;
	}
	
	if (memo[n] != 0) {
		return memo[n];
	}
	// res  i*(n-i) i*integerBreak(n-i)
	//  1     1*2       1*1
	//  2     2*1       2*1
	for (int i = 1; i <= n-1; i++) { // memo[3] == 2
		res = max3(res, i*(n-i), i*integerBreak(n-i));
	}
	memo[n] = res;

	return res;
}
发布了19 篇原创文章 · 获赞 32 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/wangwangmoon_light/article/details/103810059