1. 排序问题
最好 时间复杂度 | 平均 时间复杂度 | 最坏 时间复杂度 | 空间复杂度 | |
---|---|---|---|---|
冒泡排序 | O(n) | O(n²) | O(n²) | O(1) |
选择排序 | O(n²) | O(n²) | O(n²) | O(1) |
插入排序 | O(n) | O(n²) | O(n²) | O(1) |
快速排序 | O(nlogn) | O(nlogn) | O(n²) | O(logn) |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) |
希尔排序 | O(nlogn) | O(nlog²n) | O(nlog²n) | O(1) |
稳定的算法:冒泡排序、插入排序、归并排序、基数排序。
(1)冒泡排序
简易版
void BubbleSort(std::vector<int>& nums)
{
int len = nums.size();
for (int i = 0; i < len; ++i)
for (int j = 1; j < len; ++j)
if (nums[j - 1] > nums[j])
std::swap(nums[j - 1], nums[j]);
}
改进:不再判断右边有序部分;当数组已经有序,终止排序函数。
void BubbleSort(std::vector<int>& nums)
{
int len = nums.size();
int lastIdx = len; // 每趟排序后, 最右边那个被交换的元素的索引
for (int i = 0; i < len; ++i)
{
bool hadExchanged = false; // 发生交换
int tmpIdx = 0; // 每次交换, 被交换的右边元素的索引
for (int j = 1; j < lastIdx; ++j)
{
if (nums[j - 1] > nums[j])
{
std::swap(nums[j - 1], nums[j]);
hadExchanged = true;
tmpIdx = j;
}
}
if (tmpIdx)
lastIdx = tmpIdx;
if (!hadExchanged) return;
}
}
(2)选择排序
简易版
void SelectSort(std::vector<int>& nums)
{
int len = nums.size();
for (int i = 0; i < len; ++i)
for (int j = i + 1; j < len; ++j)
if (nums[j] < nums[i])
std::swap(nums[i], nums[j]);
}
改进:找到右侧最小的数,再与当前数进行交换。
void SelectSort(std::vector<int>& nums)
{
int len = nums.size();
for (int i = 0; i < len; ++i)
{
int minNumIdx = i; // 找到的最小值的索引
for (int j = i + 1; j < len; ++j)
if (nums[j] < nums[minNumIdx])
minNumIdx = j;
std::swap(nums[i], nums[minNumIdx]);
}
}
(3)插入排序
// 从nums[i]的前一个元素nums[i - 1]向前遍历
// 若遍历到的元素nums[j]大于nums[i]则将nums[j]后移一位
// 否则将nums[i]放在该元素的后面,即nums[j + 1]的位置
// (若nums[i]大于前面所有的元素,则此时i = j + 1)
void InsertSort(std::vector<int>& nums)
{
int len = nums.size();
for (int i = 1; i < len; ++i)
{
int tmp = nums[i];
int j = i - 1;
for (; j >= 0 && nums[j] > tmp; --j)
nums[j + 1] = nums[j];
nums[j + 1] = tmp;
}
}
(4)快速排序
递归写法
int Partition(std::vector<int>& nums, int left, int right)
{
int key = nums[left]; // 基准
while (left < right)
{
while (left < right && nums[right] >= key)
--right;
nums[left] = nums[right];
while (left < right && nums[left] <= key)
++left;
nums[right] = nums[left];
}
nums[left] = key;
return left;
}
void QSort(std::vector<int>& nums, int left, int right)
{
if (left >= right) return;
int keyIdx = Partition(nums, left, right); // 基准的索引
QSort(nums, left, keyIdx - 1);
QSort(nums, keyIdx + 1, right);
}
迭代写法
void QSort(std::vector<int>& nums)
{
if (nums.size() < 2) return;
int preLeft = 0; // 每趟快排前的left索引
int preRight = nums.size() - 1; // 每趟快排前的right索引
std::stack<int> idxStack; // 存索引的辅助栈
idxStack.emplace(preLeft);
idxStack.emplace(preRight);
while (!idxStack.empty())
{
preRight = idxStack.top();
idxStack.pop();
preLeft = idxStack.top();
idxStack.pop();
int nextLeft = preLeft; // 每趟快排后的left索引
int nextRight = preRight; // 每趟快排后的right索引
int key = nums[nextLeft]; // 基准
while (nextLeft < nextRight)
{
while (nextLeft < nextRight && nums[nextRight] >= key)
--nextRight;
nums[nextLeft] = nums[nextRight];
while (nextLeft < nextRight && nums[nextLeft] <= key)
++nextLeft;
nums[nextRight] = nums[nextLeft];
}
nums[nextLeft] = key;
// keyIdx = nextLeft;
if (preLeft < nextLeft - 1)
{
idxStack.emplace(preLeft);
idxStack.emplace(nextLeft - 1);
}
if (nextLeft + 1 < preRight)
{
idxStack.emplace(nextLeft + 1);
idxStack.emplace(preRight);
}
}
}
(5)堆排序
void Heapify(std::vector<int>& nums, int len, int parent)
{
if (parent >= len) return;
int left = (parent << 1) + 1; // 左子节点的索引, parent * 2 + 1
int right = parent + 1 << 1; // 右子节点的索引, parent * 2 + 2
int maxValueIdx = parent; // 三个节点中值最大的节点对应的索引
if (left < len && nums[maxValueIdx] < nums[left])
maxValueIdx = left;
if (right < len && nums[maxValueIdx] < nums[right])
maxValueIdx = right;
if (maxValueIdx != parent)
{
std::swap(nums[maxValueIdx], nums[parent]);
Heapify(nums, len, maxValueIdx);
}
}
void HeapSort(std::vector<int>& nums)
{
// 建堆
int len = nums.size();
for (int i = (len >> 1) - 1; i >= 0; --i)
Heapify(nums, len, i);
// 排序
for (int i = nums.size() - 1; i >= 1; --i)
{
std::swap(nums[i], nums[0]);
Heapify(nums, i, 0);
}
}
(6)二路归并排序
void Merge(std::vector<int>& nums, int left, int mid, int right) //合并函数
{
std::vector<int> helpArray(right - left + 1);
int part1_idx = left; // 第1组的遍历索引
int part2_idx = mid + 1; // 第2组的遍历索引
int helpArray_idx = 0; // helpArray中待插入元素的位置的索引
// 两组都未遍历完
while (part1_idx <= mid && part2_idx <= right)
{
if (nums[part1_idx] > nums[part2_idx])
helpArray[helpArray_idx++] = nums[part1_idx++];
else
helpArray[helpArray_idx++] = nums[part2_idx++];
}
// 第1组未遍历完
while (part1_idx <= mid)
helpArray[helpArray_idx++] = nums[part1_idx++];
// 第2组未遍历完
while (part2_idx <= right)
helpArray[helpArray_idx++] = nums[part2_idx++];
// 将helpArray中排好序的数放回nums
for (int i = left; i <= right; i++)
nums[i] = helpArray[i - left];
}
void MergeSort(std::vector<int>& nums, int left, int right) //归并排序
{
if (left >= right) return;
int mid = left + (right - left)/ 1;
MergeSort(nums, left, mid); // 注意这里别写成mid - 1
MergeSort(nums, mid + 1, right);
Merge(nums, left, mid, right);
}
2. 排列组合问题
(1)排列
(2)组合(求子集\子序列)
#include <iostream>
#include <vector>
std::vector<int> nums{1, 2, 3, 4, 5};
std::vector<std::vector<int>> ans;
std::vector<int> buf;
// 问题1:求所有真子集
// 方法一 字典序穷举
void t1f1()
{
int n = 1 << nums.size();
ans.reserve(n - 1); // 真子集个数2的n次方减1
buf.reserve(nums.size()); // 每个子集最多n个数
for (int i = 1; i < n; ++i) // 对所有情况逐一输出
{
buf.clear();
int tmp = i, idx = 0;
while (tmp)
{
if (tmp & 1)
buf.emplace_back(nums[idx]);
tmp >>= 1;
++idx;
}
ans.emplace_back(buf);
}
}
// 方法二 dfs回溯穷举
void dfs1(int idx, int n)
{
if (idx > nums.size())
return;
if (buf.size() == n)
{
ans.emplace_back(buf);
return;
}
buf.emplace_back(nums[idx]);
dfs1(idx + 1, n);
buf.pop_back();
dfs1(idx + 1, n);
}
void t1f2()
{
int n = nums.size();
ans.reserve((1 << n) - 1); // 真子集个数2的n次方减1
buf.reserve(n); // 每个子集最多n个数
for (int i = 1; i < n + 1; ++i)
dfs1(0, i);
}
// 问题2:求元素个数为k的所有子集
// 方法一 dfs回溯 + 剪枝
void dfs2(int idx, int k)
{
int n1 = nums.size(), n2 = buf.size();
if (idx > n1 || n2 + n1 - idx + 1 < k)
return;
if (n2 == k)
{
ans.emplace_back(buf);
return;
}
buf.emplace_back(nums[idx]);
dfs2(idx + 1, k);
buf.pop_back();
dfs2(idx + 1, k);
}
void t2f1(int k)
{
ans.reserve(k);
dfs2(0, k);
}
// 方法二:字典序
void t2f2(int k)
{
buf.reserve(k);
// 朴素方法 空间复杂度O(n)
// int n = nums.size();
// std::vector<bool> flag(n, false);
// for (int i = 0; i < k; ++i)
// flag[i] = true;
// while (true)
// {
// int idx = 0;
// while (idx < n - 1)
// {
// if (flag[idx] && !flag[idx + 1])
// {
// flag[idx] = false;
// flag[idx + 1] = true;
// break;
// }
// ++idx;
// }
// if (!flag[0] && idx < n - 1)
// for (int i = 0; i < idx / 2; ++i)
// {
// flag[i] = true;
// flag[idx - 1 - i] = false;
// }
// for (int i = 0; i < n; ++i)
// if (flag[i])
// buf.emplace_back(nums[i]);
// ans.emplace_back(buf);
// buf.clear();
// if (idx == n - 1)
// break;
// }
// 优化方法 空间复杂度O(k)
std::vector<int> index;
index.reserve(k + 1);
for (int i = 1; i <= k; ++i)
index.emplace_back(i);
index.emplace_back(nums.size() + 1);
int j = 0;
while (j < k)
{
for (int i = 0; i < k; ++i)
buf.emplace_back(nums[index[i] - 1]);
ans.emplace_back(buf);
buf.clear();
j = 0;
while (j < k && index[j] + 1 == index[j + 1])
{
index[j] = j + 1;
++j;
}
++index[j];
}
}
// 打印结果
void LogSet()
{
for (const std::vector<int> &buf : ans)
{
for (int num : buf)
std::cout << num << ' ';
std::cout << '\n';
}
}
int main()
{
// t1f1();
// t1f2();
// t2f1(3);
t2f2(3);
LogSet();
std::cout << std::endl << '\n';
std::cin.get();
}
(3)排列 + 组合
3. 字符串
(1)KMP
参考资料: 很详尽KMP算法(厉害) - ZzUuOo666 - 博客园
/// <summary>
/// 查找str中是否有子串与matchStr匹配
/// </summary>
/// <returns>
/// 找到了:返回第一个与match匹配的子串的索引;
/// 未找到:返回-1;
/// </returns>
int FindSubStr(std::string str, std::string matchStr)
{
int strLen = str.size(), subStrLen = matchStr.size();
if (!subStrLen) return -1;
// 求next 数组
std::vector<int> next(subStrLen);
int strIdx = 0;
int subStrIdx = next[0] = -1;
while (strIdx < subStrLen - 1)
{
if (subStrIdx == -1 || matchStr[strIdx] == matchStr[subStrIdx])
{
++strIdx, ++subStrIdx;
next[strIdx] = matchStr[strIdx] == matchStr[subStrIdx] ? next[subStrIdx] : subStrIdx;
}
else
{
subStrIdx = next[subStrIdx];
}
}
strIdx = subStrIdx = 0;
while (strIdx < strLen && subStrIdx < subStrLen)
{
if (subStrIdx == -1 || str[strIdx] == matchStr[subStrIdx])
++strIdx, ++subStrIdx;
else
subStrIdx = next[subStrIdx];
}
return subStrIdx == subStrLen ? strIdx - subStrIdx : -1;
}
呃,还是直接用find()函数吧……find()函数返回size_t,按F12就能找到如下定义(IDE是VS),windows64位操作系统下,size_t就是unsigned long long,所以没找到会返回SIZE_MAX。
// Definitions of common types
#ifdef _WIN64
typedef unsigned __int64 size_t;
typedef __int64 ptrdiff_t;
typedef __int64 intptr_t;
#else
typedef unsigned int size_t;
typedef int ptrdiff_t;
typedef int intptr_t;
#endif
如果没找到要输出-1,稍微处理一下输出就行。
int main()
{
std::string str = "abc";
std::string subStr = "213";
// string.size() <= LLONG_MAX
std::cout << (long long)str.find(subStr) << '\n';
// string.size() < ULLONG_MAX
unsigned long long subStrIdx = str.find(subStr);
if (subStrIdx == ULLONG_MAX)
std::cout << -1 << '\n'; // 处理没找到
else
std::cout << subStrIdx << '\n'; // 处理找到了
}
(2)Split
C++ 的 string 为什么不提供 split 函数? - 知乎 (zhihu.com)
// 备注:
// 分割符在开头或结尾的处理结果
// params{str:".1.1.", spliter:'.'} -> return{"","1","1",""}
vector<string> Split(const string &str, char spliter)
{
vector<string> strs;
int first = 0;
int last;
while ((last = str.find(spliter, first)) != string::npos)
{
strs.emplace_back(str.begin() + first, str.begin() + last);
first = last + 1;
}
strs.emplace_back(str.begin() + first, str.end());
return strs;
}
4. 树
(1)求树的最大深度
int GetDepth(TreeNode *root)
{
if (root == nullptr) return 0;
int leftDepth = GetDepth(root->left);
int rightDepth = GetDepth(root->right);
return max(leftDepth, rightDepth) + 1; // 该节点的深度
}
(2)二叉树遍历
// 前序遍历
void PreOrder(vector<vector<int>> &res, TreeNode *root)
{
if (root == nullptr) return;
res.emplace_back(root->val);
PreOrder(root->left);
PreOrder(root->right);
}
// 中序遍历
void InOrder(vector<vector<int>> &res, TreeNode *root)
{
if (root == nullptr) return;
InOrder(root->left);
res.emplace_back(root->val);
InOrder(root->right);
}
// 后序遍历
void PostOrder(vector<vector<int>> &res, TreeNode *root)
{
if (root == nullptr) return;
PostOrder(root->left);
PostOrder(root->right);
res.emplace_back(root->val);
}
// 层序遍历
void LevelOrder(vector<vector<int>> &res, TreeNode *root, int level)
{
if (root == nullptr) return;
if (level >= res.size())
res.push_back({});
res[level].emplace_back(root->val);
LevelOrder(res, root->left, level + 1);
LevelOrder(res, root->right, level + 1);
}
5. 其他
(1)两数交换(int类型)
inline void Swap(int a, int b) { a ^= b ^= a ^= b; }
(2)vector去重
// sort + unique + erase
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
// 利用unordered_set
unordered_set<int> orderedNums(nums.begin(), nums.end());
nums.assign(orderedNums.begin(), orderedNums.end());
推荐使用第1种方法。
(3)二分查找
nuns为升序数组。
① 找到target的索引
int BinarySearch(const vector<int>& nums, int target)
{
int left = 0;
int right = nums.size() - 1;
while (left <= right)
{
int mid = (left + right) >> 1;
if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
else return mid;
}
return -1;
}
② 找到target的索引,没找到返回大于target的最小值的索引(若不存在大于target的值则会返回非法索引即可)
int BinarySearch(const vector<int>& nums, int target)
{
int left = 0;
int right = nums.size() - 1;
while (left <= right)
{
int mid = (left + right) >> 1;
if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
else return mid;
}
return left;
}
③ 统计target出现的次数
int BinarySearch(const vector<int> &nums, float target)
{
int left = 0;
int right = nums.size() - 1;
while (left <= right)
{
int mid = (left + right) >> 1;
if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
else return mid;
}
return left;
}
int GetTargetCnt(const vector<int>& nums, int target)
{
return BinarySearch(nums, target + 0.5f) -
BinarySearch(nums, target - 0.5f);
}
(4)cin && cout 的性能优化
// 核心代码模式,放到class外边
static const nullptr_t io_sync_false = []() -> nullptr_t
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
return nullptr;
}();
// ACM模式,直接放在mian里面就行
ios::sync_with_stdio(false);
cin.tie(nullptr);
6. 例题
(1)两数之和
题目1:给出一个整型数组nums和一个目标值target,请在数组中找出所有加起来等于目标值的数对,返回这些数对的列表(数对不能重复)。数对中的值按及数对都按升序排列。
题目2:给出一个整型数组nums和一个目标值target,请在数组中找出1个加起来等于目标值的数对,返回该数对所对应的索引对。索引对中的索引按升序排列。
补充:若题目1中值相同但索引不同的数视为两个数,即数对可以重复,或者题目2中要求所有符合条件的索引对,感觉还是得用穷举法。
① 返回数对列表
思路:排序,双索引。
vector<pair<int, int>> GetPairs(vector<int>& nums, int target)
{
vector<pair<int, int>> ans;
sort(nums.begin(), nums.end());
int left = 0, right = nums.size() - 1;
while (left < right)
{
int sum = nums[left] + nums[right];
if (sum > target) --right;
else if (sum < target) ++left;
else
{
ans.emplace_back(nums[left], nums[right]);
int oldLeftNum = nums[left], oldRightNum = nums[right];
while (left < right && nums[++left] == oldLeftNum) {}
while (left < right && nums[--right] == oldRightNum) {}
}
}
return ans;
}
② 返回索引对
pair<int, int> GetPairs(vector<int>& nums, int target)
{
vector<pair<int, int>> ans;
unordered_map<int, int> valToIdx; // 值到索引的映射
for (int i = 0; i < nums.size(); ++i)
{
if (valToIdx.find(target - nums[i]) == valToIdx.end())
valToIdx[nums[i]] = i;
else return { target - nums[i], i };
}
return { 0,0 };
}
7. 时间复杂度 && 空间复杂度
(1)时间复杂度
O(k^n)与O(n!)性能很差只能处理少量数据。
因为f(n) = log(n)时,f(n)' = 1/n,所以O(x*log(n))的性能更接近于O(x)而非O(x*n)。因此,将O(x*n)优化为O(x*log(n))时性能会明显提升。其中x为1或单项式。