《LeetCode零基础指南》(第五讲) 指针
Posted 英雄哪里出来
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《LeetCode零基础指南》(第五讲) 指针相关的知识,希望对你有一定的参考价值。
文章目录
零、了解网站
1、输入输出
对于算法而言,就是给定一些输入,得到一些输出,在一般的在线评测系统中,我们需要自己手写输入输出函数(例如 C语言中的scanf
和printf
),而在 LeetCode 这个平台,只需要实现它提供的函数即可。函数的传入参数就代表了的算法的输入,而函数的返回值则代表了的算法的输出。
2、刷题步骤
找到一道题,以 两整数之和 为例,如何把这道题通过呢?如下图所示:
第 1 步:阅读题目;
第 2 步:参考示例;
第 3 步:思考数据范围;
第 4 步:根据题意,实现函数的功能;
第 5 步:本地数据测试;
第 6 步:提交;
3、尝试编码
这个题目比较简单,就是求两个数的和,我们可以在编码区域尝试敲下这么一段代码。
int getSum(int a, int b) // (1)
return a + b; // (2)
-
(
1
)
(1)
(1) 这里
int
是C/C++中的一种类型,代表整数,即 Integer,传入参数是两个整数; - ( 2 ) (2) (2) 题目要求返回两个整数的和,我们用加法运算符实现两个整数的加法;
4、调试提交
第 1 步:实现加法,将值返回;
第 2 步:执行代码,进行测试数据的调试;
第 3 步:代码的实际输出;
第 4 步:期望的输出,如果两者符合,则代表这一组数据通过(但是不代表所有数据都能通过哦);
第 5 步:尝试提交代码;
第 6 步:提交通过,撒花 🌻🌼🌺🌷🥀;
这样,我们就大致知道了一个刷题的步骤了。上一章我们了解了数组,今天我们就来学习一下指针,看看指针和数组到底有哪些千丝万缕的关系。
一、概念定义
1、指针即地址
计算机中所有的数据都必须放置在内存中,不同类型的数据占用的字节数也不一样,例如 32位整型int
占据 4 个字节,64位整型long long
占据 8 个字节,字符型char
占据 1 个字节。
为了能够正确地访问这些数据,必须为每个字节都编上编号,每个字节的编号是唯一的。
我们把内存中字节的编号称为 地址 或 指针。地址从 0 开始依次递增,对于 32 位环境下,程序能够使用的内存为 4GB,最小的地址为 0,最大的地址为 0XFFFFFFFF。
2、指针的定义
在C语言中,可以用一个变量来存放指针,这种变量称为指针变量。指针变量的值,通俗来说就是某一个变量的地址。
现在假设有一个char·
类型的变量x
,它存储了字符'o'
,并占用了地址为0xCF
的内存(地址通常用十六进制表示)。另外有一个指针变量p
,它的值为0xCF
,正好等于变量x
的地址,这种情况我们就称p
指向了x
,或者说p
是指向变量x
的指针。
其中
p
p
p 是指针变量名,
x
x
x 是被指向的变量的变量名;0xCF
是 指针变量
p
p
p 的值,也是
x
x
x 的地址;'o'
是
x
x
x 的值。
3、定义指针变量
定义指针变量和普通变量类似,只不过在变量名前面加上一个星号*
即可。C语言实现如下:
DataType *dataName;
我们用到最多的指针变量类型为整型,可以这么写:
int *p;
对于字符类型的指针变量,可以这么写:
char *p;
4、取地址
我们回到上面的问题,假设 x x x 是一个字符型变量, p p p 是 x x x 的地址,那么伪代码应该是这样的:
char x = 'o';
char *p = (x)的地址;
而实际写代码的时候,我们通过&
来表示取地址符号,也就是代码可以写成:
char x = 'o';
char *p = &x;
5、数组的地址
对于数组而言,其中的元素的地址都是连续的,数组第一个元素的地址可以用数组名来表示,实现如下:
int a[] = 5,2,0,1,3,1,4;
int *p = a;
简而言之,p
指向数组第一个元素的地址,a
也是数组第一个元素的地址。
6、解引用
解引用是取地址的逆操作,即根据给定的地址,获取它的值。用*
运算符来实现,如下:
int a;
int *p = &a;
a == *p;
一句话解释,p
代表a
的地址,*p
代表a
的值;
7、内存申请
C语言中,利用函数malloc
来申请内存,传入参数为申请内存的字节数,返回值为申请到的内存的首地址,是什么类型的地址,就要强转成什么类型,如下:
int *p = (int *)malloc(1024);
p
代表的是申请到的内存,并且有效内存字节数为 1024。
如果我们要申请一个长度为
n
n
n 的整型数组的内存,可以这么写:
int *p = (int *)malloc( sizeof(int) * n );
其中sizeof(int)
表示的是一个int
占用的字节数,那么一个长度为
n
n
n 的int
类型的数组,需要的字节数自然就是 sizeof(int) * n
。
8、返回数组
一般在刷题的时候,要求返回一个数组,这就比较尴尬,尴尬在哪里呢?
因为函数只能有一个返回值,而数组除了需要知道数组的首地址,还需要知道数组元素的个数。
所以,LeetCode 的做法是,通过函数的参数传递一个指针变量进来,然后让调用者去填充这个指针变量,如下:
int *getList(int *nums, int numsSize, int *returnSize)
// ...
这里的returnSize
是一个指针,它指向的就是一个代表数组长度的变量,它是没有赋值的,需要我们通过解引用对它进行赋值。
函数的调用方,其实是这么调用的:
int a[7] = 5, 2, 0, 1, 3, 1, 4;
int rSize;
int *ret = getList(a, 7, &rSize);
这里的&rSize
含义就是取了rSize
的地址,传递给returnSize
时,自然就成了指针。这样,在函数内部对*returnSize
进行修改,自然就改了rSize
的值,调用函数的一方自然就知道这个数组的长度了。
好了,今天的内容比较难,如果不懂,一定要在评论区留言告诉我,我会不断改善这块内容,争取能让更多的人听懂。
9、范式
所谓范式,就是规范写法的意思。就是按照这种规范写法写,能解决大部分问题。有关返回一维数组的问题,我们有通用范式,如下:
/**
* Note: The returned array must be malloced, assume caller calls free(). // (1)
*/
int *func(int *nums, int numsSize, int *returnSize) // (2)
int *ret = (int *)malloc( sizeof(int) * xxx ); // (3)
// TODO // (4)
*returnSize = xxx; // (5)
return ret; // (6)
- ( 1 ) (1) (1) 这一句话一般是系统提示你,需要申请内存,并且返回申请内存的首地址;
- ( 2 ) (2) (2) 这里会有两个返回值,一个是数组首地址(体现在返回值上),一个是数组大小(体现在指针传参上);
-
(
3
)
(3)
(3) 利用
malloc
申请一块自定义大小的内存; - ( 4 ) (4) (4) 做你要做的事情;
- ( 5 ) (5) (5) 利用解引用,将需要返回的数组的长度告诉调用者;
- ( 6 ) (6) (6) 返回申请的数组内存的数组首地址;
10、概念总结
今天一下子接触到了太多概念,最后做个统一的总结:
名词 | 简介 |
---|---|
指针 | 变量的地址 |
取地址 | 变量 -> 地址 |
解引用 | 地址 -> 变量 |
数组首地址 | 用数组名表示 |
范式 | 规范写法 |
二、题目分析
1、重新排列数组
给你一个数组
nums
,数组中有 2 n 2n 2n 个元素,按 [ x 1 , x 2 , . . . , x n , y 1 , y 2 , . . . , y n ] [x_1,x_2,...,x_n,y_1,y_2,...,y_n] [x1,x2,...,xn,y1,y2,...,yn] 的格式排列。请你将数组按 [ x 1 , y 1 , x 2 , y 2 , . . . , x n , y n ] [x_1,y_1,x_2,y_2,...,x_n,y_n] [x1,y1,x2,y2,...,xn,yn] 格式重新排列,返回重排后的数组。
/**
* Note: The returned array must be malloced, assume caller calls free(). // (1)
*/
int* shuffle(int* nums, int numsSize, int n, int* returnSize) // (2)
int i;
int *ret = (int *)malloc( sizeof(int) * numsSize ); // (3)
for(i = 0; i < numsSize; ++i) // (4)
if(i & 1)
ret[i] = nums[n + i/2];
else
ret[i] = nums[(i+1)/2];
*returnSize = numsSize; // (5)
return ret; // (6)
-
(
1
)
(1)
(1) 这句话是核心,返回的数组必须是通过
malloc
进行内存分配的,因为调用者会对它进行free
的清理操作; -
(
2
)
(2)
(2) 这个函数的返回值是
int *
,即返回的是一个数组的首地址,但是数组的大小也需要让调用者知道,所以通过一个传参returnSize
来传递出去; -
(
3
)
(3)
(3) 申请一个长度为
numsSize
的数组,首地址为ret
; -
(
4
)
(4)
(4) 根据题目要求,将
nums
的数据填充到ret
中; - ( 5 ) (5) (5) 最后,别忘了利用解引用,将需要返回的数组的长度告诉调用者;
- ( 6 ) (6) (6) 返回你申请的数组的首地址;
2、数组串联
给你一个长度为 n n n 的整数数组
nums
。请你构建一个长度为 2 n 2n 2n 的答案数组ans
,数组下标 从 0 开始计数 ,对于所有 0 ≤ i < n 0 \\le i < n 0≤i<n 的 i i i ,满足下述所有要求:
a n s [ i ] = = n u m s [ i ] a n s [ i + n ] = = n u m s [ i ] ans[i] == nums[i] \\\\ ans[i + n] == nums[i] ans[i]==nums[i]ans[i+n]==nums[i]具体而言,ans
由两个nums
数组 串联 形成。返回数组ans
。
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* getConcatenation(int* nums, int numsSize, int* returnSize)
int i;
int *ret = (int *)malloc(2*numsSize*sizeof(int)); // (1)
for(i = 0; i < numsSize; ++i)
ret[i+numsSize] = ret[i] = nums[i]; // (2)
*returnSize = 2 * numsSize; // (3)
return ret;
-
(
1
)
(1)
(1) 这题和上题的不同之处在于:申请一个长度为
2 * numsSize
的数组,首地址为ret
; -
(
2
)
(2)
(2) 根据题目要求,将
nums
的数据填充到ret
中; - ( 3 ) (3) (3) 别忘了利用解引用,将需要返回的数组的长度告诉调用者;
- ( 4 ) (4) (4) 返回你申请的数组的首地址;
3、基于排列构建数组
给你一个 从 0 开始的排列
nums
(下标也从 0 开始)。请你构建一个 同样长度 的数组ans
,其中,对于每个 i ( 0 ≤ i < n u m s . l e n g t h ) i(0 \\le i < nums.length) i(0≤i<nums.length),都满足ans[i] = nums[nums[i]]
。返回构建好的数组ans
。
从 0 开始的排列nums
是一个由 0 到 n u m s . l e n g t h − 1 nums.length - 1 nums.length−1(0 和 n u m s . l e n g t h − 1 nums.length - 1 nums.length−1 也包含在内)的不同整数组成的数组。
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* buildArray(int* nums, int numsSize, int* returnSize)
int i;
int *ret = (int *)malloc以上是关于《LeetCode零基础指南》(第五讲) 指针的主要内容,如果未能解决你的问题,请参考以下文章
《LeetCode零基础指南》(第十四讲) 力扣常见报错集锦