第三章 数组
数组的理论基础
- 数组下标都是从0开始
- 数组在内存空间的地址是连续的
- 数组的元素不能删除,只能被覆盖。如果是C++编程,要注意vector和array的区别
二分查找
在一个有序无重复的数组nums中,寻找一个目标元素target,如果找到了就返回对应的下标,如果没找到就返回-1。
-
写法一
- 区间为左闭右闭
int search(int* nums, int numsSize, int target){ int left,right,middle; left = 0; right = numsSize - 1; while(left <= right){ middle = (left + right) / 2; if (nums[middle] > target){ right = middle - 1; } else if (nums[middle] < target){ left = middle + 1; } else if (nums[middle] == target){ return middle; } } return -1; }
-
写法二
- 区间为左闭右开
int search(int* nums, int numsSize, int target){ int left,right,middle; left = 0; right = numsSize; while(left < right){ middle = (left + right) / 2; if (nums[middle] > target){ right = middle; } else if (nums[middle] < target){ left = middle + 1; } else if (nums[middle] == target){ return middle; } } return -1; }
移除元素
为什么题目中没有要求返回移除元素后的数组?因为数组中的元素无法真正移除,只能靠后一个元素覆盖前一个元素,返回移除后的数组没有意义
-
C++中的函数传递
-
值传递:有一个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象或是大的结构体对象,将耗费一定的时间和空间。
void fun(int x){ x += 5; //修改的只是y在栈中copy x,x只是y的一个副本,在内存中重新开辟的一块临时空间把y的值 送给了x;这样也增加了程序运行的时间,降低了程序的效率。 } void main(void){ int y = 0; fun(y); cout<<\"y = \"<<y<<endl; //y = 0; }
-
指针传递:同样有一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地址。
void fun(int *x){ *x += 5; //修改的是指针x指向的内存单元值 } void main(void){ int y = 0; fun(&y); cout<<<<\"y = \"<<y<<endl; //y = 5; }
-
引用传递:同样有上述的数据拷贝过程,但其是针对地址的,相当于为该数据所在的地址起了一个别名。
void fun(int &x){ x += 5; //修改的是x引用的对象值 &x = y; } void main(void){ int y = 0; fun(y); cout<<<<\"y = \"<<y<<endl; //y = 5; }
-
-
暴力解法
int removeElement(int* nums, int numsSize, int val){ int i,j; int temp=0; for(i=0;i<numsSize;i++){ if (nums[i] == val){ for(j=i;j<numsSize-1;j++){ nums[j] = nums[j+1]; } i--; numsSize--; } } return numsSize; }
-
双指针法
注意nums[slowIndex++]的含义
int removeElement(int* nums, int numsSize, int val){
int slow = 0;
int fast;
for (fast = 0;fast < numsSize;fast++){
if (nums[fast] != val){
nums[slow++] = nums[fast];#等价于nums[slow] = nums[fast];slow++;
}
}
return slow;
}
补充
i++与++i的区别:
(简单记忆):
-
i++ 是先赋值在再自加,如:i = 1;k = i++;此时k的值为1,i的值为2;即i先把自身的值赋给了k之后才进行的自加。
-
++i是先自加在赋值,如:i = 1;k = ++i;此时k的值为2,i的值也为2;即i先进行自加,再将自加后的值赋给k。
-
当i++或者++i单独使用,没有其他表达式参与运算时,它们两个所实现的效果是一样的,都是实现自加1的效果。
写了几段代码并进行反汇编,结果如下:
可以清楚看到,a=i++是把i原来的值赋给eax,最后直接赋给a(-0x8(%rbp));而b=++j则是先加1再赋值。
长度最小的子数组
-
暴力解法
int minSubArrayLen(int target, int* nums, int numsSize){ int i,j; int sum = 0; int length,final_length; length = 0; final_length = INT_MAX; for (i = 0;i < numsSize;i++){ sum = 0; for (j = i;j < numsSize;j++){ sum += nums[j]; if (sum >= target) { length = j - i + 1; final_length = final_length < length ? final_length : length; break; } } } return final_length == INT_MAX ? 0 : final_length; }
-
滑动窗口(双指针法)
关键:
- 窗口内的元素是什么
- 如何移动窗口的起始位置
- 如何移动窗口的终止位置
int minSubArrayLen(int target, int* nums, int numsSize){
int final_length = INT_MAX;
int i,j;
int sum = 0;
i = 0;
int length = 0;
for (j = 0;j < numsSize;j++) {
sum += nums[j];
while (sum >= target) {
length = j - i + 1;
final_length = final_length < length ? final_length : length;
sum -= nums[i++];
}
}
return final_length == INT_MAX ? 0 : final_length;
}
螺旋矩阵Ⅱ
-
补充
在c语言中,使用变量前,需要先对变量的值进行初始化。数组在内存中占用一片连续的存储块。而c语言提供了memset函数(头文件string.h)对数组进行组团赋值。
**函数原型:**void memset ( void *s , char ch, unsigned n )
memset函数是对n个字节进行赋值;而char类型占1个字节,但是int类型占4个字节,所以对int、 short等类型赋值时,需要乘上字节数。并且memset赋值试会直接将数组拆分成总字节数个字节赋 值,并不会将四个字节看成一个整体,也就是说对整数类型数组赋值为非0的数并不能得到预期的结 果。
-
错误代码
/** * Return an array of arrays of size *returnSize. * The sizes of the arrays are returned as *returnColumnSizes array. * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free(). */ int** generateMatrix(int n, int* returnSize, int** returnColumnSizes){ int** matrix = malloc(sizeof(int*) * n); *returnSize = n; *returnColumnSizes = malloc(sizeof(int) * n); int k; for (k = 0; k < n; k++) { matrix[k] = malloc(sizeof(int) * n); memset(matrix[k], 0, sizeof(int) * n); (*returnColumnSizes)[k] = n; } int temp = n * n; int highboard,lowboard,leftboard,rightboard; highboard = 0; lowboard = n-1; leftboard = 0; rightboard = n-1; int i = 0; int j = 0,flag = 1; while (flag <= temp){ matrix[i][j] = flag; if (i == highboard && j < rightboard) { if (j == leftboard && highboard != 0) { leftboard += 1; } j += 1; } else if (j == rightboard && i < lowboard) { if (i == highboard) { highboard += 1; } i +=1; } else if (i == lowboard && j > leftboard) { if (j == rightboard) { rightboard -= 1; } j -= 1; } else if (j == leftboard && i > highboard) { if (i == lowboard) { lowboard -= 1; } i -= 1; } flag += 1; } return matrix; }
边界值变了后,数组下标也变了,导致其永远能满足判断条件。最好是以“圈”为循环单位而非已遍历的元素个数
-
模拟
处理原则需要统一,否则容易欠考虑
/** * Return an array of arrays of size *returnSize. * The sizes of the arrays are returned as *returnColumnSizes array. * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free(). */ int** generateMatrix(int n, int* returnSize, int** returnColumnSizes){ int** matrix = malloc(sizeof(int*) * n); *returnSize = n; *returnColumnSizes = malloc(sizeof(int) * n); int k; for (k = 0; k < n; k++) { matrix[k] = malloc(sizeof(int) * n); memset(matrix[k], 0, sizeof(int) * n); (*returnColumnSizes)[k] = n; } int i; int j; int loop = n / 2;//循环圈数 可以这么理解:画个对角线,看其一半有几格(奇数向下取整,忽略的那格另外考虑) int startx = 0; int starty = 0; int offset = 1;//每次循环通过偏移量来控制圈的边长 int flag = 1; while (loop--){ i = startx; j = starty; for (j = starty;j < starty + n - offset;j++) { matrix[i][j] = flag++;//注意flag++是先赋值再自加 } for (i = startx;i < startx + n - offset;i++) { matrix[i][j] = flag++; } for (;j > starty;j--) { matrix[i][j] = flag++; } for (;i > startx;i--) { matrix[i][j] = flag++; } startx++; starty++; offset += 2;//第一圈边长被占了两格,第二圈边长须要再减二 } i = n / 2; if (n % 2) { matrix[i][i] = flag; } return matrix; }