背景
通过对经典编程题练习总结,培养解题思维,锻炼解题能力。
一、题目大纲
1 寻找一个单身狗/两个单身狗
题目描述:
一个单身狗
题目:一个数组中只有一个数字是出现一次,其他所有数字都出现了两次。编写一个函数找出这个只出现一次的数字。
Example:
有数组的元素是:1,2,3,4,5,1,2,3,4
只有5出现1次,要找出5.
两个单身狗
题目:一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。编写一个函数找出这两个只出现一次的数字。
Example:
有数组的元素是:1,2,3,4,5,6,1,2,3,4
只有5和6出现1次,要找出5和6.
思路如下:
寻找一个单身狗代码如下:
//寻找一个单身狗
int find_dog(int arr[], int num)
{
int tmp = 0;
for (int i = 0; i < num; i++) //将数组中的每个数组进行异或结果赋给tmp
{
tmp = tmp ^ arr[i];
}
return tmp;
}
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 1, 2, 3, 4 };
int num = sizeof(arr) / sizeof(arr[0]);
//find_dog(arr, num);
printf("%d\n", find_dog(arr, num));
return 0;
}
寻找两个单身狗代码如下:
//寻找两个单身狗
void find_two_dog(int arr[], int num, int* dog1, int* dog2)
{
//1、所有数字的异或
int tmp = 0;
int pos = 0; //用于统计两个单身狗的第几位为1,通过为1的为对其分组
for (int i = 0; i < num; i++)
{
tmp = tmp ^ arr[i]; //result为两个单身狗的异或
}
//2、计算tmp的第pos为1
for (int i = 0; i < 32; i++)
{
if (((tmp>>i) & 1) == 1) //判断5、6两单身狗异或结果tmp中第pos为1
{
pos = i;
break;
}
pos++;
}
for (int i = 0; i < num; i++)
{
//计算数组中元素的第pos为1的异或
//(就是按数组中元素第pos位为1的元素分成一组进行异或得到一个单身狗)
if (((arr[i] >> pos) & 1) == 1)
{
*dog1 = *dog1 ^ arr[i];
}
//计算数组中元素的第pos为0的异或
//(就是按数组中元素第pos位为0的元素分成一组进行异或得到一个单身狗)
if (((arr[i] >> pos) & 1) == 0)
{
*dog2 = *dog2 ^ arr[i];
}
}
}
int main()
{
int arr[] = { 1, 2, 3, 4, 7, 6, 1, 2, 3, 4 };
int num = sizeof(arr) / sizeof(arr[0]);
int dog1 = 0;
int dog2 = 0;
find_two_dog(arr, num, &dog1, &dog2);
printf("%d\n", dog1);
printf("%d\n", dog2);
return 0;
}
2 消失数字
题目描述:
题目:数组nums包含从0到n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗?
Leetcode原题链接
思路代码如下:
思路1:先对0-n进行等差数列求和sum,然后用一个for循环遍历这个数组用sum减去数组中的每一个数字,最后求得的sum中就是我们所缺失的那一个数字 。
时间复杂度:O(n)
int missingNumber(int* nums, int numsSize) //数组 数组的大小
{
int sum = (1 + numsSize) * numsSize / 2; //0-n等差数列求和
for (int i = 0; i < numsSize; i++) //sum减去数组中的每一个数
{
sum -= nums[i];
}
return sum;
}
思路2:用单身狗思想解题(成对的数据中找不是成对的数据),利用for循环产生1-n个数字,对每个数字进行异或得到一个结果,利用for循环对数组中的每一个数进行异或得到一个结果,最后对这两个结果进行异或
时间复杂度:O(n)
int missingNumber(int* nums, int numsSize) //数组 数组的大小
{
int x = 0; //用于记录每次异或的结果
for (int i = 0; i <= numsSize; i++) //x与0-n每个数字进行异或 结果赋给x
{
x = x ^ i;
}
for (int i = 0; i < numsSize; i++) //将0-n异或的结果x与数组中的每个数组进行异或,结果赋给x
{
x = x ^ nums[i];
}
return x;
}
3 数字添加逗号
题目描述:
题目:对于一个整数N(1<= N <=2,000,000,000)比如980354535,我们常常要一位一位数这几个数字是多少位,但是如果在这个数字每三位加一个逗号,它会变得更加的易于朗读,因此,这个数字加上逗号成了如下的模样:98,364,535请写一个程序帮她完成这件事情
example:
输入:980354535
输出:98,364,535
思路如下:
思路:我们将这个整数N一个一个的拆开成一个数字,然后将这个数字转换成字符型放入一个字符数组中,
关于逗号的打印两种思路
1、我们在这个数组中将先放入一个逗号在放入三个数据,然后遍历这个数组即可
2、我们在这个数组中将先放入三个数据在放入一个逗号,然后遍历这个数组即可
思路1代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int N = 0;
scanf("%d", &N); //1234567
char arr[14] = {0};
int i = 0; //用于统计放了几个数
int k = 0; //记录存了几个数字
while (N)
{
if (k != 0 && k % 3 == 0) //k!=0 表示这个数组中第一个位子不需要放逗号
{
arr[i++] = ','; //每存3个数字就存一个逗号
}
arr[i++]= N % 10 + '0'; //将数字转换为字符类型的数据倒着放入字符数组arr中
N = N / 10; //7,6,5,4,3,2,1
//if (k != 0 && k % 3 == 0) //k!=0 表示这个数组中第一个位子不需要放逗号
//{
//arr[i++] = ','; //每存3个数字就存一个逗号
//}
k++;
}
for (--i; i >= 0; i--) //倒着遍历这个数组
{
printf("%c", arr[i]);
}
return 0;
}
思路2代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int N = 0;
scanf("%d", &N); //123456789
char arr[14] = { 0 };
int i = 0; //用于统计放了几个数
int k = 0; //记录存了几个数字
while (N)
{
arr[i++] = N % 10 + '0'; //将数字转换为字符类型的数据倒着放入字符数组arr中
N = N / 10;
k++;
if (k % 3 == 0) //k!=0 表示这个数组中第一个位子不需要放逗号
{
arr[i++] = ','; //每存3个数字就存一个逗号
}
}
//以上循环完成之后数组中[9 8 7 , 6 5 4 , 3 2 1 ,]
i = i - 1; //最后的一个数组不要打印
for (--i; i >= 0; i--) //倒着遍历这个数组
{
printf("%c", arr[i]);
}
return 0;
}
4 轮转数组
题目描述:
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
Example:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
Leetcode原题链接
思路代码如下:
方法一:暴力轮转(time O(n^2)(最坏情况) Space O(1))
数组中的元素向右轮转k个位置,我们可以拆分成每次轮转一个位置,先将数组中最后的一个数字保存,数组之前的元素往后面移动一位,然后将最后的一位数字,放到数组的第一个位置(下标为0的位置)。具体代码如下:
void rotate(int num[], int n, int k)
{
k = k % n; //注意:每轮转n次为一个周期
int tmp;
for (int i = 0; i < k; i++)
{
tmp = num[n -1]; //最后一个数字保存到tmp
//int end = n - 2;
//while (end >= 0)
//{
// num[end + 1] = num[end];
// end--;
//}
for (int j = n - 1; j > 0; j--) //数组中最后一位数字之前的数字依次往后移动一位
{
num[j] = num[j-1]; //注意这是j-1,而不是j--,如果你是j--则j = j-1,两边都是num[j]你这样没有交换呀
}
num[0] = tmp; //将tmp中的数字保存到数组第0个位置
}
}
方法二:辅助空间法(time O(n) Space O(n))
构造一个辅助空间arr,我们将数组nums中后k个元素移动到辅助空间arr前k个位置
将数组nums后numsSize - k个元素移动到辅助空间arr的后numsSize - k个位置
典型的以空间换时间
//一个数组中有10个数,如果这个10作为下标,则表示数组中最后一个数的下一个位置
void rotate(int* nums, int numsSize, int k)
{
int* arr = (int*)malloc(sizeof(int) * numsSize);
assert(arr!=NULL); //arr不等于空成立才会执行下面的程序,否则的话就会在这断言
//for (int i = 0; i < numsSize; i++)
//{
// arr[(i + k) % numsSize] = nums[i];
//}
k = k % numsSize;
memcpy(arr, nums + numsSize - k, sizeof(int)*k); //将nums数组中的后k个数拷贝到数组arr中的前k个位置
memcpy(arr + k, nums , sizeof(int) * (numsSize -k)); //将nums前n-k个数拷贝到arr数组中的后n-k个位置
memcpy(nums, arr , sizeof(int) * k);
for (int j = 0; j < numsSize; j++)
{
nums[j] = arr[j];
}
free(arr);
arr = NULL;
}
方法三:三段逆置法(time O(n^2)(最坏情况) Space O(1))
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
//要被反转的数组首地址,区间[left,right]反转
void reverse(int *nums,int left,int right)
{
assert(nums);
while(left<right)
{
//需要使用交换两数功能,你可以也把这个常用功能封装成函数,这里就不封装了
int tmp =nums[left];
nums[left]=nums[right];
nums[right]=tmp;
//区间左右两头数交换后,向中间靠拢
left++;
right--;
}
}
void rotate(int* nums, int numsSize, int k)
{
assert(nums);
//k超过numsSize大小的部分没意义
k%=numsSize;
//我们自己写一个反转数组的函数,注意传递的区间参数
reverse(nums,0,numsSize-k-1); //前n-k个逆置
reverse(nums,numsSize-k,numsSize-1); //后k个逆置
reverse(nums,0,numsSize-1); //整体逆置
return;
}
5 移除元素
题目描述
题目:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
Example:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
Leetcode原题链接
思路如下
1、先定义两个指向数组nums中0位置处的指针src与des,
2、如果nums[src]与val相等则将nums[src++] = num[des++] ; 如果不相等src++;
3、返回des值
代码如下
int removeElement(int* nums, int numsSize, int val)
{
int src = 0;
int des = 0;
while(src<numsSize)
{
//如果不等于val值,覆盖尾插到des位置,des++,src++
//等于val,就src++
if(nums[src] != val)
{
nums[des++] = nums[src++];
}
else
{
src++;
}
}
return des; //这里注意返回的不是des+1,因为每次给nums[des]赋值完之后,都对des进行了++操作
//所以des指向的是最后一个元素的下一个位置。也就是元素的个数
}
6 删除有序数组中的重复项
题目描述
题目:给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
Example:
输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
Leetcode原题链接
思路如下:
1、如果nums[src] == nums[dst],则src++
2、如果nums[src]!=nums[dst],则++dst,令nums[dst] = nums[src]
3、最后由于dst表示的最后一个数的下标位置,我们要返回新数组的个数我们还需要+1;
(我们这个题目要求是找不同,只有不同的时候我们将dst指向的值赋值给src)
代码如下
int removeDuplicates(int* nums, int numsSize)
{
int dst = 0; //定义指向起始位置的指针
int src = 0;
//遍历数组 如果dst指向的值与src指向的值相等就继续遍历
//如果不相等,dst就先指向下一个位置,在对dst进行赋值。
while(src< numsSize)
{
if(nums[dst] == nums[src])
{
src++;
}
else
{
dst++;
nums[dst] = nums[src];
}
}
return dst+1;
}
7 合并两个有序数组
题目描述
题目:给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
Example:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
Leetcode原题链接
思路如下
思路1(space O(n)):
1、开辟一个新数组nums3,将nums1与nums2中的每一个数进行比较,然后将其中较小的值放入nums3中
2、如果nums1先遍历完,则将nums2中的剩余元素赋值给nums3,否则将nums1中剩余数据赋值给nums3
3、将nums3中的数据放入nums1中;打印nums1;
思路2(space O(1)):
1、定义指针end1,end,end2分别指向nums1中的m-1,m+n-1以及nums2中的n-1的位置
2、遍历nums1[end1]和nums2[end2]之前的元素。并对指针end1与指针end2的数据进行比较如果nums1[endl]>=nums2[end2],则将较大的数据放入nums1[end–]中,较大数组对应的指针也减减。只要有一个先遍历完,则循环终止。
3、如果指针end2比end1先遍历完,则说明nums2中的数据全部合并到了nums1中;如果指针end1比end2先遍历完,则还需将nums2中的剩余元素赋值给nums1[end1]之前的元素.
思路2代码如下:
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
int end1 = m-1; //定义三个指针
int end2 = n-1;
int end = n+m-1;
//遍历nums1与nums2,判断指针end1与end2指向数据的大小
while(end1>=0&&end2>=0)
{
if(nums1[end1]>=nums2[end2])
{
nums1[end--] = nums1[end1--]; //
}
else
{
nums1[end--] = nums2[end2--];
}
}
//nums2中的数据还有剩余,将其剩余数字放入nums1中
while(end2>=0&&end1<0)
{
nums1[end--] = nums2[end2--];
}
}