算法记录 | Day2数组基础Ⅱ

 977-有序数组的平方

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

输入:nums = [-4,-1,0,3,10]

输出:[0,1,9,16,100]

解释:平方后,数组变为 [16,1,0,9,100] 排序后,数组变为 [0,1,9,16,100]

解题思路

  • 该题直接暴力平方后排序,时间复杂度O(n+nlogn);双指针法,时间复杂度O(n)
  • 数组其实是有序的,只不过负数平方之后可能成为最大数了,暴力法没有充分利用这点
  • 数组平方的最大值就在数组的两端,不是最左边就是最右边,越往中间越小,因此可以考虑双指针

 暴力排序法

Class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        # 1.暴力排序 
        for i in range(0,len(nums)):
            nums[i] *= nums[i];
            nums.sort();
            return nums

 暴力排序法+列表推导法

Class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        # 2.暴力排序+列表推导
        for i in range(0,len(nums)):
            return sorted(x*x for x in nums)

注:列表推导法:结构是在一个中括号里包含一个表达式 + 一个for语句+ 0个 或 多个 for 或 if 语句;返回结果是一个新的列表;逻辑上等价于一个循环语句,只是形式上更加简洁。如:aList = [w.strip() for w in freshfruit]。

 双指针法

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        # 3.双指针
        i,j=0,len(nums)-1;
        loc=len(nums)-1;
        results=[0]*len(nums) # 需要提前定义列表,存放结果
        while i<=j:
            if (nums[i]**2<nums[j]**2):  # 左右边界进行对比,找出最大值
                results[loc]=nums[j]**2;
                j-=1; # 右指针往左移动
            else:
                results[loc]=nums[i]**2;
                i+=1; # 左指针往右移动
            loc-=1; # 存放结果的指针需要往前平移一位
        return results

难点

  • 双指针法变化较多,难以想到

 总结

因为这个数值排列一定是两边的绝对值相互比较,中间的值更小,可以用双指针。与昨天不同的是,27-移除元素双指针(快慢指针)是同一个方向的,977双指针是相向而行的。

209-长度最小的子数组

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

输入:target = 7, nums = [2,3,1,2,4,3]

输出:2

解释:子数组 [4,3]是该条件下的长度最小的子数组。

解题思路

  • 该题直接用双重循环,时间复杂度O(n^2),力扣会超时
  • 可以运用双指针的变种:滑动窗口法,时间复杂度O(n),通过不断的调节子序列的起始位置和终止位置,得出想要的结果
  • 在暴力解法中,是一个for循环为滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程;
  • 滑动窗口法中,用一个for循环实现,那么这个循环的索引,一定是表示 滑动窗口的终止位置。否则又回到暴力解法。

暴力法(超时)

暴力的解法就是两层for循环

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        #1. 暴力(不用把起始值单独拿出来算
        min_len = float("inf"); # 正无穷
        for i in range(0,len(nums)):
            cur_sum = 0; # 起始移动后需要清零
            for j in range (i,len(nums)):
                cur_sum += nums[j];
                if cur_sum >= target:
                    min_len = min(min_len,j-i+1);
                    break;
        return min_len if min_len!=float('inf') else 0;

双指针法(滑动窗口法)

一个for循环控制终止位置,再根据当前子序列和大小的情况,不断调节子序列的起始位置

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        #2. 双指针(滑动窗口法)
        i,j = 0,0;
        cur_sum = 0;
        min_len = float('inf')
        for j in range(0,len(nums)):
            cur_sum += nums[j]; # 当前的累加值。不用每回清零,渐少重复运算
            while cur_sum >= target: # 当前累加值大于目标值
                min_len = min(min_len,j-i+1)
                cur_sum -= nums[i]
                i += 1;
        return min_len if min_len!=float('inf') else 0

窗口的移动:

  • 窗口的起始位置 i 如何移动:如果当前窗口的值大于target了,窗口就要向前移动了(也就是该缩小了)
  • 窗口的结束位置 j 如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

难点

  • 题目在什么条件限制下,可以利用滑动窗口的解法
  • 关于时间复杂度:不要以为for里放一个while就以为是O(n^2), 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

 总结

双指针法一般是减少循环层数,数组当中目前接6触到的感觉可分为2类:① 一个负责循环遍历,一个负责记录/更新(快慢指针法、滑动窗口法);② 相向的指针,职能相同,对称(977有序数组平方)。

 59-螺旋矩阵Ⅱ

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

输入:n = 3

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

解题思路

  • 本题属于模拟过程,不涉及算法
  • 求解本题要坚持循环不变量原则(二分法也),解决拐角的处理
  • 这里边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就要反复调节,很可能在循环中迷失方向。
  • 一圈下来,我们要画四条边,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。

class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        nums = [[0]*n for _ in range(n)];
        count = 1;
        for offset in range (1,n//2+1):  # 每循环一层偏移量加1;n为偶数循环n//2次,因为一次少2宽度
            for j in range(offset-1,n-offset): # 从左至右,左闭右开
                nums[offset-1][j] = count;
                count += 1;
            for i in range(offset-1,n-offset): # 从上至下,左闭右开
                nums[i][n-offset] = count;
                count += 1;
            for j in range(n-offset,offset-1,-1): # 从右至左,左闭右开
                nums[n-offset][j] = count;
                count += 1;
            for i in range(n-offset,offset-1,-1): # 从下至上,左闭右开
                nums[i][offset-1] = count;
                count += 1;
        if n%2 != 0: # 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
            nums[n//2][n//2] = count;
        return nums;

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

难点

  • 找到循环不变量,确定边处理的统一原则,解决拐角问题
  • 循环次数:n为偶数循环n//2次,因为一次少2宽度/高度;n为奇数,需要单独给矩阵最中间的位置赋值

 总结

循环不变量原则是写程序中的重要原则。平时可能感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实真正解决题目的代码都是简洁的,或者有原则性的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值