1 动态规划

从背包问题开始:

最重要的是,能够用dp数组,1到3维度一般,去表示最终结果,对于具体的题目,dp[i][j]表示什么意思,将成为解答的关键

很多动态规划都可以使用带记忆化的搜索去做

2 例题

0410splitArrayMinMax 分割出最小的子数组最大值

1 题目

https://leetcode-cn.com/problems/split-array-largest-sum/

2 解题思路

参考官方解答:https://leetcode-cn.com/problems/split-array-largest-sum/solution/fen-ge-shu-zu-de-zui-da-zhi-by-leetcode-solution/

  • 1 dp: 首先搞明白动态规划的单元,注意,不仅仅是说增加一个元素,对分割造成了什么影响,而且还要考虑,不通的分割数目,本题目是分割,那么一定是分割数目以及分割对象带来的变化为dp的状态迁移, dp[i][j] means: res of: nums[:i] to be splited in j’s segments, dp[i][j] = max {dp[k][j-1], sum[k+1, i] | j <= k <= i - 1},所以
  • 2 二分查找法:这样,判断一个数字能否作为分割m个子数组的方案?应该很好判断,顺序遍历即可
    • 2.1 那么记录该数字为x,最小就是数组里的最大值,最大即为数组和,于是仅仅需要用二分法,从这个范围中找出该数字即可
    • 2.2 具体如何二分?若x过于小了,会导致分割数目太大,然后我们就知道往大处搜索,反之同理
  • 3 二分法的标准写法:
    • 3.1 注意用>>代表除2,尤其是考虑负数时候有区别
    • 3.2 注意当往大地方搜,st = mid+1,往小地方则不用,不然可能导致ed漏搜索
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      bool lastCheck = false;
      while(st < ed) {
      x = (st + ed) >> 1; // means: floor of (st + ed)
      lastCheck = xIsLarge(x, nums, m);
      if(lastCheck) {
      ed = x; // when ed - st = 1, (st + ed) >> 1 == st
      } else {
      st = x + 1;
      }
      }
      return st;


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Solution {
public:
int splitArray(vector<int>& nums, int m) {
int n = nums.size();
vector<int> preSum = { 0 };
for(int i = 0; i < n; ++i) {
preSum.push_back(preSum.back() + nums[i]);
}
// // dp[i][j] means: res of: nums[:i] to be splited in j's segments
// // dp[i][j] = max {dp[k][j-1], sum[k+1, i] | j <= k <= i - 1}
// vector<vector<int>> dp(n+1, vector<int>(m+1, INT_MAX));

// dp[1][1] = nums[0];
// for(int i = 1; i <= n; ++i) {
// for(int j = 1; j <= min(m, i); ++j) {
// if(j == 1) {
// dp[i][1] = preSum[i] - preSum[0];
// continue;
// }
// int tmpMaxMin = 0;
// for(int k = j - 1; k < i; ++k) {
// tmpMaxMin = max(dp[k][j-1], preSum[i] - preSum[k]);
// dp[i][j] = min(dp[i][j], tmpMaxMin);
// }
// }
// }
// return dp[n][m];

// binsearch x as the min max res
int st = *max_element(nums.begin(), nums.end());
int ed = preSum[n];
int x = -1;


bool lastCheck = false;
while(st < ed) {
x = (st + ed) >> 1;
lastCheck = xIsLarge(x, nums, m);
if(lastCheck) {
ed = x; // when ed - st = 1, (st + ed) >> 1 == st
} else {
st = x + 1;
}
}

// at last, st == ed
return st;
}

bool xIsLarge(int x, vector<int>& nums, int m) {
int cnt = 1;
int curSum = 0;
for(int i = 0; i < nums.size(); ++i) {
if(curSum + nums[i] > x) {
++cnt;
curSum = nums[i];
} else {
curSum += nums[i];
}
}
// cout << ">> x/cnt is" << x << "/" << cnt << endl;
return cnt <= m;
}
};

0403frogCrossRiver 青蛙过河

1 题目

https://leetcode-cn.com/problems/frog-jump/

2 解题思路

  • 1 动态规划:首先明白动态规划最终是指向结果的,于是,定义dp[i][k],为到达i的最后一跳长度为k,是否能够跳到目的地
    • 1.1 首先有疑问:那k岂不是max(stone) - min(stone),那k就会非常大,不合理啊?错,因为每一跳长度最多增加1,然而你最多有n-1跳,于是 k <= n-1
    • 1.2 状态转移: dp[i][k] = dp[j][k] || dp[j][k-1] || dp[j][k+1],j就是i的上一跳,那么我们可以直到,j到i,k=stone[i] - stone[j]
    • 1.3 需要注意到上面的状态转移: 因为是从j跳,必须保证: k <= j+1,因为你从第j个石头开始跳,最远长度就是j+1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
bool canCross(vector<int>& stones) {
int n = stones.size();
// jump to i, and last jump dis
vector<vector<bool>> jump(n , vector<bool>(n, false));
jump[0][0] = true;

// the i th jump len <= i
for(int i = 1; i < n; ++i) {
if(stones[i] - stones[i - 1] > i) {
return false;
}
}

// dp, from j jump to i
bool res = false;
for(int i = 1; i < n; ++i) {
for(int j = 0; j < i; ++j) {
int k = stones[i] - stones[j];
// cout << j << " -> " << i << " 's dis: " << k << endl;
if(k > j + 1) {
continue;
}
// cout << jump[j].size() << " / " << k + 1 << " jump[j][k] || jump[j][k-1] || jump[j][k+1] " << jump[j][k] << jump[j][k-1] << jump[j][k+1] << endl;
jump[i][k] = jump[j][k] || jump[j][k-1] || jump[j][k+1];
if(i == n - 1 && jump[i][k]) {
res = true;
}
}
}
return res;
}
};

0488zumaGame 祖玛游戏

1 题目

https://leetcode-cn.com/problems/zuma-game/

2 解题思路

  • 1 对于hand中,每次挑选一个去填补到board中,然后消除board中的球,接着用剩下的hand再选择一个,到board里中去消除,dfs去遍历即可
  • 2 使用记忆化搜索 memo[board + “ “ + hand]记录了对应的board和hand的结果,为何” “是必须的?
    • 2.1 考虑以下例子:
    • 2.2 可以知道必须在中间的RR中先插入B,那么假设我们的第一次搜索从第一个字符G,第二个字符是B,开始,那么我们的memo中会有结果(若是不带空格):memo[RRYGGYYRBRYYGGYRRGGBB],这样当第一个字符变成B,我们会在memo发现一个失败的方法直接返回结果,导致改题变成没有结果,同时这个例子也解释了为何减枝的条件3需要在两个相同字符之间插入字符,如果带了空格,就能够绝对避免这个问题,因为:
    • memo[RRYGGYYRBRYYGGYRR GGB] 和 memo[RRYGGYYRBRYYGGYRR GGBB]就能够区分开

      “RRYGGYYRRYYGGYRR”

      “GGBBB”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
class Solution {
public:
int allBallsCnt = -1;
map<string, int> memo;

int findMinStep(string board, string hand) {
// simulate this game
int res = 0;
allBallsCnt = hand.size();
res = bfs(board, hand);
return res == INT_MAX ? -1 : res;
}
int bfs(string board, string hand) {
// space mustn't be eliminated! it's neutig!
if(memo.find(board + " " + hand) != memo.end()) {
return memo[board + " " + hand];
}

if(0 == board.size()) {
return allBallsCnt - hand.size();
}
if(0 == hand.size()) {
return INT_MAX;
}

int useRes = INT_MAX;
string lastTarBallStr = "";
for(int k = 0; k < hand.size(); ++k) {
string nextHand = hand.substr(0, k) + hand.substr(k + 1);
string tarBallStr = hand.substr(k, 1);

// case1: cut the same ball
if(tarBallStr == lastTarBallStr) {
continue;
}

// use this char, find put pos
for(int i = 0; i <= board.size(); ++i) {
// case2: only insert at the start of str with same chars
if(i > 0 && board[i - 1] == hand[k]) {
continue;
}

// case3: only put when cur is equal current || when cur is not equal to two continuous same chars
if(i < board.size() && board[i] == hand[k] || \
i > 0 && board[i] == board[i-1] && hand[k] != board[i-1]) {
string tmpBoard1 = board;
tmpBoard1.insert(i, tarBallStr);
// reduce repeat balls
reduceRepeat(tmpBoard1);

// put to tarBall left and right
int lRes = bfs(tmpBoard1, nextHand);

useRes = min(lRes, useRes);
}
}

lastTarBallStr = tarBallStr;
}
memo[board + " " + hand] = useRes;
return useRes;
}

inline void reduceRepeat(string& board) {
int idx = 0;
// cout << "reducing " << board << endl;
while(board.length() > 0 && idx < board.length()) {
int st = idx, cur = st;
char head = board[st];
while(++cur < board.length() && board[cur] == head) {
}
if(cur - st >= 3) {
board.erase(st, cur - st);
idx = 0;
} else {
idx = cur;
}
}
// cout << "after redu " << board << endl;
}

};

0552checkRecord 学生出勤记录

1 题目

https://leetcode-cn.com/problems/student-attendance-record-ii/

2 解题思路

  • 1 初步思路 dp[i][j]作为直到i天,以j为出勤记录的所有记录数,但是会发现无法处理连续的L的情况
  • 2 更改,采用官方题解思路: dp[i][j]为以连续j个l为结尾的记录数,首先只考虑PL,但是此方法不行,因为,A可以隔断两个L,所以,如果先算出所有PL的方法,然后将A插入,那么结果一定会比用A去隔断两个L少。
  • 3 官方接单: dp[i][j][k]是含有j个A的k个L为结尾的记录数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class Solution {
public:

int bigInt = 1000000007;
int checkRecord(int n) {
if(n <= 2) {
return n == 1 ? 3 : 8;
}

// we can use A to interrupt the LLL, so we calculate A after only PL
// // n's day without 'A'
// vector<vector<long long>> dp(n + 1, vector<long long>(3, 0));

// // dp[i][j], j means end with n's L
// dp[1][0] = 1; dp[1][1] = 1; dp[1][2] = 0;


// for(int i = 2; i <= n; ++i) {
// // end with p
// dp[i][0] = dp[i - 1][0] + dp[i - 1][1] + dp[i - 1][2];

// // ent with l
// dp[i][1] = (dp[i - 1][0]) % bigInt;

// // end with ll
// dp[i][2] = dp[i - 1][1] % bigInt;
// cout << i << " th: P/L: " << dp[i][1] << " " << dp[i][0] << endl;
// }

// // when there is a A:
// long long res = 0;
// res += ((dp[n][0] + dp[n][1]) % bigInt + dp[n][2]) % bigInt;
// res += (((dp[n-1][0] + dp[n-1][1]) % bigInt + dp[n-1][2]) % bigInt * n) % bigInt;


// n's day without 'A'
vector<vector<vector<long long>>> dp(n + 1, vector<vector<long long>>(2, vector<long long>(3, 0)));

// dp[i][j], j means end with n's L
dp[0][0][0] = 1;

for(int i = 1; i <= n; ++i) {
// end with p
for(int j = 0; j < 2; ++j) {
for(int k = 0; k <= 2; ++k) {
dp[i][j][0] = (dp[i][j][0] + dp[i - 1][j][k]) % bigInt;
}
}

// end with a
for(int k = 0; k <= 2; ++k) {
dp[i][1][0] = (dp[i][1][0] + dp[i-1][0][k]) % bigInt;
}

// ent with l
for(int j = 0; j < 2; ++j) {
for(int k = 1; k <= 2; ++k) {
dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k-1]) % bigInt;
}
}
}

int res = 0;

for(int j = 0; j < 2; ++j) {
for(int k = 0; k < 3; ++k) {
res = (res + dp[n][j][k]) % bigInt;
}
}


return res;
}
};

0600countUncontinous1 不含连续1的非负整数

1 题目

https://leetcode-cn.com/problems/non-negative-integers-without-consecutive-ones/

2 解题思路

  • 1 解题思路:
    • 1.1 很显然的动态规划,首先考虑问题: 给你一个二进制数字a = 2^n,判断从0到a有多少个数字不含有连续的两个1?动态规划即可:
      • dp[len][0] = dp[len - 1][1] + dp[len - 1][0];
      • dp[len][1] = dp[len - 1][0];
      • 其中dp[len][0]表示长度为len然后以0开始的二进制数字的集合中,含有多少个不连续为1的数字
    • 1.2 有了上述的思考,那么对于1024,32这种数字的解答就很显而易见了,比如32 = 100000,那么答案就是:首先假设最高位为0,然后res += dp[5][0] + dp[5][1],这是所有: 0xxxx和1xxxx组成的答案,但是32是6位数字,还需要加上32本身即可
    • 1.3 更近一步,对于a = 101010 和 b = 110000 这样的呢?
      • 以每一个1对结果的贡献来思考,从高位到低位这样去思考:
      • 首先拿a来说,我们看最高位,和32一样的解法,接下来我们找到下一个1,那么就是变成,找以前缀为10,后缀为 0xxx 的有多少种,那么动态规划直接找出来就行,那么为什么不是1xxx,因为1xxx加上前缀10可能就大于a了,就超出了范围,那么我们接着找到下一个1,也就是前缀为1010,找0x有多少种,然后最后找不到1,看看a本身是否合理加上即可
      • 对于b,首先第一个1对最终结果的贡献都是和32一样的,那么第二个1呢?很显然,遇到了连续的第二个1,意味着后面的1对答案都不会有贡献,因为以11为前缀的都是不合法的,所以仅仅需要考虑,将第二个连续的1变成0,以10为前缀,xxxx有多少中方案,很简单,就是 dp[4][0] + dp[4][1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class Solution {
public:
int findIntegers(int n) {
int tmp = n;
int bit = -1;
vector<int> bits = {};

while(tmp > 0) {
bits.push_back(tmp & 1);
tmp = tmp >> 1;
}

int k = bits.size();
vector<vector<int>> dp(k, vector<int>(2, 0));

if(k <= 1) {
return 2;
}

dp[0][0] = 1; // mainly for the last bit is 1
dp[0][1] = 0;

dp[1][0] = 1;
dp[1][1] = 1;

for(int len = 2; len <= k - 1; ++len){
dp[len][0] = dp[len - 1][1] + dp[len - 1][0];
dp[len][1] = dp[len - 1][0];
// cout << "dp " << len << "/0,1 = " << dp[len][0] << "," << dp[len][1] << endl;
}

int lastOneBitIdx = k - 1;

// assume the biggest bit starts with 0
int res = dp[lastOneBitIdx][0] + dp[lastOneBitIdx][1];
// cout << "assume biggest bit = 0's res : " << res << endl;

bool mySelfOk = true;
while(lastOneBitIdx > 0) {
int nextOneBitIdx = lastOneBitIdx - 1;
cout << ">> last/next: " << lastOneBitIdx << "/" << nextOneBitIdx << endl;
cout << "bits[next] " << bits[nextOneBitIdx] << endl;

// find next one bit
while(bits[nextOneBitIdx] != 1) {
cout << "not 1 bit: " << nextOneBitIdx << endl;
-- nextOneBitIdx;
if(nextOneBitIdx == -1) {
return res + mySelfOk;
}
}

cout << "last/next: " << lastOneBitIdx << "/" << nextOneBitIdx << endl;
if(lastOneBitIdx - nextOneBitIdx < 2) {
// view the bits[nextOneBitIdx] as 0
res += dp[nextOneBitIdx][0] + dp[nextOneBitIdx][1];
mySelfOk = false;

// cout << "+ dp " << nextOneBitIdx << "," << 0 << " break!!!" << endl;
break;
} else {
// view the bits[nextOneBitIdx] as 0
res += dp[nextOneBitIdx][0] + dp[nextOneBitIdx][1];
lastOneBitIdx = nextOneBitIdx;
// cout << "+ dp " << nextOneBitIdx << "," << 0 << endl;
// cout << "lastOneBitIdx is " << lastOneBitIdx << endl;
// cout << "curRes = " << res;

}
}

cout << "me ok: " << mySelfOk << endl;

return res + mySelfOk;
}
};

1 欧拉图基本概念:

圈:任选图中一个顶点为起点,沿着不重复的边,经过不重复的顶点为途径,之后又回到起点的闭合途径称为圈。

欧拉路径:通过图中所有边一次且仅一次遍历所有顶点的路径称为欧拉(Euler)路径;

欧拉回路:通过图中所有边一次且仅一次行遍所有顶点的回路称为欧拉回路;

欧拉图:具有欧拉回路的图称为欧拉图;

半欧拉图:有欧拉路径但没有欧拉回路的图称为半欧拉图。

欧拉图与半欧拉图的判定:

G是欧拉图 ⇔ G中所有顶点的度均为偶数 ⇔ G是若干个边不重的圈的并。

G是半欧拉图 ⇔ G中恰有两个奇数度顶点。

  • 2 hierholzer算法

    • 2.1 dfs,当一个节点没邻居了
    • 2.2 将节点入栈reversePath
    • 2.3 dfs完成,reversePath则为逆序栈
  • 3 例子:
    https://media.geeksforgeeks.org/wp-content/uploads/Euler-3.jpg

从0开始的话,那么
访问栈为:0 -> 1 -> 2 -> 0, 此时reversePath可以将访问栈里的0弹出加入,则reversePath = [0, 2]
此时访问栈为: 0-> 1 ,接着访问3,4,然后弹出4,3,1,0
则 reversPath = [0,2,4,3,1,0],然后逆序则为:
0 1 3 4 2 0,为目标的eular path

2 一句话总结hierhozer算法: dfs后续遍历节点的逆序为eular路径

1 深度优先遍历

最常见的优化:

  • 1 记忆化搜索: 使用hash记录遍历起点对应的值,然后直接从hash中获得,避免重复计算

  • 2 常见算法:
    对于欧拉图和半欧拉图算欧拉路径:hierholzer算法

2 例子

0332HierholzerToFindEulerPath 找欧拉路径

1 题目

https://leetcode-cn.com/problems/reconstruct-itinerary/

2 解题思路

hierholzer算法参考:https://www.geeksforgeeks.org/hierholzers-algorithm-directed-graph/
https://blog.csdn.net/qq_34292517/article/details/105463522?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_title~default-0.pc_relevant_default&spm=1001.2101.3001.4242.1&utm_relevant_index=3

  • 1 自己的思路:
    • 1.1 主要是使用hierholzer算法找到欧拉路径,由于需要字典序,那么我们邻接表则使用优先队列来存储
  • 2 hierholzer算法
    • 2.1 dfs,当一个节点没邻居了(因为每访问一条边就删除一条边)
    • 2.2 将节点入栈reversePath
    • 2.3 dfs完成,reversePath则为逆序栈
  • 3 欧拉图等等理解:参考上述第二篇文章

    基本概念

圈:任选图中一个顶点为起点,沿着不重复的边,经过不重复的顶点为途径,之后又回到起点的闭合途径称为圈。

欧拉路径:通过图中所有边一次且仅一次遍历所有顶点的路径称为欧拉(Euler)路径;

欧拉回路:通过图中所有边一次且仅一次行遍所有顶点的回路称为欧拉回路;

欧拉图:具有欧拉回路的图称为欧拉图;

半欧拉图:有欧拉路径但没有欧拉回路的图称为半欧拉图。

欧拉图与半欧拉图的判定:

G是欧拉图 ⇔ G中所有顶点的度均为偶数 ⇔ G是若干个边不重的圈的并。

G是半欧拉图 ⇔ G中恰有两个奇数度顶点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Solution {
public:

int edgeNums = -1;
int nodeNums = -1;
unordered_map<string, int> nodes;
unordered_map<int, string> toStr;
vector<string> findItinerary(vector<vector<string>>& tickets) {
// map str to int according to dictionary order
set<string, std::less<string>> airports;
for(auto& vec : tickets) {
airports.insert(vec[0]);
airports.insert(vec[1]);
}

int i = 0;
for(auto& str : airports) {
toStr[i] = str;
nodes[str] = i++;
}

// construct the adj table
int nodeNums = airports.size();
int edgeNums = tickets.size();
// vector<vector<int>> table(nodeNums, vector<int>(0));
vector<priority_queue<int, vector<int>, greater<int>>> table(nodeNums, priority_queue<int, vector<int>, greater<int>>());
for(auto& vec : tickets) {
table[nodes[vec[0]]].push(nodes[vec[1]]);
}

vector<string> strRes;
vector<priority_queue<int, vector<int>, greater<int>>> tableTmp(table);
vector<int> res;
dfs(nodes["JFK"], tableTmp, res);
reverse(res.begin(), res.end());
for(auto& node : res) {
strRes.push_back(toStr[node]);
}

return strRes;
}

//
void dfs(int st, vector<priority_queue<int, vector<int>, greater<int>>>& map, vector<int>& res) {

while(!map[st].empty()) {
int nextSt = map[st].top();
map[st].pop();
// cout << "from to: " << toStr[st] << " -> " << toStr[nextSt] << endl;
dfs(nextSt, map, res);
}
res.emplace_back(st);
}
};

0753crackSafe 破解保险箱(变形欧拉路)

1 题目

https://leetcode-cn.com/problems/freedom-trail/
https://leetcode-cn.com/problems/reconstruct-itinerary/

2 解题思路

  • 1 对于 n = 3, k = 3, 我们的图的节点为(k ^ (n-1)个): 00, 01, …, 22, 然后每个节点都有k个边,这样一共是k^n个边
    • 1.1 那么如何认为走过一条边就是尝试一次密码呢?
    • 1.2 比如: 00的邻接顶点为0,1,2, 那么当dfs访问从00节点到其邻接点分别组成的边为000,001,002,则他们对应的下一跳就为: 00,01,02,也就是取当前dfs访问得到的边的后n-1位
  • 2 hierholzer算法
    • 2.0 选择一个节点开始dfs
    • 2.1 当一个节点没邻居了
    • 2.2 将节点入栈reversePath
    • 2.3 dfs完成,reversePath则为逆序栈
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      class Solution {
      public:
      int n = -1;
      int k = -1;
      vector<char> kVec;
      string crackSafe(int n, int k) {
      // since for each bit, there a k's possiblity
      // so the final str's length = k^n
      // consider a G, vertices are {0, 1, ..., k-1}
      // for each edge: vi -> vj(vi could equal vj),
      // there shall be n-1's such same edge
      // we just need a way to walk through the G
      // try hierholzer algo
      if(1 == n) {
      string tmpRes = "";
      for(int i = 0; i < k; ++i) {
      tmpRes.push_back(i + '0');
      }
      return tmpRes;
      }

      this->k = k;
      this->n = n;
      for(int i = 0; i < k; ++i) {
      kVec.push_back(i + '0');
      }

      unordered_set<string> seen;
      unordered_map<string, vector<char>> graph;
      buildGraph("", n - 1, graph);

      string stStr(n-1, '0');

      string res = "";
      hierholzer(stStr, graph, res, seen);

      // when n=3, k=3, we start from "00" node, so we add reverse of "00" to the end of the res, cause hierholzer produce a reverse eular path (start from "00", end to "00")
      res += stStr;
      return res;
      }

      void buildGraph(string tmp, int leftBitNum, unordered_map<string, vector<char>>& graph) {
      if(0 == leftBitNum) {
      // cout << "len: " << leftBitNum << "finish node: " << tmp << endl;
      graph[tmp] = kVec;
      return;
      }

      for(int st = 0; st < k; ++st) {
      buildGraph(tmp + kVec[st], leftBitNum-1, graph);
      }
      }

      void hierholzer(
      string st,
      unordered_map<string, vector<char>>& graph,
      string& res,
      unordered_set<string>& seen) {
      // cout << "doing : " << st << endl;
      bool hasOut = false;
      for(int edIdx = 0; edIdx < k; ++edIdx) {
      string curEdge(st);
      curEdge.push_back(graph[st][edIdx]);

      if(seen.count(curEdge)) {
      continue;
      }

      hasOut = true;
      string nextSt = curEdge.substr(1);
      // cout << "st -> nextSt: " << st << " -> " << nextSt << endl;
      seen.insert(curEdge);
      // cout << "see edge: " << curEdge << endl;
      hierholzer(nextSt, graph, res, seen); // post order
      res.push_back(graph[st][edIdx]); // hierholzer
      }
      }

      };

0514wayOfFreedom 自由之路

1 题目

https://leetcode-cn.com/problems/freedom-trail/

2 解题思路

  • 1 首先考虑到,key中的每一个字符,环的所有同种字符的所有位置都是遍历的可能
    • 1.1 于是使用dfs去尝试对于key的一个字符的每个位置即可,然后讲每个位置得到的结果比较取一个最小值
    • 1.2 1.1中提到的算法肯定是有问题的,比如key: abc, 然后ring: aaabbbccc
      • 会出现哪种情况呢? ring中a的三个位置都会搜索,然后对于剩下的key和ring bc以及bbbccc会由于a有三个位置而搜索了三遍,所以需要记忆化搜索
    • 1.3 使用memo[i][j]记录: key[i:]和ring[j:]对应的最小步数即可
  • 2 经过1的思考,也较为容易知道,本题目,动态规划也能做
  • 3 写代码的教训,由于我将dfsmemo写好,然后调用dfs的地方也改了,但是依然超时?
    • 3.1 因为dfsmemo递归调用不是自身,而是dfs函数,所以务必及时清理不需要的代码
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      class Solution {
      public:

      int ringLen = -1;
      int keyLen = -1;
      int findRotateSteps(string ring, string key) {
      unordered_map<char, vector<int>> ringMap;
      ringLen = ring.length();
      keyLen = key.length();
      int pos = 0;
      for(auto& c : ring) {
      if(0 == ringMap.count(c)) {
      vector<int> tmp = {pos};
      ringMap[c] = tmp;
      } else {
      ringMap[c].push_back(pos);
      }
      ++pos;
      }
      vector<vector<int>> memo(keyLen, vector<int>(ringLen, INT_MAX));
      return dfsMemo(0, 0, ringMap, key, memo);
      }

      // no memo dfs, too slow
      int dfs(int tar, int markPos, unordered_map<char, vector<int>>& ringMap, string& key) {
      if(tar == key.length()) {
      return 0;
      }

      int minStep = INT_MAX;
      // for cur key char, try ervery possible way on the ring
      for(auto tarPos : ringMap[key[tar]]) {
      int curStep = minDis(tarPos, markPos); // rotate
      curStep += 1; // write

      minStep = min(
      minStep,
      dfs(tar + 1, tarPos, ringMap, key) + curStep
      );
      }
      return minStep;
      }

      // memo version
      int dfsMemo(int tar, int markPos, unordered_map<char, vector<int>>& ringMap, string& key,
      vector<vector<int>>& memo) {
      if(tar == key.length()) {
      return 0;
      }

      if(INT_MAX != memo[tar][markPos]) {
      return memo[tar][markPos];
      }

      // for cur key char, try ervery possible way on the ring
      for(auto tarPos : ringMap[key[tar]]) {
      int curStep = minDis(tarPos, markPos); // rotate
      curStep += 1; // write
      memo[tar][markPos] = min(
      memo[tar][markPos],
      dfsMemo(tar + 1, tarPos, ringMap, key, memo) + curStep
      );
      }
      // cout << "memo ing: " << tar << ", " << markPos << ": " << memo[tar][markPos] << endl;
      return memo[tar][markPos];
      }

      int minDis(int tarPos, int markPos) {
      int gt = max(tarPos, markPos);
      int lt = min(tarPos, markPos);
      return min(gt - lt, lt + ringLen - gt);
      }
      };

0968minCameraCover 监控二叉树

1 题目

https://leetcode-cn.com/problems/binary-tree-cameras/submissions/

2 解题思路

  • 1 首先得在思考的过程中,理解改题目的本质就是,
    • 1.1 在dfs的过程中,在每个节点可以放置或者不放置相机,重要的是,如何去体现放置还是不放置相机
    • 1.2 如何提现呢?很简单,比如root放置,然后你想让它的两个子节点都不放置,那么直接递归调用两个子节点的后代即可,具体看代码即可
    • 1.3 那么对于每个节点,有几种放置相机的可能呢?
      • 一共三种,要么root,要么left,要么right,然后取最小代价即可,这里以选择left子节点仔细说明:
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        // choosing: right 
        int tmpRightCover = min(
        // r的监控器对r的两个子节点都有监控作用,于是直接去计算两个子节点的子节点
        1 + minCameraCover(r_rll) + minCameraCover(r_rlr) + minCameraCover(r_rrl) + minCameraCover(r_rrr) + minCameraCover(r_l),
        min(
        // r的监控器对r的两个子节点中的右孩子有监控作用,于是计算方式变为算右孩子两个子节点加上左侧节点
        // partly ignore, will not put cam on r_rr, may on r_r
        1 + minCameraCover(r_rrl) + minCameraCover(r_rrr) + minCameraCover(r_rl) + minCameraCover(r_l),
        // r的监控器对r的两个子节点中的左孩子有监控作用,于是计算方式变为算左孩子两个子节点加上右侧节点
        // partly ignore, will not put cam on r_rl, may on r_l
        1 + minCameraCover(r_rll) + minCameraCover(r_rlr) + minCameraCover(r_rr) + minCameraCover(r_l)
        )
        );
  • 2 优化思路:
    • 2.1 同一层递归里面,相同函数名不要反复出现,用临时变量存储以加速
    • 2.2 使用hash存储对应的节点的最小监控值,如果能在hash命中就不用反复计算
    • 2.3 优先计算小规模,然后计算大规模
  • 3 关于为什么需要hash来避免反复计算:
    • 3.1 看例子:
      [0,null,0,null,0,null,0,null,0,null,0,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,0,null,null,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null,0,null]
    • 3.2 也就是单链表,你会发现,到达第4个节点,可以有两种监控方式,那么说明第4个节点的计算会重复2次,于是需要记忆化搜索
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int level = 0;
unordered_map<TreeNode* , int> memo;
int minCameraCover(TreeNode* root) {
++level;
if(nullptr == root) {
return 0;
}

if(nullptr == root->left && nullptr == root->right) {
return 1;
}

// dp:
TreeNode* r_l = root->left;
TreeNode* r_r = root->right;

TreeNode* r_ll = getL(r_l);
TreeNode* r_lr = getR(r_l);
TreeNode* r_rl = getL(r_r);
TreeNode* r_rr = getR(r_r);

TreeNode* r_lll = getL(r_ll);
TreeNode* r_llr = getR(r_ll);
TreeNode* r_lrl = getL(r_lr);
TreeNode* r_lrr = getR(r_lr);
TreeNode* r_rll = getL(r_rl);
TreeNode* r_rlr = getR(r_rl);
TreeNode* r_rrl = getL(r_rr);
TreeNode* r_rrr = getR(r_rr);

int cover_r_lll = getFromMemo(r_lll);
int cover_r_llr = getFromMemo(r_llr);
int cover_r_lrl = getFromMemo(r_lrl);
int cover_r_lrr = getFromMemo(r_lrr);
int cover_r_rll = getFromMemo(r_rll);
int cover_r_rlr = getFromMemo(r_rlr);
int cover_r_rrl = getFromMemo(r_rrl);
int cover_r_rrr = getFromMemo(r_rrr);

int cover_r_ll = getFromMemo(r_ll);
int cover_r_lr = getFromMemo(r_lr);
int cover_r_rl = getFromMemo(r_rl);
int cover_r_rr = getFromMemo(r_rr);

int cover_r_l = getFromMemo(r_l);
int cover_r_r = getFromMemo(r_r);

// // choosing: root
// int tmpRootCover = min(
// // min(
// // do not ignore
// 1 + minCameraCover(r_ll) + minCameraCover(r_lr) + minCameraCover(r_rl) + minCameraCover(r_rr),
// // 1 + minCameraCover(r_l) + minCameraCover(r_r)
// // ),
// // partly ignore root choosen
// min(
// 1 + minCameraCover(r_ll) + minCameraCover(r_lr) + minCameraCover(r_r),
// 1 + minCameraCover(r_rl) + minCameraCover(r_rr) + minCameraCover(r_l)
// )
// );

int tmpRootCover = min(
1 + cover_r_ll + cover_r_lr + cover_r_rl + cover_r_rr,
min(
1 + cover_r_ll + cover_r_lr + cover_r_r,
1 + cover_r_rl + cover_r_rr + cover_r_l
)
);

// // choosing: right
// int tmpRightCover = min(
// // don't ignore, will not put cam on r_rl, r_rr
// 1 + minCameraCover(r_rll) + minCameraCover(r_rlr) + minCameraCover(r_rrl) + minCameraCover(r_rrr) + minCameraCover(r_l),
// min(
// // partly ignore, will not put cam on r_rr, may on r_r
// 1 + minCameraCover(r_rrl) + minCameraCover(r_rrr) + minCameraCover(r_rl) + minCameraCover(r_l),
// // partly ignore, will not put cam on r_rl, may on r_l
// 1 + minCameraCover(r_rll) + minCameraCover(r_rlr) + minCameraCover(r_rr) + minCameraCover(r_l)
// )
// );

int tmpRightCover = min(
1 + cover_r_rll + cover_r_rlr + cover_r_rrl + cover_r_rrr + cover_r_l,
min(
1 + cover_r_rrl + cover_r_rrr + cover_r_rl + cover_r_l,
1 + cover_r_rll + cover_r_rlr + cover_r_rr + cover_r_l
)
);

// // choosing: left
// int tmpLeftCover = min(
// 1 + minCameraCover(r_lll) + minCameraCover(r_llr) + minCameraCover(r_lrl) + minCameraCover(r_lrr) + minCameraCover(r_r),
// min(
// 1 + minCameraCover(r_ll) + minCameraCover(r_lrl) + minCameraCover(r_lrr) + minCameraCover(r_r),
// 1 + minCameraCover(r_lr) + minCameraCover(r_lll) + minCameraCover(r_llr) + minCameraCover(r_r)
// )
// );
int tmpLeftCover = min(
1 + cover_r_lll + cover_r_llr + cover_r_lrl + cover_r_lrr + cover_r_r,
min(
1 + cover_r_ll + cover_r_lrl + cover_r_lrr + cover_r_r,
1 + cover_r_lr + cover_r_lll + cover_r_llr + cover_r_r
)
);

return min(tmpRootCover, min(tmpLeftCover, tmpRightCover));
}

TreeNode* getR(TreeNode* root) {
if(nullptr == root) {
return nullptr;
} else {
return root->right;
}
}
TreeNode* getL(TreeNode* root) {
if(nullptr == root) {
return nullptr;
} else {
return root->left;
}
}

int getFromMemo(TreeNode* root) {
if(memo.end() == memo.find(root)) {
memo[root] = minCameraCover(root);
}
return memo[root];
}

};

四种基础排序

  • 归并排序
  • 堆排序
  • 快速排序
  • 桶排序

1 题目:

https://leetcode-cn.com/problems/sort-an-array/

2 解题思路:

  • 1 分别详细解释 快速排序,归并排序,堆排序
    • 1.1 快速排序
      • 算法核心:选择一个数字,然后将比他大的都移动到他左边,比他小的都是右边,然后分别对左侧区域和右侧区域递归执行即可
      • 速度和稳定性:在单调情况下达到最坏,为O(n**2),不稳定的原因,中枢元素会和最后一个比他小的数字交换位置(以此将左侧分为比他小的,右侧比他大的);
    • 1.2 归并排序
      • 算法核心:切分成左右两部分,然后对于左右两部分分别执行算法本身,知道之剩下2个以及1个元素,返回后合并即可
      • 速度稳定性:速度稳定,元素之间次序稳定
    • 1.3 堆排序:
      • 算法核心:首先建立一个大根堆,之后第i次将大根堆的top移动到nums的nums[n-i]处,而后将nums[0:n-i-1]重新排列成大根堆,一次选出一个最大元素排在末尾,可以看出这里主要代价在于将maxHeapfiy(nums[0:n-i-1])
      • 速度稳定性:速度稳定,元素之间次序不稳定,因为对于7,5,5 你会发现一开始将最后的5移动到了第一个,打乱了次序
      • 图解参考:https://zhuanlan.zhihu.com/p/124885051
    • 1.4 使用桶排序
      • 1.4.1 根据最大最小值算桶的大小
      • 1.4.2 将桶排列好,将每个值放入(桶内部插入排序)
      • 1.4.3 将所有的值从桶里取出来,然后形成排序后的数组
      • 平均时间复杂度o(n + k),最烂:o(n**2),为稳定排序
      • 空间复杂度o(n)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
// quick sort, most bad when it is sorted o(n**2), not stable
// quickSort(0, nums.size() - 1, nums);

// merge sort, stable
// mergeSort(0, nums.size() - 1, nums);

// heap sort, not stable
head_sort(0, nums.size() - 1, nums);
return nums;
}

void head_sort(int st, int ed, vector<int>& nums) {
// init
buildMaxHeap(nums);

// everytime select max and put to end and call maxHeapify
int len = nums.size();
for(int ed = len - 1; ed >= 0; --ed) {
swap(st, ed, nums);
// nums[ed:] is sorted
maxHeapify(st, ed - 1, nums);
}
}

void buildMaxHeap(vector<int>& nums) {
// start from the last dad node, till all dad node be the max heap root
int len = nums.size();
for(int dad = len / 2 - 1; dad >= 0; --dad) {
maxHeapify(dad, len - 1, nums);
}
}

void maxHeapify(int st, int ed, vector<int>& nums) {
if(ed <= st) {
return;
}

while(st <= ed) {
// cmp the st and its lChild and rChild and decide which side to descend
int lChild = st * 2 + 1;
int rChild = st * 2 + 2;
// cout << "l/r: " << lChild << " " << rChild << endl;
if(lChild > ed) {
return;
} else if(rChild > ed) {
if(nums[st] < nums[lChild]) {
swap(st, lChild, nums);
}
return;
}
if(nums[st] >= nums[lChild] && nums[st] >= nums[rChild]) {
return;
} else {
if(nums[lChild] > nums[rChild]) {
swap(st, lChild, nums);
st = lChild;
} else {
swap(st, rChild, nums);
st = rChild;
}
}
}
}

void mergeSort(int st, int ed, vector<int>& nums) {
// sort when |st - ed| <= 1
if(ed - st == 1) {
if(nums[ed] < nums[st]) {
swap(ed, st, nums);
}
return;
} else if(ed == st) {
return;
}
int mid = (st + ed) / 2;
cout << mid << endl;
mergeSort(st, mid, nums);
mergeSort(mid + 1, ed, nums);

// merge
int curL = 0;
int curR = 0;
vector<int> tmpL(nums.begin() + st, nums.begin() + mid + 1);
vector<int> tmpR(nums.begin() + mid + 1, nums.begin() + ed + 1);

for(int i = st; i <= ed; ++i) {
if(curL == mid - st + 1) {
// cout << "using R to fullly fill from " << curR << endl;
for(; curR < ed - mid; ++curR) {
nums[i++] = tmpR[curR];
}
break;
} else if(curR == ed - mid) {
// cout << "using L to fullly fill from " << curL << endl;
for(; curL < mid - st + 1; ++curL) {
nums[i++] = tmpL[curL];
}
break;
} else if(tmpL[curL] < tmpR[curR]) {
nums[i] = tmpL[curL++];
// cout << "using L: " << nums[i] << endl;
} else {
nums[i] = tmpR[curR++];
// cout << "using R: " << nums[i] << endl;
}
}
}

void quickSort(int st, int ed, vector<int>& nums) {
if(st >= ed) {
return;
}

int newPivot = partition(st, ed, nums);
quickSort(st, newPivot-1, nums);
quickSort(newPivot + 1, ed, nums);
}

int partition(int st, int ed, vector<int>& nums) {
// put 'smaller than st' to left, others right
int r = ed;
int l = st;
// st is the first blank place
int pivot = nums[st];
while(l < r){
// r form right to left
while(r > l && nums[r] > pivot) {
r--;
}
nums[l] = nums[r];

// l form left to right
while(l < r && nums[l] <= pivot) {
l++;
}
nums[r] = nums[l];
}
nums[l] = pivot;
return l;
}

void swap(int& a, int& b, vector<int>& nums) {
// cout << "swaping! " << a << " " << b << endl;
int tmp = nums[a];
nums[a] = nums[b];
nums[b] = tmp;
}

void print(vector<int>& nums) {
for(auto& i : nums) {
cout << i << " ";
}
cout << "\n";
}
};

0164maxGap 最大间距

1 题目

https://leetcode-cn.com/problems/maximum-gap/

2 解题思路

  • 1 使用桶排序
    • 1.1 平均时间复杂度o(n + k),最烂:o(n**2)
    • 1.2 空间复杂度o(n)
  • 2 桶排序算
    • 2.1 根据最大最小值算桶的大小
    • 2.2 将桶排列好,将每个值放入(桶内部插入排序)
    • 2.3 将所有的值从桶里取出来,然后形成排序后的数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution {
public:
int len = -1;
int maximumGap(vector<int>& nums) {
len = nums.size();
if(len <= 1) {
return 0;
}
// bucket sort
bucketSort(nums);

// getMaxGap
int gap = INT_MIN;
int last = nums[0];
for(auto i : nums) {
gap = max(i - last, gap);
last = i;
}
return gap;
}

void bucketSort(vector<int>& nums) {
int maxNum = *max_element(nums.begin(), nums.end());
int bucketSize = maxNum / len + 1;
int bucketNum = len + 1;
vector<list<int>> buckets(bucketNum, list<int>(0));

// put to buckets
for(int i = 0; i < len; ++i) {
insertSort(buckets[nums[i] / bucketSize], nums[i]);
}

// merge
int idx = 0;
for(auto& l : buckets) {
for(auto num : l) {
nums[idx++] = num;
}
}
}

void insertSort(std::list<int>& l, int num) {
for(list<int>::iterator it = l.begin(); it != l.end(); ++it) {
if(*it > num) {
l.insert(it, num);
return;
}
}

l.push_back(num);
}
};

Item 2: 用 consts, enums 和 inlines 取代 #defines

使用define的缺陷:

  • 1 预处理器盲目多次拷贝替换宏名,导致产生更多的代码 -> 使用const常量解决
  • 2 宏函数的嵌套需要打上括号 -> 使用内连函数达到相同的性能解决

使用enum替换const的用例:

1
2
3
4
5
6
7
8
class GamePlayer {
private:
enum { NumTurns = 5 }; // "the enum hack" - makes
// NumTurns a symbolic name for
5
int scores[NumTurns]; // fine
...
};

优点有其二:

  • 1 避免常量的指针和引用被取走
    首先,the enum hack
    的行为在几个方面上更像一个 #define 而不是 const,而有时这正是你
    所需要的。例如:可以合法地取得一个 const 的 address(地址),
    但不能合法地取得一个 enum 的 address(地址),这正像同样不能
    合法地取得一个 #define 的 address(地址)。如果你不希望人们得
    到你的 integral constants(整型族常量)的 pointer(指针)或
    reference(引用),enum(枚举)就是强制约束这一点的好方法。
  • 2 被模板元编程大量使用,属于实用主义

总结:

a. 对于 simple constants(简单常量),用 const objects(const 对
象)或 enums(枚举)取代 #defines。

b. 对于 function-like macros(类似函数的宏),用 inline
functions(内联函数)取代 #defines

Item 3: 只要可能就用 const

1 const用于指针,迭代器:

  • 1 用于指针:
    如果 const 出现在星号左边,则指针 pointed to(指向)的内容为 constant(常量);
    如果 const 出现在星号右边,则 pointer itself(指针自身)为
    constant(常量);如果 const 出现在星号两边,则两者都为
    constant(常量)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    char greeting[] = "Hello";
    char *p = greeting; // non-const pointer,
    // non-const data
    const char *p = greeting; // non-const pointer,
    // const data
    char * const p = greeting; // const pointer,
    // non-const data
    const char * const p = greeting; // const pointer,
    // const data
    void f1(const Widget *pw); // f1 takes a pointer to a
    // constant Widget object
    void f2(Widget const *pw); // so does f2
  • 2 用于迭代器:
    声明一个iterator 为 const 就类似于声明一个 pointer(指针)为 const(也就是说,声明一个 T* const pointer(指针)):不能将这个 iterator 指向另外一件不同的东西,但是它所指向的东西本身可以变化。
    若需要iterator指向的东西不能变,使用const_iterator即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    std::vector<int> vec;
    ...
    const std::vector<int>::iterator iter = // iter acts like a T*
    const
    vec.begin();
    *iter = 10; // OK, changes what iter
    points to
    ++iter; // error! iter is const
    std::vector<int>::const_iterator cIter = // cIter acts like a
    const T*
    vec.begin();
    *cIter = 10; // error! *cIter is const
    ++cIter; // fine, changes cIter

    2 用于函数

  • 1 返回值和传参是const
    避免了=和==的错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Rational { ... };
    const Rational operator*(const Rational& lhs, const Rational& rhs);

    Rational a, b, c;
    ...
    (a * b) = c; // invoke operator= on the
    // result of a*b!

    if (a * b = c) ... // oops, meant to do a comparison!

    参数在不会被改变的时候就应该传入const

  • 2 const member functions(const 成员函数)
    有两个原因:

    • 2.1 它使一个 class(类)的 interface(接口)更容易被理
      解。知道哪个函数可以改变 object(对象)而哪个不可以是很重要
      的。
    • 2.2 它们可以和 const objects(对象)一起工作。因为,书写
      高效代码有一个很重要的方面,就像 Item 20 所解释的,提升一个
      C++ 程序的性能的基本方法就是 pass objects by reference-to const(以传引用给 const 的方式传递一个对象)。

具体看如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // operator[]
for
{ return text[position]; } // const objects

char& operator[](std::size_t position) // operator[]
for
{ return text[position]; } // non-const objects

private:
std::string text;
};
// ------------------- 1 -----------------------
TextBlock tb("Hello");
std::cout << tb[0]; // calls non-const
// TextBlock::operator[]
const TextBlock ctb("World");
std::cout << ctb[0]; // calls const
TextBlock::operator[]

// -------------------- 2 ----------------------
void print(const TextBlock& ctb) // in this function, ctb is
const
{
std::cout << ctb[0]; // calls const TextBlock::operator[]
...
}
// --------------------- 3 ---------------------
std::cout << tb[0]; // fine — reading a
// non-const TextBlock
tb[0] = 'x'; // fine — writing a
// non-const TextBlock
std::cout << ctb[0]; // fine — reading a
// const TextBlock
ctb[0] = 'x'; // error! — writing a
// const TextBlock

3 const的二进制常量性和逻辑常量性

bitwise(二进制位)const 派别坚持认为,一个 member function(成
员函数),当且仅当它不改变 object(对象)的任何 data
members(数据成员)(static(静态的)除外),也就是说如果不改
变 object(对象)内的任何 bits(二进制位),则这个 member
function(成员函数)就是 const。bitwise constness(二进制位常量
性)的一个好处是比较容易监测违例:编译器只需要寻找对 data
members(数据成员)的 assignments(赋值)。实际上,bitwise
constness(二进制位常量性)就是 C++ 对 constness(常量性)的
定义,一个 const member function(成员函数)不被允许改变调用它
的 object(对象)的任何 non-static data members(非静态数据成
员)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CTextBlock {
public:
...
char& operator[](std::size_t position) const // inappropriate
(but bitwise
{ return pText[position]; } // const)
declaration of
// operator[]
private:
char *pText;
};
// ----------------------------------------------
const CTextBlock cctb("Hello"); // declare constant object
char *pc = &cctb[0]; // call the const operator[]to get a
// pointer to cctb's data
*pc = 'J'; // cctb now has the value "Jello"

上述过程中:你用一个 particular value(确定值)创建一个
constant object(常量对象),然后你只是用它调用了 const member
functions(成员函数),但是你还是改变了它的值!
这就引出了 logical constness(逻辑常量性)的概念。这一理论的信
徒认为:一个 const member function(成员函数)可能会改变调用它
的 object(对象)中的一些 bits(二进制位),但是只能用客户无法
察觉的方法。例如,你的 CTextBlock class(类)在需要的时候可以
储存文本块的长度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class CTextBlock {
public:
...
std::size_t length() const;
private:
char *pText;
// 可以解脱的代码
// mutable std::size_t textLength; // these data members may
// mutable bool lengthIsValid; // always be modified, even
in
std::size_t textLength; // last calculated length of
textblock
bool lengthIsValid; // whether length is currently
valid
};
std::size_t CTextBlock::length() const
{
if (!lengthIsValid) {
textLength = std::strlen(pText); // error! can't assign to textLength
lengthIsValid = true; // and lengthIsValid in a const
} // member function
return textLength;
}

上述代码会由于成员函数length()后面跟了一个const使得编译器保证了类的二进制常量性,那么想要修复这个问题,则只用mutable将其从中解脱。

4 const和non-const函数有相同实现的单向调用

为避免代码重复,则non版本调用const版本,具体例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // same as
before
{
...
...
...
return text[position];
}
char& operator[](std::size_t position) // now just calls
const op[]
{
return
const_cast<char&>( // cast away const on
// op[]'s return type;
static_cast<const TextBlock&>(*this)[position] // add const to *this's type; // call const version of op[]
); }
...
};

cosnt_cast去掉const属性,然后用static_cast将this转为const去调用对应的const函数。
而不能用const成员函数去调用非const版本的是因为需要将cosnt的
this转为non-const的this,会导致值发生改变,这就导致const成员函数失去本身意义。

总结:

将某些东西声明为 const 有助于编译器发现使用错误。const 能
被用于任何 scope(范围)中的 object(对象),用于 function
parameters(函数参数)和 return types(返回类型),用于整
个 member functions(成员函数)。

编译器坚持 bitwise constness(二进制位常量性),但是你应该
用 conceptual constness(概念上的常量性)来编程。

当 const 和 non-const member functions(成员函数)具有本质
上相同的实现的时候,使用 non-const 版本调用 const 版本可以
避免 code duplication(代码重复)。

1 有序集合

泛指set, map, pirority_queue等能够按照key进行排序,然后使用lower_bound和upper_bound来进行log(n)复杂度查询的基础数据结构
(注意priority_queue仅能够在堆顶进行操作)
示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
        set<int, std::less<int>> spareServers;
<被作者省略>
// request distribute, find the server
int tarServer = -1;
auto itr = spareServers.lower_bound(arrivalIdx % k);
// cout << "trying to find: " << arrivalIdx % k << endl;
if(itr != spareServers.end()) {
tarServer = *itr;
// cout << "find tar1: " << tarServer << endl;
} else { // search from the start just like search like a cycle
tarServer = *spareServers.lower_bound(0);
// cout << "find tar2: " << tarServer << endl;
}

2 具体示例

0363maxSumSubMatrix 最大子区域和

1 题目:

https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/

2 解题思路:

  • 1 普通思路:一维前缀和,按行计算前缀和,使用m x n的矩阵存储,然后计算矩形区域,则只需要O(m)的计算复杂度
  • 2 优化:二维前缀和,pre[i][j]存储的是0,0到i,j处的矩形区域和,那么计算方式为:

    preMat2[i+1][j+1] = preMat2[i][j+1] + preMat2[i+1][j] - preMat2[i][j] + matrix[i][j];

  • 3 使用二位前缀和去计算子区域的和,搜索子区域的方式:
    • 3.1 对于每个子区域的上下边界搜索左右边界,上下左边界搜索复杂度为o(n^2), 而后搜索左右边界,对于每一个右边界O(n),搜索左边界,什么样的左边界?left满足sum(up, down, 0, right) - k <= sum(up, down, 0, left), 也就是在[0, right]的范围内找到最小的大于sum(up, down, 0, right) - k的left,在遍历的过程中使用set存所有的sum(up, down, 0, right), 在这个set里面找left,使用lower_bound函数,只需要log(n)复杂度,于是总复杂度为 o(m^2 * nlog(n))
    • 3.2 上述核心思想: 本来需要找right和left的对子,但是现在借助k,找left就变成在right的历史中搜索即可
      • 3.2.1 原来思想:sum(up, down, 0, right) - sum(up, down, 0, left) <= k
      • 3.2.2 新的思想:sum(up, down, 0, right) - k <= sum(up, down, 0, left)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        class Solution {
        public:
        static constexpr int matLen = 102;
        vector<vector<int>> preMat; // 2d prefix sum
        int m = -1;
        int n = -1;
        int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
        // cal 2d prefix sum
        m = matrix.size();
        n = matrix[0].size();
        preMat.resize(m+1, vector<int>(n+1, 0));
        for(int i = 0; i < m; ++i){
        for(int j = 0; j < n; ++j){
        preMat[i+1][j+1] = preMat[i][j+1] + preMat[i+1][j] - preMat[i][j] + matrix[i][j];
        // cout << preMat[i+1][j+1] << endl;
        }
        }

        // for each up and down, find max l,r
        int res = INT_MIN;
        for(int d = 0; d < m; ++d) {
        for(int u = d; u >= 0; --u) {
        set<int> lSet = {0};
        for(int r = 0; r < n; ++r) {
        int sumRight = sumRegion(u, d, 0, r);
        set<int>::iterator lbPtr = lSet.lower_bound(sumRight - k);
        lSet.insert(sumRight);
        if(lbPtr != lSet.end()) {
        res = max(res, sumRight - *lbPtr);
        }
        }
        }
        }

        return res;
        }

        int sumRegion(int up, int down, int left, int right) {
        return preMat[down+1][right+1] - preMat[down+1][left] - preMat[up][right+1] - preMat[up][left];
        }
        };

0699fallingSquares 掉落的方块

1 题目:

https://leetcode-cn.com/problems/falling-squares/

2 解题思路:

  • 1 普通思路:使用一维数组模拟每个位置方块高度,复杂度为o(N^2),由于N很大,所以会超时
  • 2 优化:使用坐标压缩,将所有方块的边界存储在2000以内(因为一共就1000个方块),然后更新高度就按照2000以内去更新即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    class Solution {
    public:
    vector<int> fallingSquares(vector<vector<int>>& positions) {
    // coordinate compression
    set<int, std::less<int>> coords;
    for(vector<int>& coord : positions) {
    coords.insert(coord[0]);
    coords.insert(coord[0] + coord[1] - 1);
    }

    unordered_map<int, int> borderToIdx;
    int t = 0;
    for(auto& i : coords) {
    borderToIdx[i] = t++;
    }

    // cal heights
    vector<int> heights(t);
    vector<int> res;
    int curHeightest = INT_MIN;
    for(vector<int>& square : positions) {
    int l = borderToIdx[square[0]];
    int r = borderToIdx[square[0] + square[1] - 1];
    int h = square[1];
    int updatedHeight = update(l, r, h, heights);
    curHeightest = max(curHeightest, updatedHeight);
    res.emplace_back(curHeightest);
    }
    return res;
    }

    // update and return updated height of [l, r]
    int update(int l, int r, int h, vector<int>& heights) {
    int oldHeight = INT_MIN;
    for(int i = l; i <= r; ++i) {
    oldHeight = max(oldHeight, heights[i]);
    }
    int newHeight = INT_MIN;
    for(int i = l; i <= r; ++i) {
    heights[i] = oldHeight + h;
    }
    return oldHeight + h;
    }
    };

0715 Range 模块 rangeModule

1 题目:

https://leetcode-cn.com/problems/range-module/

2 解题思路:

  • 1 普通思路:使用map记录这些离散的区间
    • 1.1 添加、删除: 遍历map,看map的每一个区间和当前区间的关系,这样处理最为合适
    • 1.2 查询:使用upper_bound查询到第一个(左侧)比目标的left小的区间,然后看目标的left和right是否在map对应的区间内部
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      class RangeModule {
      public:
      map<int, int, std::less<int>> intervals;
      RangeModule() {

      }

      void addRange(int left, int right) {
      if(intervals.size() == 0) {
      intervals[left] = right;
      print();
      return ;
      }
      for(auto it = intervals.begin(); it != intervals.end(); ++it) {
      if(it->first >= left) {
      if(it->first > right) {
      continue;
      }
      while(it != intervals.end() && it->second <= right) {
      it = intervals.erase(it);
      }
      if(it == intervals.end()){
      intervals[left] = right;

      } else {
      if(it->first <= right) {
      int newRight = it->second;
      intervals.erase(it);
      intervals[left] = newRight;
      }else {
      intervals[left] = right;
      }
      }
      print();
      return;
      } else { // it->first < left
      if(it->second < left) {
      continue;
      }
      int newLeft = it->first;
      while(it != intervals.end() && it->second <= right) {
      it = intervals.erase(it);
      }
      if(it == intervals.end()){
      intervals[newLeft] = right;
      } else {
      if(it->first <= right) {
      int newRight = it->second;
      intervals.erase(it);
      intervals[newLeft] = newRight;
      }else {
      intervals[newLeft] = right;
      }
      }
      print();
      return;
      }
      }
      intervals[left] = right;
      print();
      }

      bool queryRange(int left, int right) {
      if(intervals.empty()) {
      return false;
      }

      auto lBig = intervals.upper_bound(left);
      if(lBig != intervals.begin()) {
      --lBig;
      return lBig->second >= right;
      } else {
      return false;
      }
      return false;
      }

      void removeRange(int left, int right) {
      // for(auto it = intervals.begin(); it != intervals.end(); ++it) {
      // for(int i = 0)
      // }

      auto lBig = intervals.lower_bound(left);
      int newLeft = 0;
      if(lBig != intervals.begin()) {
      // cout << "d1.5" << endl;
      --lBig;
      if(right < lBig->second) {
      intervals[right] = lBig->second;
      intervals[lBig->first] = left;
      print();
      return ;
      } else {
      // cout << "d2" << endl;
      if(lBig->second > left) {
      // cout << "d2.5" << endl;
      intervals[lBig->first] = left;
      }
      }
      ++lBig;

      while (lBig != intervals.end() && lBig->second < right) {
      lBig = intervals.erase(lBig);
      }
      if(lBig != intervals.end()) {
      if(lBig->first < right) {
      int secondRight = lBig->second;
      intervals.erase(lBig);
      intervals[right] = secondRight;
      }
      }
      } else {
      while (lBig != intervals.end() && lBig->second < right) {
      lBig = intervals.erase(lBig);
      }
      if(lBig != intervals.end()) {
      if(lBig->first < right) {
      int secondRight = lBig->second;
      intervals.erase(lBig);
      intervals[right] = secondRight;
      }
      }
      }

      print();

      }

      void print() {
      // cout << "-----st-----" << endl;
      // for(auto it = intervals.begin(); it != intervals.end(); ++it) {
      // cout << it->first << " " << it->second << endl;
      // }
      // cout << "-----ed-----" << endl;
      }
      };

      /**
      * Your RangeModule object will be instantiated and called as such:
      * RangeModule* obj = new RangeModule();
      * obj->addRange(left,right);
      * bool param_2 = obj->queryRange(left,right);
      * obj->removeRange(left,right);
      */

0850. 矩形面积 II

1 题目:

https://leetcode-cn.com/problems/rectangle-area-ii/solution

2 解题思路:

  • 1 参考官方思路的扫描线:https://leetcode-cn.com/problems/rectangle-area-ii/solution/ju-xing-mian-ji-ii-by-leetcode/
  • 2 总结过程:
    • 2.1 将一个矩形看成x1,x2,y1,ST; x1,x2,y2,ED;的两条线
    • 2.2 而后用active记录还没有遇到ED的那些矩形的第一个扫描线集合,每回新来了一根线,则将active中的所有线和当前线计算对应面面积加起来
    • 2.3 当active中遇到了矩形的ED,则将active中对应的矩形的ST删除
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      class Solution {
      public:
      static constexpr long long bigPrime = 1000000007;
      int rectangleArea(vector<vector<int>>& rectangles) {
      // lines
      int ST = 0;
      int ED = 1;

      vector<vector<int>> lines;
      for(auto& vec : rectangles) {
      lines.emplace_back(vector<int>{vec[0], vec[2], vec[1], ST});
      lines.emplace_back(vector<int>{vec[0], vec[2], vec[3], ED});
      }

      // sort the lines by the y
      sort(lines.begin(), lines.end(),
      [](vector<int>& a, vector<int>& b) {
      return a[2] < b[2];
      });

      // scan the lines
      vector<vector<int>> actives;
      long long res = 0;
      int lastY = lines[0][2];
      for(auto& line : lines) {
      int curY = line[2], type = line[3], x1 = line[0], x2 = line[1];
      int width = 0;
      int actX = -1;

      // actives: those opend lines, sorted by y
      for(auto& act : actives) {
      // cout << "act: " << act[0] << " " << act[1] << endl;
      actX = max(actX, act[0]);
      width += max(act[1] - actX, 0);
      actX = max(actX, act[1]);
      }

      res += static_cast<long long>(width) * (curY - lastY) % bigPrime;
      // cout << "w: " << width << endl;

      // add new actives
      if(type == ST) {
      actives.emplace_back(vector<int>{x1, x2, curY, type});
      // cout << "insert x1,2: " << x1 << " " << x2 << endl;
      // sort the opend lines of the started points
      sort(actives.begin(), actives.end(),
      [](vector<int>& line1, vector<int>& line2) {
      return line1[0] < line2[0];
      });
      } else {
      // find the active and rm it
      for(int i = 0; i < actives.size(); ++i) {
      if(actives[i][0] == x1 && actives[i][1] == x2) {
      actives.erase(actives.begin() + i);
      // cout << "earse x1,2: " << x1 << " " << x2 << endl;
      break;
      }
      }
      }

      lastY = curY;
      }

      return res % bigPrime;

      }

      vector<vector<int>> getSkyLine(vector<vector<int>>& buildings) {
      function<bool(pair<int, int>&, pair<int, int>&)> cmp =
      [](pair<int, int>& a, pair<int, int>& b) {
      return a.second < b.second;
      };

      // <rightBound, height>
      priority_queue<pair<int, int>, vector<pair<int, int>>, function<bool(pair<int, int>&, pair<int, int>&)> > queue(cmp);

      vector<int> bounds;
      vector<vector<int>> res;
      int n = buildings.size();
      for(int i = 0; i < n; ++i) {
      bounds.emplace_back(buildings[i][0]);
      bounds.emplace_back(buildings[i][1]);
      }

      sort(bounds.begin(), bounds.end(), std::less<int>());
      // std::cout << "c1" << endl;

      int j = 0;
      for(auto& curBound : bounds) {
      // std::cout << "c " << j << endl;
      // push buildings lefter than curBound until one righter meet
      while(j < buildings.size() && curBound >= buildings[j][0]) {
      queue.push(make_pair(buildings[j][1], buildings[j][2]));
      ++j;
      }
      // std::cout << "c " << "adf" << endl;

      // pop out those rec unrelevant
      while(!queue.empty() && curBound >= queue.top().first) {
      queue.pop();
      }

      int curBoundHeight = queue.empty() ? 0 : queue.top().second;

      // if(res.size() == 0 || res.back()[1] != curBoundHeight) {
      // res.emplace_back(vector<int>{curBound, curBoundHeight});
      // }
      if(res.size() == 0 || res.back()[1] != curBoundHeight) {
      res.emplace_back(vector<int>{curBound, curBoundHeight});
      }
      }

      return res;
      }

      void print(vector<vector<int>>& vec) {
      for(auto& v : vec) {
      for(auto& i : v) {
      cout << i << " ";
      }
      cout << endl;
      }

      }
      };

0895maxFreqStack 最大频率栈

1 题目:

https://leetcode-cn.com/problems/maximum-frequency-stack/

2 解题思路:map/hash栈

  • 1 参考官方思路的扫描线:https://leetcode-cn.com/problems/maximum-frequency-stack/solution/zui-da-pin-lu-zhan-by-leetcode/
  • 2 总结过程:
    • 2.1 首先用map维持每一个变量的频率
      • 2.1.1 维持方法: push中对应的key加一,pop对应的key减一
    • 2.2 如何获得当前最大频率?
      • 2.2.1 在每个变量push和pop的时候我们可以获得对应的key的频率,那么所有的key的频率变动都是在push和pop执行完成之后,那么只需要用一个变量maxFreq维持最大频率即可
    • 2.3 知道最大频率,如何获得当前最大频率的变量?
      • 2.3.1 使用map<频率,频率对应的数的集合>来记录即可,然后用2.2中使用maxFreq获取最大频率对应的数字的集合,那如何从这个集合中获取在栈顶的数据呢?map<频率,频率对应的数的集合>中 频率对应的数的集合 使用stack去存就好了,因为stack的栈顶总是存着最新来的数据
    • 2.4 一个实例: 3,3,3都是push,那么在频率为1,2,3的栈中,很自然的都有一个3,自然体现在哪里呢?现在pop一下,频率为3对应的stack为空,然后最大频率变为2,然后2里面同样是3
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      class FreqStack {
      public:
      int opNum;
      map<int, int> opNumMap;
      map<int, int> freqMap;
      map<int, vector<int>*> groupByFreq;
      int maxFreq;
      FreqStack() {

      }

      void push(int val) {
      // cout << "pushing st: with max freq: " << maxFreq << endl;
      freqMap[val]++;
      if(groupByFreq[freqMap[val]] == nullptr) {
      groupByFreq[freqMap[val]] = new vector<int>();
      }
      groupByFreq[freqMap[val]]->emplace_back(val);
      maxFreq = max(maxFreq, freqMap[val]);
      // cout << "pushing done: " << val << "with maxFreq: " << maxFreq << endl;
      }

      int pop() {
      // cout << "pop st with maxFreq: " << maxFreq << endl;
      // find biggest frequency and most newly element to rm
      int popRes = groupByFreq[maxFreq]->back();
      groupByFreq[maxFreq]->pop_back();
      if(groupByFreq[maxFreq]->size() == 0) {
      delete groupByFreq[maxFreq];
      groupByFreq[maxFreq] = nullptr;
      --maxFreq;
      }
      // cout << "pop ed" << popRes << " with maxFreq: " << maxFreq << endl;
      freqMap[popRes]--;
      return popRes;
      }
      };

      /**
      * Your FreqStack object will be instantiated and called as such:
      * FreqStack* obj = new FreqStack();
      * obj->push(val);
      * int param_2 = obj->pop();
      */

1606busiestServers 找到处理最多请求的服务器

1 题目:

https://leetcode-cn.com/problems/find-servers-that-handled-most-number-of-requests/

2 解题思路:

  • 1 自然的思路:对于每一个到来的请求,我们将服务器分为两部分,一部分是空闲服务器,一部分是繁忙服务器
    • 1.1 如何快速找到空闲服务器?我们用set记录空闲服务器即可,两次使用lower_bound完成cycle查询
    • 1.2 如何记录繁忙服务器?在每一个请求到来以及处理完成,都可能出现繁忙服务器的改动和空闲服务器的改动,于是采用map<到期时间,相应服务器列表>来存储繁忙服务器
    • 1.3 如何处理请求处理完成时的服务器从繁忙变为空闲?
      • 1.3.1 采用小根堆存储当前所有繁忙服务器的到期时间,那么只需要在请求到来的时候,将所有比当前请求小的到期时间的繁忙服务器变为空闲即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
class Solution {
public:
vector<int> busiestServers(int k, vector<int>& arrival, vector<int>& load) {
// using small root heap to store the smallest avaliable server
// auto cmp = [](const int& a, const int b)
set<int, std::less<int>> spareServers;
map<int, vector<int>> busyServers;
priority_queue<int, vector<int>, std::greater<int>> dues;
vector<pair<int, int>> serverSumLoad;

for(int i = 0; i < k; ++i) {
spareServers.insert(i);
serverSumLoad.push_back(make_pair(i,0));
}

// deal request
int reqNum = arrival.size();
int arrivalIdx = 0;
for(int arrival : arrival) {
// cout << "req: " << arrival << "load: " << load[arrivalIdx] << endl;
// release servers
while(!dues.empty() && dues.top() <= arrival) {
int due = dues.top();
dues.pop();
vector<int>& toRelease = busyServers[due];
for(auto i : toRelease) {
spareServers.insert(i);
// cout << "releasing " << i << endl;
}
// busyServers.erase(due);
}

// abandon the request
if(spareServers.empty()) {
++ arrivalIdx;
// cout << "abandon!" << endl;
continue;
}

// request distribute, find the server
int tarServer = -1;
auto itr = spareServers.lower_bound(arrivalIdx % k);
// cout << "trying to find: " << arrivalIdx % k << endl;
if(itr != spareServers.end()) {
tarServer = *itr;
// cout << "find tar1: " << tarServer << endl;
} else { // search from the start just like search like a cycle
tarServer = *spareServers.lower_bound(0);
// cout << "find tar2: " << tarServer << endl;
}
spareServers.erase(tarServer);

// set the server to busy
int due = arrival + load[arrivalIdx];
if(busyServers.find(due) == busyServers.end()) {
vector<int> tmpVec = {tarServer};
busyServers[due] = tmpVec;
dues.push(due);
// cout << "init busy due : " << tarServer << " to " << due << endl;
} else {
busyServers[due].push_back(tarServer);
// cout << "add busy due : " << tarServer << " to " << due << endl;
}
serverSumLoad[tarServer].second ++;

++ arrivalIdx;
}

sort(serverSumLoad.begin(), serverSumLoad.end(),[](
const pair<int, int>& p1, const pair<int, int>& p2
) {
return p1.second > p2.second;
});

// for(int ed = 0; ed < serverSumLoad.size(); ++ed) {
// cout << "server: " << serverSumLoad[ed].first << " > " << serverSumLoad[ed].second << endl;
// }
int ed = 1;
vector<int> res = {serverSumLoad[0].first};


for(; ed < serverSumLoad.size(); ++ed) {
if(serverSumLoad[ed].second != serverSumLoad[0].second) {
break;
}
res.emplace_back(serverSumLoad[ed].first);
}

return res;
}
};

单调栈

其基本特性:

  • 1 单调栈的极值性质:单调递减栈的第一个字符为目前最大的元素,单调递增栈则相反; 关于目前的解释,由于单调栈是遍历整个数组出栈入栈的过程,假设目前遍历到节点i,则arr[:i]为目前单调栈遍历过的元素们,单调栈递增递减栈的第一个数字分别为arr[:i]的最大最小值
  • 2 单调栈的单调性:单调栈内的元素严格单调

1 单调栈写法

一下为求每个元素左侧最大值的一个示例:

1
2
3
4
5
6
7
8
9
10
vector<int> normalOrderMono;
int n = height.size();
vector<int> leftMax(n);
for(int i = 0; i < n; ++i){
while(!normalOrderMono.empty() && height[normalOrderMono.back()] <= height[i]) {
normalOrderMono.pop_back();
}
leftMax[i] = normalOrderMono.empty() ? height[i] : height[normalOrderMono[0]];
normalOrderMono.emplace_back(i);
}

示例

0000_interview_1712trapWater 接雨水

1 题目:

https://leetcode-cn.com/problems/volume-of-histogram-lcci/

2 解题思路:

  • 1 参照提示的一句话:

    每个长方形的顶部都有水,水的高度应与左侧最高长方形和右侧最高长方形的较小值相匹配,也就是说,water_on_top[i] = min(tallest_ bar(0->i), tallest_bar(i, n))。

  • 2 使用单调栈计算当前节点左侧(右侧)最大值
    • 2.1 很简单: 考虑单调栈的性质,单调递减栈的第一个字符为目前最大的元素,单调递增栈则相反,为最小元素
    • 2.2 关于目前的解释,由于单调栈是遍历整个数组出栈入栈的过程,遍历到节点i,则arr[:i]为目前单调栈遍历过的元素们,单调栈递增递减栈的第一个数字分别为arr[:i]的最大最小值
    • 2.3 由于需要统计右侧最大值,则我们只需要逆序遍历数组即可,最大值可以使用单调递减获得
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public:
int trap(vector<int>& height) {
// using descending mono stack to find maxValue in left or right
vector<int> normalOrderMono;
vector<int> reverseOrderMono;
int n = height.size();
vector<int> leftMax(n);
vector<int> rightMax(n);
for(int i = n - 1; i >= 0; --i){
while(!reverseOrderMono.empty() && height[reverseOrderMono.back()] <= height[i]) {
reverseOrderMono.pop_back();
}
rightMax[i] = reverseOrderMono.empty() ? height[i] : height[reverseOrderMono[0]];
reverseOrderMono.emplace_back(i);
}

for(int i = 0; i < n; ++i){
while(!normalOrderMono.empty() && height[normalOrderMono.back()] <= height[i]) {
normalOrderMono.pop_back();
}
leftMax[i] = normalOrderMono.empty() ? height[i] : height[normalOrderMono[0]];
normalOrderMono.emplace_back(i);
}

int res = 0;
for(int i = 0; i < n; i++) {
res += max(min(leftMax[i], rightMax[i]) - height[i], 0);
}

return res;
}
};

2030. 含特定字母的最小子序列 smallestSubsequence

1 题目:

https://leetcode-cn.com/problems/smallest-k-length-subsequence-with-occurrences-of-a-letter/

2 解题思路:

  • 1 考虑一个简单化的问题,选出长度为k的最小字典序的字符串,算法如下:
    • 1.1 采用单调栈维护一个递增栈,自然的保持了最小字典序
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      class Solution {
      public:
      string smallestSubsequence(string s, int k, char letter, int repetition) {
      // first, using monostack to get the min sub arr whose len = k
      // and check if there are rep's 'letter' in sub arr

      vector<int> mono; // abscending chars
      int n = s.size();
      int specialCharCnt = 0;
      int avaliableSpeChar = 0;
      for(auto c : s) {
      avaliableSpeChar += (c == letter);
      }


      for(int i = 0; i < n; ++i) {
      while(!mono.empty() && s[mono.back()] >= s[i] && mono.size() - 1 + n - i >= k) {
      specialCharCnt -= static_cast<int>(s[mono.back()] == letter);
      mono.pop_back();
      }

      mono.emplace_back(i);
      if(s[i] == letter) {
      --avaliableSpeChar;
      ++specialCharCnt;
      }
      }

      string res = "";
      for(auto i : mono){
      res += s[i];
      }

      return res;
      }
      };
  • 2 考虑另一个子问题,需要选出含有rep个特殊字符的子序列,可以使用一个队列存储特殊字符的下标,当队列长度达到rep个,则记为一个子序列
  • 3 将两个问题结合起来考虑就是是说:
    • 3.1 在递增栈构造的过程中,要保证当前位置后面剩余的特殊字符加上当前栈内的字符大于等于repetition,否则将不能出栈特殊字符(因为如果出栈则无法满足有repetition个特殊字符的要求
    • 3.2 经过3.1步骤,单调栈内含有我们的答案,但是一定有一些额外的字符存在,那么如下说明从栈内获得答案的方式:
      • 3.2.1 eg: 当aaabbbcccddd为输入,则单调栈为aaabbbcccddd,那么我们想要的结果为在至少有2个b的字符串,那么我们获得最终结果的方式为:在保证有大于repetition个letter的情况下,从尾部开始删除字符串直到单调栈内剩下k个字符即可
        1
        2
        3
        4
        "aaabbbcccddd"
        3
        "b"
        2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class Solution {
public:
string smallestSubsequence(string s, int k, char letter, int repetition) {
// first, using monostack to get the min sub arr whose len = k
// and check if there are rep's 'letter' in sub arr

clock_t start,end;   //定义clock_t变量
start = clock();    //开始时间

string mono; // abscending chars
int n = s.size();
int specialCharCnt = 0;
int avaliableSpeChar = 0;
for(auto &c : s) {
avaliableSpeChar += (c == letter);
}


for(int i = 0; i < n; ++i) {
while(!mono.empty() && mono.back() > s[i] && mono.size() - 1 + n - i >= k) {
if(mono.back() == letter) {
// when not enough special letter, we do not pop special char
if(avaliableSpeChar <= repetition - specialCharCnt) {
break;
}
--specialCharCnt;
}
mono.pop_back();
}

mono.push_back(s[i]);
if(s[i] == letter) {
--avaliableSpeChar;
++specialCharCnt;
}
}

start = clock();    //开始时间
string res = "";
// eliminate some extra chars reversely
int delNum = mono.size() - k;
// cout << "letter Cnt: " << specialCharCnt << endl;
for(int i = mono.size() - 1; i >= 0; --i){
// make sure there are more than rep's 'letter'
if(delNum != 0 ) {
if(specialCharCnt > repetition) {
specialCharCnt -= (mono[i] == letter);
--delNum;
continue;
} else {
if(mono[i] != letter) {
--delNum;
continue;
}
}
}
// spend: 0.311s
// res = mono[i] + res; // this spend two much time, ocuppy nearly 100% time! so we change our policy
// spend: 0.000153s, 1000 times faster!
res.push_back(mono[i]);
}

reverse(res.begin(), res.end());
end = clock(); //结束时间
cout<<"time = "<<double(end-start)/CLOCKS_PER_SEC<<"s"<<endl; //输出
return res;
}
};


3 关于string的+和push_back

如果是一个字符一个字符的话,使用push_back会比+快1000倍!如上代码可以自己尝试统计

1
2
3
4
// spend: 0.311s
// res = mono[i] + res; // this spend two much time, ocuppy nearly 100% time! so we change our policy
// spend: 0.000153s, 1000 times faster!
res.push_back(mono[i]);

0321 拼接最大数

1 题目

https://leetcode-cn.com/problems/longest-duplicate-substring/

2 解题思路

  • 1 首先分解问题:
    • 1.1 从长度为m和n(假设m <= n)中的字符串里选出k个,然后这个字串要求最大,遍历的思路:
      • 1.2 首先一共要选k个,自然想到从m和n中各挑选几个?那就是遍历了,m中的挑选长度的起点为: max(0, k - n),最少一个不挑,然后从m中挑的个数身下还有k - m个一定能够从n中挑出,所以起点是从0到k-n,(为什么区最大值?因为当n,m均大于k的时候,k-n为负数),挑选终点:自然是k,或者没有那么多可以调k个,则挑m个,则min(k, m)
      • 1.3 那么已经知道所有从m,n中挑选出k个字符串的方法,那么对于每一个方法,如何获取最大字符串呢?其实就是分别从该方法的m和n串中各选出他们的最大字串,然后合并即可,于是问题转化为:从m中如何选出某个长度记为l的最大字串?
        • 1.3.1 我们考虑一个使用单调递减栈,因为它的栈顶总是当前字符串最大的值,然后后面都是递减的,这正是我们需要的,比如 9 1 2 5 8 3选择3个的时候,使用单调栈可以直接获得9,8,3,但是有个问题,比如从 9 1 2 5 8,单调栈遍历完则为9 5,这3个没选够,所以何时停止从单调栈里弹出呢?遍历位置以及后面剩余的元素刚好够挑选长度的时候,就不再弹出了(即使单调栈内的元素已经不单调了)
      • 1.4 在下一个问题,对于一个挑选方法,m中挑选l个,n中挑选k-l个,分别得到一个最大字串,如何合并成最终字串呢?
        • 1.4.1 其实很简单,两个字符串分别维护一个head叫ha,hb吧,若ha比hb大,那么就把ha的值压入最终结果,直到ha < hb,同理移动b即可,但是需要考虑ha == hb的情况,直接比较ha,hb对应的尾串即可,参考如下测试样例即可:
          1
          2
          3
          4
          5
          6
          7
          8
          9
          eg1: 
          [2,5,6,4,4,0]
          [7,3,8,0,6,5,7,6,2]
          15

          eg2:
          [6,7]
          [6,0,4]
          5
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          63
          64
          65
          66
          67
          68
          69
          70
          71
          72
          73
          74
          75
          76
          77
          78
          79
          80
          81
          class Solution {
          public:

          string getMaxConcat(vector<int>& longVec, vector<int>& shortVec, int lenInLonger, int lenInShorter) {
          string monoLong, monoShort;

          // choose biggest lenInLonger's subArr from longer vec
          for(int i = 0; i < longVec.size(); ++i) {
          while(monoLong.size() > 0 && monoLong.back() - '0' < longVec[i] && monoLong.size() + longVec.size() - i > lenInLonger) {
          // cout << "pop_long: " << monoLong.back() << " monoLong's std len: " << lenInLonger << " curback: " << monoLong.back() << endl;
          monoLong.pop_back();
          }
          if(monoLong.size() < lenInLonger) {
          // cout << "push_long: in " << static_cast<char>(longVec[i] + '0') << endl;
          monoLong.push_back(static_cast<char>(longVec[i] + '0'));
          }
          }
          for(int i = 0; i < shortVec.size(); ++i) {
          // while(monoLong.back() < longVec[i] && monoLong.size() <= lenInLonger && monoLong.size() + longVec.size() - i + 1 < lenInLonger) {
          while(!monoShort.empty() && monoShort.back() - '0' < shortVec[i] && monoShort.size() + shortVec.size() - i > lenInShorter) {
          // cout << "pop_short: " << monoShort.back() << " monoLong's std len: " << lenInShorter << " curback: " << monoShort.back() << endl;
          monoShort.pop_back();
          }
          if(monoShort.size() < lenInShorter) {
          // cout << "push_short: in " << static_cast<char>(longVec[i] + '0') << endl;
          monoShort.push_back(static_cast<char>(shortVec[i] + '0'));
          }
          }

          int j = 0;
          // merger the two biggest substr,
          string finalRes = "";
          // cout << "longMax and shortMax str: " << monoLong << " " << monoShort << endl;
          for(int i = 0; i < monoShort.size(); ++i) {
          while(j < monoLong.size() && monoLong[j] > monoShort[i]) {
          finalRes.push_back(monoLong[j++]);
          }
          // decided whether to use long str or short when the char compared is true
          if(monoLong[j] == monoShort[i]) {
          if(monoLong.substr(j) > monoShort.substr(i)) {
          finalRes.push_back(monoLong[j++]);
          i--;
          continue;
          }
          }
          finalRes.push_back(monoShort[i]);
          }
          finalRes += monoLong.substr(j);
          // cout << "finalRes string is: " << finalRes << endl;
          return finalRes;
          }

          vector<int> maxNumber(vector<int>& nums1, vector<int>& nums2, int k) {
          int m = nums1.size();
          int n = nums2.size();
          // cout << "m/n" << m << " " << n << endl;

          // let k split into nums1 and nums2
          string maxStr(k, '0');
          if(m <= n) {
          for(int lenInShorter = max(0, k - n); lenInShorter <= min(m, k); ++lenInShorter) {
          int lenInLonger = k - lenInShorter;
          // cout << "lenInLong/short" << lenInLonger << " " << lenInShorter << endl;
          string curMax = getMaxConcat(nums2, nums1, lenInLonger, lenInShorter);
          maxStr = maxStr > curMax ? maxStr : curMax;
          }
          } else {
          for(int lenInShorter = max(0, k - m); lenInShorter <= min(n, k); ++lenInShorter) {
          int lenInLonger = k - lenInShorter;
          string curMax = getMaxConcat(nums1, nums2, lenInLonger, lenInShorter);
          maxStr = maxStr > curMax ? maxStr : curMax;
          }
          }

          vector<int> res;
          for(auto& c : maxStr) {
          res.emplace_back(c - '0');
          }
          return res;
          }
          };

0768 最多能完成排序的块 II maxChunksToSroted

1 题目

https://leetcode-cn.com/problems/max-chunks-to-make-sorted-ii

2 解题思路

  • 1 普通思路:

    • 1.1 利用已经排好序的数组,和当前数组进行比较,得到分块方式,也就是题目的提示:

      Each k for which some permutation of arr[:k] is equal to sorted(arr)[:k] is where we should cut each chunk.

    • 1.2 具体算法:
      • 记原数组为arr,然后其排序为sortArr,而后遍历arr,如何确定一个下标k是否为chunk的分割点呢?
      • 使用hashA记录arr[:k]子数组中每个元素的出现次数,使用diffCount记录arr[:k]和sortArr[:k](在两个数组里出现次数不同的)元素个数
      • 当diffCount为0,就找到一个k,最后返回所有diffCount为0的地方即可
    • 1.3 普通解法:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      class Solution {
      public:

      int maxChunksToSorted(vector<int>& arr) {
      // monoStack: every ele in mono represent: the biggest value in a chunk
      unordered_map<int, int> hash;

      // diffCnt, the size of those numbers whose cnt are not equal in arr[:k] and sortArr[:k]
      int ans = 0, n = arr.size(), diffCnt = 0;
      vector<int> sortArr(arr);
      sort(sortArr.begin(), sortArr.end(), std::less<int>());
      for(int k = 0; k < n; ++k) {
      ++ hash[arr[k]];
      if(hash[arr[k]] == 1) {
      diffCnt++;
      }
      if(hash[arr[k]] == 0){
      diffCnt--;
      }

      -- hash[sortArr[k]];
      if(hash[sortArr[k]] == 0) {
      diffCnt--;
      }
      if(hash[sortArr[k]] == -1) { // sortArr[k] is redundant
      diffCnt++;
      }

      ans += diffCnt == 0;
      }

      return ans;
      }
      };
  • 2 单调栈:

    • 2.1 考虑例子: 1 4 3 7 5,很容易发现,就三个分块,然后arr的单调增栈为1 4 7,刚好为每个分块的最大值,所以有这么一个单调栈定义:单调栈里存入的数字为每个分块的最大值
    • 2.2 当然这也有问题,会涉及到单调栈需要合并分块的情况: 1 4 3 7 5 2,当没检测到2的时候,单调栈为3个分块,最大值分别为1,4,7,当检测到2的时候,我们需要先弹出所有比2大的分块的最大值,因为2在这些分块后面意味着必须将2和这些分块合并,这样才能保证最终从小到大的排序,然后压入合并的这些分块里的最大值,也就是遇到2之前单调栈的栈顶 7,单调栈变成了1,7
    • 2.3 具体看代码:单调栈解法:可视化参考
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      class Solution {
      public:

      int maxChunksToSorted(vector<int>& arr) {
      // monoStack: every ele in mono represent: the biggest value in a chunk
      vector<int> mono = {INT_MIN}; // from small to bigger
      int n = arr.size();
      for(int i = 0; i < n; ++i) {
      if(arr[i] >= mono.back()) {
      mono.emplace_back(arr[i]);
      } else { // arr[i] < mono.back()
      // merge the chunk untill arr[i] > mono.back()
      int curChunkMax = mono.back();
      while(mono.back() > arr[i]) {
      mono.pop_back();
      }
      mono.emplace_back(curChunkMax);
      }
      }

      return mono.size() - 1;
      }
      };

1793 好子数组的最大分数 maximumScore

1 题目:

https://leetcode-cn.com/problems/maximum-score-of-a-good-subarray/

类似题目:

2 解题思路:

  • 1 单调栈:
    • 1.1 想法很简单,遍历上述的柱状图的一列,栈顶总是最大,栈底最小,一旦有小于等于前栈的值,那么栈里面那些小于等于当前栈顶的值都不可能再在遍历的后续位置发生作用了,因为已经有小于等于它的值出现了,所以我们把这些值弹出然后算弹出的这一部分的体积,那么栈里面剩下的就是任然能够为后续遍历的位置贡献面积的值,所以就是这样,具体的看代码吧。
    • 1.2 计算过程中,我们只有当矩形的两边分别位于k的两边才会更新结果
  • 2 强调单调栈的几个特性(以递增单调栈为例子)
    • 2.1 栈底一定是整个数组最小的
    • 2.2 弹出当前元素记其下标为j后,当前栈顶元素下标记为i,那么i是第一个下标满足: i < j && arr[i] <= arr[j]
    • 2.3 **TIPS: **注意单调栈会将所有元素都入栈,但并不会都出栈,很多时候我们要求arr中的每个元素都出栈,那么常见操作为在arr末尾加一个比所有元素都要小的值即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public:
int maximumScore(vector<int>& nums, int k) {
// k split nums into left and right
vector<int> lPart(nums.begin(), nums.begin() + k + 1);

// construct the monostack, abscending
vector<int> mono;

int res = nums[k];
nums.emplace_back(0); // make sure all the heights are used for nums
for(int r = 0; r < nums.size(); ++r) {
while(!mono.empty() && nums[mono.back()] >= nums[r]) {
int h = nums[mono.back()];
// cout << "h: " << h << endl;
mono.pop_back();
int l = mono.empty() ? -1 : mono.back();
int w = r - l - 1;
cout << "w/r/l/h: " << w << "/" << r << "/" << l << "/" << h << endl;
if(r - 1 >= k && l + 1 <= k) {
res = max(res, w * h);
}
}
mono.emplace_back(r);
}

return res;
}
}

1 字典树、前缀树、Trie

将一个单词列表使用words组装起来,实现如下:(仅含有小写的字典),可以在log(m)时间内查询一个单词是否在字典中。

1.1 可能的小技巧

  • 1 一些可以想到的优化:
    • 1.1 如果对一个长串反复查询,则使用一个node指针指向当前查询位于Trie里面的位置,避免反复查询相同的前缀
    • 1.2 如果对一个长串反复查询,尝试使用hash记录尾串的目标信息,避免对尾串反复查询
    • 1.3 逆序建树,构建后缀树等等,见本篇最后两个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Trie {
public:
vector<Trie *>curLevel;
bool isEnd = false;

Trie() : curLevel(26) {

}

void insert(string word) {
Trie * curNode = this;
for(char c : word) {
c -= 'a';
if(nullptr == curNode->curLevel[c]) {
curNode->curLevel[c] = new Trie();
}
// check nextLevel
curNode = curNode->curLevel[c];
}
curNode->isEnd = true;
}

bool search(string word) {
Trie * curNode = this;
for(char c : word) {
c -= 'a';
if(nullptr == curNode->curLevel[c]) {
return false;
}
curNode = curNode->curLevel[c];
}
return curNode->isEnd;
}

bool startsWith(string prefix) {
Trie * curNode = this;
for(char c : prefix) {
c -= 'a';
if(nullptr == curNode->curLevel[c]) {
return false;
}
curNode = curNode->curLevel[c];
}
return true;
}
};

2 例题

0212 单词搜索 II

1 题目

https://leetcode-cn.com/problems/word-search-ii

2 解题思路

  • 1 要搜索整个字母棋盘,很自然想到使用回溯
  • 2 要知道一个字符串是否在字典里,很自然想到trie
  • 3 具体解答方法
    • 3.1 如下代码的注释的backTrack函数很清晰的阐述啦思路,称之为old way
    • 3.2 old way的缺陷,对于tmpRes的前面的公共部分反复调用啦startWith函数重复计算啦,
    • 3.3 改进,使用curNode记录tmpRes在前缀树里面的位置,那么只需要根据curNode来判断tmpRes即将加入的新的字符是否为curNode的子节点就可以啦,提升了速度
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      192
      193
      194
      195
      196
      197
      198
      199
      200
      201
      202
      203
      204
      class Solution {
      public:
      class Trie {
      public:
      vector<Trie*> curLevel;
      bool isEnd = false;
      bool hasNext = true;
      Trie() : curLevel(26) {
      }

      void insert(string word) {
      Trie* curNode = this;
      for(char c : word) {
      c -= 'a';
      if(nullptr == curNode->curLevel[c]) {
      curNode->curLevel[c] = new Trie();
      }
      curNode = curNode->curLevel[c];
      curNode->hasNext = true;
      }
      curNode->isEnd = true;
      }

      bool inTrie(string word) {
      Trie* curNode = this;
      for(char c : word) {
      c -= 'a';
      if(nullptr == curNode->curLevel[c]) {
      return false;
      }
      curNode = curNode->curLevel[c];
      }
      return curNode->isEnd;
      }

      bool startWith(string word) {
      Trie* curNode = this;
      for(char c : word) {
      c -= 'a';
      if(nullptr == curNode->curLevel[c]) {
      return false;
      }
      curNode = curNode->curLevel[c];
      }
      return true;
      }

      bool endWith(string word) {
      Trie* curNode = this;
      for(char c : word) {
      c -= 'a';
      if(nullptr == curNode->curLevel[c]) {
      return false;
      }
      curNode = curNode->curLevel[c];
      }
      return !curNode->hasNext;
      }
      };


      vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
      int m = board.size();
      int n = board[0].size();



      // build trie in normal or reverse order
      // std::shared_ptr<Trie> treeNormal = make_shared<Trie>();
      Trie* treeNormal = new Trie();
      // std::unique_ptr<Trie> treeReverse = new Trie();
      for(auto w : words) {
      treeNormal->insert(w);
      // treeReverse.insert(reverse(w.begin(), w.end()));
      }

      int deltaX[] = {1, 0, -1, 0};
      int deltaY[] = {0, 1, 0, -1};
      // get the answer
      vector<string> res;
      unordered_set<string> hash;
      for(int i = 0; i < board.size(); ++i) {
      for(int j = 0; j < board[0].size(); ++j) {
      // std::cout << "check next st point!" << endl;
      string tmpRes = "";
      vector<vector<bool>> exploredFlag(m, vector<bool>(n, false));
      backTrack(i, j, board, tmpRes, res, treeNormal,
      deltaX, deltaY, exploredFlag, hash
      );
      }
      }
      return res;
      }


      /**
      [["o","a","a","n"],
      ["e","t","a","e"],
      ["i","h","k","r"],
      ["i","f","l","v"]]
      ["oath","pea","eat","rain","oathi","oathk","oathf","oate","oathii","oathfi","oathfii"]
      **/
      void backTrack(int i, int j, vector<vector<char>>& board, string& tmpRes, vector<string>& res, Trie* curNode,
      const int* deltaX, const int* deltaY,
      vector<vector<bool>>& exploredFlag,
      unordered_set<string>& hash) {
      char ch = board[i][j];
      if(nullptr == curNode->curLevel[ch - 'a']) {
      return ;
      }

      // start from i, j
      tmpRes.push_back(board[i][j]);
      // Trie* lastNode = curNode;
      curNode = curNode->curLevel[ch - 'a'];
      if(nullptr == curNode) {
      cout << "a???" << endl;
      return ;
      }

      // cout << "start : in " << i << "->" << j << " " << board[i][j] << "with tmpRes : " << tmpRes << endl;
      // we check the tmpRes directly using the trie
      // if(tree->inTrie(tmpRes) && 0 == hash.count(tmpRes)) {
      // res.emplace_back(tmpRes);
      // hash.insert(tmpRes);
      // if(tree->endWith(tmpRes)) {
      // tmpRes.pop_back();
      // return;
      // }
      // }
      if(nullptr != curNode && curNode->isEnd == true && 0 == hash.count(tmpRes)) {
      // cout << "find! >>>>> " << tmpRes << endl;
      res.emplace_back(tmpRes);
      hash.insert(tmpRes);
      if(!curNode->hasNext) {
      tmpRes.pop_back();
      return;
      }
      }

      // cout << "current[i, j] : in " << i << "->" << j << " " << board[i][j] << "with tmpRes : " << tmpRes << endl;
      exploredFlag[i][j] = true;
      if(nullptr != curNode) {
      // not null
      for(int mvIdx = 0; mvIdx < 4; ++mvIdx) {
      int nextX = i + deltaX[mvIdx];
      int nextY = j + deltaY[mvIdx];
      // cout << "tryStart: [x, y] :" << nextX << " " << nextY << endl;;
      if( nextX < board.size() && nextX >= 0 && nextY < board[0].size() && nextY >= 0 && \
      ! exploredFlag[nextX][nextY]) {
      backTrack(nextX, nextY, board, tmpRes, res, curNode,
      deltaX, deltaY, exploredFlag, hash
      );
      }
      // cout << "tryFinish: [x, y] :" << nextX << " " << nextY << endl;;
      }
      }
      exploredFlag[i][j] = false;
      tmpRes.pop_back();
      }

      // OLD WAY TO DO, will exceed the time limitation
      // void backTrack(int i, int j, vector<vector<char>>& board, string& tmpRes, vector<string>& res, unique_ptr<Trie>& tree,
      // const int* deltaX, const int* deltaY,
      // vector<vector<bool>>& exploredFlag,
      // unordered_set<string>& hash) {

      // // start from i, j
      // tmpRes.push_back(board[i][j]);

      // // cout << "start : in " << i << "->" << j << " " << board[i][j] << "with tmpRes : " << tmpRes << endl;
      // if(tree->inTrie(tmpRes) && 0 == hash.count(tmpRes)) {
      // res.emplace_back(tmpRes);
      // hash.insert(tmpRes);
      // if(tree->endWith(tmpRes)) {
      // tmpRes.pop_back();
      // return;
      // }
      // }


      // // cout << "current[i, j] : in " << i << "->" << j << " " << board[i][j] << "with tmpRes : " << tmpRes << endl;
      // exploredFlag[i][j] = true;
      // if(tree->startWith(tmpRes)) {
      // for(int mvIdx = 0; mvIdx < 4; ++mvIdx) {
      // int nextX = i + deltaX[mvIdx];
      // int nextY = j + deltaY[mvIdx];
      // if( nextX < board.size() && \
      // nextX >= 0 && \
      // nextY < board[0].size() && \
      // nextY >= 0 && \
      // ! exploredFlag[nextX][nextY]) {
      // backTrack(nextX, nextY, board, tmpRes, res, tree,
      // deltaX, deltaY, exploredFlag, hash
      // );
      // }
      // // cout << "tryFinish: [x, y] :" << nextX << " " << nextY << endl;;
      // }
      // }
      // exploredFlag[i][j] = false;
      // tmpRes.pop_back();
      // // cout << "current[i, j] : exit " << i << "->" << j << " " << board[i][j] << "with tmpRes : " << tmpRes << endl;
      // }
      };

0336. 回文对

1 题目

https://leetcode-cn.com/problems/palindrome-pairs/

2 解题思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
class Solution {
public:
class Trie {
public:
vector<Trie*> curLevel;
bool isEnd = false;
int wordIdx = -1;
Trie() : curLevel(26), wordIdx(-1) {

}
void insert(string& word, int idx) {
Trie* curNode = this;
for(auto c : word) {
c -= 'a';
if(nullptr == curNode->curLevel[c]) {
curNode->curLevel[c] = new Trie();
}
curNode = curNode->curLevel[c];
}
curNode->isEnd = true;
curNode->wordIdx = idx;
}

int inTrie(string word) {
Trie* curNode = this;
for(auto c : word) {
c -= 'a';
if(nullptr == curNode->curLevel[c]) {
return -1;
}
curNode = curNode->curLevel[c];
}
return curNode->wordIdx;
}

int inTrie(string& word, int left, int right) {
Trie* curNode = this;
for(int i = right; i >= left; --i) {
char c = word[i] - 'a';
if(nullptr == curNode->curLevel[c]) {
return -1;
}
curNode = curNode->curLevel[c];
}
return curNode->wordIdx;
}
};

vector<vector<int>> palindromePairs(vector<string>& words) {
// travel all prefix and subfix of a word,
// find the palindrome and check the existence in the words of
// reverse of the other part
Trie* tree = new Trie();
// Trie* treeReverse = new Trie();
unordered_map<string, int> strToIdx;
// int idx = 0;
// for(auto word : words) {
// tree->insert(word);
// strToIdx[word] = idx++;
// }

// int ans = 0;
// vector<vector<int>> res = {};
// for(auto word : words) {
// deque<string> prefix;
// deque<string> subfix;
// int n = word.size();
// for(int st = 0; st <= n; ++st) {
// string pre = word.substr(0, st);
// string sub = word.substr(st);
// // cout << "p/s : " << pre << " / " << sub << endl;
// if(checkPalindrome(pre)) {
// reverse(sub.begin(), sub.end());
// if(tree->inTrie(sub)) {
// vector<int> resItem = {strToIdx[sub], strToIdx[word]};
// if(resItem[1] != resItem[0]) {
// // cout << "push: " << word + sub << endl;
// res.emplace_back(resItem);
// }
// }
// }
// if(checkPalindrome(sub) && pre.size() != n) {
// reverse(pre.begin(), pre.end());
// if(tree->inTrie(pre)) {
// vector<int> resItem = {strToIdx[word], strToIdx[pre]};
// if(resItem[1] != resItem[0]) {
// // cout << "push: " << pre + word << endl;
// res.emplace_back(vector<int>(resItem));
// }
// }
// }

// }
// }

int idx = 0;
for(auto word : words) {
// tree->insert(word, idx);
// reverse(word.begin(), word.end());
tree->insert(word, idx);
++idx;
}
int tmp = 0;

int ans = 0;
vector<vector<int>> res = {};

int curWordIdx = 0;
for(auto word : words) {
int n = word.size();
for(int st = 0; st <= n; ++st) {
string pre = word.substr(0, st);
string sub = word.substr(st);
// cout << "p/s : " << pre << " / " << sub << " curWordIdx : " << curWordIdx << endl;
// if(checkPalindrome(pre)) {
if(0 != st && checkPalindrome(word, 0, st-1)) {
// reverse(sub.begin(), sub.end());
int subIdx = tree->inTrie(word, st, n-1);
// cout << "subIdx = " << sub << "with idx = " << subIdx << endl;
if(subIdx != -1 && curWordIdx != subIdx) {
// cout << "curWordIdx / subIdx" << curWordIdx << "/" << subIdx << endl;
// cout << "push: " << sub << "+" << word << endl;
res.emplace_back(vector<int>({subIdx, curWordIdx}));
}
}
if(checkPalindrome(word, st, n-1)) {
// reverse(pre.begin(), pre.end());
int preIdx = tree->inTrie(word, 0, st-1);
if(preIdx != -1 && preIdx != curWordIdx) {
// cout << "curWordIdx / preIdx" << curWordIdx << "/" << preIdx << endl;
// cout << "push: " << pre << "+" + word << endl;
res.emplace_back(vector<int>({curWordIdx, preIdx}));
}
}

}
++curWordIdx;
}
return res;
}
// for (int i = 0; i < n; i++) {
// int m = words[i].size();
// for (int j = 0; j <= m; j++) {
// if (isPalindrome(words[i], j, m - 1)) {
// int left_id = findWord(words[i], 0, j - 1);
// if (left_id != -1 && left_id != i) {
// ret.push_back({i, left_id});
// }
// }
// if (j && isPalindrome(words[i], 0, j - 1)) {
// int right_id = findWord(words[i], j, m - 1);
// if (right_id != -1 && right_id != i) {
// ret.push_back({right_id, i});
// }
// }
// }
// }

bool checkPalindrome(string& s, int left, int right) {
int len = right - left + 1;
for(int i = 0; i < len / 2; ++i) {
if(s[left + i] != s[right - i]) {
return false;
}
}
return true;
}


// bool checkPalindrome(string& s) {
// int n = s.size();
// for(int i = 0; i < n / 2; ++i) {
// if(s[i] != s[n - i - 1]) {
// return false;
// }
// }
// return true;
// }
};

3 使用hash表来查前后缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
class Solution {
public:
class Trie {
public:
vector<Trie*> curLevel;
bool isEnd = false;
int wordIdx = -1;
Trie() : curLevel(26), wordIdx(-1) {

}
void insert(string& word, int idx) {
Trie* curNode = this;
for(auto c : word) {
c -= 'a';
if(nullptr == curNode->curLevel[c]) {
curNode->curLevel[c] = new Trie();
}
curNode = curNode->curLevel[c];
}
curNode->isEnd = true;
curNode->wordIdx = idx;
}

int inTrie(string word) {
Trie* curNode = this;
for(auto c : word) {
c -= 'a';
if(nullptr == curNode->curLevel[c]) {
return -1;
}
curNode = curNode->curLevel[c];
}
return curNode->wordIdx;
}

int inTrie(string& word, int left, int right) {
Trie* curNode = this;
for(int i = right; i >= left; --i) {
char c = word[i] - 'a';
if(nullptr == curNode->curLevel[c]) {
return -1;
}
curNode = curNode->curLevel[c];
}
return curNode->wordIdx;
}
};

vector<string> wordReverse;
unordered_map<string, int> strToIdx;

int findWord(string& s, int left, int right) {
string tmp = s.substr(left, right - left + 1);
auto it = strToIdx.find(tmp);
int res = it == strToIdx.end() ? -1 : it->second;
// cout << "finding: " << tmp << " with ans = " << res << endl;
return res;
}


vector<vector<int>> palindromePairs(vector<string>& words) {
// travel all prefix and subfix of a word,
// find the palindrome and check the existence in the words of
// reverse of the other part
int idx = 0;
for(auto word : words) {
reverse(word.begin(), word.end());
wordReverse.emplace_back(word);
strToIdx[word] = idx;
++idx;
}

vector<vector<int>> res = {};

int curWordIdx = 0;
for(auto word : words) {
int n = word.size();
for(int st = 0; st <= n; ++st) {
// cout << "p/s : " << " / " << " curWordIdx : " << curWordIdx << endl;
if(0 != st && checkPalindrome(word, 0, st-1)) {
int subIdx = findWord(word, st, n-1);
// cout << "subIdx = " << subIdx << endl;
if(subIdx != -1 && curWordIdx != subIdx) {
res.emplace_back(vector<int>({subIdx, curWordIdx}));
}
}
if(checkPalindrome(word, st, n-1)) {
int preIdx = findWord(word, 0, st-1);
if(preIdx != -1 && preIdx != curWordIdx) {
res.emplace_back(vector<int>({curWordIdx, preIdx}));
}
}

}
++curWordIdx;
}
return res;
}


bool checkPalindrome(string& s, int left, int right) {
int len = right - left + 1;
for(int i = 0; i < len / 2; ++i) {
if(s[left + i] != s[right - i]) {
return false;
}
}
return true;
}

};

0140. 单词拆分 II

1 题目

https://leetcode-cn.com/problems/word-break-ii/

2 解题思路

  • 1 首先确定搜索思路:
    • 1.1 很显然这个问题的解答思路不会随着问题规模的减小而改变,于是采用递归/回溯方案
    • 1.2 由于需要确认当前子问题处于什么位置,于是采用回溯
    • 1.3 回溯方法
      • 1.3.1 每次在头部尝试字符串headWord,直到找到一个在字典里面的headWord
      • 1.3.2 将原来字符串去掉headWord,然后递归到下一层
      • 1.3.3 当前的headWord的所有可能尝试完毕,则回溯到尝试headWord之前,然后去尝试下一个headWord
    • 1.4 以上可以看出回溯和递归的区别,递归不带有当前搜索状态,而回溯需要维持搜索状态
  • 2 有了大体思路,那么如何解决: 找到一个在字典里面的headWord?
    • 2.1 采用字典前缀树即可快速获得该字符串是否在字典树里面,复杂度为O(m),m为字典树中的
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      class Solution {
      public:
      class Trie {
      public:
      vector<Trie*> curLevel;
      bool isEnd = false;

      Trie() : curLevel(26){}

      void insert(string word ) {
      Trie* curNode = this;
      for(char c : word) {
      c -= 'a';
      if(nullptr == curNode->curLevel[c]) {
      curNode->curLevel[c] = new Trie();
      }
      curNode = curNode->curLevel[c];
      }
      curNode->isEnd = true;
      }

      bool inTrie(string word) {
      Trie* curNode = this;
      for(char c : word) {
      c -= 'a';
      if(nullptr == curNode->curLevel[c]) {
      return false;
      }
      curNode = curNode->curLevel[c];
      }
      return curNode->isEnd;
      }

      bool startWith(string word) {
      Trie* curNode = this;
      for(char c : word) {
      c -= 'a';
      if(nullptr == curNode->curLevel[c]) {
      return false;
      }
      curNode = curNode->curLevel[c];
      }
      return true;
      }
      };

      vector<string> wordBreak(string s, vector<string>& wordDict) {
      // implement a trie to sort the word Dict
      Trie* tree = new Solution::Trie();
      for(string s : wordDict) {
      tree->insert(s);
      }

      vector<string> tmpRes;
      vector<vector<string>> res;
      backTrack(s, tmpRes, res, tree);

      vector<string> finalRes;
      for(auto& strVec : res) {
      string resItem = "";
      for(auto& str : strVec) {
      resItem += (str + " ");
      }
      finalRes.emplace_back(resItem.substr(0, resItem.size() - 1));
      }
      return finalRes;
      }

      void backTrack(string s, vector<string> tmpRes, vector<vector<string>>& res, Trie* trie) {
      if(0 == s.size()) {
      res.emplace_back(tmpRes);
      }
      // try every possibility
      for(int i = 0; i < s.size(); ++i) {
      string headWord = s.substr(0, i + 1);
      tmpRes.emplace_back(headWord);
      // cout << "head -> " << headWord << endl;
      if(trie->inTrie(headWord)) {
      // cout << "in it!" << endl;
      backTrack(s.substr(i + 1), tmpRes, res, trie);
      }
      tmpRes.pop_back();
      }
      }
      };

0472. 连接词

1 题目

https://leetcode-cn.com/problems/concatenated-words/

2 解题思路

  • 1 首先很容易想到一点:
    • 1.1 由于需要快速定位一个单词是否在字典里,则采用字典树获取该信息
    • 1.2 对于一个单词,我们对于每个isEnd(也就是搜索前缀对应的单词在字典里)的位置,都从下一个字符从新开始在字典中匹配,然后每个isEnd位置后面的字符,需要继续匹配,eg:[cat, cats, catsdog, dog],对于catsdog,从sdog和dog分别重新匹配
    • 1.3 对于一个单词,它的构成成分比他小,于是将字符串排序,一边插入,一边找
  • 2 通过后缀记忆剪枝dfs, eg: 对于[“a”, “aa”, “aaaa”, “aaaakaa”]中的aaaakaa,运行代码会有如下日志:
    • 因为在第一次 a,a,a,a,k的时候记录了k位置往后的后缀无法成功匹配
    • 那么对于后面的 aa,a,a,k以及aaa,a,k等等搜索都会直接跳过k后缀的匹配
      1
      2
      3
      4
      when checking : aaaakaa the sufix start from pos : 4 has been validated to be failure!
      when checking : aaaakaa the sufix start from pos : 3 has been validated to be failure!
      when checking : aaaakaa the sufix start from pos : 2 has been validated to be failure!
      when checking : aaaakaa the sufix start from pos : 4 has been validated to be failure!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
class Solution {
public:
class Trie{
public:
vector<Trie*> curLevel;
bool isEnd = false;
// bool hasNext = false;

Trie() : curLevel(26) {

}

void insert(string& s, int idx) {
// cout << "inserting : " << s << endl;
Trie* curNode = this;
for(auto c : s) {
c -= 'a';
if(nullptr == curNode->curLevel[c]) {
curNode->curLevel[c] = new Trie();
}

curNode = curNode->curLevel[c];
// curNode->hasNext = true;
}
curNode->isEnd = true;
}
};

vector<string> findAllConcatenatedWordsInADict(vector<string>& words) {
int n = words.size();
Trie* tree = new Trie();

// build trie
int idx = 0;

sort(words.begin(), words.end(), [](string& a, string& b){
return a.size() < b.size();
});

vector<string> ans;


for(auto w : words) {
// cout << "-------checking " << w << endl;
// bool ableToConnect = false;
// findMaxConnectedCnt(w, 0, tree, 0, ableToConnect);
// if(ableToConnect ) {
// ans.emplace_back(w);
// }
vector<bool> trap(w.size(), false);
if(dfsToCheck(w, 0, tree, 0, trap)) {
ans.emplace_back(w);
}
tree->insert(words[idx], idx);
++idx;
}

return ans;
}

// here we search all the possible ways, but we only need one possible way, so we need return early
void findMaxConnectedCnt(string& s, int pos, Trie* root, int curCnt, bool& ableToConnect) {

if(s.size() == pos) {
if(curCnt >= 2) {
ableToConnect = true;
}
// cout << "<<<<<< finish one!" << endl;
return ;
}

int curPos = pos;
Trie* curNode = root;
while(curPos < s.size()) { // serach all prefix
int ch = s[curPos] - 'a';
if(nullptr != curNode->curLevel[ch]) {
if(curNode->curLevel[ch]->isEnd) {
// cout << ">>>>> curPos: " << curPos << " char is " << s[curPos] << " dive with curCnt: " << curCnt << endl;
// using this or next end
findMaxConnectedCnt(s, curPos + 1, root, curCnt + 1, ableToConnect);
}
} else {
return;
}
// cout << "curPos: " << curPos << " char is " << s[curPos] << " with curCnt: " << curCnt << endl;
curNode = curNode->curLevel[ch];
++curPos;
}
}

bool dfsToCheck(string& s, int pos, Trie* root, int curCnt, vector<bool>& trap) {

if(s.size() == pos) {
return curCnt >= 2;
}

if(trap[pos]) {
cout << "when checking : " << s << " the sufix start from pos : " << pos << " has been validated to be failure!" << endl;
return false;
}

int curPos = pos;
Trie* curNode = root;
while(curPos < s.size()) { // serach all prefix
int ch = s[curPos] - 'a';
if(nullptr != curNode) {
if(nullptr != curNode->curLevel[ch]) {
if(curNode->curLevel[ch]->isEnd) {
// cout << ">>>>> curPos: " << curPos << " char is " << s[curPos] << " dive with curCnt: " << curCnt << endl;
// using this or next end
if(dfsToCheck(s, curPos + 1, root, curCnt + 1, trap)) {
return true;
}
}
}
} else {
break;
}

// cout << "curPos: " << curPos << " char is " << s[curPos] << " with curCnt: " << curCnt << endl;
curNode = curNode->curLevel[ch];
++curPos;
}

trap[pos] = true;
return false;
}

};

0745WordFilter 前缀和后缀搜索

1 题目

https://leetcode-cn.com/problems/prefix-and-suffix-search/

2 解题思路

  • 1 构建后缀拼接前缀树
    1.1 参考解释即可:

    For a word like “test”, consider “#test”, “t#test”, “st#test”, “est#test”, “test#test”. Then if we have a query like prefix = “te”, suffix = “t”, we can find it by searching for something we’ve inserted starting with “t#te”.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    class WordFilter {
    public:
    // containning suffix
    class Trie {
    public:
    vector<Trie*> curLevel;
    int idx = -1;
    Trie() : curLevel(27) {}

    void insert(string& word, int idx, int left, int right) {
    Trie* curNode = this;
    for(int i = left; i < right; ++i) {
    char c = word[i];
    c = (c == '#' ? 26 : c - 'a');
    if(nullptr == curNode->curLevel[c]) {
    curNode->curLevel[c] = new Trie();
    }
    curNode = curNode->curLevel[c];
    curNode->idx = idx;
    }
    curNode->idx = idx;
    }

    int startWith(string& word) {
    Trie* curNode = this;
    int lastIdx = -1;
    for(auto c : word) {
    // cout << "checking char: " << c << endl;
    c = (c == '#' ? 26 : c - 'a');
    if(nullptr == curNode->curLevel[c]) {
    return -1;
    }
    curNode = curNode->curLevel[c];
    }
    return curNode->idx;
    }
    };

    public:
    Trie* tree;


    WordFilter(vector<string>& words) {
    tree = new Trie();
    for(int wIdx = 0; wIdx < words.size(); ++wIdx) {
    int n = words[wIdx].size();
    string word = words[wIdx] + "#" + words[wIdx];
    for(int j = 0; j <= n - 1; ++j) {
    // string tmp = word.substr(j);
    // overwrite those who start with a same suffix and prefix
    // cout <<"insert : " << tmp << endl;
    tree->insert(word, wIdx, j, 2*n +1);
    }
    }
    }

    int f(string prefix, string suffix) {
    int ans = -1;
    string tmp = suffix + "#" + prefix;
    // cout << "target >> " << tmp << endl;
    return tree->startWith(tmp);

    }
    };

1032. 字符流 StreamChecke

1 题目

https://leetcode-cn.com/problems/stream-of-characters/

2 解题思路

  • 1 倒序建立搜索树即可,因为可以观察到,总是从字符流的倒序开始查询
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    class StreamChecker {
    public:
    class Trie{
    public:
    vector<Trie*> curLevel;
    bool isEnd = false;

    Trie() : curLevel(26) {}

    void insert(string& word) {
    Trie* curNode = this;
    int n = word.size();
    for(int i = n-1; i >= 0; --i) {
    char c = word[i] - 'a';
    if(nullptr == curNode->curLevel[c]) {
    curNode->curLevel[c] = new Trie();
    }
    curNode = curNode->curLevel[c];
    }
    curNode->isEnd = true;
    }

    bool inTrie(string& word) {
    Trie* curNode = this;
    int n = word.size();
    for(int i = n-1; i >= 0; --i) {
    char c = word[i] - 'a';
    if(nullptr == curNode->curLevel[c]) {
    return false;
    }
    if(curNode->curLevel[c]->isEnd) {
    return true;
    }
    curNode = curNode->curLevel[c];
    }
    return curNode->isEnd;
    }

    };

    Trie* root;
    string curStr;

    StreamChecker(vector<string>& words) {
    root = new Trie();
    for(auto& w : words) {
    root->insert(w);
    }

    curStr = "";
    }

    bool query(char letter) {
    curStr += letter;
    return root->inTrie(curStr);
    }
    };

    /**
    * Your StreamChecker object will be instantiated and called as such:
    * StreamChecker* obj = new StreamChecker(words);
    * bool param_1 = obj->query(letter);
    *

0 拉宾-卡普算法

来自wiki
在计算机科学中,拉宾-卡普算法(英語:Rabin–Karp algorithm)或卡普-拉宾算法(Karp–Rabin algorithm),是一种由理查德·卡普与迈克尔·拉宾于1987年提出的、使用散列函数以在文本中搜寻单个模式串的字符串搜索算法单次匹配。该算法先使用旋转哈希以快速筛出无法与给定串匹配的文本位置,此后对剩余位置能否成功匹配进行检验。此算法可推广到用于在文本搜寻单个模式串的所有匹配或在文本中搜寻多个模式串的匹配。

1 算法本身主要思想

  • 1 朴素匹配: text = “abcdefabc”长度为n,去匹配长度为m的模式串abc,
    • 具体做法则是找出所有长度为m的字串,然后为每一个m字串去匹配模式串abc,复杂度为O(nm)
  • 2 使用拉宾-卡普算法改进:
    • 2.1 找出了所有长度为m的字串,那么能不能在O(1)的时间内去判断两个长度为m的字串是否相等?
    • 2.2 很自然的想到hash,那么如何计算一个字串的hash?比如abc,使用26进制编码(因为text中只有小写字母)即可: hash(abc) = 26^2 * (‘a’ - ‘a’) + 26^1 * (‘b’ - ‘a’) + 26^0 * (‘c’ - ‘a’);
    • 2.3 很容易注意到上面,若字符串很长,比如100,那么hash值就有26^100,显然太大,需要取模,取模后带来问题,造成hash碰撞,则需要散列,常用的有拉宾指纹,我们可以使用两个不同模算出来一对hash值当做为一个整体hash,降低hash碰撞的概率
    • 2.4 那么计算hash明明需要读取模式串,复杂度为O(m)啊?
      • 那是因为对于text,我们只需要计算第一个长度为m的字串的hash,后面的字串hash都可以通过O(1)时间获取:
      • 直接看图:o(1)算hash图片来源
      • 解释: ft_i为第i个字串的hash,在o(1)时间内得到ft_i+1的方案就如图所示,或者看如下例子的代码的check函数

例子

1044. 最长重复子串 longestDupSubstring

1 题目

https://leetcode-cn.com/problems/longest-duplicate-substring/

2 解题思路

  • 0 使用官方思路: rabin-karp + binarySearch
  • 1 这里需要使用rabin-karp算法,在长度为n的text中寻找长度为m的模式串,其复杂度为o(m),然后用二分法去确定最长字符串的长度,故整体复杂度为O(n logn),拉宾-卡普算法参考:https://xychen5.github.io/2021/12/28/rabinKarp/
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103

    typedef pair<long long, long long> pll;
    class Solution {
    public:
    static constexpr int big = 1000000006;
    // cal: a^m % mod, when m = 1000, a = 26, there will be overflow
    long long pow(int a, int m, int mod) {
    long long ans = 1;
    long long curNum = a;
    while(m > 0) {
    if(m % 2 == 1) {
    ans = ans * curNum % mod;
    // overflow
    if(ans < 0) {
    ans += mod;
    }
    }
    curNum = curNum * curNum % mod;
    // overflow
    if(curNum < 0) {
    curNum += mod;
    }
    m /= 2;
    }
    return ans;
    }

    // return st of substr with len = len
    int check(vector<int>& arr, int len, int a1, int a2, int mod1, int mod2) {
    int n = arr.size();
    long long powA1 = pow(a1, len, mod1);
    long long powA2 = pow(a2, len, mod2);
    long long hashA1 = 0, hashA2 = 0;

    // cout << "d2.5" << endl;
    // cal hash of the first substr
    // hashA1 = arr[0] * a1^(len-1) + arr[1] * a1^(len-2) + ... + arr[len-1]
    for(int i = 0; i < len; ++i) {
    hashA1 = (hashA1 * a1 % mod1 + arr[i]) % mod1;
    hashA2 = (hashA2 * a2 % mod2 + arr[i]) % mod2;
    hashA1 += hashA1 >= 0 ? 0 : mod1;
    hashA2 += hashA2 >= 0 ? 0 : mod2;
    }

    // cout << "d3" << endl;
    // calculate all substr's hash with len = len
    set<pll> seen;
    seen.emplace(hashA1, hashA2);
    for(int st = 1; st <= n - len; ++st) {
    // cout << "d4" << endl;
    // O(1) to cal next hash
    hashA1 = (hashA1 * a1 % mod1 - arr[st - 1] * powA1 % mod1 + arr[st + len - 1]) % mod1;
    hashA2 = (hashA2 * a2 % mod2 - arr[st - 1] * powA2 % mod2 + arr[st + len - 1]) % mod2;
    hashA1 += hashA1 >= 0 ? 0 : mod1;
    hashA2 += hashA2 >= 0 ? 0 : mod2;
    // before cursubstr, there is a same one
    if(seen.count(make_pair(hashA1, hashA2))) {
    return st;
    }
    seen.emplace(hashA1, hashA2);
    }

    return -1;
    }

    string longestDupSubstring(string s) {
    int n = s.size();

    // code the string
    vector<int> arr(n);
    for(int i = 0; i < n; ++i) {
    arr[i] = s[i] - 'a';
    }

    // two random base and mod
    srand((unsigned)time(NULL));
    int a1 = random()%75 + 26;
    int a2 = random()%75 + 26;
    int mod1 = random()%(INT_MAX - big) + big;
    int mod2 = random()%(INT_MAX - big) + big;

    // bin search the length of longest dup substr
    int l = 1, r = n - 1;
    int finalSt = -1, finalLen = -1;
    while(l <= r) {
    // m represents target len
    // int m = (l + r) / 2;
    int m = l + (r - l + 1) / 2;
    // cout << "d1" << endl;
    int st = check(arr, m, a1, a2, mod1, mod2);
    // cout << "d2" << endl;
    if(st != -1) {
    finalLen = m;
    l = m + 1;
    finalSt = st;
    } else {
    r = m - 1;
    }
    }
    return finalLen == -1 ? "" : s.substr(finalSt, finalLen);
    }
    }

1 线段树原理

1.1 数组实现

1.2 线段树树形实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class SegTree {
public:
struct SegNode {
long long leftBound = 0, rightBound = 0, curSum = 0;
SegNode* lChild = nullptr;
SegNode* rChild = nullptr;
SegNode(long long lb, long long rb) :
leftBound(lb),
rightBound(rb),
curSum(0),
lChild(nullptr),
rChild(nullptr) {
}
};

SegNode* root;

SegTree(long long left, long long right) {
root = build(left, right);
}

SegTree() {}

SegNode* build(long long l, long long r) {
SegNode* node = new SegNode(l, r);
if(l == r) {
return node;
}

long long mid = (l + r) / 2;
node->lChild = build(l, mid);
node->rChild = build(mid + 1, r);
return node;
}

void insert(SegNode* root, long long tarIdx, long long val) {
root->curSum += val;
if(root->leftBound == root->rightBound) {
return;
}
long long mid = (root->leftBound + root->rightBound) >> 1;
// long long mid = (root->leftBound + root->rightBound) / 2;
// there are identicial difference between them two:
// eg: when left == -1, right = 0;
// case1 => (left + right) / 2 == 0
// case1 => (left + right) >> 1 == -1
if(tarIdx <= mid) {
if (nullptr == root->lChild) {
root->lChild = new SegNode(root->leftBound, mid);
}
insert(root->lChild, tarIdx, val);
}
else{
if (nullptr == root->rChild) {
root->rChild = new SegNode(mid + 1, root->rightBound);
}
insert(root->rChild, tarIdx, val);
}
}

long long getSum(SegNode* root, long long left, long long right) const {
if(nullptr == root) {
return 0;
}
// 当前节点位于目标区间外
if(left > root->rightBound || right < root->leftBound) {
return 0;
}
// 当前节点位于目标区间内
if(left <= root->leftBound && right >= root->rightBound) {
return root->curSum;
}
return getSum(root->lChild, left, right) + getSum(root->rChild, left, right);
}
};

0327. 区间和的个数

1 题目

https://leetcode-cn.com/problems/count-of-range-sum/

题和逆序数对的计算方式相同:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/
就是做了一个小改变而已,很多统计区间值的,st-ed < tar, 本来是让你找一个st,ed的对子的,那么就会转换思路为:
对于每一个ed找st,什么样的呢? st < ed + tar
然后找这样的st就有很多方法,比如hash,前缀和,bitree,priority_queue

2 解题思路

  • 1 求逆序对的思路:
    • 1.1 首先注意到:对于数组{5,5,2,3,6}而言,得到每个value的个数的统计:
      • index -> 1 2 3 4 5 6 7 8 9
      • value -> 0 1 1 0 2 1 0 0 0
    • 1.2 那么上述过程中,比如对于5,其贡献的逆序数对为5之前所有数字出现次数的和,也就是value数组中2之前的前缀和!
  • 2 那么如何快速获得前缀和呢?考虑使用BST来获取,参考:https://xychen5.github.io/2021/12/15/dataStructure-BinaryIndexedTree/
    • 2.1 整体思路如下:
      • a 使用数字在数组中的排名来代替数字(这不会对逆序数对的个数产生影响)
      • b 对数组nums中的元素nums[i]从右到左构建BITree(i 从 n-1 到 0),注意,BITree所对应的前缀和是数组里数字出现次数的和
        • 比如进行到nums[i],那么nums[i]右边的数字都已经统计了他们的出现次数,而后获取nums[i] - 1的前缀和,即可获取所有 < nums[i]的数字在nums[i:n]中的出现次数之和,也就是nums[i]贡献的逆序数对的个数
        • 之所以是逆序遍历构建BITree,是因为对于nums[i],它能够贡献的逆序数对的个数仅仅出现在它的右侧,所以需要在右侧进行
    • 2.2 额外说一下数组离散化,也就是不关系数字大小本身,只关心他们之间的相对排位
  • 3 使用线段树解题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
class Solution {
public:
class SegTree {
public:
struct SegNode {
long long leftBound = 0, rightBound = 0, curSum = 0;
SegNode* lChild = nullptr;
SegNode* rChild = nullptr;
SegNode(long long lb, long long rb) :
leftBound(lb),
rightBound(rb),
curSum(0),
lChild(nullptr),
rChild(nullptr) {
}
};

SegNode* root;


SegTree(long long left, long long right) {
root = build(left, right);
}

SegTree() {}

SegNode* build(long long l, long long r) {
SegNode* node = new SegNode(l, r);
if(l == r) {
return node;
}

long long mid = (l + r) / 2;
node->lChild = build(l, mid);
node->rChild = build(mid + 1, r);
return node;
}

void insert(SegNode* root, long long tarIdx, long long val) {
root->curSum += val;
if(root->leftBound == root->rightBound) {
return;
}
long long mid = (root->leftBound + root->rightBound) >> 1;

if(tarIdx <= mid) {
// cout << "d2" << endl;
if (nullptr == root->lChild) {
// cout << "d2.5" << endl;
root->lChild = new SegNode(root->leftBound, mid);
}
insert(root->lChild, tarIdx, val);
}
else{
// cout << "d3" << endl;
if (nullptr == root->rChild) {
root->rChild = new SegNode(mid + 1, root->rightBound);
}
insert(root->rChild, tarIdx, val);
}
}

long long getSum(SegNode* root, long long left, long long right) const {
if(nullptr == root) {
return 0;
}
// 当前节点位于目标区间外
if(left > root->rightBound || right < root->leftBound) {
return 0;
}
// 当前节点位于目标区间内
if(left <= root->leftBound && right >= root->rightBound) {
// cout << "left/right" << left << "/" << right << " => " << root->curSum << endl;
return root->curSum;
}
return getSum(root->lChild, left, right) + getSum(root->rChild, left, right);
}

};

int countRangeSum(vector<int>& nums, int lower, int upper) {
unordered_map<int, int> numToIdx;
set<long long> tmpNums;
vector<long long> prefixSum = {0};

for(int i = 0; i < nums.size(); ++i) {
prefixSum.emplace_back(nums[i] + prefixSum.back());
}

for(auto ps : prefixSum) {
tmpNums.insert(ps);
tmpNums.insert(ps - lower);
tmpNums.insert(ps - upper);
}

int i = 1;
for(auto num : tmpNums) {
numToIdx[num] = i++;
}

// for a valid s(i, j) we shall find:
// preSum[j] - ub <= preSum[i] <= preSum[j] - lb
// we just need to statistic those preSum[i] for each j
int n = tmpNums.size();
// BITree tree(n + 1);
long long ans = 0;

// we do not do the deserialization
long long minLeft = LLONG_MAX, maxRight = LLONG_MIN;
for(long long x : prefixSum) {
minLeft = min({minLeft, x, x - lower, x - upper});
maxRight = max({maxRight, x, x - lower, x - upper});
}

// cout << "minL, maxR" << minLeft << " " << maxRight <<endl;
SegTree tree;
tree.root = new SegTree::SegNode(minLeft, maxRight);
// reason why we insert the prefixSum of 0, because for the first ele:
// if it statisfy the interval, then it will be statisticed because the 0
for(long long x : prefixSum) {
ans += tree.getSum(tree.root, x - upper, x - lower);
// cout << "lb, rb = " << x - upper<< " " << x - lower << " ==> ans = " << ans << endl;
tree.insert(tree.root, x, 1);
// cout << "insert: " << x << "curRoot: " << tree.root->curSum << endl;
}


return ans;
}
}

5 可能发生的问题

注意其中很重要的一点:

对于线段树中插入一个节点时,需要对沿路所有节点的sum加上要插入的节点的值,找这个节点位置的时候,
需要找到root左右管辖范围的中间值mid,此时务必使用>>1去做,因为获得mid我们要求其为 floor(left + right),
(cpp和python对于移位和除法的逻辑是相同的),这里就显示出了2者的区别,
当然在数字都为正数的时候不会出错!

1
2
3
4
>>> int((-1 + 0) / 2)
0
>>> int((-1 + 0) >> 1)
-1