总言
思路汇总,对初学者的我来说值得学习。
会慢慢学习和补充。
文章目录
一、数组交换
题目描述: 将大小一致的两数组A、B元素进行交换。
思路分析:
如果是字符数组,有相应的字符串函数可供使用,此处题目没有强调数组类型,因此我们选择普遍类型进行思路学习。
数组是一系列类型相同的元素集合,对数组整体的交换目前暂未接触到,但是我们可将问题拆分为对数组内部元素进行交换。这样问题就变为了数与数的交换、以及如何将数与数的交换运用到多个需要交换的数之间。(此处强调的是要解决大问题,可将其拆分为小问题,若小问题还是无法解决,再将其拆分为更小的问题,这一思维模式)
代码如下:
void trans(int* a, int* b, int len)
{
int tmp;
while (len--)//注意事项
{
tmp = *a;
*a = *b;
*b = tmp;
a++;
b++;
}
}
void printarr(const int* arr,int len)
{
int i;
for (i = 0; i < len; i++)
printf("%d", *(arr+i));
putchar('\n');
}
int main(void)
{
//准备工作
int a[5] = { 0,1,2,3,4 };
int b[5] = { 5,6,7,8,9 };
int len = sizeof(a) / sizeof(a[0]);
//交换
trans(a, b,len);
//输出结果
printarr(a,len);
printarr(b,len);
return 0;
}
细节处理:
此处强调数组交换,对准备工作部分可自行更改。
注意事项中,此处有一个边界问题,while循环中,len- -能否写在控制表达式内?和写在循环体内有何区别?
此处有个简单的小经验,要区分变量len是否能放在循环的控制表达式内,需要注意该变量是否在循环体中发生改变?此外,还需注意何时用到该变量的改变量。
诸如此题,len在控制表达式中和在循环体中,其改变后的量都用于下一次循环条件的判断,区别在于,前者在数组交换前先发生改变,后者与数组交换一起,在循环体中进行改变(可以把控制表达式作为一步骤,执行循环体中的内容为控制表达式的后续衔接步骤)。
二、牛客题:获取月份天数
此题解法多样,容易想到的是用switch语句或if级联式语句:
#include<stdio.h>
int main(void)
{
int year, month;
while (scanf("%d %d", &year, &month)!=EOF)
{
switch (month)
{
case 1:case 3:case 5: case 7:case 8: case 10:case 12:
printf("31\n");
break;
case 4:case 6:case 9:case 11:
printf("30\n");
break;
case 2:
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
printf("29\n");
else printf("28\n");
break;
}
}
return 0;
}
使用上述方法需要对每月进行判断,除此之外还有一种相对优化的解法。我们知道一年12月,除了闰年的2月特殊外,其余月份天数固定,而闰年也属于一种特殊的年份。
因此我们可以先假设月份为常规情况,即非闰年下每月的天数,再根据此找出是否是闰年2月并做出修改。
int main(void)
{
int month, year;
int months[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
while (~scanf("%d %d", &year, &month))
{
int aim = months[month - 1];
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
{
if (month == 2)
aim++;
}
printf("%d\n", aim);
}
return 0;
}
三、调整数组元素奇偶顺序
题目描述: 调整数组使奇数全部都位于偶数前面。
输入一个整数数组,实现一个函数,来调整该数组中数字的顺序,使得数组中所有的奇数位于数组的前半部分,所有偶数位于数组的后半部分。
思路分析:
容易想到的常规方法:遍历数组元素,分别找出奇数偶数,放入与原数组大小相同的新数组中,由于题目要求调整数组奇偶序,故最后还需要将新数组中得到的元素顺序放入到原数组中。
需要注意数组奇数偶数不相等的情况,即需要确定奇数/偶数个数。此处相对简单的方法是在遍历奇数时顺带统计奇数个数。另一种方法则是查找新数组中0元素首次出现的位置,但这样容易把问题复杂化。
#include<stdio.h>
int main(void)
{
//int arr[10] = { 0,4,3,1,5,6,9,8,2,7 };
//奇偶相等,正确排序:3,1,5,9,7;0,4,6,8,2;
int arr[10] = { 1,4,2,5,11,9,8,13,7,3 };
//奇偶不同,正确排序:1,5,11,9,13,7,3;4,2,8;
//缺点:不能任意设置数组元素个数,任意输入数组元素
int sz = sizeof(arr) / sizeof(arr[1]);
int arr2[10] = { 0 };//副本:缺点是数组元素个数必须明确给出
//遍历找出奇数
int i,count=0,j=0;
for (i = 0; i < sz; i++)
{
if (arr[i] % 2 != 0)
{
arr2[j] = arr[i];
count++;//统计奇数个数
j++;//下一个奇数元素位置
}
}
//遍历找出偶数
j = count;
for (i = 0; i < sz; i++)
{
if (arr[i] % 2 == 0)
{
arr2[j] = arr[i];
j++;
}
}
//还原至原数组中
for (i = 0; i < sz; i++)
arr[i] = arr2[i];
//打印
for (i = 0; i < sz; i++)
printf("%d ", arr[i]);
return 0;
}
思路优化:
仔细分析题目还可知,本题只要求奇偶在前在后,对原先数组内部奇偶元素顺序不做要求,则我们也可以分别使用两个变量指向数组首元素和尾元素,找出各自对应的第一次遇见的偶数和奇数,分别交换这两元素,即可将奇偶前后分开排列。只不过这样会改变数组原先的奇偶序。
void move(int* arr, int sz)
{
int* left = arr;
int* right = arr + sz - 1;
//循环交换的需要的轮数
while (left < right)
{
//从左到右找第一次遇见的偶数
while ((left < right) && (*left % 2 == 1))
left++;//left<right防止数组全为奇数时越界
//从右到左找第一次遇见的奇数
while ((left < right) && (*right % 2 == 0))
right--;//left<right防止数组全为偶数时越界
//找到奇数、偶数后分别做交换
if (left < right)//交换也要限制条件
{
int tmp = *left;
*left = *right;
*right = tmp;
}
}
}
void print(int arr[],int sz)
{
int i;
for (i = 0; i < sz; i++)
printf("%d ", arr[i]);
}
int main(void)
{
int arr[] = { 2,3,8,1,0,7,9,5 };
int sz = sizeof(arr) / sizeof(arr[1]);
move(arr, sz);
print(arr, sz);
return 0;
}
四、牛课题:序列中删除指定数字
思路分析:
相对容易想到的两种方法:遍历数组,找到目标元素,此时要将目标元素清除,一种方法是创建两个相同大小的数组,后者用来保存被清除目标元素后的序列,缺点是容易占用内存空间,另一种方法使用一个函数,直接将后续元素往前挪,缺点是数组元素较多的情况下比较耗费时间。
#include<stdio.h>
int main(void)
{
int n; scanf("%d", &n);//序列总长
int arr[50];//数组元素种类最大值
int i=0;
for (i = 0; i < n; i++)
scanf("%d ", &arr[i]);//输入序列
int de; scanf("%d", &de);//目标元素
int carr[50] = {0}, k = 0;
//副本数组,用于控制副本元素位置的变量
for (i = 0; i < n; i++)
{
if (arr[i] != de)
{
carr[k] = arr[i];
k++;
//为下一次符合条件的元素进入副本数组做准备
//也是统计副本数组元素个数
}
}
//注意打印数组的长度,此处是k不是k-1,也不是k+1
//原因在于上个for循环结束后用于统计carr元素个数的k值总会比实际元素多一个
for (i = 0; i <k; i++)
printf("%d ", carr[i]);
return 0;
}
五、在杨氏矩阵中查找某个元素
题目描述: 有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,请编写程序在这样的矩阵中查找某个数字是否存在。
要求: 时间复杂度小于O(N);
#include<stdio.h>
#define cow 3
#define row 4
void find(int arr[row][cow], int k)
{
int x = 0;
int y = cow - 1;
while (x < row && y >= 0)
{
if (arr[x][y] > k)
y--;
else if (arr[x][y] < k)
x++;
else if (arr[x][y] == k)
{
printf("存在该元素,元素下标为%d,%d\n",x,y);
return;
}
}
printf("不存在该元素\n");
}
int main(void)
{
int arr[4][3] = { {1,3,4},{2,6,8},{4,12,16},{8,24,32} };
int k;
scanf("%d", &k);
find(arr, k);
return 0;
}
#include<stdio.h>
#define cow 3
#define row 4
void find(int arr[row][cow], int k,int *px,int *py)
{
int x = 0;
int y = cow - 1;
while (x < row && y >= 0)
{
if (arr[x][y] > k)
y--;
else if (arr[x][y] < k)
x++;
else if (arr[x][y] == k)
{
*px = x;
*py = y;
return;
}
}
*px = -1;
*py = -1;
}
int main(void)
{
int arr[4][3] = { {1,3,4},{2,6,8},{4,12,16},{8,24,32} };
int k;
int x=row, y=cow;
scanf("%d", &k);
find(arr, k, &x, &y);
if (x == -1 && y == -1)
printf("元素不存在\n");
else printf("元素下标为%d,%d\n", x, y);
return 0;
}
六、力扣题:原地移除数组元素
题目:力扣题源
思路一:暴力覆盖
类似于顺序表中的顺序表删除,用后面的元素覆盖前面的元素。此方法解析下来,时间复杂度为O(N^2),空间复杂度为O(1)。
int removeElement(int* nums, int numsSize, int val){
int i=0;
for(i=0;i<numsSize;i++)
{
if(nums[i]==val)//判断是否为目标值,是则暴力往前覆盖数组
{
int j=i;
for(j=i;j+1<numsSize;j++)
{
nums[j]=nums[j+1];
}
numsSize--;
i--;//存在连续是被删除值的情况,因此i要从修改数开始检验:1 1 2 2 3 4,val=2时
}
}
return numsSize;
}
思路二:双指针法
保留不是val的值,将其挪到新数组中,再将新数组复制到原数组中。此方法时间复杂度为O(N),空间复杂度为O(N)。从解题角度不符合题目要求。
思路三:法二改进
在同一数组中保留不是val的值,覆盖前面的值。相当于scr指针是控制数组遍历,des指针是用来判断当前所在位置是否需要被覆盖,des是否变动取决于scr所指向的元素是否非val值。
int removeElement(int* nums, int numsSize, int val){
int*des=nums,*src=nums;
//细节一:若以指针的形式实现,边界范围应该在哪?
//src最终要指向数组最后一个元素,因此此处不能是nums+numsSize-1.(注意理解指针间元素相减的含义)
while(src<nums+numsSize)
{
if(*src!=val)
{
*des++ = *src;
}
src++;//细节二:无论if语句为真为假,此处src++都起效一次
//因此在if循环中,使用*des++ = *src++;在条件为真时src将执行两次。
}
return des-nums;//细节三:在考虑了指针元素相减含义后,des每次if为真时都会+1,
//即其实际指向为一个虚位,以待src判断后填入该值。因此此处不能用des-nums+1;
//事实上此题使用数组的形式相较于指针的形式要容易理解,也不容易出错。
}
int removeElement(int* nums, int numsSize, int val){
int src,det;
src=det=0;
// int count=0;
while(src<numsSize)
{
if(nums[src]!=val)
{
nums[det++]=nums[src];
// count++;
}
src++;
}
//return count;
return det;//此处返回的仍旧是det,不必+1,原因是while循环中,满足if条件后,det最后会自增一次。
}
七、力扣题:删除有序数组中的重复项
题目:力扣题源
实际为去重算法
思路一:
和上题类似但理解起来又有些不同,scr依旧是用来遍历的,只不过这一次scr指针每次往后遍历,找到和det指针相同的元素时,自增的不再是det指针,而是scr指针,其代表含义为该区域作废(即元素重复),可以任意放数值。直到scr指针指向一个大于det指针的数时,det才会在作废区域将scr指向的元素复制过来。所以这里是det先自增再赋值。
int removeDuplicates(int* nums, int numsSize){
int det=0,scr=1;
while(scr<numsSize)
{
if(nums[scr]!=nums[det])
{
nums[++det]=nums[scr];
}
scr++;
}
return det+1;//det恰指在数组尾元素上,是数组下标,但需要返回的是数组长度
//注意此处若数组元素个数为0(极端情况,则会返回1,可以加个if语句特殊对待)
}
八、力扣题:合并两个有序数列
题目:力扣题源
并归排序
思路一:qsort快排
若不限制时间复杂度,实际上可以使用qsort快排实现。
思路二:
通常情况下,容易想到的一个方法是,创建第三个数组,比较一、二两个数组元素,筛选适合元素放入数组三中,依此类推比较直至一、二数组中一方元素遍历完。此时再把未遍历完的数组元素逐个放入数组三中,最后再将数组三的元素复制给数组一。
但此方法中需要创建数组占据空间,此处由于题目说明数组一有序且实际长度为一、二数组长度和。因此我们可以从大到小遍及数组,此时代表数组三的元素指向数组一的末尾,既节约了空间,又能达到题目要求(若从小到大会覆盖到有效数据)。
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
int end1=m-1,end2=n-1,end=m+n-1;
while(end1>=0&&end2>=0)
{
//nums1>nums2
if(nums1[end1]>nums2[end2])
{
nums1[end--]=nums1[end1--];
}
else
{
nums1[end--]=nums2[end2--];
}
}
//当比较双方元素个数不相同时,存在一方先结束的情况,因此要将此情况考虑进去。
while(end1>=0)
{
nums1[end--]=nums1[end1--];
}
while(end2>=0)
{
nums1[end--]=nums2[end2--];
}
}
九、力扣题:轮转数组
void reverse(int *left,int *right)
{
while(left<right)
{
int tmp=*left;
*left=*right;
*right=tmp;
left++;
right--;
}
}
void rotate(int* nums, int numsSize, int k){
k %= numsSize;
//第一次:0 ~ numsSize-k
reverse(nums,nums+numsSize-k-1);
//第二次:numsSize-k+1 ~ numsSize
reverse(nums+numsSize-k,nums+numsSize-1);
//第三次:0 ~ numsSize
reverse(nums,nums+numsSize-1);
}