【C语言】浅_刷 —— 数组OJ题,看了就会~(详解版)

目录

前言

消失的数字

数组轮转

移除元素

删除有序数组中的重复项

合并有序数组 

总结


前言

本章将带来五道简单的关于数组的OJ题目,当然每一道题目都有不同的解题思路,在权衡时间复杂度和空间复杂度之后,在此只提供个人认为最为优化的一种算法。


消失的数字

运用了位运算中的按位异或“ ^ "。

按位异或:(位操作符)

int main()
{
	int a = 3;//a,b用的是补码
	int b = -5;

	int c = a ^ b;
	printf("%d", c);

	return 0;
}

特点:

按位异或,相同为0,相异为1。

00000000000000000000000000000011  3的补码

11111111111111111111111111111011 -5补码

11111111111111111111111111111000 c的补码

11111111111111111111111111110111 c的反码

10000000000000000000000000001000 - c的原码 -8

消失的数字

OJ链接

数组nums包含从0n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗?

注意:本题相对书上原题稍作改动

示例 1:

输入:[3,0,1]
输出:2

示例 2:

输入:[9,6,4,2,3,5,7,0,1]
输出:8
int missingNumber(int* nums, int numsSize)
{
    int i = 0;
    int tmp = 0;
    int a = 0;
    for(i = 0;i < numsSize;i++)
    {
        tmp ^= nums[i];
    }
    for(i = 0;i <= numsSize;i++)
    {
        a ^= i;
    }
    return a ^ tmp;
}

本题运用了异或操作的特点:

第一步将数组中的数字全部异或在一起,将得到的结果放在变量tmp中。

第二步将 0 ~ n 的数字全部异或在一起,将得到的结果放在变量a中。

 再将tmp和a两个数字异或就是将如图所述的两行数字异或在一起,根据异或的特点 即相同为0,不同为1,0和任何数异或都为任何数,当a ^ tmp操作之后,箭头所指的数字异或之后的结果为0。最后剩下的就是2,前面提到0和任何数字异或的特点,最后的操作就等价于0 ^ 2,这样就巧妙的将缺失的数字找出来了。


数组轮转

数组轮转

OJ链接

给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 1:

输入: 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]

示例 2:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释: 
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]
void func(int* nums,int left,int right)
{
    while(left <= right)
    {
        int tmp = nums[left];
        nums[left] = nums[right];
        nums[right] = tmp;
        left++;
        right--;
    }
}
void rotate(int* nums, int numsSize, int k)
{
    k %= numsSize;
    func(nums,numsSize - k,numsSize - 1);
    func(nums,0,numsSize - k - 1);
    func(nums,0,numsSize - 1);
}

 这题提供一个精妙的方法:

第一步:先逆置前N-K项
第二步:再逆置后K项
第三步:再整体逆置

本题注意要精确的判断数组的下标,还要考虑 k 的极端情况,当k为0时的时候调用函数,参数不满足函数内的循环进入条件,只会执行第二三条函数语句。

 

当k > numsSize 的时候会出现循环轮转的情况,也就是整个数组被从头到尾轮转一遍之后继续轮转,这样显然效率不够高。

这时将 k %= numsSize;这样操作之后便可以提高效率,同时还会避免数组越界的情况。


移除元素

移除元素

OJ链接

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

输入: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],也会被视作正确答案。
示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

int removeElement(int* nums, int numsSize, int val)
{
    int s1 = 0;
    int s2 = 0;
    while(s2 < numsSize)
    {
        if(nums[s2] == val)
        {
           s2++;
        }
        else
        {
            nums[s1] = nums[s2];
            s2++;
            s1++;
        }
    }
    return s1;
}

本题思路:(双指针)

题目要求是原地移除,这里定义两个指针 s1 和 s2 ,是用数组下标来访问数组内容,s2用来找要删除的 val 值,找到val值就跳过,找到不是 val 的数并将它赋值给下标为s1的位置。然后s1和s2同时向后走一个单位,直到s2遍历完整个数组为止,最后 s1 的值就是新数组的长度,这种算法没有开辟新的空间,在时间复杂的和空间复杂度上都是最优的,时间复杂度是〇(N),空间复杂度是〇(1)。

val == 3的情况下:

这种方法s1和s2的关系始终都是 s1 <= s2.

   

 备注:第一张图片为初始的位置,后面的图均是一段代码结束后的结果图。

此时s1的值为2。

 本题思路简单概述就是:s2遍历数组,只要找到一个 非val 的值就将它赋值给数组前面,新数组的长度就是数组中非val元素的个数,这个 个数 是使用s1来计数的,最后返回新数组的长度也就是s1即可。


删除有序数组中的重复项

删除有序数组中的重复项

OJ链接

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。

不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 2:

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

int removeDuplicates(int* nums, int numsSize)
{
    int src = 1;
    int dst = 0;
    while(src < numsSize)
    {
        if(nums[src] == nums[src - 1])
        {
        src++;
        }
        else
        {
            nums[dst] = nums[src - 1];
            src++;
            dst++;
        }
    }
    nums[dst++] = nums[src - 1];
    return dst;
}

 本题思路:(双指针)

本题是定义两个指针 src 和 dst,是用数组下标来访问数组内容,一开始指向src在dst的后一个

src用来找区某间段内不同的元素,如果nums[src] == nums[src - 1]src就向后走一个单位,一旦找到nums[src] != nums[scr - 1]的时候,那么就将nums[src - 1]的值赋值给为dst下标的元素,然后dst和src均向后走1一个单位,这样直到src将整个数组遍历完。值得注意的是,当src找最后一个相同数字的区段时是不会记录最后一个符合题目要求的元素,这是就需要在最后加上一步nums[dst++] = nums[src - 1]。

 

 

 

 备注:第一张图片为初始的位置,后面的图均是一段代码结束后的结果图。

此时新数组的长度就是dst的值,那么返回的值就是新数组的长度dst。


合并有序数组 

合并有序数组

OJ链接

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

示例 1:

输入: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 中的元素。


示例 2:

输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。


示例 3:

输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
    int i = m - 1;
    int j = n - 1;
    int sz = m + n - 1;
    while(i >= 0 && j >= 0)
    {
        if(nums1[i] > nums2[j])
        {
            nums1[sz] = nums1[i]; 
            sz--;
            i--;
        }
        else
        {
            nums1[sz] = nums2[j];
            sz--;
            j--;
        }
    }
    if(i < 0)
    {
        while(j >= 0)
        {
            nums1[sz] = nums2[j];
        sz--;
        j--;
        }
        
    }
  
}

这一题用到了归并排序的思想

因为这题给的是两个有序的数组,要充分利用好这个条件。 按照题目要求是将数据存储在nums1的数组中,这就对空间复杂的有要求,同样本题还是采用两个指针的方法,一个遍历nums1数组,一个遍历nums2这个数组,但是因为这两个数组均为升序数组且合并后的数组也是升序的排列,所以这两个指针这里是从后往前遍历。

两个指针同时出发,将较大的一个数放在nums1这个数组中,如果遇到相等的情况就随便给一个到nums中,然后一次向前遍历。如果其中一个数组遍历完了,就将剩下的那一个遍历完的数组直接接到nums1中,因为它本身就是有序的。

当nums2数组遍历完了之后就不需要再写一个循环将nums1剩下的元素接到nums1上了,因为它原来就在nums1中。

  

 

  备注:第一张图片为初始的位置,后面的图均是一段代码结束后的结果图。


总结

做题前应先明确思路,梳理好逻辑,最好画图将过程推演一般,代码实现的过程就像用一门语言将你的逻辑表达出来一样,所以做题前应该深思熟虑,而不是上手就写。

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
孪生素数是指两个相邻的素数,其差为2,例如(3, 5)、(5, 7)、(11, 13)等。下面是一道关于C语言中孪生素数的OJ目: 目描述: 输入一个正整数n,求出n以内的所有孪生素数(若存在)。输出格式为每个孪生素数对占一行,数字之间用一个空格隔开。如果不存在孪生素数,则输出"NO"。 输入格式: 输入一个正整数n(n<=1000000)。 输出格式: 按照格式输出符合条件的孪生素数对。 样例输入: 20 样例输出: 3 5 5 7 11 13 思路分析: 我们可以使用筛法来解决这道目。先用筛法求出质数,然后遍历质数数组,如果当前质数与前一个质数相差为2,则说明是一对孪生素数。 参考代码: ```c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #define MAX_N 1000000 bool is_prime[MAX_N + 1]; // 标记是否为质数 int prime[MAX_N + 1]; // 存储质数 int prime_cnt = 0; // 记录质数的数量 void sieve(int n) { for (int i = 2; i <= n; i++) { if (!is_prime[i]) { prime[prime_cnt++] = i; } for (int j = 0; j < prime_cnt && i * prime[j] <= n; j++) { is_prime[i * prime[j]] = true; if (i % prime[j] == 0) { break; } } } } int main() { int n; scanf("%d", &n); sieve(n); int prev_prime = -1; // 上一个质数 for (int i = 0; i < prime_cnt; i++) { if (prev_prime != -1 && prime[i] - prev_prime == 2) { printf("%d %d\n", prev_prime, prime[i]); } prev_prime = prime[i]; } if (prev_prime == -1) { printf("NO\n"); } return 0; } ```
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yy_上上谦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值