算法题
算法题
数据结构
逻辑结构
- 集合结构
- 线性结构
- 树形结构
- 图形结构
物理结构
- 顺序存储
需要分配连续的存储空间
- 链式存储
不需要分配连续的存储空间,每个单元不仅要存数据也要存一个后继元素的地址
- 哈希存储
通过哈希算法计算存储位置
- 索引存储
需要一个索引表
算法题
简单
1.两数之和
问题描述:给定一个数组和目标值target,查找数组中和为target的两个整数,返回两个整数的索引。
限制:两个输入只对应一个答案;不能使用相同的元素。
解法:
双层遍历,或者外层遍历,内层二分查找,时间复杂度为O(nn), O(nlog2n)
首先字典存储所有键值,然后再进行循环查询target-nums[i],此时O(n*n),圈复杂度为 5;两个循环串行
也是使用字典,但是使用一个循环,首先查询字典中是否存在target-nums[i]键值,如果存在直接,返回索引;如果不存在,插入。相比于第二种方法好处是避免了另外起一个循环去存字典,而且能够避免使用相同的元素。
// 哈希表法
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> tempMap;
for (int i = 0; i < nums.size(); ++i) {
if (tempMap.count(target - nums[i]) != 0) {
return {tempMap[target - nums[i]], i};
}
tempMap[nums[i]] = i;
}
return {};
}
};
2235.两整数相加
输入:两个整数;输出:两数之和;限制条件:无
思路:直接返回两数之和,使用constexpr直接使用编译时运算
class Solution {
public:
// constexpr是c++17特性
constexpr int sum(int num1, int num2) {
return num1 + num2;
}
};
1929.数组串联
输入:一个数组;输出:生成一个新的数组;限制条件:新的数组为两个输入数组的串联A->AA
思路:直接向后插入
法一:更省空间
class Solution {
public:
vector<int> getConcatenation(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n; ++i) {
nums.push_back(nums[i]);l
}
return nums;
}
};
法二:感觉更好
class Solution {
public:
vector<int> getConcatenation(vector<int>& nums) {
vector<int> ans(nums);
copy(nums.begin(),nums.end(),back_inserter(ans));
return ans;
}
};
771.宝石与石头
输入:宝石字符串,石头字符串,都是字母;输出:宝石数量;限制:字母区分大小写。
思路:1. 两层遍历O(m+n),不可取
先将宝石字符串存为哈希值,然后遍历石头,计数,O(m+n)
1480.一维数组动态和
输入:一个数组;输出:一个数组;限制:[1,2,3,4]->[1,3,6,10]
思想:
两层遍历
滚动数组更新,每一个元素都是前几个元素的和,直接在原数组上累加,每次元素都加上前一个元素,前一个元素已经是它之前元素的和了。需要一个哨兵变量。
解法:
class Solution {
public:
vector<int> runningSum(vector<int>& nums) {
int prev = nums[0];
for (int i = 1; i < nums.size(); ++i) {
nums[i] = nums[i] + prev;
prev = nums[i];
}
return nums;
}
};
709.转换成小写字母
输入:一个字符串,全是字母;输出:一个字符串;限制:所有字母都转换为小写字母
class Solution {
public:
string toLowerCase(string s) {
for_each(s.begin(),s.end(),[](auto& item){item = tolower(item);});
return s;
}
};
1672.最富有资产总数
输入:一个mxn数组accounts,accounts[i][j]
代表第i个人在第j家银行的资产额度
输出:最富有的资产总数
思路:
哨兵:两层遍历,使用accumulator缩短代码量,计算每一个数组的和,用一个变量保存最大值即可。
class Solution {
public:
int maximumWealth(vector<vector<int>>& accounts) {
int maxCount = 0;
for (int i = 0; i < accounts.size(); ++i) {
// accumulate累加计算,从值0开始
maxCount = max(maxCount, accumulate(accounts[i].begin(),accounts[i].end(),0));
}
return maxCount;
}
};
66.加一
输入:一个数组,存储的是一个整数的每一位,每一位在0~9之间
输出:加一之后的,数组
限制:除0之外无前导0
思路:
其实就是向前传递进位,但是要注意999->1000,需要添加1
所以,先反转数组,则多余的进位只需要压入1即可。因为不知道进位有多少次,所以使用while循环进行,其实就是两数加法
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
reverse(digits.begin(), digits.end());
int sum = digits[0] + 1;
digits[0] = sum % 10;
int carry = sum / 10;
int i = 1;
while (carry > 0) {
if (i == digits.size()) {
digits.push_back(carry);
break;
}
sum = digits[i] + carry;
digits[i] = sum % 10;
carry = sum / 10;
i++;
}
reverse(digits.begin(), digits.end());
return digits;
}
};
724.寻找数组中心下标
问题描述:寻求数组中心下标,其左侧所有元素和等价于右侧所有元素和。
输入:一个整数数组,每个元素在-1000~1000之间
输出:数组中心下标
思路:
1、遍历数组,使用accumulate函数分别对左右元素加和,然后求是否相等即可。没找到默认返回-1;初始即可设置index=-1;O(n),圈复杂度为2,
456ms。
class Solution {
public:
int pivotIndex(vector<int>& nums) {
int index = -1; // 默认返回下标-1
for (auto it = nums.begin(); it != nums.end(); ++it) {
int lnum = accumulate(nums.begin(), it, 0);
int rnum = accumulate(it + 1, nums.end(), 0);
if (lnum == rnum) {
index = it - nums.begin();
break;
}
}
return index;
}
};
2、分别利用两个循环从正反两个方向进行一维动态数组求和,然后再采用一个循环,进行比较即可, 4ms。2
中等
198.数组轮转
问题描述:要求数组向后进行轮转,例如[1,2,3,4,5,6,7],向后轮转3位,变为[5,6,7,1,2,3,4]
输入:一个数组,一个整数代表轮转位数
输出:无返回值
思路:
1、首先避免多余的轮转操作,先计算最小的k,然后先前插入,但是这样带来的后果是会产生begin(), begin()+k元素的移位操作,超时作法:这种超时是由移位操作带来的
class Solution {
public:
void rotate(vector<int>& nums, int k) {
k %= nums.size();
while(k > 0) {
int tmp = nums.back();
nums.pop_back();
nums.insert(nums.begin(), tmp);
k--;
}
}
2、避免移位操作的做法:利用一个额外数组存储元素,然后将更新后的元素按顺序复制到新的数组中,然后对数组进行交换。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
vector<int> fnums(nums);
k %= nums.size();
copy(fnums.end()-k, fnums.end(), nums.begin());
copy(fnums.begin(), fnums.end()-k, nums.begin() + k);
}
};
swap可以交换两个容器
48.旋转图像
问题描述:图像顺时针旋转90度
输入:一个矩阵
输出:无
限制:原位旋转
思路:这道题的思路其实比较简单主要是需要看出,要将第一行旋转到最后一列,第二列旋转到倒数第二列。
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
vector<vector<int>> tmpMatrix(matrix);
int m = tmpMatrix.size(), n = tmpMatrix[0].size();
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
matrix[j][n -1 -i] = tmpMatrix[i][j];
}
}
}
};
6.Z字型变换
算术评级:4
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入:s = "PAYPALISHIRING", numRows = 3
输出:"PAHNAPLSIIGYIR"
示例 2:
输入:s = "PAYPALISHIRING", numRows = 4
输出:"PINALSIGYAHRPI"
解释:
P I N
A L S I G
Y A H R
P I
示例 3:
输入:s = "A", numRows = 1
输出:"A"
class Solution {
public:
string convert(string s, int numRows) {
// 考虑到字符串可能只有1个
if (s.size() == 1) return s;
// 用一个容器存储,每行的字符串,可能会出现行数比字符串长的情况
vector<string> rows(min(numRows, int(s.size())));
int currentRow = 0;
bool goingDown = false;
for (auto c : s) {
// 先完成将字符放进去的逻辑
rows[currentRow] += c;
// 处理边界
if ( currentRow == 0 or currentRow == numRows -1 ) {
goingDown = !goingDown;
}
// 以方向转变做判断,确定下一行行号
currentRow = goingDown? ++currentRow : --currentRow;
}
// 生成结果字符串
// string result;
// for (auto item : rows) {
// result += item;
// }
string result = accumulate(rows.begin(), rows.end(), string());
return result;
}
};
7.整数反转
class Solution {
public:
int reverse(int x) {
int rev = 0;
int max_div_10 = INT_MAX / 10; // 214748364
while (x != 0) {
int pop = x % 10;
x /= 10;
// 检查是否溢出
if (rev > max_div_10 || rev < (-1 * max_div_10)) {
return 0;
}
rev = rev * 10 + pop;
}
return rev;
}
};
8.字符串转为整数
class Solution {
public:
int myAtoi(std::string s) {
int i = 0; // 用于遍历字符串
int n = s.size(); // 字符串长度
int sign = 1; // 符号,默认为正
long long num = 0; // 使用 long long 防止溢出
// 1. 跳过前导空格
while (i < n and s[i] == ' ') {
i++;
}
// 2. 检查符号
if (i < n && (s[i] == '-' || s[i] == '+')) {
sign = (s[i] == '-') ? -1 : 1;
i++;
}
// 3. 读取数字
while (i < n && isdigit(s[i])) {
int digit = s[i] - '0'; // 将字符转换为数字
num = num * 10 + digit;
// 4. 检查是否超出 32 位整数范围
if (num * sign > INT_MAX) {
return INT_MAX;
} else if (num * sign < INT_MIN) {
return INT_MIN;
}
i++;
}
// 5. 应用符号并返回结果
return num * sign;
}
};
困难
10.正则表达式匹配
#include <iostream>
#include <vector>
#include <string>
class Solution {
public:
bool isMatch(std::string s, std::string p) {
int m = s.size(); // 字符串 s 的长度
int n = p.size(); // 模式 p 的长度
// dp[i][j] 表示 s 的前 i 个字符是否能被 p 的前 j 个字符匹配
std::vector<std::vector<bool>> dp(m + 1, std::vector<bool>(n + 1, false));
// 空字符串和空模式匹配
dp[0][0] = true;
// 处理模式 p 中的 '*',使得 dp[i][j] 可以由 dp[i][j-2] 转移而来
for (int j = 1; j <= n; j++) {
if (p[j - 1] == '*') {
dp[0][j] = dp[0][j - 2];
}
}
// 动态规划填表
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (p[j - 1] == '*') {
// '*' 表示前面的字符可以出现零次或多次
dp[i][j] = dp[i][j - 2]; // '*' 表示前面的字符出现零次
if (s[i - 1] == p[j - 2] || p[j - 2] == '.') {
dp[i][j] = dp[i][j] || dp[i - 1][j]; // '*' 表示前面的字符出现多次
}
} else if (s[i - 1] == p[j - 1] || p[j - 1] == '.') {
// 当前字符匹配
dp[i][j] = dp[i - 1][j - 1];
}
}
}
return dp[m][n];
}
};