代码随想录算法训练营第二天 |-数组篇977&209&59

数组977-有序数组的平方

今天状态不好(叹气)。

题目&难度

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
提示:

1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 已按 非递减顺序 排序

示例

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

写在前面

通读一遍题目,可以知道的是,这里的非递减排序可以解释如下:

∀ i < j , A [ i ] ≥ A [ j ] \forall i<j,A[i] \geq A[j] i<j,A[i]A[j]

**快速排序:**排序算法中效率很高但不够稳定的算法,时间复杂度为 O ( log ⁡ n ) ∼ O ( n 2 ) O(\log{n}) \sim O(n^2) O(logn)O(n2)

算法思想:

​ 这里其实是借用的双指针思路,一个左指针,一个右指针,两者指向边界元素;基准元素定下后,右指针先移动,左指针再移动,指针所指的数大小与基准元素比较,若是右边较基准元素小而左边较基准元素大则两者互换;如此往复,直到两指针所指位置重合结束(递归思想)。

这里参考大佬的分析过程,贼细致,认真观摩学习了。

参考文章:

https://blog.csdn.net/qq_40941722/article/details/94396010?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168197439616800192264664%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=168197439616800192264664&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-94396010-null-null.142v85wechat,239v2insert_chatgpt&utm_term=%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F&spm=1018.2226.3001.4187

算法——暴力+快速排序&双指针法

1.暴力+快速排序

这里就不多对暴力解法进行赘述了,上一篇博客有说了暴力解法的相关,这边既然已经分析清楚了,那就直接see see code。

这里的主函数不是leecode的标准解答里的,是我自己调试方便写的。

我个人喜欢把东西拆开揉碎了看,所以代码就没有那么酷炫,比较朴实吧(笑,耸肩),不过更多的是我不够强罢了。

#include<stdio.h>

//暴力+快排

//数组自身元素求平方
int* multip(int *nums, int returnSize){
	int numsSize = returnSize;
	for(int i = 0; i < numsSize; i++)
		nums[i] = nums[i] * nums[i];
	return nums;
}

//用来交换的函数
void swap(int nums[], int left, int right){
    int temp = nums[left];
    nums[left] = nums[right];
    nums[right] = temp;
}

//分区操作
int partition(int nums[], int left, int right){
	//基准值,一开始选数组第0位
    int base = nums[left];

    while(left < right){
        //右边先走(不能调换顺序!)
        while(left < right && base <= nums[right]){
            right--;
        }
        swap(nums, left, right);

        //左边走
       while(left < right && base >= nums[left]){
            left++;
        }
        swap(nums, left, right);
    }

    //返回基准值的位置
    return left;
}

//快速排序
int* quickSort(int nums[], int left, int right){
    if(left < right){
        //基准值
        int base = partition(nums, left, right);
		//递归开始
        //左半边排序
        quickSort(nums, left, base - 1);
        //右半边排序
        quickSort(nums, left+1, right);
    }
	return nums;
}


int main(){
	int nums_[] = {5,13,6,24,2,8,19,27,6,12,1,17};
	//计算数组长度
	int numsSize = sizeof(nums_) / sizeof(int);

	int* nums = multip(nums_, numsSize);

	int *realnums = quickSort(nums, 0, numsSize - 1);

	for(int i = 0; i < numsSize; i++)
		printf("%d\n", nums[i]);

	return 0;
}

所得结果:

备注:现在是2023/4/20/23:26

在这里插入图片描述

嗯……就是用官方的函数的话不知道为什么一直不太行,所以就自己写主函数了。嗯,感觉……有空的话要好好看下这个问题。

不建议拿到力扣上运行,因为我感觉不太行。

算法复杂度分析

时间复杂度:左右遍历为 O ( n ) O(n) O(n),而进一步递归的复杂度为 O ( log ⁡ 2 n ) O(\log_{2}{n}) O(log2n),两者结合为 O ( n l o g 2 n ) O(nlog_{2}{n}) O(nlog2n)

空间复杂度: O ( log ⁡ 2 n ) O(\log_{2}{n}) O(log2n),哎,这里不要看了吧,我感觉今天写的很不好。以后还要好好看下这个问题。

但是我感觉还是没有好好搞清楚这个问题,不够透彻,我觉得这里不能误导大家。

2.双指针法

来了,所谓的简便方法。

题目里说到,非降序,是升序排列的,那么经过平方后我们需要注意三种情况:

1.数组元素均为非负数,直接一路递增,很简单。

2.数组元素均为非正数,经过平方后,为降序。

3.数组里有负数:原数组元素从负数递增到正数,经过平方后需要讨论分界。

综上所述,最大值一定不在中间,而在两端

int* sortedSquares(int* nums, int numsSize, int* returnSize){
    //returnSize就是返回的数组大小
    *returnSize = numsSize;

    //初始化双指针
    int i = 0;
    int j = numsSize - 1;

    //定义新数组result
    int* result = (int *)malloc(sizeof(int) * numsSize);
    //指向数组末位置,让result数组逆着输出
    int k = numsSize - 1;

    while(i <= j){
        if(nums[i] * nums[i] > nums[j] * nums[j]){
            result[k] = nums[i] * nums[i];
            k--;
            i++;
        }
        else{
            result[k] = nums[j] * nums[j];
            k--;
            j--;
        }
    }return result;
}

时间复杂度: O ( n ) O(n) O(n)

空间复杂度:嘶……力扣说是O(1)就是O(1)了?不行,我迟早要好好研究下这两个问题。

数组209-长度最小的子数组

题目&难度

给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0

难度:中等题。

示例

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

2023/4/20/23:48

受不了了,累了累了,从晚8点15一直写现在。洗漱睡觉,明天再写。

算法——滑动窗口(双指针法)

一刷和二刷的代码:

/*一刷代码-有bug
int minSubArrayLen(int target, int* nums, int numsSize){
    
    //初始化子数组起始位置i,
    //滑动窗口最小长度result,
    //滑动窗口数组元素之和sum
    //滑动窗口长度subLength
    int result=INT_MAX;
    int i=0;
    int sum=0;
    int subLength=0;

    //滑动窗口
    //这里的j表示终止位置
    for(int j=0;j<numsSize;j++){
        sum += nums[j];
        
        //持续滑动过程,必须用while,不可用if
        while(sum>=target){

            subLength=j-i+1;
            result=fmin(result,subLength);
            sum=sum-nums[i];
            

            //变更滑动窗口起始位置
            i++;
        }
    }return result;

}
*/


//二刷代码
int minSubArrayLen(int target, int* nums,int numsSize){
    //滑动窗口(双指针)标记起始位置i
    int i = 0;

    //初始化最小长度result
    int result = INT_MAX;
    //初始化滑动窗口之和sum为0
    int sum = 0;
    //初始化滑动窗口长度
    int subLength = 0;

    //滑动窗口,终止位置为j,由j遍历一遍后面的元素
    for(int j = 0; j < numsSize; j++){
        sum += nums[j];

        //这里因为是滑动窗口,需要循环动作,所以必须用while不能用if
        while(sum >= target){
            //表示连续子数组长度
            subLength = j - i + 1;

            result = fmin(result, subLength);
            //滑动窗口的关键——当sum大于目标值target则将初始位置的
            //值减去,然后再让i往后移动
            sum = sum - nums[i];
            i++;
        }
    }return result;
}

复杂度分析

时间复杂度:O(n)

空间复杂度:O(1)

输出结果:

在这里插入图片描述

无法通过的原因分析:

这是因为没有考虑当滑动窗口之和小于等于target的情况,所以当target=11时,即使滑动窗口走完也无法找到最小的长度,所以会输出INT_MAX,然而正确的情况是此时应该返回0。

关键代码为:return result == INT_MAX ? 0 : result;

代码修正后:

int minSubArrayLen(int target, int* nums,int numsSize){
    //滑动窗口(双指针)标记起始位置i
    int i = 0;

    //初始化最小长度result
    int result = INT_MAX;
    //初始化滑动窗口之和sum为0
    int sum = 0;
    //初始化滑动窗口长度
    int subLength = 0;

    //滑动窗口,终止位置为j,由j遍历一遍后面的元素
    for(int j = 0; j < numsSize; j++){
        sum += nums[j];

        //这里因为是滑动窗口,需要循环动作,所以必须用while不能用if
        while(sum >= target){
            //表示连续子数组长度
            subLength = j - i + 1;

            result = fmin(result, subLength);
            //滑动窗口的关键——当sum大于目标值target则将初始位置的
            //值减去,然后再让i往后移动
            sum = sum - nums[i];
            i++;
        }
    }return result == INT_MAX ? 0 : result;
}

此时运行成功,案例全部通过。

运行结果图:

在这里插入图片描述

以下是失败的提交结果统计(包含第一第二次的刷题提交)

在这里插入图片描述

算法复杂度分析

时间复杂度:O(n)

PS(碎碎念):不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

空间复杂度:O(1)

数组59-螺旋矩阵Ⅱ

题目

给你一个正整数 n ,生成一个包含 1n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix

示例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rSEx6Y3u-1682042517335)(null)]

输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

值得注意的

这道题需要明白的是对于边界条件的判定,如对于左闭右闭、左闭右开之类的选择。这里转圈的边界条件在于左闭右开

比如顶上的是红色,然后留下最后一个节点给右边遍历,留下最下方的节点给地下的一条边,左边就不再读取边1的第一个,如此转圈得到外圈。

内圈的话由于外圈的缘故,因此缩进一个。

在这里插入图片描述

循环不变量:每条边的处理规则必须相同。

算法——按层模拟

第一次提交:

/**
 * 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){
    //初始化返回的结果数组的大小
    *returnSize = n;
    //ColumnSizes的意思是指列的大小
    *returnColumnSizes = (int*)malloc(sizeof(int) * n);

    //初始化返回结果数组ans——但是这一坨代码是真不明白啊
    int** ans = (int**)malloc(sizeof(int) * n);
    for(int i = 0; i < n; i++){
        ans[i] = (int*)malloc(sizeof(int) * n);
        (*returnColumnSizes)[i] = n;
    }

    //设置每循环一个圈的起始位置
    int startX = 0;
    int startY = 0;

    //设置二维数组的中间的位置,当n=3那么中间的数就是(1,1)
    int mid = n / 2;
    //每个圈循环几次,如果n为奇数3,那么loop=1,只是循环一圈
    int loop = n / 2;

    //用来给矩阵中每一个空格赋值
    int count = 1;
    //每一圈循环都需要控制每一条边遍历的长度,就是与其他边对接时的右开
    int offset = 1;

    //定义行列索引
    int i ,j;
    /*这一段是外圈的顺时针遍历*/
    while(loop){

        //顶上的边遍历
        //n-offset为边界,表示不处理最后一个点
        for(j = startY; j < n - offset; j++){
            ans[startX][j] = count++;
        }

        //右边的边遍历
        for(i = startX; i < n - offset; i++){
            ans[i][j] = count++;
        }

        //底下的边遍历(i=2,j=2开始,因此需要让i不变,j自减)
        for(; j > startY; j--){
            ans[i][j] = count++;
        }
        //左边的边遍历(从i=2,j=0开始,此时只需要让i自减)
        for(; i > startX; i--){
            ans[i][j] = count++;
        }

        //第二圈开始时,起始位置各自+1
        startX++;
        startY++;

        //此时进入内圈,所以缩进+1
        offset++;
    }
    /*这里是单独给矩阵中间赋值*/
    if(n % 2 == 1)
        ans[mid][mid] = count;
    
    return ans;
}

提交结果:

在这里插入图片描述

内存溢出,存在数组越界的情况,于是回头复查。

错误排查

经过排查和对比,原来是offset应该自加2,因为是偶数圈;以及loop应该自减1,因为圈数减少了。

在这里插入图片描述

但是还是有错,继续排查。

把代码又改了好几个地方,但是还是报错,是为啥呢?

/**
 * 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){
    //初始化返回的结果数组的大小
    *returnSize = n;
    //ColumnSizes的意思是指列的大小
    *returnColumnSizes = (int*)malloc(sizeof(int) * n);

    //初始化返回结果数组ans——但是这一坨代码是真不明白啊
    int** ans = (int**)malloc(sizeof(int) * n);
    for(int i = 0; i < n; i++){
        ans[i] = (int*)malloc(sizeof(int) * n);
        (*returnColumnSizes)[i] = n;
    }

    //设置每循环一个圈的起始位置
    int startX = 0;
    int startY = 0;

    //设置二维数组的中间的位置,当n=3那么中间的数就是(1,1)
    int mid = n / 2;
    //每个圈循环几次,如果n为奇数3,那么loop=1,只是循环一圈
    int loop = n / 2;

    //用来给矩阵中每一个空格赋值
    int count = 1;
    //每一圈循环都需要控制每一条边遍历的长度,就是与其他边对接时的右开
    int offset = 1;

    //定义行列索引
    int i ,j;
    /*这一段是外圈的顺时针遍历*/
    while(loop){
        i = startX;
        j = startY; 
        //顶上的边遍历
        //n-offset为边界,表示不处理最后一个点
        for(; j < startY + n - offset; j++){
            ans[startX][j] = count++;
        }

        //右边的边遍历
        for(; i < startX + n - offset; i++){
            ans[i][j] = count++;
        }

        //底下的边遍历(i=2,j=2开始,因此需要让i不变,j自减)
        for(; j > startY; j--){
            ans[i][j] = count++;
        }
        //左边的边遍历(从i=2,j=0开始,此时只需要让i自减)
        for(; i > startX; i--){
            ans[i][j] = count++;
        }

        //第二圈开始时,起始位置各自+1
        startX++;
        startY++;

        //此时进入内圈,所以偏移值+2
        offset+=2;

        loop--;
    }
    /*这里是单独给矩阵中间赋值*/
    if(n % 2 == 1)
        ans[mid][mid] = count;
    
    return ans;
}

在这里插入图片描述

这就很胃疼了,我打算下去把晒得被子换一面再回来看,或者干脆下一题。

2023/4/18拖延症犯了前两天,因为是个人学习,所以遇到与些问题一直解决不了的话就会颓废一下,要好好克服一下这点。今天发现是一个地方错了,就是对于两层指针的这里。

在这里插入图片描述

第十五行代码那里,自己之前写成了sizeof(int) * n),因此一直报错,以后要多加注意这种类型题。

以下是正确代码:

/**
 * 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){
    //初始化返回的结果数组的大小
    *returnSize = n;
    //ColumnSizes的意思是指列的大小
    *returnColumnSizes = (int*)malloc(sizeof(int) * n);

    //初始化返回结果数组ans——但是这一坨代码是真不明白啊
    int** ans = (int**)malloc(sizeof(int*) * n);//注意这里的是int*而不是int
    for(int i = 0; i < n; i++){
        ans[i] = (int*)malloc(sizeof(int) * n);
        (*returnColumnSizes)[i] = n;
    }

    //设置每循环一个圈的起始位置
    int startX = 0;
    int startY = 0;

    //设置二维数组的中间的位置,当n=3那么中间的数就是(1,1)
    int mid = n / 2;
    //每个圈循环几次,如果n为奇数3,那么loop=1,只是循环一圈
    int loop = n / 2;

    //用来给矩阵中每一个空格赋值
    int count = 1;
    //每一圈循环都需要控制每一条边遍历的长度,就是与其他边对接时的右开
    int offset = 1;

    //定义行列索引
    int i ,j;
    /*这一段是外圈的顺时针遍历*/
    while(loop){
        i = startX;
        j = startY; 
        //顶上的边遍历
        //n-offset为边界,表示不处理最后一个点
        for(; j < startY + n - offset; j++){
            ans[startX][j] = count++;
        }

        //右边的边遍历
        for(; i < startX + n - offset; i++){
            ans[i][j] = count++;
        }

        //底下的边遍历(i=2,j=2开始,因此需要让i不变,j自减)
        for(; j > startY; j--){
            ans[i][j] = count++;
        }
        //左边的边遍历(从i=2,j=0开始,此时只需要让i自减)
        for(; i > startX; i--){
            ans[i][j] = count++;
        }

        //第二圈开始时,起始位置各自+1
        startX++;
        startY++;

        //此时进入内圈,所以偏移值+2
        offset+=2;

        loop--;
    }
    /*这里是单独给矩阵中间赋值*/
    if(n % 2 == 1)
        ans[mid][mid] = count;
    
    return ans;
}

疑难分析

自己还是对**这种指针不熟悉,需要多做几道题好好体会一下这类表示。

而且这道题目一开始就没有思路,所以必然后面还要二刷好好体会。

算法复杂度分析

时间复杂度:O(n^2),模拟遍历二维矩阵的时间

空间复杂度:O(1)

哼哧哼哧写完了,感觉今天的还是挺难的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值