每天五道算法题(一)——二分查找/移除元素/有序数组的平方/长度最小的子数组/螺旋矩阵II

数组-二分查找

题目链接

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

  • 你可以假设 nums 中的所有元素是不重复的。
  • n 将在 [1, 10000]之间。
  • nums 的每个元素都将在 [-9999, 9999]之间。

题上已知是有序数组且数组中无重复元素,这两个条件是使用二分查找的必要条件,说明我们可以考虑一下能不能用二分查找实现算法

注意:一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的 ,就不能使用二分查找了

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        while (left <= right) {
            int middle = (left + right)/2;
            if (nums[middle] > target) {
                right = middle - 1; // target 在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,所以[middle + 1, right]
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

数组-移除元素

题目链接

给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。

示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。

示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

暴力解法:

两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组.时间复杂度为O(n^2),空间复杂度为O(1)

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        for (int i = 0; i < size; i++) {
            if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
                for (int j = i + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下表i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;

    }
};

双指针法:

双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作,在数组和链表的操作中非常常见,很多考察数组、链表、字符串等操作的面试题,都使用双指针法

// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slowIndex = 0; 
        for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {  
            if (val != nums[fastIndex]) { 
                nums[slowIndex++] = nums[fastIndex]; 
            }
        }
        return slowIndex;
    }
};

数组-有序数组的平方

题目链接

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

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

示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

暴力排序:每个数平方之后再排序 :

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) {
        for (int i = 0; i < A.size(); i++) {
            A[i] *= A[i];
        }
        sort(A.begin(), A.end()); // 快速排序
        return A;
    }
};

这里用伪c语言说一下上述的快速排序方法sort()吧:

void QSort(SqList &L,int low,int high) { //对顺序表L快速排序
    if (low < high) { //长度大于1
        privotloc = Partition(L,low,high); //将L.[low...high]一分为二,privotloc为中轴元素排好序的位置
        QSort(L,low,privotloc-1);//对低字标递归排序
        QSort(L,privotloc+1,high);//对高子表递归排序
    }
}

int Partition(SqList &L,int low,int high) {
    L.r[0] = L.r[low];
    privotkey = L.r[low].key;
    while (low < high) {
        //嵌套的这两个while循环的low<high是必不可少的,因为有可能这个嵌套的while跑着跑着low >= high
        while (low < high && L.r[righ].key >= privotkey) --high;
        L.r[low] = L.r[high];
        while (low < high && L.r[righ].key <= privotkey) ++low;
        L.r[high] = L.r[low];
    }
    L.r[low] = L.r[0];
    return low;
}

Qsort()的时间复杂度:O(logn)

Partition:因为这个函数没调用一次所有函数就都和privotkey比较一次,故时间复杂度是O(n)

所以暴力排序总体时间复杂度是O(n+logn),可以说是O(nlogn)的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,我记为 O(n + nlogn)

双指针法:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) {
        int k = A.size() - 1;
        vector<int> result(A.size(), 0);//创建一个result数组,我起初认为题中没有说可以用额外的数组那就不能用,现在我认为只要题中没有不让干什么事那就可以干
        for (int i = 0, j = A.size() - 1; i <= j;) { // 注意这里要i <= j,因为最后要处理两个元素
            if (A[i] * A[i] < A[j] * A[j])  {
                result[k--] = A[j] * A[j];
                j--;
            }
            else {
                result[k--] = A[i] * A[i];
                i++;
            }
        }
        return result;
    }
};

时间复杂度为O(n)


数组-长度最小的子数组

题目链接

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

示例:

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

暴力解法:时间复杂度O(n^2),空间复杂度O(1)

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX; // 最终的结果
        int sum = 0; // 子序列的数值之和
        int subLength = 0; // 子序列的长度
        for (int i = 0; i < nums.size(); i++) { // 设置子序列起点为i
            sum = 0;
            for (int j = i; j < nums.size(); j++) { // 设置子序列终止位置为j
                sum += nums[j];
                if (sum >= s) { // 一旦发现子序列和超过了s,更新result
                    subLength = j - i + 1; // 取子序列的长度
                    result = result < subLength ? result : subLength;
                    break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
                }
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

滑动窗口:

  • 其实滑动窗口也可以理解为双指针法的一种,只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更合适一些
  • 窗口就是满足其和>=s的长度最小的连续子数组
  • 窗口的起始位置如何移动:如果当前窗口的值大于s了,先要判断一下窗口能否缩小(就是窗口的左边界能否向右移动并且窗口仍>=s)
  • 判断结果如果能缩小就循环缩小直到不能再缩小,然后窗口右边界右移,如果不能缩小就直接右边界右移
class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX;
        int sum = 0; // 滑动窗口数值之和
        int i = 0; // 滑动窗口起始位置
        int subLength = 0; // 滑动窗口的长度
        for (int j = 0; j < nums.size(); j++) {
            sum += nums[j];
            // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= s) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

时间复杂度:O(n)

空间复杂度:O(1)

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


螺旋矩阵II

题目链接

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

示例:

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

模拟顺时针画矩阵的过程:

  • 填充上行从左到右
  • 填充右列从上到下
  • 填充下行从右到左
  • 填充左列从下到上

由外向内一圈一圈这么画下去

这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开又闭的原则,这样这一圈才能按照统一的规则画下来

这里我用左闭右开的原则:

class Solution {
    public int[][] generateMatrix(int n) {
        int i=0,j=0;//数组的坐标,用来确定每次在哪个位置填充数字
        int num=1;//定义填充数字
        int m=1;//1.用来记录循环了几次,从而判断是否要继续while循环2.规定每一边画到哪个位置
        int[][] a=new int[n][n];
        while(m<n/2+1)//规定循环次数
        {
            while(j<n-m) a[i][j++]=num++;//规定每一边画到哪个位置
            while(i<n-m) a[i++][j]=num++;
            while(j>m-1) a[i][j--]=num++;
            while(i>m-1) a[i--][j]=num++;
            //这一圈画完后ia[i][j]定位到这一圈的起始位置,所以i,j需要自增
            i++;
            j++;
            m++;
        }
        if(n%2==1) a[n/2][n/2]=n*n;//如果n为奇数则需要在中心点额外填充数字
        return a;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

认真生活的灰太狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值