数组
二分查找
题目
力扣题目链接
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
适用范围
数组有序,数组中无重复元素
思路
每次找数组的中点,然后与目标值进行对比。
- if(nums[mid] > target ) high = mid - 1
- else if((nums[mid] < target )) low = mid + 1
- else 找到,返回对应下标
循环终止条件判断,while(low <= high) 此时low == high有意义,所以要加等号。
代码
class Solution { public: int search(vector<int>& nums, int target) { //二分查找 int n = nums.size(); int left = 0, right = n - 1; //定义初始查找边界为左闭右闭while(left <= right){ //等号要加上 int mid = left + (right - left)/2; //等同于 (left+right)/2,防止数组溢出if(nums[mid] > target) right = mid - 1; //在左半部分查找 else if(nums[mid] < target) left = mid + 1; //在右半部分查找 else return mid; //找到,返回对应下标 } return -1; //未找到,返回-1 }
};//在c++的语法当中,存在内置函数进行二分的查找,在编程的时候可以采用内置函数的方法
//lower_bound(val),在数组中找到大于等于val值的第一个元素下标位置
//upper_bound(val),在数组中找到大于val值的第一个元素下标位置
//时间复杂度为O(logn)
//空间复杂度为O(1)
移除元素
题目
力扣题目链接
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
思路
数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。
所以采用双指针的写法对原先的内存进行覆盖
代码
写法一
- 定义快慢指针
- left指针作为慢指针,用来更新元素下标,存储不等于val值的元素
- right指针作为快指针,用来遍历数组
一次循环,只有在right指针不等于值val的情况下,left才进行移动。
class Solution { public: int removeElement(vector<int>& nums, int val) { //双指针,当数组下表为val的时候,left不移动,只有当数组值不为val的时候,left才进行移动int n = nums.size(); int left=0; //left作为数组的存储下标,存储不等于val值的元素 int right = 0; //right作为遍历数组的下标 for(right = 0; right < n; right++){ if(nums[right] != val){ nums[left++] = nums[right]; } } return left; }
};
// 时间复杂度:O(n)
// 空间复杂度:O(1)
写法二
类似于快速排序的思想,左指针用来寻找等于val值的元素,右指针来寻找不等于val值的元素,然后找到之后,进行元素之间的交换,这个写法改变了元素之间的相对位置。
class Solution { public: int removeElement(vector<int>& nums, int val) { /*写法二也是双指针写法,但是与第一种有些区别。左右指针,一个从头开始找元素等于val,一个从末尾开始找不是val的,然后元素之间的值进行交换*/int n = nums.size();int left = 0, right = n - 1;while(left <= right){while(left <= right && nums[left] != val) left++;while(left <= right && nums[right] == val) right--;if(left <= right){nums[left++] = nums[right--];}}return left;}
};
有序数组的平方
题目
力扣题目链接
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
思路
双指针解法
一个指针从头开始比较,
一个指针从尾开始比较,
大的数的话,放在最后面,然后对应下标减去1,
一个指针在每次比较的时候往后移,用于计数。
代码
- 定义左右指针
- i指针作为左指针,比较负数的最大元素
- j指针作为右指针,用来比较正数的最大元素
- pos指针用来模拟下标的移动
class Solution {
public:vector<int> sortedSquares(vector<int>& nums) {//双指针解法int n = nums.size();vector<int> ans(n,0);int i = 0, j = n - 1, pos = n - 1;while(pos >= 0){if(nums[i] * nums[i] > nums[j] * nums[j]){ans[pos--] = nums[i]*nums[i];i++;}else{ans[pos--] = nums[j]*nums[j];j--;}}return ans;}
};
// 时间复杂度:O(n)
// 空间复杂度:O(n)
长度最小的子数组
题目
力扣题目链接
给定一个含有 n
个正整数的数组和一个正整数
target
。
找出该数组中满足其总和大于等于 target
的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度**。**如果不存在符合条件的子数组,返回 0
。
思路
经典滑动窗口思路题
也是类似于双指针的解法,定义两个指针left
和right
,分别代表滑动窗口的左右边界,同时维护一个变量sum存储子数组,也就是滑动窗口的元素之和。
- 刚开始,
left
和right
都指向0 - 然后右指针不断往前走,然后
num[end]
加到sum
中 - 如果说
sum
大于s
,说明此时可以对左边界指针也就是left
进行更新,此时获取当前最短的滑动窗口值,然后左指针不断进行移动。此时需要将已经不在滑动窗口里面的值减去 - 不断地进行迭代,直到右指针走到了数组的末尾,就可以结束了。
代码
- 定义左右指针
- i指针作为左指针,作为滑动窗口的左边界
- j指针作为右指针,作为滑动窗口的右边界
- sum用来计算滑动窗口的总合,方便左右指针进行更新
class Solution {
public:int minSubArrayLen(int target, vector<int>& nums) {//滑动窗口典型代表题目int n = nums.size();int minSize = n + 1; //初始化滑动窗口的长度为n+1int flag = 0;int i = 0, j = 0;int sum = 0;while(j < n){sum+=nums[j++]; //右指针往后移动while(sum >= target){ //滑动窗口总和大于target时,可以求最小滑动窗口的长度,以及更新左指针minSize = min(j - i, minSize);sum -= nums[i++]; //左指针不断往后移动}}//三目运算符,如果滑动窗口的值没有进行改变的话,说明是不存在return minSize == n + 1 ? 0 : minSize;}
};
螺旋矩阵II
题目
力扣题目链接
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
思路一
-
模拟题,这里设置了一个loop代表顺时针转了几圈,然后根据圈的数量进行模拟左右边界。
-
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
-
这里有一个比较值得注意的点是,这里需要保持一个代码规划,就是左右边界的问题,每条边都需要遵循左闭右开的原则。
-
还有一个需要注意的就是这里loop循环一圈是单数,所以如果n为奇数的话,会剩下最后一个中间的数没有填充,所以最后面还需要判断n是否为奇数。
代码
class Solution {
public:vector<vector<int>> generateMatrix(int n) {vector<vector<int>> matrix(n, vector<int>(n));int i = 0, j = 0, loop = 0;int num = 1;//螺旋矩阵,最重要的几个点,根据圈数来决定是否要结束循环//每次循环的左右边界都需要确定好,是左闭右开,还是左闭右闭//圈数如果为偶数,直接循环遍历,但是如果圈数为奇数的话,就需要对最里面的一个数单独赋值while(loop < n/2){//最上面的一行,根据圈数来决定起始位置\终止位置和每圈的个数,列在增大for(i = loop; i < n-loop-1; i++) matrix[loop][i] = num++;//最右边的一行,根据圈数来决定起始位置\终止位置和每圈的个数,行在增大for(j = loop; j < n-loop-1; j++) matrix[j][n-loop-1] = num++;//最下面的一行,根据圈数来决定起始位置\终止位置和每圈的个数,列在减小,初始值可以利用前面的ifor(;i > loop; i--) matrix[n-loop-1][i] = num++;//最左边的一行,根据圈数来决定起始位置\终止位置和每圈的个数,行在减小,初始值可以利用前面的jfor(;j > loop; j--) matrix[j][loop] = num++;//循环完一圈,继续下一圈loop++;}//如果圈数为奇数,需要对最里面的数单独赋值if(n % 2)matrix[loop][loop] = num;return matrix;}
};
思路二
- 设置上下左右边界,四个值进行模拟。整体理解难度和模拟难度要比思路一要简单很多,代码实现的话同样需要遵守边界一致性原则,这里采用的是左闭右闭。
- 模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
代码
class Solution {
public:vector<vector<int>> generateMatrix(int n) {vector<vector<int>> matrix(n, vector<int>(n));//模拟,设置上下左右边界,每次遍历完某一行或者某一列,边界进行换int num = 1;//设置上下左右边界int left = 0, high = 0, right = n - 1, low = n - 1;while(true){for(int i = left; i <= right; i++) matrix[high][i] = num++;if(++high > low) break;for(int j = high; j <= low; j++) matrix[j][right] = num++;if(--right < left) break;for(int i = right; i >= left; i--) matrix[low][i] = num++;if(--low < high) break;for(int j = low; j >= high; j--) matrix[j][left] = num++;;if(++left > right) break;}return matrix;}
};