文章目录
1. 操作系统
2. 算法
2.1 深搜
- 找可dfs元素(路径的第i个位置)
- 找搜索范围并确定是否需要访问标记(限制dfs当前元素搜索范围)
- 找所有的初始dfs元素
- dfs当前元素:
a. 剪枝
b. 处理结果
c. 在当前搜索范围中找满足条件的值,如有访问标记则修改
d. dfs和当前节点关联的所有后续有效节点
e. 回改当前元素和访问标记(当前节点一定不可能到达终点,若是不需要回改)
2.2 广搜
- 找到队列Q的起始元素,即初始搜索元素;
- 遍历Q中当前元素,对各元素相关联元素操作
while(Q.empty())
{
int count = Q.size();
while(count--)
{
auto e = Q.front();
Q.pop();
for(相关联元素)
{
Q.push();
}
}
}
2.3 递归
- 掌控递归函数要实现的功能:即对每个节点实现该功能,即可达到所求答案
- 递归函数的边界(即底部):包括结束条件和裁剪条件
确定递归函数功能,对所有节点实现该功能既得所求
- 确定前、中、后序遍历
- 确定结果存储位置:成员变量、函数传参、函数返回值
a. 成员变量
b. 函数传参
c. 函数返回值
2.4 回溯
关于回溯的访问标记是否需要回改的问题:主要不同方式到达当前位置时,其后续相关联的位置是否一致,若一致,之前在该节点向下失败,后续将不必向下,此时不需要回改,若不一致,则需要回改。
这一题是很经典的网格路径题,乍一看很简单,可以用回溯来做,但是有一个坑:如果回溯的时候把visited数组设为false,会超时。而visited不设成false,会大大提高速度,最终可以通过。
所以有第一个问题:为什么visited可以不设为false呢?
这里面其实牵涉到了动态规划的一个基本原理:无后效性。简单点说,就是我T时间到达了某个状态,在对后面要到达什么的状态进行决策的时候,和T时间之前的决策没有关系,也可以说,T时间之前的决策,对T时间之后的决策,不会产生任何影响。
这就是如果这题里面的约束条件,只能向右或向下走,产生的最关键的影响。
结合本题解释就是,不管我之前是怎么到达(row, col)这个坐标的,我在选择要走到别的格子的时候,只能向下或向右,和之前怎么走到(row, col)无关的。那么可以推出:如果我曾经经过(row, col)这个坐标,并且继续往下走得很深,但是最终失败了,那么当我再次从别的格子走到(row, col)时,也就没有必要继续往下搜了,因为它接下来还是只能往下或往右走,并且最终一定会失败,那么也就没有必要接着走到(row, col),所以也没有必要在回溯的时候,把visited[row][col]设为false了。
当然无后效性是动态规划的基本性质之一,但是它山之玉,可以攻石,这里只是借助其概念,来帮助理解回溯解法中的这个关键细节。
第二个问题:如果题目改为每个格子的上下左右都能走,那么解法会变吗?
答案是:会的。原因是如果每个格子的上下左右都能走,那么这题就不满足无后效性这个性质了,为什么呢?
其实也很好分析,假设我现在在(row, col)这个位置,并且是从上面的格子(row - 1, col)过来的,那么我从(row, col)接着走,就只能往下、左、右这三个方向,其他方向也是同理,有以下关系:
(row - 1, col) - > (row, col),上边过来, 接下来的决策方向:下、左、右
(row, col - 1) - > (row, col),左边过来, 接下来的决策方向:上、下、右
(row + 1, col) - > (row, col),下边过来, 接下来的决策方向:上、左、右
(row, col + 1) - > (row, col),右边过来, 接下来的决策方向:上、下、左
这个列表的意思就是说,(row, col)前的决策,会影响(row, col)后的决策,所以也就不满足无效性这个性质。
2.5 数学和位运算
- 二进制全0数:0,二进制全1数:-1
- c++不支持负数二进制偏移 :
(unsigned int)num<<1;
- int 32 位 long 64位
- 找第k位:
mark&(1<<k);
- 找第k位不同:
(a&(1<<k)) ^ (b&(1<<k))
- 找正负号:
(long)a>>63 & 1
若为0则正,否则负; - 加法:
int add(int a, int b) { if(b == 0) return a; return add(a^b, (unsigned int)(a&b)<<1); }
- 将目标数字修改指定位置,如奇数位:
//互换奇偶位置 int exchangeBits(int num) { int tab = 0b01010101010101010101010101010101; int a = (num&tab)<<1; int b = (num&(tab<<1))>>1; return a|b; } //i-j为0,其余为1 int mark = -1; for(int k = i; k <= j; k++) { mark -= (1<<k); }
- 打印数字的二进制:
std::cout << bitset<8>(119) << "\n";
2.6 双指针
双指针问题,一般问题复杂度不高,用两个关键指针即可解决问题,对复杂问题的子问题,熟练掌握双指针可以快速解决
- 同侧初始化指针:
- 不同位置出发,有间隔,目的:不修改已更新位置的数据(合并排序数组),利用间隔距离(链表倒数k节点)
- 同位置出发,同时移动(连接两链表,找相同节点),不同时移动,指针移动维护固定条件(最小差)
- 不同侧初始化指针:
- 两指针不相关,不同侧找不同条件值,以当前位置节点为单位考虑:
储水:当前节点最大储水(所有节点求和)(找当前节点左边最大值,右边最大值,更新最大值) - 部分排序:当前节点需左移/右移(求最左需右移id和最右需左移id)(需左移:从左开始遍历,左边有比当前位置大的,更新最大值)
- 两指针不相关,不同侧找不同条件值,以当前位置节点为单位考虑:
2.7 滑动窗口
右指针右移,满足条件后,左指针右移至不满足(更新结果)
int slidingWindow(vector<int> nums)
{
int n = nums.size();
int ans = 0;
// 记录窗口内的元素及其个数,非必要
map<int, int> um;
// l:窗口左边界; r:窗口右边界
int l = 0, r = 0;
// r 指针负责探索新的区间,直到搜索到nums的某末尾
while (r < n)
{
// 右指针右移,搜索
um[r]++;
r++;
//l指针右移,窗口收缩
while(区间 [l, r] is Invalid)
{
// 此处处理结果, deal with(ans, 区间[l, r])
res = max(ans, r - l + 1); // 或者res = min(ans, r - l + 1);
um[l]--;
l++;
}
}
return ans;
}
2.8 KMP算法
字符串匹配KMP算法:在主串T中,找模式串P,返回主串位置
构建next数组:最大公共前后缀右移一位,首位补-1,遍历用while,方便更新id
class Solution {
public:
int strStr(string haystack, string needle) {
int n = needle.size();
vector<int> next(n+1);
getFail(needle, next);
int i = 0, j = 0;
while(i < haystack.size() && j < n)
{
if(j == -1 || haystack[i] == needle[j])
{
j++;
i++;
}
else j = next[j];
}
if(j == needle.size()) return i - j;
else return -1;
}
void find(vector<int>& ans, string& T, string& P, vector<int>& next)
{
int n = P.size();
int i = 0, j = 0;
while(i < T.size())
{
if(j == -1 || T[i] == P[j])
{
i++;
j++;
}
else j = next[j];
if(j == n) ans.push_back(i - j);
}
}
void getFail(string p, vector<int>& next)
{
int k = -1;
int j = 0;
next[j] = k;
while(j < p.size())
{
if(k == -1 || p[j] == p[k])
{
j++;
k++;
next[j] = k;
}
else k = next[k];
}
return;
}
};
2.9 并查集
class Digsta {
public:
map<string, string> parent;
//初始化并查集所有元素
Digsta(vector<string>& names)
{
for(auto& e : names)
{
int pos = e.find("(");
string tmp = e.substr(0, pos);
parent[tmp] = tmp;
}
}
~Digsta(){}
//递归找父节点
string find(string& x)
{
return parent[x] == x ? x : find(parent[x]);
}
//小集合加入并查集中,分别找到其父节点,并将父节点用parent连接
void add(string& a, string& b)
{
string fa = find(a);
string fb = find(b);
if(fa != "" && fb != "")
{
if(strcmp(fa.c_str(), fb.c_str()) > 0)
{
parent[fa] = fb;
}
else
{
parent[fb] = fa;
}
}
}
//额外添加元素
void addStr(string& x)
{
parent[x] = x;
}
bool isSame(string& a, string& b)
{
return find(a) == find(b);
}
};
class Solution {
public:
vector<string> trulyMostPopular(vector<string>& names, vector<string>& synonyms) {
Digsta setName(names);
map<string, int> ansTmp;
for(auto& e : synonyms)
{
int pos = e.find(",");
string nameA = e.substr(1, pos - 1);
string nameB = e.substr(pos+1, e.length()-pos-2);
if(setName.find(nameA) == "")
{
setName.addStr(nameA);
}
if(setName.find(nameB) == "")
{
setName.addStr(nameB);
}
setName.add(nameB, nameA);
}
for(auto& e : names)
{
int pos = e.find("(");
string nameTmp = e.substr(0, pos);
string countStr = e.substr(pos+1, e.length()-pos-2);
int count = atoi(countStr.c_str());
ansTmp[setName.find(nameTmp)] += count;
}
vector<string> ans;
for(auto& e : ansTmp)
{
string tmp = e.first + "(" + std::to_string(e.second) + ")";
ans.push_back(tmp);
}
return ans;
}
};
2.10 字典树(Trie)
class Trie {
public:
Trie* next[26] = {nullptr};
bool isEnd;
Trie(){
isEnd = false;
}
void insert(string& s)
{
Trie* cur = this;
int n = s.length();
for(int i = n - 1; i >= 0; i--)
{
int pos = s[i] - 'a';
if(cur->next[pos] == nullptr)
{
cur->next[pos] = new Trie();
}
cur = cur->next[pos];
}
cur->isEnd = true;
}
};
class Solution {
public:
int respace(vector<string>& dictionary, string sentence) {
Trie* dicTree = new Trie();
for(string& e : dictionary)
{
dicTree->insert(e);
}
int n = sentence.size();
vector<int> dp(n+1, INT_MAX);
dp[0] = 0;
for(int i = 0; i < n; i++)
{
int j = i;
Trie* cur = dicTree;
dp[i+1] = dp[i] + 1;
for(; j >= 0; j--)
{
int pos = sentence[j] - 'a';
if(cur->next[pos] == nullptr)
{
break;
}
else
{
cur = cur->next[pos];
}
if(cur->isEnd)
{
dp[i+1] = min(dp[j], dp[i+1]);
//break; 找到,且找最长匹配
}
}
}
return dp[n];
}
};
2.11 c++库函数
//自定义比较函数
class MyComp
{
public:
bool operator()(const pair<int, int>& a, const pair<int, int>& b)
{
return a.first == b.first ? a.second > b.second : a.first < b.first;
}
};
class Solution {
public:
int bestSeqAtIndex(vector<int>& height, vector<int>& weight) {
//数组对排序
vector<pair<int, int>> hw;
for(int i = 0; i < height.size(); i++) hw.push_back(make_pair(height[i], weight[i]));
sort(hw.begin(), hw.end(), MyComp());
vector<int> dp;
for(auto& [h, w] : hw)
{
//二分查找,已排序数组,找第一个不小于w的元素,返回迭代器
auto p = lower_bound(dp.begin(), dp.end(), w);
if(p == dp.end()) dp.push_back(w);
else *p = w;
}
return dp.size();
}
};