《LeetCode零基础指南》(第五讲) 指针

Posted 英雄哪里出来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《LeetCode零基础指南》(第五讲) 指针相关的知识,希望对你有一定的参考价值。

零、了解网站

1、输入输出

  对于算法而言,就是给定一些输入,得到一些输出,在一般的在线评测系统中,我们需要自己手写输入输出函数(例如 C语言中的scanfprintf),而在 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 nint类型的数组,需要的字节数自然就是 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 0i<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(0i<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.length1(0 和 n u m s . l e n g t h − 1 nums.length - 1 nums.length1 也包含在内)的不同整数组成的数组。

/**
 * 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零基础指南》(第十四讲) 力扣常见报错集锦

雅思基础课程语法基础课程-第五讲

九国列车《 力扣 》零基础打卡指南(第五天)指针的处理及题解总结

《LeetCode零基础指南》(第一讲) 函数

解题报告《LeetCode零基础指南》(第三讲) 循环

解题报告《LeetCode零基础指南》(第二讲) 函数