【ONE·C || 与数组有关的一些小练习】

总言

  思路汇总,对初学者的我来说值得学习。
  会慢慢学习和补充。

一、数组交换

题目描述: 将大小一致的两数组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);
}

  
  

  
  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值