编程基础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 = answer
,y = carry
返回 x 的二进制形式
- 计算当前 x 和 y 的无进位相加结果:
为什么这个方法是可行的呢?在第一轮计算中,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+1
到 nm
已经被反转,而我们正处于 nk
。
n1→…→nk−1→nk→nk+1←…←nm
我们希望 nk+1
的下一个节点指向 nk
。
所以,nk.next.next=nk
。
需要注意的是 n1
的下一个节点必须指向 ∅
。如果忽略了这一点,链表中可能会产生环。