你还不懂复杂度?快来看看这篇文章。【数据结构】

在这里插入图片描述

🌵0.前言🌵

hello 大家好啊,几天不见。因深感自己对数据结构掌握不扎实,决定好好回顾数据结构并输出博客,今天先回顾的是复杂度

🍰1.什么是数据结构🍰

数据结构是计算机存储,组织数据的方式,相互之间存在一种或多种特定关系

🍰2.什么是算法🍰

Algorithm:
定义良好的计算过程,取一个输入,产生一个输出
算法就是一系列的计算步骤,将输入数据转化成输出结果

特征:

  1. 输入:一个算法有零个或多个输入。可以没有输入,例如一个定时闹钟程序,它不需要输入,但是能够每隔一段时间就输出一个报警。
  2. 输出:一个算法有一个或多个输出。程序可以没有输入,但是一定要有输出。
  3. 有穷性:一个算法必须在执行有穷步之后结束,且每一步都在有穷时间内完成。
  4. 确定性:算法中的每一条指令必须有确切的含义,对于相同的输入只能得到相同的输出。
  5. 可行性:算法描述的操作可以通过已经实现的基本操作执行有限次来实现。

注意:有些算法不能编程实现

例如一个有趣的排序算法,珠排序(Bead sort),如果它用重力法,能够在 O(1)O(1) 或 O(\sqrt{n})O(n) 时间内得到排序结果,效率高到令人惊叹,但是无法编程实现。

重要性

最近几年校招笔试
OJ形式
中小厂20-30选择题,3-4编程题
大厂全是编程题
校招面试,数据结构与算法权重很高

🍰3.复杂度🍰

算法效率

时间效率,空间效率
衡量不同算法的优劣,主要还是根据算法所占的空间时间两个维度去考虑。但是,世界上不会存在完美的代码,既不消耗最多的时间,也不占用最多的空间,也就是鱼和熊掌不可得兼。

🍰4.时间复杂度🍰

概念

算法中基本操作的执行次数就是算法的时间按复杂度
算法的时间复杂度是一个函数

实际情况中,不能真正计算时间**(机器/环境不同,时间就不一样)**

常见的时间复杂度量级

常数阶O(1)
对数阶O(logN)
线性阶O(n)
线性对数阶O(nlogN)
平方阶O(n2)
立方阶O(n3)
K次方阶O(nk)
指数阶(2n)

从上至下时间复杂度越来越大,执行的效率越来越低。

问题规模 nO(logn)O(log**n)O(n)O(n)O(nlogn)O(nlogn)O(n^2)O(n2)O(2^n)O(2n)O(n!)O(n!)
n ≤ 11n≤11
n ≤ 25n≤25×
n ≤ 5000n≤5000××
n ≤ 10^6n≤106×××
n ≤ 10^7n≤107××××
n > 10^8n>108×××××

对数阶O(logN)

int i = 1;
while(i<n)
{
    i = i * 2;
}

i* 2* *2 *2 …… = n
2^x = n
x = log2^n

线性对数阶O(nlogN)

for(m=1; m<n; m++)
{
    i = 1;
    while(i<n)
    {
        i = i * 2;
    }
}

平方阶O(n2)

for(x=1; i<=n; x++)
{
    for(i=1; i<=n; i++)
    {
        j = i;
        j++;
    }
}

O(m*n)

for(x=1; i<=m; x++)
{
    for(i=1; i<=n; i++)
    {
        j = i;
        j++;
    }
}

大O的渐进表示法

时间复杂度计算,大O的渐进表示法( 估算)

2n^2+2n+10--->O(n^2)
n->∞时,f(n)->n^2

算法的时间复杂度是一个函数,取函数表达式中最影响结果的一项
Big O notation 大O符号
取最高阶那一项,并去掉系数
如果是常数,则是O(1)

有些算法的时间复杂度存在最好、平均和最坏情况:

最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)

例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到

在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

练习

// 请计算一下Func1中++count语句总共执行了多少次?
void Func1(int N)
{
    int count = 0;
    for (int i = 0; i < N; ++i)
    {
        for (int j = 0; j < N; ++j)
        {
            ++count;
        }
    }

    for (int k = 0; k < 2 * N; ++k)
    {
        ++count;
    }
    int M = 10;
    while (M--)
    {
        ++count;
    }
    printf("%d\n", count);
}
//N^2 + 2N + 10  --> O(N^2)
// 计算Func2的时间复杂度?
void Func2(int N)
{
	int count = 0;
	for (int k = 0; k < 2 * N; ++k)
	{
		++count;
	}
	int M = 10;
	while (M--)
	{
		++count;
	}
	printf("%d\n", count);
}
//2N + 10 --> O(N)
// 计算Func3的时间复杂度?
void Func3(int N, int M)
{
	int count = 0;
	for (int k = 0; k < M; ++k)
	{
		++count;
	}
	for (int k = 0; k < N; ++k)
	{
		++count;
	}
	printf("%d\n", count);
}
//O(N+M)
//不确定M N哪个影响大
//如果告知N >> M ---> O(N)
//如果告知N M 差不多大 ---> O(N)或者O(M)
// 计算Func4的时间复杂度?
void Func4(int N)
{
	int count = 0;
	for (int k = 0; k < 100; ++k)
	{
		++count;
	}
	printf("%d\n", count);
}
//O(1)
strchr
// 计算strchr的时间复杂度?
const char * strchr ( const char * str, int character )
{
    while(*str)
    {
        if(*str == character)
            return str;
        else
            ++str;
    }
    return NULL;
}
//看最坏的情况  O(N)
冒泡
// 计算BubbleSort的时间复杂度?
void BubbleSort(int *a, int n)
{
	assert(a);
	for (size_t end = n; end > 0; --end)
	{
		int exchange = 0;
		for (size_t i = 1; i < end; ++i)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}
		if (exchange == 0)
			break;
	}
}
//最坏 1+2+...+N-1 --> (N-1)*N/2 --> O(N^2)
//最好 (N-1) -->O(N)
二分
// 计算BinarySearch的时间复杂度?
int BinarySearch(int *a, int n, int x)//对已经有序的数组进行搜索
{
	assert(a);
	int begin = 0;
	int end = n;
	while (begin < end)//这里用的是左闭右开 [begin,end)
	{
		int mid = begin + ((end - begin) >> 1);//避免相加可能溢出
		if (a[mid] < x)
			begin = mid + 1;//如果给的是 mid 有可能会死循环
		else if (a[mid] > x)
			end = mid;//如果到了mid-1 因为是开区间,其实找不到mid-1 的
		else
			return mid;
	}
	return -1;
}
//最好:O(1) 
//假设找了x次,1*2*2...*2 = N  --> 2^x = N --> x = log2N 			-->O(logN)

时间复杂度里面,习惯简写成logN
有些资料会写成lgN 严格来说这是不对的,跟数学中混乱了

90%的程序员不能实现一个完全正确的二分查找代码。
注意控制搜索区间,左闭右开,后面就也要左闭右开

详情可以参考这篇博客➡️ 一文梳理二分查找算法

递归
// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
	if (0 == N)
		return 1;

	return Fac(N - 1) * N;
}
//Fac(N)-->Fac(N-1)-->...Fac(1) 向下递归了N次  --> O(N)

递归算法时间复杂度=递归次数* 每次递归函数中次数

如果再套一层for循环,则就是O(N^2)

long long Fac(size_t N)
{
	if (0 == N)
		return 1;
	for(int i = 0; i < N; i++)
    {
        printf("%d",i);
	}
    printf("\n");
	return Fac(N - 1) * N;
}
//N-1+N-2+...+1 --> O(N^2)
Fib
// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
	if (N < 3)
		return 1;

	return Fib(N - 1) + Fib(N - 2);//双路递归
}
10
9 8
8 7 7 6
......
总共递归 N-1层  递归到2就结束
假设每一层都是满的 估算:
1+2+4+8+......+2^(N-2) --> 2^(N-1)-1 ---> O(2^N)

image-20220305121150403

实际上还要减去右边提前完成递归的一些次数,假设为x,整体是成一个斜三角,左上角是填满的,右下角的x是空缺的
2^(N-1) - 1 - x ==》O(2^N)
image-20220305121920677

递归版本的Fib时间复杂度相当之糟糕

#include<time.h>
long long Fibonacci(size_t N)
{
       return N < 2 ? N : Fibonacci(N - 1) + Fibonacci(N - 2);
}
int main()
{
       int begin = clock();
       Fibonacci(40);
       int end = clock();
       printf("%d\n", end - begin);
}
/*
40算了3339ms,45算了36351ms
这个算法时间复杂度太高,在实际中没有意义
O(2^N)
考虑改成循环--->O(N)
*/

非递归形式的FIb

#include<stdio.h>
//时间复杂度O(N)
int fib(int n)
{
    int a = 1;
    int b = 1;
    int c = 1;//n = 1/2 不走循环直接返回1
    while (n > 2)
    {
        c = a + b;
        a = b;
        b = c;
        n--;
    }
    return c;
}
注意:

要准确分析算法时间复杂度,一定要去看思想,不能只是去看程序是几层循环。

NO(N)O(logN)
1000100010约2^10
100w100w20约2^20
10亿10亿30约2^30

Fun(100) 不能认为它是O(1)
看的是算法,而不是某一次具体的调用
函数传进去的值可以是任意的

strchar("abcdef",'x');这次调用中,字符串长度为6,难道复杂度就是O(1)吗

O(1)表示它是常数次,10w,100w也是O(1)

#include<stdio.h>
#include<time.h>
int main()
{
       int n = 100000000;
       int begin = clock();
       for (int i = 0; i < n; ++i)
       {
           
       }
       int end = clock();
       printf("%d\n", end - begin);
       //clock 显示的程序执行开始的时间戳,单位是毫秒
       //1亿次只花了39ms
       return 0;
}

一万次还是一亿次都是瞬间运行完毕 几十ms级别
硬件的飞速发展,CPU运行速度太快了

🍰5.空间复杂度🍰

随着技术的发展,对空间的要求比较低了,不太关注空间复杂度了
空间复杂度不是去算程序占用多少byte,算的是变量的个数
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势

空间复杂度O(1)

int i = 1;
int j = 2;
++i;
j++;
int m = i + j;
//代码中的 i、j、m 所分配的空间都不随着处理数据量变化,因此它的空间复杂度 O(1)

空间复杂度O(n)

int[] m = new int[n]
for(i=1; i<=n; ++i)
{
   j = i;
   j++;
}
//第一行new了一个数组出来,这个数组占用的大小为n,2-6行,虽然有循环,但没有再分配新的空间,因此它的空间复杂度 O(N)

BubbleSort

void BubbleSort(int *a, int n)
{
	assert(a);
	for (size_t end = n; end > 0; --end)
	{
		int exchange = 0;
		for (size_t i = 1; i < end; ++i)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}
		if (exchange == 0)
			break;
	}
}
//实际上只定义了end exchange i 3个变量,占用3个空间
//冒泡排序空间复杂度O(1) —> 常数个空间

不考虑形参接收的空间,考虑的是算法运行中额外搞的空间
时间是累计的,空间不累计,空间是可以复用的
第一次用的是那个空间,结束了销毁了下次用的还是那片空间

注意:C99支持的变长数组空间复杂度也是O(N) int arr[n];

Fibonacci

// 计算Fibonacci的空间复杂度?
// 返回斐波那契数列的前n项
long long* Fibonacci(size_t n) 
{
    if(n==0)
        return NULL;
    long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));
    fibArray[0] = 0;
    fibArray[1] = 1;
    for (int i = 2; i <= n ; ++i)
    {
        fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
    }
    return fibArra;
}
//开了n+1个空间-->O(N)

recursion

long long Fac(size_t N)
{
	if (0 == N)
		return 1;

	return Fac(N - 1) * N;
}
//一层一层往下建立N个栈帧,每个栈帧常数个空间---> O(N)

Fib

long long Fib(size_t N)
{
	if (N < 3)
		return 1;

	return Fib(N - 1) + Fib(N - 2);//双路递归
}

它的空间复杂度又该怎么计算呢?

**注意:**时间是累计的,不能复用,而空间可以复用。

image-20220307195915871

栈是向下生长的,每一次函数调用结束,那片空间还在,只是把空间的使用权交还给系统。下一次函数栈帧的创建,用的也是相同的那片空间。

因此,Fib的递归实际使用了N-1个空间,空间复杂度也就是O(N)。

🐹6.练习🐹

消失的数字

image-20220307185851226

思路1:
冒泡排序–>O(N^2) 排好序后再比较就行 看前一个数+1是否等于后一个数
或者qsort(快速排序) 底层是快排 O(N* logN)也不满足O(N)要求

思路2:
映射,每个值是多少就作为下标对应的位置写多少,再遍历一遍O(N) 就能找出
缺点:空间复杂度O(N) 以空间换时间

思路3:
等差数列公式
0~n 计算等差数列的和 - 数组中值相加
O(1) O(N)

思路4:
异或
0~n数字和数组中的值依次异或,结果就是缺失的数字

思路2:映射

int missingNumber(int *nums, int numsSize)
{
    int *array = (int *)malloc(sizeof(int) * (numsSize + 1));
    //将开辟的数组内容都置为-1
    //memset(array, -1, 4 * (numsSize + 1));
    memset(array,-1,sizeof(array)); //这样写比较好
    for (int i = 0; i < numsSize; i++)
    {
        array[nums[i]] = nums[i];
    }
    int ret = -1;
    for (int i = 0; i < numsSize + 1; i++)
    {
        if (array[i] == -1)
        {
            ret = i;
        }
    }
    free(array);
    array = NULL;
    return ret;
}
memset(array,-1,sizeof(array)); //这样写比较好
//等价于:
// for (int i = 0; i < numsSize + 1; i++)
// {
// 	array[i] = -1;
// }

思路3:求和

int missingNumber(int* nums, int numsSize)
{
   int n = numsSize+1;
   int ret1 =0;
   // 0-n之间所有值累加 O(1)
   for(int i = 0; i < n; i++)
   {
       ret1 += i;
   }
   int ret2 = 0;
   // 数组中所有值累加
   for(int j = 0; j < numsSize ; j++)
   {
       ret2 += nums[j];
   }
   return ret1 - ret2;
}
//时间复杂度O(N)

思路4:异或

两个相同的数异或==0
    9 6 4 2 3 5 7 0 1
    0 1 2 3 4 5 6 7 8 9
    依次异或只剩下8[3 0 1]
    0 1 2 3
    3---0011
    0---0000
    3^0->0011 3
    3^0^1-->0011^0001-->0010-->2
    0010^0000-->0010
    0010^0001-->0011
    0011^0010-->0001
    0001^0011-->0010--->2
    3^0^1^0^1^2^3--->2

    1^3^1--->3

异或不需要考虑顺序,只要最终2个相同的数异或了,就会没了

int missingNumber(int* nums, int numsSize)
{
    int x = 0;
    for(int i = 0; i < numsSize + 1; ++i)
    {
        x ^= i;
    }
    for(int j = 0; j < numsSize; ++j)
    {
        x ^= nums[j];
    }
    return x;
}
//这样写更清晰
int missingNumber(int* nums, int numsSize)
{
    int x = 0;
    for(int i = 0; i < numsSize + 1; ++i)
    {
        x ^= i;
        if(i < numsSize)
        {
            x ^= nums[i];
        }
    }
    
    return x;
}
//一个循环的形式

只出现一次的数字

image-20220307185925399

不使用额外的空间,表示的是常数个空间
线性时间复杂度即O(N)

int singleNumber(int* nums, int numsSize)
{
    int x = 0;
    for(int i = 0; i<numsSize; i++)
    {
        x ^= nums[i];
    }
    return x;
}

2个出现1次的数

image-20220309180828329

位运算

将数组中所有数都跟0异或一下
这样就消除掉出现2次的数了
ret=x^y
ret必定不为0,找ret里面随便一个为1的位,说明x y对应位一个为1有一个为0
把原数组中的数,第n位为1的分成1组,第n位为0的分成一组
1 3 1 2 5 2
比如 第一位为1分成一组,第一位为0分成一组
3 2 2 1 1 5
再对他们分别异或

//returnSize 表示找出来的2个数得返回,以数组的形式返回
int* singleNumbers(int* nums, int numsSize, int* returnSize)
{
    int ret = 0;
    for(int i = 0; i < numsSize; i++)
    {
        ret ^= nums[i];
    }
    //找ret里一个为1的位
    int j = 0;
    for(; j < 32; ++j) 
    {
        if(ret & (1<<j)) // if(ret>>j & 1)也行
        {
          break;
        }
        // ret的第j位为1,说明x y两个的第j位一个为1,一个为0
    }
    
    // 将原数组分为2组,第j位为1的一组,第j位为0的一组
    //x y一定会分别进入a b组,其他出现2次的数要么进a组,要么进b组
    //a组和b组数据就会变成其他数出现2次,只有一个数出现1次,再分别异或就行
    int x = 0;
    int y = 0;
    for(int k = 0; k < numsSize; ++k)
    {
        if(nums[k] & (1<<j))    //a组
        {
            x ^= nums[k];
        }
        else                    //b组
        {
            y ^= nums[k];
        }
    }
    int* arr = (int*)malloc(sizeof(int)*2);
    arr[0] = x;
    arr[1] = y;
    *returnSize = 2;//输出型参数,让外面拿到数组长度
    return arr;
}

注意:比较严格的编译器,不允许int的1左移31位,因为会跑到符号位上
可以考虑用右移
或者强制转换成另一个类型 long long 或者unsigned int

for(long long i = 0; i < 32; i++)
    {
        //if((ret >> i) & 1)
        if(ret & (1 << i))
        {
            pos = i;
            break;
        }
    }
for(unsigned int i = 0; i < 32; i++)
    {
        //if((ret >> i) & 1)
        if(ret & (1 << i))
        {
            pos = i;
            break;
        }
    }

其他数出现3次

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。

思路:
由于这次其他数字出现的次数是奇数次,全部一起异或无法消除掉其余的数字。因此我们要换种方法。
把数组中所有的数的每一个二进制位加起来放进一个数组,然后%3,最后得到的数字就是那个只出现一次的数字。
由于其他数字都出现了三次,因此它们的那一位二进制位一定是3的倍数。%3相当于把那些重复的位全部减去了,剩下的二进制位就是只出现一次的数字的二进制位。

int singleNumber(int* nums, int numsSize)
{
   unsigned int arr[32] = {0};
   //int是有符号的 最高位是符号位  有效数据为只有31位
   //改成无符号后 所有的bit位都是数据位
    int count = 0;//出现1的个数
    int k = 31;
    //先用按位与把每一位二进制位拿出来并相加存进数组中
    for(int i = 0;i < 32;i++)
    {
        for(int j = 0;j < numsSize;j++)
        {
            count += ((nums[j]>>i) & 1);
        }
        arr[k] = count;//放在数组最后一位
        count = 0;
        k--;
    }
    //再把数组中的数字都%3
    for(int i = 0;i < 32;i++)
    {
        arr[i] %= 3;
    }
    int ret = 0;
    //用按位或把数组中的数字操作在ret上
    for(int i = 0;i < 32;i++)
    {
        ret |=(arr[31-i]<<i); //从最低位开始右移,也就是数组的最高位
    }
    return ret;
}

简化一点的写法

int singleNumber(int *nums, int numsSize) 
{
    int ans = 0;
    for (int i = 0; i < 32; ++i) 
    {
        int total = 0;
        for (int j = 0; j < numsSize; ++j) 
        {
            total += ((nums[j] >> i) & 1);
        }
        if (total % 3 != 0) 
            ans |= ((unsigned int)1 << i);//LeetCode上int类型不能左移32位,最高位是符号位,可以转换成unsigned int
    }
    return ans;
}
//runtime error: left shift of 1 by 31 places cannot be represented in type 'int' [solution.c]

出现k次

给定一个长度为 n 的整型数组 arr 和一个整数 k(k>1) 。

已知 arr 中只有 1 个数出现一次,其他的数都出现 k 次。

请返回只出现了 1 次的数。

暴力循环:

int FindSingleNumber(int *arr, int arrLen, int k)
{
    for (int i = 0; i < arrLen; i++)
    {
        int count = 0;
        for (int j = 0; j < arrLen; j++)
        {
            if (arr[i] == arr[j])
                count++;
        }
        if (1 == count)
            return arr[i];
    }
    return 0;
}

位运算:

出现k次的数字的每个位(0或者1)也是出现k次,因此每一位出现次数的和能够被k整除。所以如果把每个数的二进制的每一位都加起来,对于每一位的和,如果能被k整除,那对应那个只出现一次的数字的那一位就是0,否则对应的那一位是1。

int FindSingleNumber(int* arr, int arrLen, int k)
{
    int array[32] = {0};
    //求每个二进制位的和
    for (int i = 0; i < 32; i++)
    {
        int sum = 0;
        for (int j = 0; j < arrLen; j++)
        {
            sum = sum + ((arr[j] >> 1) & 1);
        }
        array[i] = sum;
    }
    int ret = 0;//出现1次的数
    for (int i = 0; i < 32; i++)
    {
        if (array[i] % k != 0)//统计ret的二进制位出现1的情况
        {
            ret = ret + (1 << i);
        }
    }
    return ret;
}

进制转换:

2 2 2 8 7 7 7 3 3 3

	0 1 1
	0 1 1
=	0 0 0 

2个相等的2进制数做不进位加法,结果为0
10个相等的10进制做不进位加法,结果为0
==》 k个相同的k进制,做不进位加法,结果为0

解题关键:
2 2 2 8 7 7 7 3 3 3

转换成3进制,再相加,不进位加法,就只剩下3进制的8,再转换为10进制

轮转数组

在这里插入图片描述

思路一:

右旋k次,临时保存最后一个数,依次向右移动一次
时间复杂度O(N*K) 空间复杂度O(1)

但是当 k % N = N-1时,时间复杂度 --》O(N^2)
k % N = N 时,也就是不旋转。

void rotate(int *nums, int numsSize, int k)
{
    int end = numsSize - 1;
    int temp = 0;
    for (int i = 0; i < k; i++)
    {
        temp = nums[end];
        for (int j = end; j > 0; j--)
        {
            nums[j] = nums[j - 1];
        }
        nums[0] = temp;
    }
}

思路二:

以空间换时间
额外开一个空间,把后k个数放到最前面,前N-K个数顺次放到后面[1,2,3,4,5,6,7] k=3
[5,6,7,1,2,3,4]

时间复杂度O(N) 空间复杂度O(N)

void rotate(int* nums, int numsSize, int k){
    //开辟一个数组,以空间换时间,把后面k个数放到前面,前面n-k个数放到数组后面
    int array[numsSize];
    k %= numsSize;//避免k过大
    for(int i = 0; i < k; i++)
    {
        array[i] = nums[numsSize-k+i];
    }
    //前面n-k个数放到数组后面
    for(int i = 0; i < numsSize-k; i++)
    {
        array[k+i] = nums[i];
    }
    //再把array放到nums里面去
    for(int i = 0; i < numsSize; i++)
    {
        nums[i] = array[i];
    }
    return nums;
}
//更简洁的写法
void rotate(int* nums, int numsSize, int k)
{
    int newArr[numsSize];
    for(int i=0; i<numsSize; i++)
    {
        newArr[(i+k)%numsSize] = nums[i];
    }
    for(int i=0; i<numsSize; i++)
    {
        nums[i] = newArr[i];
    }
}

思路三:

3次逆置
对原数组后k个数逆置
对原数组前n-k个数逆置
对原数组整体逆置
[1,2,3,4,5,6,7] ,k=3
4 3 2 1 5 6 7
4 3 2 1 7 6 5
5 6 7 1 2 3 4

时间复杂度O(N) 空间复杂度O(1)

void Reverse(int* arr, int left, int right)
{
    while(left<right)
    {
        int tmp = arr[right];
        arr[right] = arr[left];
        arr[left] = tmp;
        left++;
        right--;
    }
}

void rotate(int* nums, int numsSize, int k)
{
    k %= numsSize;//避免k过大越界,k=numsSize+1 --> 翻转1次
    Reverse(nums, 0, numsSize-k-1);//前N-K个
    Reverse(nums, numsSize-k, numsSize-1);//后K个
    Reverse(nums, 0, numsSize-1);//整体
}
//想不清楚时,举个样例就好

移除元素

在这里插入图片描述
在这里插入图片描述

快慢指针:

  1. 如果右指针指向的元素不等于 val,它一定是输出数组的一个元素,我们就将右指针指向的元素复制到左指针位置,然后将左右指针同时右移;

  2. 如果右指针指向的元素等于 val,它不能在输出数组里,此时左指针不动,右指针右移一位。

int removeElement(int* nums, int numsSize, int val)
{
    //快慢指针
    int slow = 0, fast = 0;
    for(int i = 0; i < numsSize; i++)
    {
        if(nums[fast] != val)
        {
            nums[slow] = nums[fast];
            slow++;
        }
        fast++;
    }
    return slow;
}

双指针优化:

如果要移除的元素恰好在数组的开头,例如序列 1 2 3 4 5当 val为 1时,我们需要把每一个元素都左移一位。注意到题目中说:「元素的顺序可以改变」。实际上我们可以直接将最后一个元素 5 移动到序列开头,取代元素 1,得到序列5 2 3 4 5同样满足题目要求。
这个优化在序列中 val 元素的数量较少时非常有效。

class Solution
{
public:
    int removeElement(vector<int> &nums, int val)
    {
        int left = 0, right = nums.size();
        while (left < right)
        {
            if (nums[left] == val)
            {
                nums[left] = nums[right - 1];
                right--;
            }
            else
            {
                left++;
            }
        }
        return left;
    }
};

🌵7.尾声🌵

写文不易,如果有帮助烦请点个赞~ 👍👍👍

🌹🌹Thanks♪(・ω・)ノ🌹🌹

👀👀由于笔者水平有限,在今后的博文中难免会出现错误之处,本人非常希望您如果发现错误,恳请留言批评斧正,希望和大家一起学习,一起进步ヽ( ̄ω ̄( ̄ω ̄〃)ゝ,期待您的留言评论。
附GitHub仓库链接
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值