一,指针简单介绍:
指针对于C语言来说可以说是其灵魂所在,指针得存在大大的提升了C语言程序的灵活性和效率等问题,因为它可以直接对指定内存的数据进行访问。指针的内容是十分丰富的,例如:一维数组数组名,二维数组数组名,在使用 m a l l o c malloc malloc等动态开辟内存时候对于指针的操作,指针函数,函数指针,数组指针,指针数组等在此是远远说不完的。本次内容,就在昨天一维数组的基础上,加深一维数组和指针的关系,同时由于 L e e t C o d e LeetCode LeetCode对于C语言的要求,也会涉及到动态开辟数组。
在这里借助一道 L e e t C o d e LeetCode LeetCode需要用到动态开辟二维数组的例题来介绍一下动态开辟二维数组的方法:
void rotate(int** matrix, int matrixSize, int* matrixColSize){
//首先介绍一下各个参数的意思,matrix即使我们需要操作的二维数组,在这里可先不考虑。
//matrixSize就是我们需要开辟的数组的行数即row
//matrixColSize是一个数组
//他存储的是我们将要开辟的数组的每行的列数即col
int ** ans=(int **)malloc(sizeof(int *)*matrisSize);
//由于二维数组在内存上是连续的多个一维数组,我们先开辟一个指针数组
//ans[i]是一个指针他指向了第i行的首元素地址,在这里我们可以ans[i]当作一维数组的数组名
int col=matrixColSize[0];//获得列数
for(int i=0;i<matrixSize;i++){
ans[i]=(int*)malloc(sizeof(int)*col);
}
//接着对ans[i]进行操作,吧ans[i]这个指针指向动态开辟的col个元素的首地址
//这个操作和我们动态开辟一个一维数组一样,这样就获得了matrixSize行matrixColSize列的二维数组
}
二,做题记录:
给你一个数组 nums ,数组中有 2n 个元素,按 [x1,x2,…,xn,y1,y2,…,yn] 的格式排列。
请你将数组按 [x1,y1,x2,y2,…,xn,yn] 格式重新排列,返回重排后的数组。
示例 1:
输入:nums = [2,5,1,3,4,7], n = 3
输出:[2,3,5,4,1,7]
解释:由于 x1=2, x2=5, x3=1, y1=3, y2=4, y3=7 ,所以答案为 [2,3,5,4,1,7]
示例 2:
输入:nums = [1,2,3,4,4,3,2,1], n = 4
输出:[1,4,2,3,3,2,4,1]
示例 3:
输入:nums = [1,1,2,2], n = 2
输出:[1,2,1,2]
提示:
1 <= n <= 500
nums.length == 2n
1 <= nums[i] <= 10^3
1)对于返回数组的题目来说,
L
e
e
t
C
o
d
e
LeetCode
LeetCode会要求我们返回一个动态开辟的数组,因为函数的调用者会在调用后对其进行
f
r
e
e
free
free释放。所以首先我们需要把需要返回的数组给开辟出来。
2)在不考虑空间复杂度的情况下,本题的做法十分简单,即使利用两个辅助数组分别存取原数组奇数和偶数位置的内容,最后再将这两个数组复制到
a
n
s
ans
ans中。空间,时间复杂度都是
O
(
n
)
O(n)
O(n)
3)如果追求
O
(
1
)
O(1)
O(1)的空间复杂度也很简单,在
a
n
s
ans
ans的下标小于
n
n
n的时候,我们可以把对
a
n
s
ans
ans数组的赋值看成两部分,第一部分是偶数位置
i
i
i,他对应着原数组
i
+
n
i+n
i+n位置的元素,第二部分是奇数位置
i
i
i,他对应着原数组
i
i
i位置的元素。因此,在进行赋值操作时候,我们只需要进行
n
n
n次操作,每次操作对
a
n
s
ans
ans一个奇数位和一个偶数位进行赋值,这样
a
n
s
ans
ans数组就赋值完成了。
代码分别如下:
1.利用辅助数组:
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* shuffle(int* nums, int numsSize, int n, int* returnSize){
int * ans=(int*)malloc(sizeof(int)*2*n);
//利用malloc在堆上动态开辟2n个整形元素的内存,这些地址都是连续的可以当作数组使用。
//指针变量ans就指向了这2n个整形内存空间的首地址。
//malloc函数返回值是一个void* 类型的指针,这是为了适应各种数据类型,在使用时我们需要把malloc函数的返回值强制转换成我们需要的指针类型
*returnSize=2*n;//对指针进行解引用操作改变指针指向内存的内容
int odd[n];
int even[n];
for(int i=0;i<n;i++){
odd[i]=nums[i];
even[i]=nums[i+n];//对odd和even数组的赋值思想和下面每次进行两次赋值相似
}
int oi=0,ei=0;
for(int i=0;i<numsSize;i++){
ans[i]=i&1?even[ei++]:odd[oi++];//这里有点类似归并的思想
}
return ans;
}
2.每次进行两次赋值。
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* shuffle(int* nums, int numsSize, int n, int* returnSize){
int * ans=(int*)malloc(sizeof(int)*2*n);
*returnSize=2*n;
int size=0;
for(int i=0;i<n;i++){
ans[size++]=nums[i];
ans[size++]=nums[i+n];
}
return ans;
}
给你一个长度为 n 的整数数组 nums 。请你构建一个长度为 2n 的答案数组 ans ,数组下标 从 0 开始计数 ,对于所有 0 <= i < n 的 i ,满足下述所有要求:
ans[i] == nums[i]
ans[i + n] == nums[i]
具体而言,ans 由两个 nums 数组 串联 形成。
返回数组 ans 。
示例 1:
输入:nums = [1,2,1]
输出:[1,2,1,1,2,1]
示例 2:
输入:nums = [1,3,2,1]
输出:[1,3,2,1,1,3,2,1]
这里的操作就十分简单了,我们只需要对 a n s ans ans进行n次赋值操作,每次将 i i i和 i + n i+n i+n位置上的元素赋值为 n u m s [ i ] nums[i] nums[i]即可。
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* getConcatenation(int* nums, int numsSize, int* returnSize){
int * ans=(int*)malloc(sizeof(int)*(2*numsSize));
for(int i=0;i<numsSize;i++){
ans[i]=ans[i+numsSize]=nums[i];
}
*returnSize=2*numsSize;
return ans;
}
给你一个 从 0 开始的排列 nums(下标也从 0 开始)。请你构建一个 同样长度 的数组 ans ,其中,对于每个 i(0 <= i < nums.length),都满足 ans[i] = nums[nums[i]] 。返回构建好的数组 ans 。
从 0 开始的排列 nums 是一个由 0 到 nums.length - 1(0 和 nums.length - 1 也包含在内)的不同整数组成的数组。
示例 1:
输入:nums = [0,2,1,5,3,4]
输出:[0,1,2,4,5,3]
示例 2:
输入:nums = [5,0,1,2,3,4]
输出:[4,5,0,1,2,3]
没有什么技巧,按照题目要求模拟即可。
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* buildArray(int* nums, int numsSize, int* returnSize){
int *ans=(int*)malloc(sizeof(int)*numsSize);
*returnSize=numsSize;
for(int i=0;i<numsSize;i++){
ans[i]=nums[nums[i]];
}
return ans;
}
给你一个数组 nums 。数组「动态和」的计算公式为:runningSum[i] = sum(nums[0]…nums[i]) 。
请返回 nums 的动态和。
示例 1:
输入:nums = [1,2,3,4]
输出:[1,3,6,10]
解释:动态和计算过程为 [1, 1+2, 1+2+3, 1+2+3+4] 。
示例 2:
输入:nums = [1,1,1,1,1]
输出:[1,2,3,4,5]
解释:动态和计算过程为 [1, 1+1, 1+1+1, 1+1+1+1, 1+1+1+1+1] 。
示例 3:
输入:nums = [3,1,2,10,1]
输出:[3,4,6,16,17]
1)这是个需要特殊注意的题,我们通常把题目中成为动态和的东西叫做前缀和,即使对于数组下标为
i
i
i的元素,他所存放的内容是从原数组下标从
0
0
0到
i
i
i的元素和。
2)在他的求取过程上有点类似动态规划的递推关系,公式为:
d
p
[
0
]
=
n
u
m
s
[
0
]
,
d
p
[
i
]
=
d
p
[
i
−
1
]
+
n
u
m
s
[
i
]
dp[0]=nums[0],dp[i]=dp[i-1]+nums[i]
dp[0]=nums[0],dp[i]=dp[i−1]+nums[i],在此我们也可以用这个递推关系来解答。
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* runningSum(int* nums, int numsSize, int* returnSize){
int *ans=(int*)malloc(sizeof(int)*numsSize);
*returnSize=numsSize;
ans[0]=nums[0];
for(int i=1;i<numsSize;i++){
ans[i]=ans[i-1]+nums[i];
}
return ans;
}
5.LeetCode:剑指 Offer 58 - II. 左旋转字符串
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
输入: s = “abcdefg”, k = 2
输出: “cdefgab”
示例 2:
输入: s = “lrloseumgh”, k = 6
输出: “umghlrlose”
这就是一道十分经典的题目了,在此介绍两种方法:
1)就是通过计算翻转后的新元素的下标位置来对
a
n
s
ans
ans相应下标的元素进行赋值,代码如下:
char* reverseLeftWords(char* s, int n){
int len=strlen(s);
char *ans=(char*)malloc(sizeof(char)*(len+1));
for(int i=0;i<len;i++){
int index=i>=n?((i-n)):((i+len-n));
//对于前n个元素他们旋转后要到数组的最后位置
//那么我们可以这么想象,即是把原数组后面加上这n个元素在吧数组除了前n个元素外的元素向前移动n个位置即是新下标的位置为i-n
//由此得到了n之后的i位置的元素新的坐标就是相对于原坐标向前移动了n个位置
//前n个元素新的位置就是(i+len-n)
ans[index]=s[i];
}
ans[len]='\0';
return ans;
}
2)我们还可以这么想,要吧前n个旋转到数组末尾,后len-n个元素旋转到数组前面,那么我们可以先把前n个元素进行逆置,在对后len-n个元素逆置,最后再对整个数组进行逆置,最后得到的数组即是答案。这个自己想象一下旋转的过程就可以理解了。
代码如下:
void reverse(char * ch,int left,int right)
{
while(left<right)
{
char temp=ch[left];
ch[left]=ch[right];
ch[right]=temp;
left++,right--;
}
}
char* reverseLeftWords(char* s, int n){
int len=strlen(s)-1;
reverse(s,0,n-1);
reverse(s,n,len);
reverse(s,0,len);
return s;
}
给你一个有效的 IPv4 地址 address,返回这个 IP 地址的无效化版本。
所谓无效化 IP 地址,其实就是用 “[.]” 代替了每个 “.”。
示例 1:
输入:address = “1.1.1.1”
输出:“1[.]1[.]1[.]1”
示例 2:
输入:address = “255.100.50.0”
输出:“255[.]100[.]50[.]0”
做法就跟题目最后一句一样,遍历一遍 a d d r e s s address address数组,碰到了 ′ . ′ '.' ′.′就在 a n s ans ans中加入 [ , ] [,] [,]否则加入对应元素即可
char * defangIPaddr(char * address){
char * ans=(char *)malloc(sizeof(char)*1000);
int size=0;
for(int i=0;address[i];i++){
if(address[i]=='.'){
ans[size++]='[';
ans[size++]='.';
ans[size++]=']';
}else ans[size++]=address[i];
}
ans[size]='\0';
return ans;
}
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
输入:s = “We are happy.”
输出:“We%20are%20happy.”
限制:
0 <= s 的长度 <= 10000
和上一题做法相同,碰到空格 a n s ans ans中加入%20即可
char* replaceSpace(char* s){
char *ans=(char*)malloc(sizeof(char)*30005);
int size=0;
for(int i=0;s[i];i++){
if(s[i]==' '){
ans[size++]='%';
ans[size++]='2';
ans[size++]='0';
}else ans[size++]=s[i];
}
ans[size]='\0';
return ans;
}
给你一个数组 nums,对于其中每个元素 nums[i],请你统计数组中比它小的所有数字的数目。
换而言之,对于每个 nums[i] 你必须计算出有效的 j 的数量,其中 j 满足 j != i 且 nums[j] < nums[i] 。
以数组形式返回答案。
示例 1:
输入:nums = [8,1,2,2,3]
输出:[4,0,1,1,3]
示例 2:
输入:nums = [6,5,4,8]
输出:[2,1,0,3]
示例 3:
输入:nums = [7,7,7,7]
输出:[0,0,0,0]
提示:
2 <= nums.length <= 500
0 <= nums[i] <= 100
这道题的做法十分简单数,暴力遍历两边数组进行计数即可,这里介绍一种利用上述前缀和的解法。代码如下:
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int cnt[105];//用于统计nums数组中各数字出现个数,题目中的数据为1-100,开105个元素防止越界
int prev[105];//prev[i]的含义是数组中比i大的元素个数
int* smallerNumbersThanCurrent(int* nums, int numsSize, int* returnSize){
memset(cnt,0,sizeof(cnt));
memset(prev,0,sizeof(prev));//leetcode中的全局变量不会清空,所以函数开头先对两个数组进行初始化.
prev[0]=0;
for(int i=0;i<numsSize;i++){
cnt[nums[i]]++;//对每个数组出现频率计数
}
for(int i=1;i<=100;i++){
prev[i]=cnt[i-1]+prev[i-1];//计算前缀和
}
int *ans=(int*)malloc(sizeof(int)*numsSize);
for(int i=0;i<numsSize;i++){
ans[i]=prev[nums[i]];
}
*returnSize=numsSize;
return ans;
}
9.LeetCode:剑指 Offer 17. 打印从1到最大的n位数
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
说明:
用返回一个整数列表来代替打印
n 为正整数
很自然的想到当n足够大时,我们要打印的数组会超过 i n t int int的存储范围,但题目要求我们返回一个 i n t int int类型的数组,所以暴力即可。
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* printNumbers(int n, int* returnSize){
int target=pow(10,n)-1;
int *ans= (int *)malloc(sizeof(int)*target);
for(int i=1;i<=target;i++){
ans[i-1]=i;
}
*returnSize=target;
return ans;
}
给你两个整数数组 nums 和 index。你需要按照以下规则创建目标数组:
目标数组 target 最初为空。
按从左到右的顺序依次读取 nums[i] 和 index[i],在 target 数组中的下标 index[i] 处插入值 nums[i] 。
重复上一步,直到在 nums 和 index 中都没有要读取的元素。
请你返回目标数组。
题目保证数字插入位置总是存在。
输入:nums = [0,1,2,3,4], index = [0,1,2,2,1]
输出:[0,4,1,3,2]
输入:nums = [1,2,3,4,0], index = [0,1,2,3,0]
输出:[0,1,2,3,4]
同样是十分简单的题目,把 n u m s [ i ] nums[i] nums[i]放到对应的 a n s [ i n d e x [ i ] ] ans[index[i]] ans[index[i]]即可,值得注意的是当 a n s [ i n d e x [ i ] ] ans[index[i]] ans[index[i]]存在元素时候,我们需要把 i n d e x [ i ] index[i] index[i]后的元素全部后移一位。
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* createTargetArray(int* nums, int numsSize, int* index, int indexSize, int* returnSize){
int *ans=(int*)calloc(numsSize,sizeof(int));
bool hash[110];
memset(hash,0,sizeof(hash));
int len=0;
for(int i=0;i<numsSize;i++){
int idx=index[i];
if(hash[idx]){
for(int j=len;j>idx;j--){
ans[j]=ans[j-1];
hash[j]=true;
}
}
hash[idx]=true;
ans[idx]=nums[i];
len++;
}
*returnSize=len;
return ans;
}
三,今日收获
本次题目均来自英雄哪里出来的力扣零基础指南专栏:《LeetCode零基础指南》(第四讲) 指针感兴趣的可以亲自尝试。今天的题目主要还是对于一维数组赋值操作的巩固,在此基础上,对于指针进行了简单的操作,还学习到了前缀和的应用。