代码随想录算法训练营:2/60

非科班学习算法day2 | LeetCode977: 有序数组的平方,Leetcode209: 长度最小的子数组,Leetcode88: 合并有序数组,Leetcode59:螺旋矩阵||

目录

介绍

一、基础概念补充:

1.c++中的vector的构造

2.++i和i++的区别

++i(前缀自增)

i++(后缀自增)

二、LeetCode题目

1.LeetCode977: 有序数组的平方

题目解析

优化思路

2.Leetcode209: 长度最小的子数组

题目解析

优化思路

3.Leetcode88:合并有序数组

题目解析

 4.Leetcode59:螺旋矩阵

题目解析

总结


介绍

包含LC的两道题目,还有相应概念的补充。

相关图解和更多版本:

代码随想录 (programmercarl.com)https://programmercarl.com/#%E6%9C%AC%E7%AB%99%E8%83%8C%E6%99%AF


一、基础概念补充:

1.c++中的vector的构造

默认构造函数:创建一个空的vector

std::vector<T> vec;

填充构造函数:创建一个包含n个复制自val的元素的vector

std::vector<T> vec(n, val);

范围构造函数:根据迭代器范围[first, last)创建一个vector

std::vector<T> vec(first, last);

拷贝构造函数:创建一个新的vector,作为现有vector的副本。

2.++ii++的区别

       ++ii++都是C语言中的自增运算符,它们的作用是使变量i的值增加1。然而,它们在使用上有所不同,具体表现在它们的值何时被更新以及如何影响表达式的计算结果。

++i(前缀自增)
  • ++i首先将i的值增加1,然后再将更新后的值用于表达式的计算。
  • 如果++i是一个独立的语句或者是一个表达式的一部分,它的值会立即更新。
i++(后缀自增)
  • i++首先将i的当前值用于表达式的计算,然后再将i的值增加1。
  • 如果i++是一个独立的语句或者是一个表达式的一部分,它的值在表达式计算完毕后才会更新。

二、LeetCode题目

1.LeetCode977: 有序数组的平方

题目链接:. - 力扣(LeetCode)

题目解析

        首先,根据题目给出的示例一可以想到:对数组元素进行平方操作,得到新数组,对新数组进行冒泡排序即可得到目标数组。

 C++代码如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        for(int i = 0; i < nums.size();i++)
        {
            nums[i] *= nums[i];
        }
        
        //排序
        for (int i = 0; i < nums.size(); i++)
        {
            for(int j = i + 1; j < nums.size(); j++)
            {
                if (nums[j] < nums[i])
                {
                    int temp = nums[i];
                    nums[i] = nums[j];
                    nums[j] = temp; 
                }
            }
        }
        
        //返回目标数组
        return nums;
    }
};

时间复杂度分析:进行了双层的循环嵌套,按照最坏复杂度为O(n^2)

这段代码并不困难,可以在原有数组的基础上进行更新,但是时间复杂度爆炸,虽然在LC上能通过

优化思路

        继续观察题目要求:非递减顺序,平方。首先我们可以确定数组中的数字可能有负有正,但是取平方之后,最大的数不过就是出现在数组的两头。那么就可以借助两个指针(指向需求的位置:头部和尾部)进行比较,并且生成新的数组并且作为返回值。

注意点1:边界条件设置,为了能够遍历到每一个值,不排除最后两个指针重合的情况,所以这里采用left <= right 而不是left < right

注意点2:检查条件之后,进行左/右指针的移动。

注意点3:这里新建了一个vector,并且定义了临时指针指向该vector,left和right并不对prt有任何影响,最后返回的也是这个数组。

 调整之后C++代码如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        //左右指针指向两端
        //检查状态
        //更新数组
        
        int left = 0;
        int right = nums.size() - 1;

        vector<int>result (nums.size());

        for(int prt = nums.size() - 1; prt >= 0 && left <= right ; prt--)
        {
            if(nums[left] * nums[left] < nums[right] * nums[right])
            {
                result[prt] = nums[right] * nums[right];
                right--;
            }
            else
            {
                result[prt] = nums[left] * nums[left];
                left++;
            }
        }
        return result;

    }
};

 时间复杂度分析:按照最坏复杂度为O(n),很有效降低了时间复杂度。

2.Leetcode209: 长度最小的子数组

题目链接:209. 长度最小的子数组 - 力扣(LeetCode)

题目解析

        遍历数组,生成目标数组的左边界;

        嵌套循环,生成目标数组的右边界;        

        对区间内的元素加和判断是否满足条件;

        返回有效数组长度

注意点1:外部循环条件设置:遍历每一个元素作为起始

注意点2:内部循环条件设置:遍历从外部循环锁定的元素[i]之后的所有元素,之后累计求和。

注意点3:这里设置minlen的原因是:除了像LC示例3这种不满足if判断的例子,还可能存在多个满足条件的子数组,这时候就需要满足最小的要求,所以在循环判断的过程中,设定一个当前最小长度的变量,有效防止仅出现一次满足大于target的数组出现就返回的现象。

注意点4:借助break,可以减少计算量。显而易见,在满足了当前搜索数组大于目标时,无需再将j向后检索。使用break跳出当前循环。

 C++代码如下: 

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int minlen = INT_MAX;
        int i = 0;
        while(i < nums.size())
        {
            int sum = 0;
            for(int j = i ;j < nums.size(); ++j)
            {
                sum += nums[j];
                if (sum >= target)
                {
                    minlen =  min(minlen,j - i + 1);
                    break;
                }
            }
            ++i;
        }
        return (minlen == INT_MAX) ? 0 : minlen;
    }
};

 时间复杂度分析:按照最坏复杂度为O(n^2) 

优化思路

       采用滑动窗口来进行:

       基本上还是基于左右两个边界,不过就像是一个区间版滑动变阻器,你的每次循环调试都像是测试电阻(当前值)是否满足目标电阻(条件)。

        先移动右边窗口;

        得到满足的窗口之后,移动左窗口;

        检查是否符合条件。

           

 调整之后C++代码如下:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int left = 0;
        int right = 0;
        int sum = 0;
        int minlen = INT_MAX;

        //右窗口滑动
        for(; right < nums.size(); ++right)
        {
            sum += nums[right];
            while(sum >= target && left <= right)
            {
                minlen = min(minlen,right - left + 1);
                sum -= nums[left];
                ++left;
            }    
        }
        return (minlen == INT_MAX) ? 0 : minlen;

    }
};

 时间复杂度分析:按照最坏复杂度为O(n) 

3.Leetcode88:合并有序数组

题目链接:88. 合并两个有序数组 - 力扣(LeetCode)

题目解析

       以下思路借助于LC示例一给出:

        设置两个指针分别指向两个数组的起始位置;

        比对两个指针指向的大小;

        用小的数填充新建的m+n数组;

        遍历有效两个数组其中某一个的全部有效数字位置之后,检查另一个数组的剩余有效数字。

注意点1:在这里,我给出的快慢指针的说法并不准确,更好的应该是说1和2指针

注意点2:循环边界条件设置:不允许超出数组有效数字长度,这里有效数组长度指的分别是m和n

注意点3:在其中一个数组先全部填充到a(新建数组)后,要检查另一个数组的状态,将数组中剩余元素添加到新数组中。再次说明,新建数组之后,这三个指针相对独立,不会影响循环取值。

注意点4:根据题目要求,最后替换nums1.

C++代码如下:

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {

        //借助双指针
        //定义快指针指向数组2
        //定义慢指针指向数组1
        int fast = 0;
        int slow = 0;
        int prt = 0;

        vector<int>a (m+n);

        while(slow < m && fast < n && prt < m + n)
        {
            if(nums2[fast] >= nums1[slow])
            {
                a[prt] = nums1[slow];
                slow++;
            }
            else
            {
                a[prt] = nums2[fast];
                fast++;
            }
            prt++;
        }
        while(slow < m)
        {
            a[prt] = nums1[slow];
            slow++;
            prt++;
        }
        while(fast < n)
        {
            a[prt] = nums2[fast];
            fast++;
            prt++;
        }

        for(int i = 0; i < m+n; i++)
        {
            nums1[i] = a[i];
        }
    }
};

 补充:这道题只要说新建空间其实并不算很困难,一开始我沉浸在正向遍历,而且还想不新建空间,最后发现问题的复杂度大大增加。我们假设在数组1中插入数组2,那就要检查数组2中的元素在数组1中元素的相对位置,然后将其插入,数组1的元素向后移动,时间复杂度也爆炸,我还想用数组2来交换数组1的元素,原谅我太菜了,无果。

下面给出一种后向遍历的方法,不用新开辟空间

c++代码如下:

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int p1 = m - 1;
        int p2 = n - 1;
        int p = m + n - 1;

        while(p1 >= 0 && p2 >= 0 && p >= 0)
        {
            if (nums1[p1] > nums2[p2])
            {
                nums1[p] = nums1[p1];
                p1--;
            }
            else
            {
                nums1[p] = nums2[p2];
                p2--;
            }
            p--;
        }

        //处理剩余nums2,因为此时没有开辟新空间,极端情况是要么num2全部比num1小
        //此时相当于移动了整个num1;要么是nums2全部插入

        while(p2 >= 0)
        {
            nums1[p] = nums2[p2];
            p--;
            p2--;
        }

    }
};

以上代码运行之后发现示例3跑不过,即nums1 = [0],m = 0的情况, 头部添加检查:

        if(m == 0)
        {
            nums1 = nums2;
        }

 4.Leetcode59:螺旋矩阵

题目链接:59. 螺旋矩阵 II - 力扣(LeetCode)

题目解析

       以下思路借助于LC示例给出:

        首先给定循环的边界条件,四个边界;

        预先定义需要作为容器的矩阵matrix;

        按照左开右闭的原则遍历四条边;

        检查元素遗漏。

注意点1:vector里面是可以再放vector的,这也是C++常见的矩阵创建方式,详细见前面说的vector构造。

注意点2:最重要的无疑是边界条件的设置。设置了四个初始边界之后,我们要清楚,自己写的遍历,每一次不变的是什么,变的是什么。

比如在遍历第一行时候,明显变化的是纵坐标,也就是martix[~][i]里的i在变化,它的变化区间是什么?是左闭右开。这样子就可以一步一步确定遍历的条件。当然,外部循环的条件就是,边界不允许交叉。

注意点3:最后设置了一个检查,根据实例,不难发现,如果数字为奇数,比如3,那么最后(9)肯定会有一个单独的中心点,所以我们检查是否为奇数,如果是,那么就单独填补一下这个数字。

 C++代码如下:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        //模拟螺旋顺序
        //坚持左闭右开原则

        //预分配
        vector<vector<int>> matrix(n, vector<int>(n, 0));
        //定义循环变量
        int left = 0;
        int right = n - 1;
        int top = 0;
        int bottom = n - 1;

        int mid = n / 2;
        int r = 0;
        while(left <= right && top <= bottom)
        {
            //遍历第一条边
            for(int i = left; i< right; i++)
            {
                matrix[top][i] = ++r;
            }
            top++;  //向下一行

            //遍历第二条边
            for(int j = top - 1; j < bottom; j++)
            {
                matrix[j][right] = ++r;
            }
            right--;

            //遍历第三条边
            for(int i = right + 1; i > left; i--)
            {
                matrix[bottom][i] = ++r;
            }
            bottom--;

            //遍历第四条边
            for(int j = bottom + 1; j > top - 1; j--)
            {
                matrix[j][left] = ++r;
            }
            left++;
        }
        if(n % 2)
        {
            matrix[mid][mid] = ++r;
        }
        return matrix;
    }
    
};

总结


打卡第二天,坚持!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值