今天学习的内容是顺序表
动态顺序表的优缺点
优点:1,随机访问效率高
2,数据是依次顺序存储的
3,空间利用率高,避免给多了用不了浪费,给少了不够用的情况。
缺点:1.插入删除都需要挪动元素,时间复杂度为O(N)
2.如果空间不够了就要增容,增容就会付出一定的性能消耗。
3.查找一个有序数组,可以二分,如果无序,只能遍历。时间复杂度是O(N)
解决:1,空间上可以按需给空间(静态+宏定义)
2,不要求物理空间连续(链表)这样随机插入不需要移动数据。
3,解决搜索问题,可以用平衡搜索树。
顺序表的相关算法
1.线性枚举
枚举数组中的每个元素做出相应判断,也就相当于遍历。
2.前缀和差分
思路是,定义一个数组,用来存储目标数组的下标之前的元素之和,对于给出的需要求的区间之和,只需要用减法,就可以在O(1)的时间复杂度内求出。
题目是:给定一个数组,和区间数组l数组和r数组,求出l到r这个区间之和,记录到数组里面
int sum[maxn];
int* prefixSum(int* nums, int numsSize, int m, int *l, int *r){
int i;
int *ret;
for(i = 0; i < numsSize; ++i) {
sum[i] = nums[i];
if(i)
sum[i] += sum[i-1]; // (1)
}
ret = (int *) malloc( m * sizeof(int) ); // (2)
for(i = 0; i < m; ++i) {
int leftsum = l[i]==0? 0 : sum[l[i]-1]; // (3)
int rightsum = sum[r[i]];
ret[i] = rightsum - leftsum; // (4)
}
return ret;
}
1,是求前缀和,如果i == 0则为第一个元素,不需要求前缀和,不然会越界。
2,为返回数组开辟空间
3,求出左边前l-1个数组元素之和。如果i == 0的时候,就代表求得是整个数组元素之和,直接用最后一个前缀和数组元素-0即可。
4,减法求出和,然后放入数组。
3.双指针
双指针一般用来求出连续的最长子串或者序列问题
算法思路:定义两个指针然后通过两个指针维护一段空间,空间内的元素满足条件,然后不断调整两个指针的问题
题目:给定一个长度为n的连续字符串,求出最长的一个连续不重复的子字符串。
思路:定义两个指针,i和j,j一开始指向-1,因为字串长度是j-i+1,一开始的子串长度为0.
然后让j开始移动,将字符个数的统计放到哈希表里面每次j++,然后如果有一个字母的计数超过1,就让i++,然后在哈希表里面,这个i对于的字符的计数–,直到j对于的这个字符的计数== 1之后再去移动j。这个时候还要记录最大长度。
int getmaxlen(int n, char *str, int& l, int& r) {
int ans = 0, i = 0, j = -1, len; // 1)
memset(h, 0, sizeof(h)); // 2)
while (j++ < n - 1) { // 3)
++h[ str[j] ]; // 4)
while (h[ str[j] ] > 1) { // 5)
--h[ str[i] ];
++i;
}
len = j - i + 1;
if(len > ans) // 6)
ans = len, l = i, r = j;
}
return ans;
}
1,定义两个指针和保存长度的变量
2,初始化哈希表的元素全为0
3,保证j的元素不会超过n,最大为n-1.
4,每次将j对应的元素在哈希表内自增
5,如果对应字母的哈希表计数>1了就要减掉i对应的字母计数并自增i直到j对应的字母计数=1的时候。
6,求出现在双指针维护的长度与ans比较,比ans大就更新ans,最后返回即可。
这个算法只遍历了一次数组,所以时间复杂度是O(N)
4.二分枚举(二分查找)
首先要是有序区间,然后可以使用二分思想来找到你想要的数字。
int search(int *nums, int numsSize, int target) {
int l = 0, r = numsSize - 1; // (1)
while(l <= r) { // (2)
int mid = (l + r) >> 1; // (3)
if(nums[mid] == target) {
return mid; // (4)
}else if(target > nums[mid]) {
l = mid + 1; // (5)
}else if(target < nums[mid]) {
r = mid - 1; // (6)
}
}
return -1; // (7)
}
关于二分查找这方面我还有详细写过一个博客如果需要可以看看。链接
5.三分枚举
思想类似于二分枚举,也是一次砍掉一块不可能的区间,二分枚举一般应用于解决单调性问题,三分枚举主要是求极值问题。
6.插入排序
思想是,每次都将一个要插入的元素与前面的有序区间进行比较,若是比插入元素大,则将有序元素向后移动,直到前面的元素都小于要插入的元素,然后将该元素插入即可。
7.选择排序
思想:第一次选择数组中最小的与数组第一个元素交换,第二次选择数组从第二个元素到最后一个元素中最小的元素与第二个元素交换,依次类推
8.冒泡排序
思想:遍历n-1次数组,每次将最大值(最小值也行看你需要升序还是降序)移动到数组末尾,第一次遍历需要比较n-1次,第二次只需n-2次依次类推。因为每遍历一次,就有一个有序的元素出现在正确的位置。
题解:
思路:先枚举一个下标i,然后枚举一个下标j,j每次从i的位置开始枚举定义一个sum记录每次加和的值,然后如果两个下标之间的元素个数是奇数就将sum加到ans上。
int sumOddLengthSubarrays(int* arr, int arrSize){
int ans = 0;
int sum = 0;
for(int i =0;i<arrSize;i++)
{
sum = 0;
for(int j = i;j<arrSize;j++)
{
sum+=arr[j];
if((j-i+1)&1)
{
ans+=sum;
}
}
}
return ans;
}
思路:先枚举每个元素,判断是不是等于target然后再判断abs(i-satrt)的值是不是小于min,然后如果小于,则更新min的值,最后返回即可。
int getMinDistance(int* nums, int numsSize, int target, int start){
int min = 100000;
for(int i =0;i<numsSize;i++)
{
if(nums[i]==target&&abs(i-start)<min)
{
min = abs(i-start);
}
}
return min;
}
思路是:遍历数组,然后判断k的正负和是否等于0.进入不同的分支,分支内再次进行进行循环,将前k个或者后k个元素进行加和,(注意如果k<0要将k的拷贝ck = -k)注意数组下标可能会越界,因为是循环数组所以,
if(ci<0)
ci = codeSize-1;
if(ci==codeSize)
ci = 0;
进行如上循环数组的判定,最后将加和的赋值给要返回的数组元素即可。
这里的循环判定防止越界还有一种方法,如果是k>0自增越界,就每次让ci%codesize。如果,k<0,自减越界,就让ci+codesize然后再%codesize即可。
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* decrypt(int* code, int codeSize, int k, int* returnSize){
int* ans = (int*)malloc(sizeof(int)*codeSize);
*returnSize = codeSize;
for(int i =0;i<codeSize;i++)
{
int sum = 0;
if(k>0)
{
int ck = k;
int ci = i+1;
while(ck-->0)
{
if(ci<0)
ci = codeSize-1;
if(ci==codeSize)
ci = 0;
sum+=code[ci++];
}
ans[i] = sum;
}
else if(k<0)
{
int ck = -k;
int ci = i-1;
while(ck-->0)
{
if(ci<0)
ci = codeSize-1;
if(ci==codeSize)
ci = 0;
sum += code[ci--];
}
ans[i] = sum;
}
else
{
for(int j =0;j<codeSize;j++)
{
ans[j] = 0;
}
break;
}
}
return ans;
}
思路:根据题解的思路:先定义一个哈希表然后遍历arr数组,用哈希表保存每个元素出现的位置。然后去遍历pieces数组,如果该数组中的元素没有在arr中出现过直接返回false,如果这一行的元素大于一个,那么直接用这一行的第二列第三列…与前一列判断,如果位置的差值不是等于1,也返回false,最后循环结束都满足则返回true。
bool canFormArray(int* arr, int arrSize, int** pieces, int piecesSize, int* piecesColSize){
int hash[101] = {0};
for(int i =0;i<arrSize;i++)
{
hash[arr[i]] = i+1;//用哈希表保存每个值在arr中出现的位置。
}
for(int i =0;i<piecesSize;i++)
{
for(int j =0;j<piecesColSize[i];j++)
{
if(hash[pieces[i][j]]==0)
{
return false;
}
if(j>0)
{
int x = hash[pieces[i][j]] - hash[pieces[i][j-1]];
if(x!=1)
{
return false;
}
}
}
}
return true;
}
思路2:先有一个指针i遍历arr数组,然后判断pieces中有没有与arr相同的值,如果没有,最后直返false,如果有,那就进入对piece这一行的判断,这一行判断中不断自增i取得arr的下一个元素,与pieces这一行比较,中途如果i==arrsize则直接返回true,如果遇到两者值不同直接返回false。
bool canFormArray(int* arr, int arrSize, int** pieces, int piecesSize, int* piecesColSize){
int i =0,j=0,k=0;
bool flag = false;
while(i<arrSize)
{
flag = false;//遍历完一次piece将标志位,置false(因为可能会多次遍历pieces)
for(j = 0;j<piecesSize;j++)
{ //arr数组的值不等于pieces的值就直接j++,访问第二行的pieces,并且跳过后面的判断
if(arr[i]!=pieces[j][0])
{
continue;
}
//如果pieces的这一行第一个元素与arr中某个元素相等,则开始匹配pieces这一行是否符合条件
for(k = 0;k<piecesColSize[j];k++)
{
if(arr[i]==pieces[j][k])
{
i++;//符合条件不断自增i
flag = true;//标志位置为true,防止遍历完pieces时,还没匹配完就被返回false了
}
else//不符合直接返回false
{
return false;
}
if(i==arrSize)//i自增的途中,若是i等于了arrsize就是arr数组匹配完了,返回true即可。
return true;
}
}
if(!flag)
return false;
}
return true;
}