01.02.01 数组基础知识
学习资料:LeetCode 算法笔记
1. 数组
定义:数组(Array),一种线性表数据结构。它使用一组连续的内存空间,来存储一组具有相同类型的数据。
数组是最基础、最简单的数据结构。
特点:可以进行随机访问,使用下标。
多维数组:二维数组是一个由行列数据元素构成的特殊结构,其本质上是以数组作为数据元素的数组,即「数组的数组」。可以将二维数组看做是一个矩阵,并处理矩阵的相关问题,比如转置矩阵、矩阵相加、矩阵相乘等等。
c++中的数组,使用的是一块存储相同类型数据的、连续的内存空间。
int arr[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};
2. 数组的基本操作
数据结构的操作一般涉及到增、删、改、查共4种情况
2.1 访问元素
使用下标访问第 i i i个元素:
void value(vector<int>& nums, int i){
// 数组的长度判断需额外引入参数或用std::vector<int>
if(i>=0 && i < nums.size())
std::cout << value[i] << std::endl;
}
vector<int> arr = {0, 5, 2, 3, 7, 1, 6};
value(arr, 3);
2.2 查找元素(查)
查找数组中元素值为 v a l val val的位置:
int find(vector<int>& nums, int val){
for(int i = 0; i < nums.size(); i++){
if(nums[i] == val)
return i;
}
return -1;
}
vector<int> arr = {0, 5, 2, 3, 7, 1, 6};
std::cout<<(find(arr, 5))<<std::endl;
2.3 插入元素(增)
在数组尾部插入值为val的元素:
vector<int> arr = {0, 5, 2, 3, 7, 1, 6};
val = 4;
arr.push_back(val);
for (int v : arr) {
std::cout << a << " ";
}
在数组第i个位置插入值为val的元素:
vector<int> arr = {0, 5, 2, 3, 7, 1, 6};
i = 2; val = 4;
arr.insert(arr.begin()+i, val);
for (int v : arr) {
std::cout << a << " ";
}
2.4 改变元素(改)
将数组中第 i i i个元素值改为:
void change(vector<int>& nums, int i, int val){
if(i>=0 && i < nums.size())
nums[i] = val;
}
vector<int> arr = {0, 5, 2, 3, 7, 1, 6};
i = 2; val = 4;
change(arr, i, val);
for (int v : arr) {
std::cout << a << " ";
}
2.5 删除元素(删)
删除元素分为三种情况:「删除数组尾部元素」、「删除数组第 i i i个位置上的元素」、「基于条件删除元素」。
删除数组尾部元素
- 只需将元素计数值减一即可。
vector<int> arr = {0, 5, 2, 3, 7, 1, 6};
arr.pop_back();
for (int v : arr) {
std::cout << a << " ";
}
删除数组第 i i i个位置上的元素:
- 先检查下标 i i i 是否合法,即 0 ≤ i ≤ l e n ( n u m s ) − 1 0 \le i \le len(nums) - 1 0≤i≤len(nums)−1。
- 如果下标合法,则将第 i + 1 i + 1 i+1 个位置到第 l e n ( n u m s ) − 1 len(nums) - 1 len(nums)−1 位置上的元素依次向左移动。
- 删除后修改数组的元素计数值。
c++中vector删除中间元素可用erase方法,输入参数为指向删除位置的迭代器
vector<int> arr = {0, 5, 2, 3, 7, 1, 6};
int i = 3;
arr.erase(arr.begin()+i);//返回抹除位置pos的下一个位置的迭代器
for (int v : arr) {
std::cout << a << " ";
}
基于条件删除元素:这种操作一般不给定被删元素的位置,而是给出一个条件要求删除满足这个条件的(一个、多个或所有)元素。这类操作也是通过循环检查元素,查找到元素后将其删除。
这里以删除数组中第
1
1
1 个值为
v
a
l
val
val 的操作为例。
c++ 的vector没有相应的方法吗,但STL有相应的函数
vector<int> arr = {1, 2, 3, 4, 5, 2, 6};
int val = 2;
auto it = std::find(arr.begin(), arr.end(), val);
if(it != arr.end())
arr.erase(it);
for (int v : arr) {
std::cout << a << " ";
}
练习题
0066. 加一
描述:给定一个非负整数数组,数组每一位对应整数的一位数字。
要求:计算整数加 1 1 1 后的结果。
说明:
- 1 ≤ d i g i t s . l e n g t h ≤ 100 1 \le digits.length \le 100 1≤digits.length≤100。
- 0 ≤ d i g i t s [ i ] ≤ 9 0 \le digits[i] \le 9 0≤digits[i]≤9。
分析: 因为digits的长度可能为100,恢复表示的数字在加一不可取。加一要考虑进位问题,尤其是9,或99这种加一就会改变数组长度的情况不可忽略。
//数组长度改变时,使用插入
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
int a = 1;
int n = digits.size();
int i = n-1;
while(a && i >= 0){
a += digits[i];
digits[i] = a%10;
a /= 10;
i--;
}
if(a) digits.insert(digits.begin(),a);
return digits;
}
};
0724. 寻找数组的中心下标
描述:给定一个数组
n
u
m
s
nums
nums。
要求:找到「左侧元素和」与「右侧元素和相等」的位置,若找不到,则返回 − 1 -1 −1。
说明:
- 1 ≤ n u m s . l e n g t h ≤ 1 0 4 1 \le nums.length \le 10^4 1≤nums.length≤104。
- − 1000 ≤ n u m s [ i ] ≤ 1000 -1000 \le nums[i] \le 1000 −1000≤nums[i]≤1000。
class Solution {
public:
int pivotIndex(vector<int>& nums) {
int total = std::accumulate(nums.begin(), nums.end(), 0);//sum
int prefix = 0; // prefix sum
for(int i = 0; i < nums.size(); i++){
if(prefix == total - nums[i] - prefix)
return i;// find!
prefix += nums[i];
}
return -1; // don't find.
}
};
描述:给定一个数组 n u m s nums nums,再给定一个数字 k k k。
要求:将数组中的元素向右移动 k k k 个位置。
说明:
- 1 ≤ n u m s . l e n g t h ≤ 1 0 5 1 \le nums.length \le 10^5 1≤nums.length≤105。
- − 2 31 ≤ n u m s [ i ] ≤ 2 31 − 1 -2^{31} \le nums[i] \le 2^{31} - 1 −231≤nums[i]≤231−1。
- 0 ≤ k ≤ 1 0 5 0 \le k \le 10^5 0≤k≤105。
- 使用空间复杂度为 O ( 1 ) O(1) O(1) 的原地算法解决这个问题。
思路1:环状替换
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k = k % n;
if(k == 0) return ;
// int count = gcd(k, n);
int count = 0;
for (int start = 0; count < n; ++start) {
int current = start;
int prev = nums[start];
do {
int next = (current + k) % n;
swap(nums[next], prev);
current = next;
count++;
} while (start != current);
}
}
};
//时间复杂度O(n),空间复杂度O(1)
思路2:翻转数组
class Solution {
public:
void reverse(vector<int>& nums, int start, int end) {
while (start < end) {
swap(nums[start], nums[end]);
start += 1;
end -= 1;
}
}
void rotate(vector<int>& nums, int k) {
k %= nums.size();
reverse(nums, 0, nums.size() - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.size() - 1);
}
};
// 时间复杂度O(2n),空间复杂度O(1)
描述:给定一个 n × n n \times n n×n 大小的二维矩阵(代表图像) m a t r i x matrix matrix。
要求:将二维矩阵 m a t r i x matrix matrix 顺时针旋转 90°。
分析:对于矩阵中的第一行,在旋转后,它出现在倒数第一列的位置;第二行,出现在倒数第二列的位置。即matrix
[
i
]
[
j
]
[i][j]
[i][j]在旋转后的位置为matrix
[
j
]
[
n
−
i
−
1
]
[j][n-i-1]
[j][n−i−1]
**思路1:**最简单的方法就是使用一个和matrix相同规模的矩阵存储旋转后的结果
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
auto matrix_new = matrix;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
matrix_new[j][n - i - 1] = matrix[i][j];
}
}
matrix = matrix_new;
}
};
//时间复杂度O(n^2),空间复杂度O(n^2)
思路2:一层层,每层调换三次,从外到内
class Solution {
public:
void rotate(vector<vector<int>>& a) {
int n = a.size();
if (n == 1) return ;
for (int i = 0; i < n / 2; i++) {
for (int j = i; j < i + n - 2 * i - 1; j++) {
swap(a[i][j], a[j][n - i - 1]);
swap(a[i][j], a[n - j - 1][i]);
swap(a[n - j - 1][i], a[n - i - 1][n - j - 1]);
}
}
}
};
思路3:两次翻转。1:先上下翻转
(
i
,
j
)
−
−
>
(
n
−
i
−
1
,
j
)
(i,j)--> (n-i-1, j)
(i,j)−−>(n−i−1,j),再转置
(
n
−
i
−
1
,
j
)
−
−
>
(
j
,
n
−
i
−
1
)
(n-i-1, j)-->(j,n-i-1)
(n−i−1,j)−−>(j,n−i−1).
2: 先转置
(
i
,
j
)
−
−
>
(
j
,
i
)
(i, j)-->(j,i)
(i,j)−−>(j,i),再水平翻转
(
j
,
i
)
−
−
>
(
j
,
n
−
i
−
1
)
(j, i)-->(j,n-i-1)
(j,i)−−>(j,n−i−1).
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size() ;
//首先进行上下翻转
for( int i = 0 ; i < n/2 ; i ++ ){
for (int j = 0; j < n; ++j) {
swap(matrix[i, j], matrix[n - i - 1, j]);
}
}
//然后进行转置
for( int i = 0 ; i < n ; i ++ ){
for( int j = i ; j < n ;j ++ ){
swap(matrix[i][j],matrix[j][i]);
}
}
}
};
描述:给定一个 m × n m \times n m×n 大小的二维矩阵 m a t r i x matrix matrix。
要求:按照顺时针旋转的顺序,返回矩阵中的所有元素。
思路按层模拟。每层四个方向
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if (matrix.size() == 0 || matrix[0].size() == 0) {
return {};
}
int rows = matrix.size(), columns = matrix[0].size();
vector<int> order;
int left = 0, right = columns - 1, top = 0, bottom = rows - 1;
while (left <= right && top <= bottom) {
for (int column = left; column <= right; column++) {
order.push_back(matrix[top][column]);
}
for (int row = top + 1; row <= bottom; row++) {
order.push_back(matrix[row][right]);
}
if (left < right && top < bottom) {
for (int column = right - 1; column > left; column--) {
order.push_back(matrix[bottom][column]);
}
for (int row = bottom; row > top; row--) {
order.push_back(matrix[row][left]);
}
}
left++;
right--;
top++;
bottom--;
}
return order;
}
};
描述:给定一个大小为 m × n m \times n m×n 的矩阵 m a t mat mat 。
要求:以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。
思路:直接模拟,其中在同一对角线的元素索引相加相同。
class Solution {
public:
vector<int> findDiagonalOrder(vector<vector<int>>& mat) {
int m = mat.size();
int n = mat[0].size();
vector<int> res;
for (int i = 0; i <= m + n - 2; i++) {
if (i % 2) {
int x = i < n ? 0 : i - n + 1;
int y = i < n ? i : n - 1;
while (x < m && y >= 0) {
res.emplace_back(mat[x][y]);
x++;
y--;
}
} else {
int x = i < m ? i : m - 1;
int y = i < m ? 0 : i - m + 1;
while (x >= 0 && y < n) {
res.emplace_back(mat[x][y]);
x--;
y++;
}
}
}
return res;
}
};