一维数组:连续内存空间上的相同类型数据的集合
二维数组:对于二维数组nums,nums[0]实际上是一个指向第一行数组的指针,借用代码随想录中的图片如下:
同时,二维数组在内存中不一定连续,与语言特性有关,但对于静态数组来说,内存是连续的。
对于c++, 二维数组是连续的,下图为打印的2*3数组元素(int)的地址
题目:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
思路:在已排序数组中使用二分查找法,数组的常见操作
关键点:重在边界处理;且是已排序数组;防溢出操作
复杂度:时间O(log n), 空间O(1)
补充:防溢出操作: mid = l + (r - l)/2;
代码:
//二分查找算法,重在边界处理上
//关键字:已排序数组,
//闭区间[0, n-1] , whilr(l <= r), r = mid-1
//防溢出操作
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
//二分查找
int r, l, mid;
r = nums.size() - 1;
l = 0;
while(l <= r){
mid = l + ((r - l)/2);//防止溢出,等同于mid = (r + l)/2;
int temp = nums[mid];
if(temp == target)
return mid;
else if(temp > target){
r = mid - 1;
}else
l = mid + 1;
}
return l;
}
};
题目:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。link
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
为什么返回数值是整数,但输出的答案是数组呢?请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
思路1:暴力解法双循环 时间O(n^2), 遇到等于val的值,则把后面的数都提前;代码略
思路2:双指针,代替双循环,时间O(n)
关键点:「双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组和链表操作的面试题,都使用双指针法。」
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int fastindex, lowindex = 0;
for( fastindex = 0; fastindex < nums.size(); fastindex++){
if(nums[fastindex] != val)
nums[lowindex++] = nums[fastindex];
}
return lowindex;
双指针优化,即对于第一个元素是要删除的值,则需要将整个数组提前一个的例子
因为元素的顺序可以改变,所以如果左指针指向的是val,可以考虑用右指针指向元素取代左指针元素,然后右指针向前一个,即【1,2,3,4,5】,val = 1,则用5取代1
//参考官网
int right = nums.size() -1, left = 0;
while(left <= right){
if(nums[left] == val){
nums[left] = nums[right];
right--;
}else{
left++;
}
}
return left;
}
题目:给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
思路1:暴力求解,双循环,时间O(n^2)
思路2: 前缀和数组+二分查找:O(nlogn), 即对于每一个位置i,查找后面某 个最小位置j,使得i和j之间的和大于等于target;位置j就需要在前缀和数组中二分查找;注意点是nums数组需要没有负数,即保证前缀和数组升序。 另外,在c++中有库函数low_bound实现二分查找位置j
(在解题评论区看到有人说明明有O(n)的解法,为什么还要考虑O9nlogn)的解法,有人回答现实中可能会因为各种考量,并不能只追求最优解法,其他的思想同样会用到。甚有道理。)
思路3:滑动窗口 时间O(n),空间O(1);滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。关键点在滑动窗口开端i和结尾j的配合上,
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int i , j, len; //滑动窗口起止,长度
int result = nums.size() +1;
int sum = 0;
i = j = 0;
int s = nums.size();
for(j = 0; j < s; j++){//将窗口一直向后扩大
sum += nums[j];
while(sum >= target){ //达到扩大边界,则开端i向后移
len = (j-i)+1;
result = len < result ? len : result;
sum -= nums[i++];
}
}
if(result > nums.size())
return 0;
else
return result;
}
};
对于滑动窗口,有一个思想是先得到初始窗口,然后保证后面窗口只平移或缩小,那么就不用专门维护一个最小长度,但逻辑上没有原始的好理解
思路1: 模拟法,设定边界
关键点:循环不变量原则,前开后闭。即上下左右边都是填充相同的数量,对于上例,分别是12 34 56 78
「本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。」
补充:二维vector数组的初始化
//直接初始化
vector<vector<int>> ans(r,vector<int>(c,0));
//或者用resize
vector<vector<int> > res;
res.resize(r);//r行
for (int k = 0; k < r; ++k){
res[k].resize(c);//每行为c列
}
代码:
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
int l_num = n-1; //一边的元素个数,前开后闭
int start = -1, end = -1; //start,end确切地说应该是每一圈左上角坐标
int count = 1;
//初始化二维的vector
vector<vector<int>> ans(n,vector<int>(n,0));
for(int i = n; i >0; i = i-2){
l_num = i-1;
start++;
end++;
//奇数n的最后一圈
if(l_num == 0 && (n%2 != 0)){
ans[start][end] = count++;
break;
}
//对于每一圈,共四边
//上边
for(int j = 0; j < l_num ; j++ ){
ans[start][end+j] = count++;
}
//右边
for(int j = 0; j < l_num; j++){
ans[start+j][end+l_num] = count++;
}
//下边
for(int j = 0; j < l_num ; j++ ){
ans[start + l_num][end+l_num - j] = count++;
}
//左边
for(int j = 0; j < l_num; j++){
ans[start+l_num - j][end] = count++;
}
}
return ans;
}
};
优化代码:通过改变left,top,right,bottom四个边界值,进行填充,取自精华帖krahets
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
int num = 1;
int left = 0, top = 0, right = n - 1, bottom = n - 1;
//初始化数组
vector<vector<int>> res(n,vector<int>(n));
while (num <= n*n ) {
//left to right
for (int i = left; i <= right; ++i) res[top][i] = num++;
++top;
//top to bottom
for (int i = top; i <= bottom; ++i) res[i][right] = num++;
--right;
//right to left
for (int i = right; i >= left; --i) res[bottom][i] = num++;
--bottom;
//bottom to top
for (int i = bottom; i >= top; --i) res[i][left] = num++;
++left;
}
return res;
}
};
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。link
示例 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]
1、简单解法:求平方后再排序,时间复杂度O(nlogn)
**2、双指针法:**根据数据的排序,可以先找到正负边界,然后两个指针分别向两侧扩展,哪个指针的数值小则优先加入数组ans(从左向右填ans)。时间复杂度O(n)
注意点:数组下标和数组值经常弄混
一个新的边界判断code方法,看注释部分和后面的版本
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int fast, low;
fast = low = 0;
vector<int> ans;
//找到非负边界
for(fast = 0; fast < nums.size(); fast++ ){
if(nums[fast] >= 0){
break;
}
}
low = fast - 1;
// while(low >= 0 && fast < nums.size()){
// if(abs(nums[low]) <= abs(nums[fast])){
// ans.push_back(nums[low] * nums[low]);
// low--;
// }else{
// ans.push_back(nums[fast] * nums[fast]);
// fast++;
// }
// }
// //处理边界问题
// if(fast < nums.size()){
// for(; fast < nums.size(); fast++){
// ans.push_back(nums[fast] * nums[fast]);
// }
// }else{
// for(; low >= 0; low--){
// ans.push_back(nums[low] * nums[low]);
// }
// }
while(low >= 0 || fast < nums.size()){
//处理边界问题
if(low < 0 ){
ans.push_back(nums[fast] * nums[fast]);
fast++;
}else if(fast == nums.size()){
ans.push_back(nums[low] * nums[low]);
low--;
}else if(abs(nums[low]) <= abs(nums[fast])){
ans.push_back(nums[low] * nums[low]);
low--;
}else{
ans.push_back(nums[fast] * nums[fast]);
fast++;
}
}
*/
**3、双指针法:**和2一样的思想,但是两个指针初始时指向nums两侧,数值大的优先放入ans右端(即从右向左填ans,所以需要提前设定ans大小)。和2相比,减少了正负交界处的寻找,且指针问题边界判断简单。时间复杂度O(n)
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int fast, low;
fast = low = 0;
vector<int> ans(nums.size(), 0);
int k = nums.size() - 1;
for(low = 0, fast = nums.size() - 1; low <= fast;){
if(abs(nums[low]) > abs(nums[fast])){
ans[k--] = nums[low] * nums[low];
low++;
}else{
ans[k--] = nums[fast] * nums[fast];
fast--;
}
}
return ans;
}
};
注:按照代码随想录的刷题指南进行
参考链接:https://leetcode-cn.com/circle/article/wGp7Y9/
题目来源:力扣(LeetCode)