剑指 Offer 21. 调整数组顺序使奇数位于偶数前面& C malloc/calloc & 位运算

64 篇文章 0 订阅
32 篇文章 0 订阅

题目

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。
提示:
1 <= nums.length <= 50000
1 <= nums[i] <= 10000
作者:Krahets
链接:https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5v8a6t/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

思路

算法思路:

很简单的一个小题,主要利用双指针的方法,一前一后两个指针遍历数组,因此
时间复杂度为O(N)
空间复杂度为O(1)

算法流程:

考虑定义双指针 i , j分列数组左右两端,循环执行:

  • 指针 i 从左向右寻找偶数;
  • 指针 j从右向左寻找奇数;
  • 将 偶数 nums[i] 和 奇数 nums[j]交换。
    可始终保证: 指针 i 左边都是奇数,指针 j 右边都是偶数 。

双指针策略总结:

双指针

两个思路:

(1)快慢指针:慢指针用于存储当前有效数据,快指针用于索引跳过无效的数据

(2)首尾指针:适合待删除元素比较少的数据
双指针,指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的。

快慢指针

类似于龟兔赛跑,两个链表上的指针从同一节点出发,其中一个指针前进速度是另一个指针的两倍。利用快慢指针可以用来解决某些算法问题,比如

计算链表的中点:快慢指针从头节点出发,每轮迭代中,快指针向前移动两个节点,慢指针向前移动一个节点,最终当快指针到达终点的时候,慢指针刚好在中间的节点。
判断链表是否有环:如果链表中存在环,则在链表上不断前进的指针会一直在环里绕圈子,且不能知道链表是否有环。使用快慢指针,当链表中存在环时,两个指针最终会在环中相遇。
判断链表中环的起点:当我们判断出链表中存在环,并且知道了两个指针相遇的节点,我们可以让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。
求链表中环的长度:只要相遇后一个不动,另一个前进直到相遇算一下走了多少步就好了
求链表倒数第k个元素:先让其中一个指针向前走k步,接着两个指针以同样的速度一起向前进,直到前面的指针走到尽头了,则后面的指针即为倒数第k个元素。(严格来说应该叫先后指针而非快慢指针)

碰撞指针

一般都是排好序的数组或链表,否则无序的话这两个指针的位置也没有什么意义。特别注意两个指针的循环条件在循环体中的变化,小心右指针跑到左指针左边去了。常用来解决的问题有

二分查找问题
n数之和问题:比如两数之和问题,先对数组排序然后左右指针找到满足条件的两个数。如果是三数问题就转化为一个数和另外两个数的两数问题。以此类推。

滑动窗口法

两个指针,一前一后组成滑动窗口,并计算滑动窗口中的元素的问题。

这类问题一般包括

字符串匹配问题
子数组问题

参考:
leet27. 移除元素 & 双指针

拓展

C 语言 malloc、calloc

用法:

C语言的动态分配内存过程中,我们经常使用到函数 malloc 与 calloc。 这两个函数均包含在“malloc.h"中

原型:

void *malloc( unsigned int num_bytes)
分配长度为num_bytes个字节的内存块,返回值为无类型指针,该指针指向所分配内存块的起始位置,因此利用该无类型指针赋值给其他类型的指针的时候,需要进行强制类型转换
void *calloc( unsigned int num, unsigned int size)
在内存的动态存储区分配num个长度为size的存储块,返回指向该存储块起始地址的无类型指针,若返回失败,返回NULL。因此,同样需要对该函数返回值进行判断。
calloc 与malloc的一个相同点在于使用之后均需要free(指针),释放内存块。

对比:

不同点:
calloc分配内存之后,会自动将这一块的内存之初始为0.
malloc则不会,分配内存的值为一些垃圾数值。
因此,在使用malloc函数之后,我们一般要调用函数memset对内存进行初始化。

memset的函数原型

void *memset(void *s, int ch, unsigned int size)
作用是:将s所指向的内存块的前size个字节全部设置为ch对应的ASCii值。 它是对较大数组或者结构体清零操作的最快方式。

位运算

trick:x&1 位运算 等价于 x%2 取余运算,即皆可用于判断数字奇偶性。

按位与运算

按位与运算符"&"是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结果位才为1
,否则为0。参与运算的数以补码方式出现

例如:9&5可写算式如下: 00001001 (9的二进制补码)&00000101 (5的二进制补码) 00000001(1的二进制补码)可见9&5=1。

巧用:

按位与运算通常用来对某些位清0或保留某些位
例如把a 的高八位清 0 , 保留低八位, 可作 a&255 运算 ( 255
的二进制数为0000000011111111)。
a. 清零特定位 (mask中特定位置0,其它位为1,s=s&mask)
b. 取某数中指定位 (mask中特定位置1,其它位为0,s=s&mask)

按位或运算

按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1。参与运算的两个数均 以补码出现
例如:9|5可写算式如下:00001001|00000101 == 00001101 (十进制为13)可9|5=13

巧用:

常用来将源操作数某些位置1,其它位不变。 (mask中特定位置1,其它位为0 s=s|mask)

按位异或运算 按位异或运算符

^ 是双目运算符。其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。参与运算数仍以补码出现
例如9^5可写成算式如下: 00001001^00000101 00001100 (十进制为12)

巧用:

a. 使特定位的值取反 (mask中特定位置1,其它位为0 s=s^mask)
b. 不引入第三变量,交换两个变量的值 (设a=a1,b=b1)
目 标 操 作 操作后状态a=a1^b1、 a=a^b 、a=a1^b1,b=b1
b=a1 ^ b1 ^ b1、 b=a^b、 a=a1^b1,b=a1
a=b1^ a1 ^ a1 、a=a^b 、a=b1,b=a1

求反运算

求反运算符~为单目运算符,具有右结合性。 其功能是对参与运算的数的各二进位按位求反。
例如~9的运算为: ~(0000000000001001)结果为:1111111111110110

左移运算

左移运算符“<<”是双目运算符。其功能把“<< ”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数,高位丢弃,低位补0。 其值相当于乘2。
例如: a<<4
指把a的各二进位向左移动4位。如a=00000011(十进制3),左移4位后为00110000(十进制48)。

右移运算

右移运算符“>>”是双目运算符。其功能是把“>> ”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数。其值相当于除2。

例如:设
a=15,a>>2 表示把000001111右移为00000011(十进制3)。对于左边移出的空位,如果是正数则空位补0,若为负数,可能补0或补1,这取决于所用的计算机系统。移入0的叫逻辑右移,移入1的叫算术右移,Turbo
C采用逻辑右移。

总结:

1. 如果乘上一个2的倍数数值,可以改用左移运算(Left Shift) 加速 300%
x = x * 2; x = x * 64; //改为: x = x << 1; // 2 = 21 x = x << 6; // 64 =
26

2. 如果除上一个 2 的倍数数值,可以改用右移运算加速 350%
x = x / 2; x = x / 64; //改为:
x = x >> 1;// 2 = 21 x = x >> 6;// 64 = 26

3. 数值转整数加速 10%

x = int(1.232) //改为:

x = 1.232 >> 0;

4. 交换两个数值(swap),使用 XOR 可以加速20%

t= a; a = b; b = t; //equals: a = a^b; b = a^b; a = a^b;

5. 正负号转换,可以加入 300%

i = -i; //改为 i = ~i + 1; // NOT 写法 //或 i = (i ^ -1) + 1; // XOR 写法

6. 取余数,如果除数为 2 的倍数,可利用 AND 运算加速 600%

x = 131 % 4; //equals: x = 131 & (4 - 1);

7. 利用 AND 运算检查整数是否为 2 的倍数,可以加速 600%

isEven = (i % 2) == 0; //equals: isEven = (i & 1) == 0;

  1. 加速 Math.abs 600% 的写法1,写法2 又比写法1加速 20%

//写法1 i = x < 0 ? -x : x;

//写法2

i = (x ^ (x >> 31)) - (x >> 31);

//写法3

i=x^(~(x>>31)+1)+(x>>31);

9. 比较两数值相乘之后是否拥有相同的符号,加速 35%

eqSign = a * b > 0; //equals: eqSign = a ^ b > 0;

其它位运算技巧

  1. RGB 色彩分离

var 24bitColor:uint = 0xff00cc; var r:uint = 24bitColor >> 16; var
g:uint = 24bitColor >> 8 & 0xFF; var b:uint = 24bitColor & 0xFF;

  1. RGB 色彩合并

var r:uint = 0xff; var g:uint = 0x00; var b:uint = 0xcc; var
24bitColor:uint = r << 16 | g << 8 | b;

参考:
https://www.cnblogs.com/ziqiongbuxi/p/3488755.html

源码

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
		int front=0,end = nums.size()-1;
		
		while(front < end){
			//从前找偶数
			while(front<end && (nums[front]&1 )== 1){front++;}
			//从后找奇数
			while(front<end && (nums[end]&1) == 0){end--;}
			
			int tmp = nums[front];
			nums[front] = nums[end];
			nums[end] = tmp;
    
    }
	return nums;
	}
};

//C实现
int* exchange(int* nums, int numsSize, int* returnSize){
    int * res = (int *)malloc(sizeof(int)*numsSize);
    //初始化两个指针,front =0 ,end = numsSize
    int front = 0,end=numsSize-1;
    //遍历数组
    while(front<=end){
        //前半部分出现偶数
        if(nums[front] % 2 == 0){
            //查找后半部分奇数
            if(nums[end]%2 == 1){
                //若当前nums[end]为奇数,则交换两者数值,并end--,front++
                res[front] = nums[end];
                res[end] = nums[front];
                front++;
                end--;
            }else{
                //若当前nums[end]并非奇数,则end往前移动
                res[end] = nums[end];
                end--;

            }         
        }else{
            res[front] = nums[front];
            front++;
        }
    }
    * returnSize = numsSize;
    return res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值