1.19 LeetCode总结(基本算法)_数学类

编程总结

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

13. 罗马数字转整数

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。

在这里插入图片描述

struct hash
{
	int interger;
	char roman;
};
// 哈希表
struct hash Hash[13] = {
	1000, 'M',
	500,  'D',
	100,  'C',
	50,   'L',
	10,   'X',
	5,    'V',
	1,    'I'
};
int charToInt(char c)
{
	for (int i = 0; i < 7; i++)
	{
		if (Hash[i].roman == c) {
			return Hash[i].interger;
		}
	}
	return 0;
}
int romanToInt(char * s)
{
	int len = strlen(s);
	int value = 0;
	int cur, next;
	for (int i = 0; i < len - 1; i++)  // 先计算 [0, len-1)
	{
		cur = charToInt(s[i]);
		next = charToInt(s[i + 1]);
		value = (cur < next) ? value - cur : value + cur;  // 小技巧,如果cur < next 则减掉cur
	}
	value += charToInt(s[len - 1]);    // 补上 len-1
	return value;
}

两种方法都使用哈希表来实现。

散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表

整数转罗马,整数可以根据更为详细的哈希表来转换,但罗马转数字不行,得挨个查找,因为,仅看字符无法知道是需要减的情况还是增的情况,所以上下都使用了哈希表,但其表是不一样的。

struct hash
{
    int interger;
    char roman[4];
}Hash[13] = {       // 这也是一种初始化的方法
    1000, "M",
    900,  "CM", 
    500,  "D", 
    400,  "CD",
    100,  "C",
    90,   "XC",
    50,   "L",
    40,   "XL",
    10,   "X",
    9,    "IX",
    5,    "V",
    4,    "IV",
    1,    "I",
};

char *intToRoman(int num) {
	char *cRoman = NULL;
	unsigned short uIndex = 0;
    cRoman = (char *)malloc(16 *sizeof(char));;
    memset(cRoman, 0, 16);

	for (uIndex = 0; uIndex <= (13 - 1); uIndex++)
	{
		while (num >= Hash[uIndex].interger)
		{
			num = num - Hash[uIndex].interger;
			strcat(cRoman, Hash[uIndex].roman); // 注意此时的Hash和之前的不一样,更为详细
		}
	}

	return cRoman;
}

注意下面直接分配栈空间地址回去在leetcode是不允许的,会报错,应该使用上面的 malloc 使用堆地址空间:

char *intToRoman(int num){
    char cRoman[16] = {0x0};   // 申请栈空间
    unsigned short uIndex = 0;

    for(uIndex = 0; uIndex <= (13-1); uIndex++)
    {
        while(num >= Hash[uIndex].interger)
        {
            num = num - Hash[uIndex].interger;
            strcat_s(cRoman, Hash[uIndex].roman); // 注意此时的Hash和之前的不一样,更为详细
        }
    }

    return cRoman;              // 返回栈空间
}

409. 最长回文串

给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。
在构造过程中,请注意区分大小写。比如 “Aa” 不能当做一个回文字符串。

#define G_LENGTH  57                     //  ASCII编码中,A~z共有57个字符
// 65~90为26个大写英文字母,97~122号为26个小写英文字母
int longestPalindrome(char *s)
{
    int g_string[G_LENGTH] = {0};
	int length = strlen(s);
	int value = 0;
	int ans   = 0;

	memset(g_string, 0, G_LENGTH);

	for(int i = 0; i < length; i++) {
		g_string[(s[i] - 'A') % 57]++;   // 哈希存储
	}

	for(int j = 0; j < G_LENGTH; j++) {
		if (g_string[j] !=0 ) {
			value = g_string[j];         // 每个字符出现的次数
			ans = ans + value/2 * 2;     // 左右都出现一次 v / 2
			if (value % 2 == 1 && ans % 2 == 0) { // 发现第一个出现次数为奇数加1,其他情况不加
				++ans;
			}
		}
	}

	return ans;
}

剑指 Offer 62. 圆圈中最后剩下的数字

  1. 0,1,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
    例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
// 递归
int lastRemaining(int n, int m){
    return f(n, m);
}
int f(int n, int m) 
{
    if (n == 1)
        return 0;
    int x = f(n - 1, m);
    return (m + x) % n;
}

// 另一种方法
int lastRemaining(int n, int m){
    if (n == 1) {  
        return 0;
	}
    int ans = 0;
    for (int i = 2; i <= n ;i++) {
        ans = (ans + m) % i;
    }
    
    return ans;
}

推导公式参考:https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-by-lee/

矩阵相乘:
https://blog.csdn.net/qq_41649694/article/details/81432663

136. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

int singleNumber(int *nums, int numsSize) 
{
	int i = 0; 
	int tmp = 0;
	// 任何数和 00 做异或运算,结果仍然是原来的数,即 a \oplus 0 = aa⊕0 = a
	// 任何数和其自身做异或运算,结果是 00,即 a \oplus a = 0a⊕a = 0
	// 异或运算满足交换律和结合律,即 a \oplus b \oplus a = b 
	for (i = 0; i < numsSize; i++) { // 数组中两两出现的数字都会异或成原来的数,剩下的0和单独出现数字异或得数字本身
		tmp = tmp ^ nums[i];
	}

	return tmp;
}

680. 验证回文字符串 Ⅱ

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串

// 判断从 i - j 是否为回文字符串
bool isPalindrome(char *s, int i, int j) 
{
	for (int k = i; k <= i + (j - i)/2; k++) {
		if (s[k] != s[j - k + i]) {
			return false;
		}
	}
	return true;
}

// 验证回文字符串Ⅱ -- 贪心算法
bool validPalindrome(char *s) 
{
	int j = strlen(s) - 1; // 计算的是真实大小
	int i = 0;

	
	while (i < j) {
		// 起始字符不相同,那只能删除头尾字符形成回文,否则就不会是回文
		if (s[i] != s[j]) {
			return (isPalindrome(s, i + 1, j) || isPalindrome(s, i, j - 1));
		}
		// 起始字符相同,继续往里面判断
		i++; // 如果s[i] == s[j], 则判断s[i+1] 和 s[j-1]
		j--;
	}

	return true;  // 如果 i >= j, 说明 s 本身就是回文字符串
}

611 有效三角形的个数

在这里插入图片描述
用了比较笨的排序方法,其实还可以使用双指针,复杂度要小些的方法。

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

int triangleNumber(int *nums, int numsSize) 
{
	int count = 0;

	qsort(nums, numsSize, sizeof(int), CmpInt);

	for (int i = 0; i < numsSize - 2; i++) {
		for (int j = i + 1; j < numsSize - 1; j++) {
			for (int k = j + 1; k < numsSize; k++) {
				if (nums[i] + nums[j] > nums[k])
					count++;
			}
		}
	}

	return count;
}

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

void swap(char *a, char *b) 
{
	char *t = NULL;
	*t = *a;
	*a = *b;
	*b = *t;
}
void reverseString(char *s, int sSize)
{
	int right = sSize - 1;
	int left  = 0;

	for (; left < right; right--, left++) {
		swap(s + left, s + right);
	}
}

.338.比特位计数

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
在这里插入图片描述

/* 获取非负数其二进制数中的 1 的个数 */
int getNumOfOne(int num)
{
    int cnt = 0;

    while(num) {
        cnt += num & 1; // 判断 num 最右一位是否为 11 ,根据结果计数
        num = num >> 1;
    }

    return cnt;
}

在这里插入图片描述
10: 0x1010
9: 0x1001
10 & 9 == 0x1000 – 把n 最右边1变成0;

int getNumOfOne(int num)
{
    int cnt = 0;

    while(num) {
        cnt++;
        num = num & (num - 1); // 二进制数字 nn 最右边的 11 变成 00 ,其余不变
    }

    return cnt;
}

48.旋转图像

给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
在这里插入图片描述

73. 矩阵置零

给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。

进阶:
一个直观的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案。
一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。
你能想出一个仅使用常量空间的解决方案吗?
在这里插入图片描述
思路:
第一次遍历时,使用标记数组,记录哪行哪列出现0值
第二次遍历时,根据标记数组清0

void setZeroes(int **matrix, int matrixSize, int *matrixColSize) {
	int m = matrixSize;         // matrixSize 是该矩阵的行数
	int n = matrixColSize[0];   // matrixColSize[0] 是该矩阵的列数
	int row[m], col[n];
	memset(row, 0, sizeof(row));
	memset(col, 0, sizeof(col));
	for (int i = 0; i < m; i++) {
		for (int j = 0; j < n; j++) {
			if (!matrix[i][j]) { // 使用标记数组,记录行列值
				row[i] = 1;
				col[j] = 1;
			}
		}
	}
	for (int i = 0; i < m; i++) {
		for (int j = 0; j < n; j++) {
			if (row[i] || col[j]) {
				matrix[i][j] = 0;
			}
		}
	}
}

789. 逃脱阻碍者

在这里插入图片描述

思路:此题看上去像是一道 DFS 的题,但实际上由于ghosts也能动,所以会有点想不明白具体怎么解。
其实只需要比较 “我”和 Ghosts 各自到 target 的距离即可,如果是都是小于就可以在ghosts之前出去。
这个距离称之为曼哈顿距离。。。虽然名字有点奇怪。

在这里插入图片描述
题目中给出了target为一维有两个的数组;
ghosts为二维数组,每个也有两个;

int manhattanDistance(int *point1, int *point2)
{
	return fabs(point1[0] - point2[0]) + fabs(point1[1] - point2[1]);
}

bool escapeGhosts(int **ghosts, int ghostsSize, int *ghostsColSize, int *target, int targetSize) {
	int source[2] = { 0, 0 };
	int distance = manhattanDistance(source, target);
	for (int i = 0; i < ghostsSize; i++) {
		int ghostDistance = manhattanDistance(ghosts[i], target);
		if (ghostDistance <= distance) {
			return false;
		}
	}
	return true;
}

390. 消除游戏

列表 arr 由在范围 [1, n] 中的所有整数组成,并按严格递增排序。请你对 arr 应用下述算法:
从左到右,删除第一个数字,然后每隔一个数字删除一个,直到到达列表末尾。
重复上面的步骤,但这次是从右到左。也就是,删除最右侧的数字,然后剩下的数字每隔一个删除一个。
不断重复这两步,从左到右和从右到左交替进行,直到只剩下一个数字。
给你整数 n ,返回 arr 最后剩下的数字。
在这里插入图片描述

int lastRemaining(int n) 
{
	int head = 1;
	int cnt = n, step = 1;
	bool dir = true; // true表示正向

	while (cnt > 1) {
		// 从左边开始移除 or(从右边开始移除,数列总数为奇数)
		if (dir || cnt % 2 == 1) { // dir为false表示反向,只有反向且 cnt % 2 == 0 时不更新head
			head = head + step;
		}
		cnt  = cnt / 2;  // 步长 * 2
		step = step * 2; // 总数 / 2
		dir = !dir;      // 取反移除方向
	}

	// 每个回合更新和记录head变量,当数组的总数变为1时,head就是最后的一个数
	return head;
}

上述过程需要自己以一个实例进行推导几次,才能体会,体会后就是上述的过程。
参考:https://leetcode-cn.com/problems/elimination-game/solution/wo-hua-yi-bian-jiu-kan-dong-de-ti-jie-ni-k2uj/

1185. 一周中的第几天

在这里插入图片描述
题目有个知识应该被告知:
1970年最后一天是周四 – 已知的先验知识
题目保证日期是在 1971 到 2100 之间,我们可以计算给定日期距离 1970 的最后一天(星期四)间隔了多少天,从而得知给定日期是周几。

具体的,可以先通过循环处理计算年份在 [1971, year - 1] 时间段,经过了多少天(注意平年为 365,闰年为 366);然后再处理当前年 yearyear 的月份在 [1, month - 1] 时间段 ,经过了多少天(注意当天年是否为闰年,特殊处理 2 月份),最后计算当前月 month 经过了多少天,即再增加 day 天。
得到距离 1970 的最后一天(星期四)的天数后进行取模,即可映射回答案。

// 题目保证日期是在 1971 到 2100 之间,我们可以计算给定日期距离 1970 的最后一天(星期四)间隔了多少天,从而得知给定日期是周几
char *dayOfTheWeek(int day, int month, int year)
{
	const char *Day[7]   = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
	int  Month[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	int  res = 4; // 1970年最后一天是周四 -- 已知的先验知识

	// 可以先通过循环处理计算年份在 [1971, year - 1] 时间段,经过了多少天(注意平年为 365,闰年为 366)
	for (int i = 1971; i < year; i++) {
		bool isLeap = (i % 4 == 0 && i % 100 != 0) || i % 400 == 0;
		res += isLeap ? 366 : 365;
	}
	// 然后再处理当前年 year 的月份在 [1,month−1] 时间段
	// 经过了多少天(注意当天年是否为闰年,特殊处理 2 月份),最后计算当前月 month 经过了多少天,即再增加 day 天。
	for (int i = 1; i < month; i++) {
		res += Month[i - 1];
		if (i == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)) {
			res++;
		}
	}
	res += day;
	res %= 7;

	return (char *)Day[res];
}

6368. 找出字符串的可整除数组

在这里插入图片描述
错误答题:
想这种题目,大概率会溢出,需要数学推演来找规律:

// 6368. 找出字符串的可整除数组
long long judge_num(long long num, long long m)
{
	long long res_s = num % m;

	return res_s == 0;
}

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int *divisibilityArray(char *word, int m, int *returnSize) 
{
	int right = 0, left = strlen(word);
	int i;
	int res = 0;
	char *tmp = (char *)malloc(sizeof(char) * strlen(word) + 1);
	int  *cnt = (int *)malloc(sizeof(int) * strlen(word) + 1);
	memset(tmp, 0, sizeof(char) * strlen(word) + 1);
	memset(cnt, 0, sizeof(int) * strlen(word) + 1);
	*returnSize = strlen(word);   
	for (i = right; i < left; i++) {
		memset(tmp, 0, sizeof(char) * strlen(word) + 1);
		memcpy((char *)tmp, (char *)word, (i + 1) * sizeof(char));
		tmp[strlen(tmp)] = '\0';
		res = judge_num(atoll(tmp), (long)m);
		if (res == 1) {
			cnt[i] = 1;
		}
	}
	
	return cnt;
}

如上为错误答案,会报:(测试用例数字太大了)
在这里插入图片描述
借鉴如下思路,每次向后增加一位时, 我们只考虑前面的余数部分, 这样是不会影响结果的。
在这里插入图片描述

int *divisibilityArray(char *word, int m, int *returnSize) 
{
	int right = 0, left = strlen(word);
	long long res = 0;
	int result ,i = 0;
	int  *cnt = (int *)malloc(sizeof(int) * strlen(word) + 1);

	memset(cnt, 0, sizeof(int) * strlen(word) + 1);
	*returnSize = strlen(word);   
	for (i = right; i < left; i++) {
		res = res * 10 + (word[i] - '0');
		result = (res % m == 0);
		if (result == 1) {
			cnt[i] = 1;
		}
		// 每次向后增加一位时, 我们只考虑前面的余数部分, 这样是不会影响结果的
		// 只保留余数部分
		res = res % m;
	}
	
	return cnt;
}

1247. 交换字符使得字符串相同

在这里插入图片描述
数学推演,找规律~
在这里插入图片描述
因此进行计数,数出s1[i],s2[i]的(‘x’, ‘y’) 和(‘y’, ‘x’) 的数量,分别计入cntxy和cntyx变量中。 按贪心思路:

(1)cntxy和cntyx均是偶数,则分别各自成对交换完成,返回:cntxy//2 + cntyx//2
(2)cntxy和cntyx均是奇数,则剩余一个(‘x’, ‘y’) 和一个(‘y’, ‘x’)需要交换两次,返回:cntxy//2 + cntyx//2 + 2
(3)cntxy和cntyx一奇一偶,则最后会剩余一个(‘x’, ‘y’) 或一个(‘y’, ‘x’)无法完成交换,返回:-1

cntxy + cntyx == 奇数一定不行,返回 -1;

int minimumSwap(char * s1, char * s2){
    short cntxy = 0, cntyx = 0;
    for (short i = 0; s1[i]; ++ i)
        if (s1[i] == 'x' && s2[i] == 'y') ++ cntxy;
        else if (s1[i] == 'y' && s2[i] == 'x') ++ cntyx;
    return (cntxy & 1) == (cntyx & 1) ? (cntxy >> 1) + (cntyx >> 1) + ((cntxy & 1) << 1) : -1;
}

面试题 05.02. 二进制数转字符串

在这里插入图片描述
在这里插入图片描述

#define MAX(a, b) ((a) > (b) ? (a) : (b))
char *printBin(double num)
{
	char *res = (char *)malloc(sizeof(int) * 33);
	int  pos  = 0;
	int  digit;
	pos  += sprintf(res, "%s", "0.");
	while (pos <= 32 && num != 0) {
		num *= 2;
		digit = num;
		res[pos++] = digit + '0';
		num -= digit;
	}
    res[pos] = '\0';
	if (pos > 32) {
		free(res);
		return "ERROR";
	}
	return res;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值