目录
  1. 1. 139. 单词拆分
    1. 1.1. 1.题目描述
    2. 1.2. 代码实现
      1. 1.2.1. 动态规划
      2. 1.2.2. dfs+剪枝
  2. 2. 5. 最长回文子串
    1. 2.1. 解题思路
    2. 2.2. 代码实现
  3. 3. 263. 丑数 I
    1. 3.1. 题目描述
    2. 3.2. 1.打家劫舍I
      1. 3.2.1. 1.1题目描述
      2. 3.2.2. 2.3代码实现
  4. 4. 5208. 穿过迷宫的最少移动次数
    1. 4.1. 题目描述
    2. 4.2. 代码实现
  5. 5. Leetcode 120. 三角形最小路径和
    1. 5.1. 题目描述
    2. 5.2. 代码实现
      1. 5.2.1. 不带空间优化
      2. 5.2.2. 空间优化
  6. 6. Leetcode 435. 无重叠区间
    1. 6.1. 题目描述
    2. 6.2. 3. 代码实现
      1. 6.2.1. 3.1 没有空间优化
      2. 6.2.2. 3.2 有空间优化
  7. 7. 91. 解码方法
    1. 7.1. 题目表述
      1. 7.1.1. 然后说一下空间优化的问题。
    2. 7.2. 代码实现
  8. 8. Leetcode 152. 乘积最大子序列
    1. 8.1. 1.题目描述
    2. 8.2. 代码实现
      1. 8.2.1. dfs+剪枝
      2. 8.2.2. 动态规划
  9. 9. Leetcode 300. 最长上升子序列
    1. 9.1. 题目描述
    2. 9.2. 代码实现
  10. 10. 84. 柱状图中最大的矩形
    1. 10.1. 题目描述
      1. 10.1.1. 接下来
    2. 10.2. 代码实现
  11. 11. Leetcode 85. 最大矩形
    1. 11.1. 题目描述
    2. 11.2. 代码实现
  12. 12. 304. 二维区域和检索 - 矩阵不可变
    1. 12.1. 题目描述
    2. 12.2. 题目描述
    3. 12.3. 解题思路
  13. 13. Leetcode 5206. 删除字符串中的所有相邻重复项 II
    1. 13.1. 题目描述
    2. 13.2. 题目描述
    3. 13.3. 代码实现
  14. 14. Leetcode 1223. 掷骰子模拟
    1. 14.1. 题目描述
  15. 15. Leetcode 401. 二进制手表
    1. 15.1. 题目描述
LeetCode题目

139. 单词拆分

1.题目描述

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明: 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1: 输入: s = "leetcode", wordDict = ["leet", "code"] 输出: true 解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。 ## 2.解题思路 这道题跟前面的[解码方法]一题非常非常地像。用\(dp[i][j]\)表示字符串\(s[i:j]\)是否可拆分,是为1,否为0。状态转移方程如下: \[ dp[i]=\left \{ \begin{array}{cl} 1 &i>j\\ dp[i][j-k-1]\ and\ s[j-k:j]\ in\ wordDict&i\neq j \end{array} \right . \] 其中\(0\leq k\leq j-i\)。 然后我发现,这道题都用不着\(i\),直接用\(dp[j]\)表示字符串\(s[0:j]\)是否可拆分,所以公式改为: \[ dp[j]=\begin{cases}1& i>j\\ dp[j-k-1]\ and\ s[j-k:j]\ in\ wordDict & otherwise\end{cases} \]

其中\(0\leq k\leq j\)。 ## 3.代码实现 ### 3.1记忆化搜索 方便理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int dp[1050];

int getDp(int j, string& s, vector<string>& wordDict) {
if (0 > j) return 1;
if (dp[j] != -1) return dp[j];
for (int k = 0; k <= j; k++) {
int tmp = find(wordDict.begin(), wordDict.end(), s.substr(j-k, k+1)) != wordDict.end();
if (getDp(j-k-1, s, wordDict) && tmp) return dp[j] = 1;
}
return dp[j] = 0;
}

bool wordBreak(string s, vector<string>& wordDict) {
memset(dp, -1, sizeof(dp));
return getDp(s.length()-1, s, wordDict);
}
};
### 3.2动态规划
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
int dp[1050];
for (int j = 0; j < s.length(); j++) {
for (int k = 0; k <= j; k++) {
int tmp = find(wordDict.begin(), wordDict.end(), s.substr(j-k, k+1)) != wordDict.end();
if (j-k-1 < 0) {
dp[j] = tmp;
}
else {
dp[j] = dp[j-k-1] && tmp;
if (dp[j] == 1) break;
}
}
}
return dp[s.length()-1];
}
};
# 279. 完全平方数 ## 题目描述 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 示例 1: 输入: n = 12 输出: 3 解释: 12 = 4 + 4 + 4. 示例 2: 输入: n = 13 输出: 2 解释: 13 = 4 + 9. ## 解题思路 ### 动态规划 用\(dp[i]\)表示组成\(i\)需要的完全平方数的最少个数。状态转移方程是: \[ dp[i]= \left \{ \begin{array}{lr} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 0\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ i=0\\ min\{dp[i-j^2]+1\ |\ i-j^2\geq 0\}\ \ \ \ i\neq0 \end{array} \right . \]

其中\(j=[1,2,3...]\)。 ### dfs+剪枝 dfs就比较好理解了。

1
2
3
void dfs(int T, int cur) { //目标是T,当前深度为cur
...
}
搜索过程如下:

代码实现

动态规划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int *dp;

int numSquares(int n) {
dp = new int[n+1];

for (int i = 0; i <= n; i++) {
int m = i;
for (int k = 1; i-k*k>=0; k++) {
m = min(m, dp[i-k*k]+1);
}
dp[i] = m;
}

int ans = dp[n];
delete[] dp;
return ans;
}
};

dfs+剪枝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int ans;
void dfs(int T, int cur) {
if (T < 0 || cur >= ans) return;
if (T == 0) {
ans = cur;
return;
}
int k = int(sqrt(T));
for (; k >=1; k--) {
dfs(T-k*k, cur+1);
}
}
int numSquares(int n) {
ans = 9999999;
dfs(n, 0);
return ans;
}
};

5. 最长回文子串

解题思路

定义:

\[ dp[i][j]= \begin{cases} 0,s[i,j]不是回文串\\ 1,s[i,j]是回文串 \end{cases} \]

状态转移方程:

\[ dp[i][j]= \begin{cases} dp[i+1][j-1],s[i]=s[j]\\ dp[i+1][j]\ or\ dp[i][j-1],s[i]\neq s[j] \end{cases} \]

举个例子:输入: "babad" 输出: "bab" 注意: "aba" 也是一个有效答案。

初始化\(dp[i][j]=1\ (i>=j)\),因为空串或者长度为1的字符串肯定是回文串。 也就是下图这样: 然后在看那个状态转移方程,搞明白要求一个\(dp[i][j]\)需要用到的的是那些元素。如下图: 可以看出,要求\(dp[i][j]\),需要知道跟他它紧挨着的左下角的三个元素,而我们最终要求的是整个串的最长回文串,所以需要求到\(dp[0][s.length-1]\)。也就是下面蓝色星星这个位置: 说到现在,整个状态转移的过程就差不多明了了: 就这么斜着,一层一层地,直到到达最右上角的位置。 上面例子的最后状态就是: 下一步就是怎么把回文串弄出来,方法是回溯,从离右上角最近的1开始,往对角线的方向走,如果其左下角为1,则直接移动到左下角,如果为0,则考虑往右或者往下走。经过的路径就是回文串的一半。

代码实现

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
class Solution {
public:
string longestPalindrome(string s) {
if (s.length() == 0) {
return "";
}
int dp[1050][1050] = {0};

for (int i = 0; i < s.length(); i++) { // 初始化
for (int j = 0; j < s.length(); j++)
dp[i][j] = 1;

}

int last_i=0, last_j=0;

// 下面开始一步一步地逼近dp[0][s.length()-1]

for (int k = 1; k < s.length(); k++) {
for (int i = 0; i < s.length()-k; i++) {
int j = i + k;
if (s[i] == s[j])
dp[i][j] = dp[i+1][j-1];
else
dp[i][j] = 0;
if (dp[i][j] == 1) { //记下最后一个是回文串的位置,方便回溯
last_i = i;
last_j = j;
}
}
}


int i = last_i, j = last_j;
string ans = "";
while (i < j) {
if (dp[i+1][j-1] == dp[i+1][j-1]) {
ans += s[i];
i++;
j--;
}
else if (dp[i][j-1] == dp[i][j]) {
j--;
}
else if (dp[i+1][j] == dp[i][j]) {
i++;
}
}

string t = ans;
if (i == j) ans += s[i];
for (int k = t.length()-1; k >= 0; k--) ans += t[k];

return ans;
}
};

263. 丑数 I

题目描述

编写一个程序判断给定的数是否为丑数。 丑数就是只包含质因数 2, 3, 5 的正整数。 示例 1: 输入: 6 输出: true 解释: 6 = 2 × 3 ## 代码实现

1
2
3
4
5
6
7
8
9
10
class Solution {
public:
bool isUgly(int num) {
if (num == 0) return false;
while (num % 5 == 0) num /= 5;
while (num % 3 == 0) num /= 3;
while (num % 2 == 0) num /= 2;
return num == 1;
}
};
# 264. 丑数 II ## 题目描述 编写一个程序,找出第 n 个丑数。 丑数就是只包含质因数 2, 3, 5 的正整数。 示例: 输入: n = 10 输出: 12 解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。 说明: 1 是丑数。 n 不超过1690。 ## 代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int nthUglyNumber(int n) {
int dp[2000];
dp[0] = 1;
int l2 = 0;
int l3 = 0;
int l5 = 0;
for (int i = 1; i < n; i++) {
dp[i] = min(dp[l2]*2, min(dp[l3]*3, dp[l5]*5));
while (dp[l2]*2 <= dp[i]) l2++;
while (dp[l3]*3 <= dp[i]) l3++;
while (dp[l5]*5 <= dp[i]) l5++;
}
return dp[n-1];
}
};
# Leetcode 198. 打家劫舍 I/II

1.打家劫舍I

1.1题目描述

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。 示例 1: 输入: [1,2,3,1] 输出: 4 解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。 ### 1.2解题思路 妥妥的动态规划。用\(dp[i][0]\)表示在\(0\)\(i\)号屋子中,不抢劫第\(i\)号屋子所能获得的最大现金;\(dp[i][i]\)表示在\(0\)\(i\)号屋子中,抢劫第\(i\)号屋子所能获得的最大现金。则状态转移方程如下: \[ \begin{array}{lr} dp[i][0]= \left \{ \begin{array}{lr} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ 0\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ i=0\\ max\{dp[i-1][0],dp[i-1][1]\}\ \ \ \ i\neq0 \end{array} \right .\\ ~\\ dp[i][1]= \left \{ \begin{array}{lr} \ \ \ \ \ \ \ \ \ \ \ \ \ nums[i]\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ i=0\\ dp[i-1][0]+nums[i]\ \ \ \ i\neq0 \end{array} \right . \end{array} \]

稍微说明一下,假如第\(i\)间房屋不抢,则第\(i-1\)间房屋可以抢也可以不抢,对应着公式$dp[i][0]=max\{dp[i-1][0],dp[i-1][1]\}$;假如第\(i\)间房屋要抢,则第\(i-1\)间房屋必然不能抢,对应着公式$dp[i][1]=dp[i-1][0]+nums[i]$; ### 1.3代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int dp[9999][2];
int rob(vector<int>& nums) {
if (nums.size() == 0) {
return 0;
}
memset(dp, 0, sizeof(dp));

for (int i = 0; i < nums.size(); i++) {
if (i == 0) {
dp[i][0] = 0;
dp[i][1] = nums[i];
}
else {
dp[i][0] = max(dp[i-1][0], dp[i-1][1]);
dp[i][1] = dp[i-1][0] + nums[i];
}
}
return max(dp[nums.size()-1][0], dp[nums.size()-1][1]);
}
};
## 2.打家劫舍II ### 2.1题目描述 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。 示例 1: 输入: [2,3,2] 输出: 3 解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。 ### 2.2解题思路 跟第一道题相比,不同之处就在第一间和最后一间房屋变成相邻的了。所以这里就有了四种情况: 1. 第一间和最后一间都不抢 2. 抢第一间,不抢最后一间 3. 不抢第一间,抢最后一间 4. 都抢

前三种情况完成可以用第一题的代码去解决。主要是第四种情况有点不一样。因为现在第一间跟最后一间变成相邻的了,所有现在需要从其二者中选择去掉一个。那么这个问题就分成了两个问题: 1) 从第一间房到倒数第二间房所能获得到最大现金 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
class Solution {
public:
int dp[9999][2];
int rob1(vector<int>& nums) {
if (nums.size() == 0) {
return 0;
}
memset(dp, 0, sizeof(dp));

for (int i = 0; i < nums.size(); i++) {
if (i == 0) {
dp[i][0] = 0;
dp[i][1] = nums[i];
}
else {
dp[i][0] = max(dp[i-1][0], dp[i-1][1]);
dp[i][1] = dp[i-1][0] + nums[i];
}
}
return max(dp[nums.size()-1][0], dp[nums.size()-1][1]);
}
int rob(vector<int>& nums) {
if (nums.size() == 0) {
return 0;
}

int ans = rob1(nums);
if (ans == dp[nums.size()-1][0]) {
return ans;
}
else {
if (nums.size() <=1) return ans;

int last = nums[nums.size()-1];
nums.pop_back();
int ans1 = rob1(nums);

nums.push_back(last);
nums.erase(nums.begin());
int ans2 = rob1(nums);

return max(ans1, ans2);
}
}
};

5208. 穿过迷宫的最少移动次数

题目描述

## 思想 想法很简单,就是用BFS一层一层的搜索就可以。但针对这个问题,有些地方需要改变下。 ### 首先是当前位置 以前在一个二维地图上进行BFS,当前位置都是用\((i,j)\)这样一个坐标表示的,但现在这条小蛇独占了2个格子,也就是需要用两个坐标来表示,即\((i_1,j_1),(i_2,j_2)\),约定第一个坐标表示蛇的尾部,第二个坐标表示蛇的头部。为了方便实现,我们定义一个结构体:

1
2
3
4
5
struct Node {
int a, b, c, d;
Node() {}
Node(int _a, int _b, int _c, int _d) : a(_a), b(_b), c(_c), d(_d) {}
};
其中(a,b)对应着\((i_1,j_1)\),同理,(c,d)对应着\((i_2,j_2)\)。 ### 然后是记录已经访问过的“小蛇的位置” 以前都是用visited[i][j]来表示\((i,j)\)有没有被访问过,而在这里,我定义了一个集合:
1
set<Node> visited;
### 再有就是“如何走” 从某个位置可以走的方案一共四种,分别是:
1
2
3
4
Node try_right =        Node(p.a, p.b+1, p.c, p.d+1);
Node try_down = Node(p.a+1, p.b, p.c+1, p.d);
Node try_clockwise = Node(p.a, p.b, p.a+1, p.b);
Node try_anti = Node(p.a, p.b, p.a, p.b+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
struct Node {
int a, b, c, d;
Node() {}
Node(int _a, int _b, int _c, int _d) : a(_a), b(_b), c(_c), d(_d) {}
bool operator < (const Node &n) const {
if (a != n.a) return a < n.a;
if (b != n.b) return b < n.b;
if (c != n.c) return c < n.c;
if (d != n.d) return d < n.d;
return false;
}
};

class Solution {
public:

int minimumMoves(vector<vector<int>>& grid) {
Node start = Node(0, 0, 0, 1);
set<Node> visited;
visited.insert(start);
deque<Node> d;
d.push_back(start);

int m = grid.size(), n = grid[0].size();
int ans = 0;
while (!d.empty()) {

int len = d.size();
for (int i = 0 ; i < len; i++) {
Node &p = d.front();
if (p.a == (m-1) && p.c == (m-1) && p.b == (n-2) && p.d == (n-1)) {
return ans;
}

Node try_right = Node(p.a, p.b+1, p.c, p.d+1);
Node try_down = Node(p.a+1, p.b, p.c+1, p.d);
Node try_clockwise = Node(p.a, p.b, p.a+1, p.b);
Node try_anti = Node(p.a, p.b, p.a, p.b+1);

if (p.a == p.c) { //小蛇是横着的
if (try_right.d < n && grid[try_right.c][try_right.d] != 1 && visited.find(try_right) == visited.end()) {
visited.insert(try_right);
d.push_back(try_right);
}
if (try_down.a < m && grid[try_down.a][try_down.b] != 1 && grid[try_down.c][try_down.d] != 1) {
if (visited.find(try_down) == visited.end()) {
visited.insert(try_down);
d.push_back(try_down);
}
if (visited.find(try_clockwise) == visited.end()) {
visited.insert(try_clockwise);
d.push_back(try_clockwise);
}
}
}
else { //小蛇是竖着的
if (try_down.c < m && grid[try_down.c][try_down.d] != 1 && visited.find(try_down) == visited.end()) {
visited.insert(try_down);
d.push_back(try_down);
}
if (try_right.b < n && grid[try_right.a][try_right.b] != 1 && grid[try_right.c][try_right.d] != 1) {
if (visited.find(try_right) == visited.end()) {
visited.insert(try_right);
d.push_back(try_right);
}
if (visited.find(try_anti) == visited.end()) {
visited.insert(try_anti);
d.push_back(try_anti);
}
}
}
d.pop_front();
}
ans++;
}

return -1;
}
};
# Leetcode 5099. 验证回文字符串 III ## 题目描述 给出一个字符串 s 和一个整数 k,请你帮忙判断这个字符串是不是一个「K 回文」。 所谓「K 回文」:如果可以通过从字符串中删去最多 k 个字符将其转换为回文,那么这个字符串就是一个「K 回文」。 示例: 输入:s = "abcdeca", k = 2 输出:true 解释:删除字符 “b” 和 “e”。 提示: 1 <= s.length <= 1000 s 中只含有小写英文字母 1 <= k <= s.length ## 解题思路 动态规划。 一开始是想着用记忆搜索那种方法,但是内存超限,然后用动态规划,时间超限。。。最后灵机一动,巧妙一变,问题引刃而解。 用\(dp[i][j]\)表示将字符串\(s[i:j]\)变成回文需要的最少操作数。状态转移方程如下: \[ dp[i][j]= \left \{ \begin{array}{clr} 0& i\geq j\\ dp[i+1][j-1]& i < j,s[i]=s[j]\\ min\{dp[i+1][j],dp[i][j-1]\}& i < j,s[i]\neq s[j] \end{array} \right . \]

这样就把k巧妙的去掉了,好理解,公式简洁,代码简单。 至于动态转移方程的具体过程讲解,请看5. 最长回文子串,二者非常非常地像。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:

bool isValidPalindrome(string s, int k) {
int dp[1050][1050];
int n = s.length();

memset(dp, 0, sizeof(dp));
for (int k = 1; k < n; k++) {
for (int i = 0; i <= n-1-k; i++) {
int j = i+k;
if (s[i] == s[j]) {
dp[i][j] = dp[i+1][j-1];
}
else {
dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 1;
}
}
}
return dp[0][n-1] <= k;
}
};

Leetcode 120. 三角形最小路径和

题目描述

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 例如,给定三角形: [ [2], [3,4], [6,5,7], [4,1,8,3]] 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 ## 解题思路 动态规划,用\(dp[i][j]\)表示从顶点到达\((i, j)\)点的最短路径和。状态转移方程如下: \[ dp[i][j]= \left \{ \begin{array}{clr} triangle[i][j]& i=0\\ triangle[i][j]+dp[i-1][j]& j=0\\ triangle[i][j]+dp[i-1][j-1]& j=triangle[i].length-1\\ triangle[i][j]+min\{dp[i-1][j],dp[i-1][j-1]\}& 0 < j < triangle[i].length-1 \end{array} \right . \]

可以看出来,更新第\(i\)行只需要用到第\(i-1\)行,所以可以进一步优化空间。 用\(dp[j]\)表示从顶点到达\((i, j)\)点的最短路径和,优化之后的公式如下: \[ dp[j]= \left \{ \begin{array}{clr} triangle[i][j]& i=0\\ triangle[i][j]+dp[j]& j=0\\ triangle[i][j]+dp[j-1]& j=triangle[i].length-1\\ triangle[i][j]+min\{dp[j],dp[j-1]\}& 0 < j < triangle[i].length-1 \end{array} \right . \]

其中,\(i=\{0,1,2,...,n-1\}\) \(j=\{triangle[i].length-1, triangle[i].length-2,...,0\}\) 注意,\(j\)必须是降序,这是因为求\(dp[j]\)需要用到\(dp[j-1]\)。如果求\(dp[j]\)需要用到\(dp[j+1]\),则\(j\)需要是升序。

代码实现

不带空间优化

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
class Solution {
public:
int minimumTotal(vector < vector < int > > & triangle) {
int dp[1050][1050] = {0};
int n = triangle.size();
if (n == 0) return 0;
if (triangle[0].size() == 0) return 0;

dp[0][0] = triangle[0][0];

for (int i = 1; i < n; i++) {
for (int j = 0; j < triangle[i].size(); j++) {
if (j == 0) {
dp[i][j] = dp[i-1][j] + triangle[i][j];
}
else if (j == triangle[i].size()-1) {
dp[i][j] = dp[i-1][j-1] + triangle[i][j];
}
else {
dp[i][j] = triangle[i][j] + min(dp[i-1][j], dp[i-1][j-1]);
}
}
}
int ans = dp[n-1][0];
for (int j = 1; j < triangle[n-1].size(); j++) {
ans = min(ans, dp[n-1][j]);
}
return ans;
}
};

空间优化

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
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int dp[1050] = {0};
int n = triangle.size();
if (n == 0) return 0;
if (triangle[0].size() == 0) return 0;

dp[0] = triangle[0][0];

for (int i = 1; i < n; i++) {
for (int j = triangle[i].size()-1; j >= 0; j--) {
if (j == 0) {
dp[j] = dp[j] + triangle[i][j];
}
else if (j == triangle[i].size()-1) {
dp[j] = dp[j-1] + triangle[i][j];
}
else {
dp[j] = triangle[i][j] + min(dp[j], dp[j-1]);
}
}
}
int ans = dp[0];
for (int j = 1; j < triangle[n-1].size(); j++) {
ans = min(ans, dp[j]);
}
return ans;
}
};

Leetcode 435. 无重叠区间

题目描述

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。 注意: 可以认为区间的终点总是大于它的起点。 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。 示例 1: 输入: [ [1,2], [2,3], [3,4], [1,3] ] 输出: 1 解释: 移除 [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
class Solution {
public:
static bool cmp(const vector<int> &a, const vector<int> &b) {
return a[0] <= b[0];
}
bool judge(vector<int> &a, vector<int> &b) {
return !(a[1] <= b[0] || b[1] <= a[0]);
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if (intervals.size() <= 0) return 0;

sort(intervals.begin(), intervals.end(), cmp);
// for (auto &au : intervals) {
// cout << au[0] << " " << au[1] << endl;
// }

int ans = 0;
int pre = 0;

for (int i = 1; i < intervals.size(); i++) {
if (judge(intervals[pre], intervals[i])) {
if (intervals[pre][0] <= intervals[i][0] && intervals[pre][1] >= intervals[i][1]) {
pre = i;
ans += 1;
}
else {
ans += 1;
}
}
else {
pre = i;
}
}

return ans;
}
};
# 542. 01 矩阵 ## 题目描述 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。 两个相邻元素间的距离为 1 。 注意: 1. 给定矩阵的元素个数不超过 10000。 2. 给定矩阵中至少有一个元素是 0。 3. 矩阵中的元素只在四个方向上相邻: 上、下、左、右。 ### 一开始想的是 一开始想的是BFS,遍历数组,如果当前元素是1,则从当前位置开始BFS,这样的话,遍历数组用时\(O(n^2)\),BFS用时\(O(n^2)\),总体时间复杂度为\(O(n^4)\),感觉不太行。 ### 动态规划? 突然起了个想法,也可能是前两天写的两篇博客都是关于动态规划的,就想着这个能不能用动态规划做呢?然后就想出了下面的东西(其实我不确定这是不是对的)。 用\(dp[i][j]\)表示位于\((i,j)\)的1到最近的 0 的距离。看一下下面的图: 要求\(dp[i][j]\)不就需要求它周围的这8个点的\(dp\)值,再加上1或者2不就好了吗。公式如下: \[ dp[i][j]=min \left \{ \begin{array}{lr} dp[i-1][j-1]+2,\\ dp[i-1][j]+1,\\ dp[i-1][j+1]+2,\\ dp[i][j-1]+1,\\ dp[i][j+1]+1,\\ dp[i+1][j-1]+2,\\ dp[i+1][j]+1,\\ dp[i+1][j+1]+2 \end{array} \right . \]

然后我发现这样还不够简单,再改改,去掉四个角点: 然后公式就变成了: \[ dp[i][j]=min \left \{ \begin{array}{lr} dp[i-1][j],\\ dp[i][j-1],\\ dp[i][j+1],\\ dp[i+1][j],\\ \end{array} \right \}+1 \]

然后我又在想,初始状态是怎么弄呢?因为这个转移方程形式是从四周向着一个中心点收缩,所有有些点是一开始就应该被初始化,这些点就是\(matrix[i][j]=0\)的点。 也即是首先把所有\(matrix[i][j]=0\)的点的\(dp\)值设为0,然后从以这些点为基础“广播”,如下图: 上图中,蓝色的是初始\(dp\)为0的点,紫色圈是不断进行状态转移,数字表示“广播”的距离。哎呦,这不就是BFS吗!? ### 回到BFS 一开始想到BFS是以\(matrix[i][j]=1\)的位置为起始位置进行BFS,不妨换一种思路:以\(matrix[i][j]=0\)的位置为起始位置进行BFS,当遇到\(matrix[i][j]=1\)的位置时,那么其\(dp\)值就是BFS的层数。问题被巧妙地解决了。时间复杂度为\(O(n^2)\)。 ### 小技巧 题目中告诉我们给定矩阵的元素个数不超过 10000,如果设置一个数组int visited[10000][10000],这样内存不够用的,技巧是设置一个int visited[10000],然后将位置\((i,j)\)离散化处理,变成\(i*n+j\),其中\(n\)\(matrix\)的列数。这样原来的visited[i][j]=1就变成了visited[i*n+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
35
36
37
38
39
40
41
42
43
44
45
class Solution {
public:
vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) {

int next[4][2] = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}};

vector<vector<int>> ans = matrix;

int visited[10002] = {0};
deque<pair<int, int>> d;

int m = matrix.size(), n = matrix[0].size();;

for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == 0) {
d.push_back(make_pair(i, j));
visited[i*n+j] = 1;
}
}
}

int step = 0;
while (!d.empty()) {
int len = d.size();
for (int k = 0; k < len; k++) {
pair<int, int> p = d.front();
d.pop_front();
int i = p.first, j = p.second;
if (matrix[i][j] == 1) {
ans[i][j] = step;
}
for (int t = 0; t < 4; t++) {
int next_i = i+ next[t][0], next_j = j + next[t][1];
if (!(next_i < 0 || next_i >= matrix.size() || next_j < 0 || next_j >= matrix[0].size() || visited[next_i*n+next_j] == 1)) {
d.push_back(make_pair(next_i, next_j));
visited[next_i*n+next_j] = 1;
}
}
}
step++;
}
return ans;
}
};
# 221. 最大正方形 ## 1. 题目描述 在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。 示例: 输入: 1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 1 0 输出: 4 ## 2. 解题思路 一开始是想和Leetoce 85. 最大矩阵这道题一起做的,所以在设计动态规划数组时候就留了一手。但随后发现最大矩阵那道题用这样的动态规划不太好弄。所以最后我又重新做了一遍这道最大正方形的题,力求让公式更加简洁。 用\(dp[i][j]\)表示以\((i,j)\)为右下角的正方形的边长。 状态转移方程如下: \[ dp[i][j]= \left \{ \begin{array}{lr} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ matrix[i][j]==1\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ i=0||j=0\\ min\{dp[i-1][j],dp[i-1][j-1],dp[i][j-1]\}+1\ \ \ \ otherwise \end{array} \right . \]

意思就是求分别以\((i-1,j)\)\((i-1,j-1)\)\((i,j-1)\)为右下角的正方形的交集的边长,如下图中阴影部分,就是紫色、蓝色、绿色三个正方形的交集。 然后就是空间优化,这里不详细说明了,请看代码。

3. 代码实现

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
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
int dp[1050][1050];
memset(dp, 0, sizeof(dp));
int m = matrix.size();
if (m <= 0) return 0;
int n = matrix[0].size();
if (n <= 0) return 0;

int ans = 0;

for (int i = 0; i < m; i++) {
dp[i][0] = matrix[i][0] == '1';
ans = max(ans, dp[i][0]);
}
for (int j = 0; j < n; j++) {
dp[0][j] = matrix[0][j] == '1';
ans = max(ans, dp[0][j]);
}

for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][j] == '1') {
dp[i][j] = min(dp[i-1][j], min(dp[i-1][j-1], dp[i][j-1])) + 1;
}
else {
dp[i][j] = 0;
}
ans = max(ans, dp[i][j]);
}
}

return ans*ans;
}
};

3.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
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
int dp[1050];
memset(dp, 0, sizeof(dp));
int m = matrix.size();
if (m <= 0) return 0;
int n = matrix[0].size();
if (n <= 0) return 0;

int ans = 0;
int cur, pre;

for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == '0') {
dp[j] = 0;
}
else {
if (i == 0) {
dp[j] = 1;
}
else {
if (j == 0) {
pre = dp[j];
dp[j] = 1;
}
else {
cur = dp[j];
dp[j] = min(dp[j], min(pre, dp[j-1])) + 1;
pre = cur;
}
}
}
ans = max(ans, dp[j]);
}
}
return ans*ans;
}
};

91. 解码方法

题目表述

一条包含字母 A-Z 的消息通过以下方式进行了编码: 'A' -> 1 'B' -> 2 ... 'Z' -> 26 给定一个只包含数字的非空字符串,请计算解码方法的总数。 示例 1: 输入: "12" 输出: 2 解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。 示例 2: 输入: "226" 输出: 3 解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/decode-ways 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

还是利用动态规划。但与上一题最长回文子串不同的是,这道题的状态转移方程不一样,但都差不多。观察状态转移方程发现,可以将二维的数组压缩到一维。最后总结一下常见的状态转移方程的形式。 ps.弱弱地说一句,其实上一题也是可以优化,由\(m\times n\)维变成\(2\times n\)维的。 ### 下面开始吧 用\(dp[i][j]\)表示字符串\(s[i:j]\)编码的最大方案数。 ### 状态转移方程: \[ dp[i][j]=max\{dp[i][j-k-1]\ |\ 1\leq s[j-k:j]\leq 26,0\leq k\leq min\{2,j-i+1\} \} \]

我试着说明一下。 假设\(dp[i][j-1]\)已经知道了,也就是说字符串\(s[i:j]\)的编码方案数已知,现在考虑地第\(j\)位字符。需要做的是,选择从\(j\)位往前,选择\(k\)位字符,将它们跟第\(j\)位拼起来并看成一个整体,也就是将\(s[i:j]\)分成\(s[i:j-k-1]\)\(s[j-k:j]\)两个部分,注意,这样分的前提是\(s[j-k:j]\)是可编码的,也就是对应着\(1\leq s[j-k:j]\leq 26\)这个条件。举个例子,\(s=226\),现在考虑\(6\)这一位,如下图: 这是两种不同的方案,然后去这两中方案中的最大那一种就可以,也就是对应着公式中\(max\{dp[i][j-k-1]\}\)的部分。 因为从1到26,最大就是2位数,所以\(k\)最大是1,也就是最多往前选1个字符,又因为还需要保证\(s[i:j-k-1]\)不为空,所以需要\(i\leq j-k-1\),即\(k\leq j-i+1\),这就对应了公式中\(0\leq k\leq min\{2,j-i+1\}\)的部分。

然后说一下空间优化的问题。

从上面的状态转移方程可以看出,求\(dp[i][j]\)只需要第\(i\)\(j\)左边的元素就行,用不着其他行的元素,所有可以二维降成一维。 降为一维后,还是进行跟原来类似的操作,只不过就是对这一维数组进行了多次操作,如下图。 优化之后的状态转移方程就变成了: \[ dp[j]=max\{dp[j-k-1]\ |\ 1\leq s[j-k:j]\leq 26,0\leq k\leq min\{2,j-i+1\} \}\\ \]

其中\(i\)表示当前是第几次操作。 ### 下面总结一下常见的状态转移形式,方程就不写了,记不住,就把图画一画吧。 紫色五角星表示要求的目标,蓝色表示求这个目标需要那些元素 第一类: 第二类: 第三类: ## 代码实现

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
class Solution {
public:
int numDecodings(string s) {
if (s.length() <= 0) return 0;

set<string> se {"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"};
int dp[3050] = {0};
for (int i = 0; i < s.length(); i++) {
if (s[i] != '0') {
dp[i] = 1;
}
}
int m = s.length();
for (int t = 1; t < m; t++) {
int i, j;
i = m - t - 1;
for (j = i+1; j < m; j++) {
int cur = 0;
for (int k = 0; k <= min(1, j-i+1); k++) {
if (se.find(s.substr(j-k, k+1)) != se.end()) {
if (j - k - 1 < i) {
cur += 1;
}
else
cur += dp[j-k-1];
}
}
dp[j] = cur;
}
}
return dp[m-1];
}
};
# 接上一题 Leetcode 95. 不同的二叉搜索树 II ## 题目描述 给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。 实例 输入: 3 输出: [ [1,null,3,2], [3,2,null,1], [3,1,null,null,2], [2,1,3], [1,null,2,null,3]] ## 解题思路 这道题用不着动态规划,也用不着卡特兰数列,只要明白了树的构建过程,然后构建树就行了,就像是根据前序和中序序列构建树那样。 稍微需要注意的就是这里,左右子树组合的方法:
1
2
3
4
5
6
7
8
for (auto& l: left) {
for (auto& r: right) {
TreeNode *root = new TreeNode(k);
root->left = l;
root->right = r;
ans.push_back(root);
}
}
## 代码实现
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
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<TreeNode*> build(int i, int j) {
vector<TreeNode*> ans;
if (i > j) {
ans.push_back(NULL);
return ans;
}
for (int k = i; k <= j; k++) {
vector<TreeNode*> left = build(i, k-1);
vector<TreeNode*> right = build(k+1, j);

for (auto& l: left) {
for (auto& r: right) {
TreeNode *root = new TreeNode(k);
root->left = l;
root->right = r;
ans.push_back(root);
}
}
}
return ans;
}
vector<TreeNode*> generateTrees(int n) {
if (n == 0) {
vector<TreeNode*> ans;
return ans;
}
return build(1, n);
}
};
# Leetcode 96. 不同的二叉搜索树 ## 题目描述 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? ## 解题思路 ### 看图说话 假设\(n=3\),那么初始数组就是\([1,2,3]\)。 首先从中选择一个数字作为根结点,可选择的点有3个,分别是\(1,2,3\)。假设选择了\(1\),然后左子树即为空,再根据剩下的\([2,3]\)用同样的方法构造右子树。过程如下图: ### 用动态规划解释 用\(f[n]\)表示从1到n,n个数能组成的二叉搜索树的数量。根据上面的二叉树生成过程可以得出: \[ f[n]= \left \{ \begin{array}{clr} 1& n\leq 1\\ \sum\limits_{k=0}^{n-1}{f[k]\times f[n-k-1]} & n>1 \end{array} \right . \]

举个例子: \[ f[5]=f[0]\times f[4]\\ =f[1]\times f[3]\\ =f[2]\times f[2]\\ =f[3]\times f[1]\\ =f[4]\times f[0] \]

也没啥好解释的,找找规律就能写出公式了。 ### 继续动态规划 如果是二维的呢? \[ f(i,j)= \left \{ \begin{array}{clr} 1& i\geq j \\ \sum\limits_{k=i}^{j-1}f(i,k)\times f(k+1,j)& i<j \end{array} \right . \]

相同颜色是相互对应的。有兴趣的童鞋可以自行搜索“卡特兰数列”。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int numTrees(int n) {
int f[1050] = {0};
f[0] = f[1] = 1;
for (int i = 2; i <= n; i++) {
for (int k = 0; k < i; k++) {
f[i] += (f[k] * f[i-k-1]);
}
}
return f[n];
}
};

Leetcode 152. 乘积最大子序列

1.题目描述

给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。 示例 1: 输入: [2,3,-2,4] 输出: 6 解释: 子数组 [2,3] 有最大乘积 6。 ## 2.解题思路 个人感觉这道题非常地有意思,其实用不上动态规划,或者说不是那种正规的动态规划。这道题的关键在于“负负得正”。 准备两个数组

1
2
int real_max[1050];
int fake_max[1050];
read_max[i]表示以nums[i]结尾的子数组的真实最大乘积; fake_max[i]表示以nums[i]结尾的子数组的可能最大乘积; ## 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
class Solution {
public:
int maxProduct(vector<int>& nums) {
int *real_max = new int[nums.size()];
int *fake_max = new int[nums.size()];

real_max[0] = fake_max[0] = nums[0];

for (int i = 1; i < nums.size(); i++) {
int a = nums[i];
int b = nums[i] * real_max[i-1];
int c = nums[i] * fake_max[i-1];

real_max[i] = max(a, max(b, c));
int tmp = min(a, min(b, c));
if (tmp < 0) fake_max[i] = tmp;
else fake_max[i] = real_max[i];

}

int ans = nums[0];
for (int i = 0; i < nums.size(); i++) {
ans = max(ans, real_max[i]);
}
delete[] real_max;
delete[] fake_max;
return ans;
}
};
# Leetcode 322. 零钱兑换 ## 题目描述 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 示例 1: 输入: coins = [1, 2, 5], amount = 11 输出: 3 解释: 11 = 5 + 5 + 1 示例 2: 输入: coins = [2], amount = 3 输出: -1 说明: 你可以认为每种硬币的数量是无限的。 ## 解题思路 这道题跟[279. 完全平方数]如出一辙。 一开始用的是dfs+剪枝,结果超时了。然后换成动态规划,通过了。

代码实现

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
//dfs+剪枝 超时
class Solution {
public:
int ans;

void dfs(vector<int>& coins, int i, int cur, int k) {
if (i < 0 || k < 0) return;

// cout << i << " " << cur << " k "<<k << endl;
if (cur >= ans) return;
if (k == 0) {
ans = min(ans, cur);
return;
}
cur++;
for (int j = i; j >= 0; j--) {
dfs(coins, j, cur, k-coins[j]);
}
}
int coinChange(vector<int>& coins, int amount) {
sort(coins.begin(), coins.end());
ans = 999999;
dfs(coins, coins.size()-1, 0, amount);
if (ans == 999999) return -1;
else
return ans;
}
};

动态规划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
int *dp = new int[amount+1];
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
int tmp = 99999;
for (int j = 0; j < coins.size(); j++) {
if ((i-coins[j]) >= 0 && dp[i-coins[j]] != -1) {
tmp = min(tmp, dp[i-coins[j]]+1);
}
}
if (tmp == 99999) dp[i] = -1;
else dp[i] = tmp;
}
int ans = dp[amount];
delete[] dp;
return ans;
}
};

Leetcode 300. 最长上升子序列

题目描述

给定一个无序的整数数组,找到其中最长上升子序列的长度。 示例: 输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。 说明: 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。 你算法的时间复杂度应该为 O(n2) 。 进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗? ## 解题思路 用\(dp[i]\)表示以\(nums[i]\)结尾的上升子序列的长度。则状态转移方程如下: \[ dp[i]=max\{1,1+d[j]\} \]

其中\(0\leq j< i\)。 这样的时间复杂度是\(O(n)\times O(n)=O(n^2)\)。 要优化的话,应该是可以把\(j\)的线性遍历变成二分查找。但具体我还没想好怎么弄。 ## 代码实现
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
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
if (n <= 0) return 0;

int *dp = new int[n+1];

int ans = 0;

for (int i = 0; i < n; i++) {
dp[i] = 1;
for (int j = i-1; j >= 0; j--) {
if (nums[i] > nums[j]) {
dp[i] = max(dp[i], dp[j]+1);
}
}
ans = max(ans, dp[i]);
}

delete[] dp;

return ans;
}
};
# Leetcode 5216. 统计元音字母序列的数目 ## 题目描述 给你一个整数 n,请你帮忙统计一下我们可以按下述规则形成多少个长度为 n 的字符串: 字符串中的每个字符都应当是小写元音字母('a', 'e', 'i', 'o', 'u') 每个元音 'a' 后面都只能跟着 'e' 每个元音 'e' 后面只能跟着 'a' 或者是 'i' 每个元音 'i' 后面 不能 再跟着另一个 'i' 每个元音 'o' 后面只能跟着 'i' 或者是 'u' 每个元音 'u' 后面只能跟着 'a' 由于答案可能会很大,所以请你返回 模 10^9 + 7 之后的结果。 示例 1: 输入:n = 1 输出:5 解释:所有可能的字符串分别是:"a", "e", "i" , "o" 和 "u"。 示例 2: 输入:n = 2 输出:10 解释:所有可能的字符串分别是:"ae", "ea", "ei", "ia", "ie", "io", "iu", "oi", "ou" 和 "ua"。 示例 3: 输入:n = 5 输出:68 提示: 1 <= n <= 2 10^4* ## 解题思路 动态规划。用\(dp[i][0]\)表示长度为\(i\)且最后一个字符是\(a\)的字符串数量;\(dp[i][1]\)表示长度为\(i\)且最后一个字符是\(e\)的字符串数量;…… $$ \[\begin{array}{clr} dp[i][0]&=dp[i-1][1]+dp[i-1][2]+dp[i-1][4]\\ dp[i][1]&=d[i-1][0]+dp[i-1][2]\\ ...&=... \end{array}\]

$$

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int countVowelPermutation(int n) {
long long int dp[20005][5];
dp[1][0] = dp[1][1] = dp[1][2] = dp[1][3] = dp[1][4] = 1;
for (int i = 2; i <= n; i++) {
dp[i][0] = dp[i-1][1] + dp[i-1][2] + dp[i-1][4];
dp[i][1] = dp[i-1][0] + dp[i-1][2];
dp[i][2] = dp[i-1][1] + dp[i-1][3];
dp[i][3] = dp[i-1][2];
dp[i][4] = dp[i-1][2] + dp[i-1][3];

for (int k = 0; k < 5; k++) dp[i][k] %= 1000000007;
}
return (dp[n][0] + dp[n][1] + dp[n][2] + dp[n][3] + dp[n][4]) % 1000000007;
}
};

84. 柱状图中最大的矩形

题目描述

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。 示例: 输入: [2,1,5,6,2,3] 输出: 10 ## 解题思路 利用单调栈。单调栈是个非常有意思的工具,利用它可以完成许多看似复杂的事务。单调栈本身的工作流程可能很容易让人困惑,但其实它背后的思想是及其简单和巧妙的。看完这个推荐去看[85. 最大矩形]。 ### 从头开始分析 针对这个题,如下图,人会怎么思考呢? 我会先看最高的那个,也就是以6为高,能组成的最大矩形,结果是6;然后在再看次高的那个,也就是以5为高,能组成的最大矩形是10;然后看3,以此类推,知道最后一个,以1为高,能组成的最大的矩形为6。

接下来

试着从前往后遍历,先忽略前面的2,从1开始。首先遍历的是1 然后加入5 然后加入6 然后,重点来了,加入2。先不着急加入,从后往前,以6为高的矩形如下: 以5为高的矩形如下: 然后是1,1比2小,先不处理。把5和6拿出去,然后把2加入,如下: 然后加入3 所有数组处理完毕。然后从后往前,以3为高的矩形: 以2为高的矩形: 以1为高的矩形: 最大的就是以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
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {

if (heights.size() <= 0) return 0;

stack<pair<int, int>> st;
heights.push_back(0);

st.push({0, heights[0]});

int ans = 0;

for (int i = 1; i < heights.size(); i++) {
pair<int, int> p = st.top();
if (heights[i] == p.second) {
continue;
}
else if (heights[i] > p.second) {
st.push({i, heights[i]});
}
else {
while (!st.empty() && st.top().second >= heights[i]) {
p = st.top();
st.pop();
ans = max(ans, p.second * ((i - 1) - p.first + 1));
}
st.push({p.first, heights[i]});
}

}
return ans;
}
};

Leetcode 85. 最大矩形

题目描述

给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。 示例: 输入: [ ["1","0","1","0","0"], ["1","0","1","1","1"], ["1","1","1","1","1"], ["1","0","0","1","0"]] 输出: 6 ## 解题思路 利用上一题84. 柱状图中最大的矩形的思想和代码。把这个矩阵一行一行的累加起来,然后就可以用上上一题的代码了。注意,这个累加跟平常的累加有些许不同,注意看下面第四行。 第一行,也就是求\([1,0,1,0,0]\)的柱状图中的最大矩形。 第二行,累加上第一行,也就是求\([2,0,2,1,1]\)的最大矩形。 第三行,就是求\([3,1,3,2,2]\)的最大矩形。 第四行,就是求\([4,0,0,3,0]\)的最大矩形。

代码实现

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
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {

if (heights.size() <= 0) return 0;

stack<pair<int, int>> st;
heights.push_back(0);

st.push({0, heights[0]});

int ans = 0;

for (int i = 1; i < heights.size(); i++) {
pair<int, int> p = st.top();
if (heights[i] == p.second) {
continue;
}
else if (heights[i] > p.second) {
st.push({i, heights[i]});
}
else {
while (!st.empty() && st.top().second >= heights[i]) {
p = st.top();
st.pop();
ans = max(ans, p.second * ((i - 1) - p.first + 1));
}
st.push({p.first, heights[i]});
}

}
return ans;
}

int maximalRectangle(vector<vector<char>>& matrix) {

int m = matrix.size();
if (m <= 0) return 0;
int n = matrix[0].size();
if (n <= 0) return 0;

vector<int> heights;
int ans = 0;

for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0) {
heights.push_back(matrix[i][j] == '1' ? 1 : 0);
}
else {
heights[j] = matrix[i][j] == '1' ? heights[j]+1 : 0;
}
}
ans = max(ans, largestRectangleArea(heights));
}

return ans;
}
};

304. 二维区域和检索 - 矩阵不可变

题目描述

给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2)。 示例: 给定 matrix = [ [3, 0, 1, 4, 2], [5, 6, 3, 2, 1], [1, 2, 0, 1, 5], [4, 1, 0, 1, 7], [1, 0, 3, 0, 5]] sumRegion(2, 1, 4, 3) -> 8 sumRegion(1, 1, 2, 2) -> 11 sumRegion(1, 2, 2, 4) -> 12 说明: 你可以假设矩阵不可变。 会多次调用 sumRegion 方法。 你可以假设 row1 ≤ row2 且 col1 ≤ col2。 ## 解题思路 树状数组。关于树状数组的有关说明可以看[220. 存在重复元素 III],这里用到的是多维树状数组。 ### 核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int lowbit(int x) {
return x & -x;
}

void add(int i, int j, int v) {
for (int k = i; k < n; k += lowbit(k)) {
for (int q = j; q < n; q += lowbit(q)) {
c[k][q] += v;
}
}
}

int mySum(int i, int j) {
int s = 0;
for (int k = i; k > 0; k -= lowbit(k)) {
for (int q = j; q > 0; q -= lowbit(q)) {
s += c[k][q];
}
}
return s;
}
## 代码实现
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
class NumMatrix {
public:
int c[1050][1050];
int n;

NumMatrix(vector<vector<int>>& matrix) {
memset(c, 0, sizeof(c));
n = 1050;
for (int i = 0; i < matrix.size(); i++) {
for (int j = 0; j < matrix[i].size(); j++) {
add(i+1, j+1, matrix[i][j]);
}
}
}

int lowbit(int x) {
return x & -x;
}

void add(int i, int j, int v) {
for (int k = i; k < n; k += lowbit(k)) {
for (int q = j; q < n; q += lowbit(q)) {
c[k][q] += v;
}
}
}

int mySum(int i, int j) {
int s = 0;
for (int k = i; k > 0; k -= lowbit(k)) {
for (int q = j; q > 0; q -= lowbit(q)) {
s += c[k][q];
}
}
return s;
}

int sumRegion(int row1, int col1, int row2, int col2) {
return mySum(row2+1, col2+1) - mySum(row1, col2+1) - mySum(row2+1, col1) + mySum(row1, col1);
}
};

/**
* Your NumMatrix object will be instantiated and called as such:
* NumMatrix* obj = new NumMatrix(matrix);
* int param_1 = obj->sumRegion(row1,col1,row2,col2);
*/
# 220. 存在重复元素 III

题目描述

给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ。

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/contains-duplicate-iii 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 ## 树状数组 树状数组的通俗解释 树状数组本身是一个数组,只不过把它画成树的形式,为了更好理解接下来的操作。下面来结合图片进行说明。 首先假设有一个原始数组\(A\)\(A=[1,2,3,4,5,6,7,8]\)。图下图所示。 然后,再申请一个数组\(C\),长度和\(A\)相同,初始状态\(C\)为空,即\(C=[0,0,0,0,0,0,0,0]\)。如下图所示。 这个\(C\)数组就是树状数组,可能现在还不不太清楚,怎么就树状数组了,别急,现在我们把\(C\)数组在形式上稍微改变一下,再和\(A\)数组对应起来,如下图所示。 现在在看,是不是\(C\)数组就像是一个树了,帅气智。然后需要了解的就是这个\(C\)数组究竟是用来干嘛的。还是看个例子。 $ C[1]=A[1]\ C[2]=C[1]+A[2]=A[1]+A[2]\ C[3]=A[3]=A[1]+A[2]+A[3]\ C[4]=C[2]+C[3]+A[4]=A[1]+A[2]+A[3]+A[4]\ C[5]=A[5]\ C[6]=C[5]+A[6]=A[5]+A[6]\ C[7]=A[7]\ C[8]=C[4]+C[6]+C[7]+A[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8]\ $ 这里直接给出计算\(C[i]\)的公式,\(C[i]=sum\{A[j]|i-2^k+1\leq j \leq i\}\ (k是i的二进制表示中末尾0的个数)\)。其实从上图也能看出来。我们把数字都标上,就变成了下图。 然后,需要知道的就要 这个数组数组可以用来做什么。它能做的事有两个:

  1. \(sum(i)\),计算\(A[1]+A[2]+...+A[i]\)
  2. \(add(i, x)\), 让\(A[i]\)加上\(x\),然后更新更个树状数组。

下面先说\(sum(i)\)\(sum(i)=A[1]+A[2]+...+A[i]\\=A[1]+A[2]+A[i-2^k]+A[i-2^k+1]+...+A[i]\\=sum(i-2^k)+C[i]\\=sum(i-lowbit(i))+C[i]\) 其中\(lowbit(i)\)是用来计算\(2^k\)的,其中\(k\)\(i\)的二进制表示中末尾0的个数。 这里不解释了,直接上代码,用的就是\(lowbit(i)\)这个函数。

1
2
3
int lowbit(int x) {
return x & -x;
}

然后是\(sum(i)\)的代码实现:

1
2
3
4
5
6
7
int sum(int x) {
int s = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
s += c[i];
}
return s;
}
说完\(sum(i)\)再说\(add(i, x)\)\(add(i, x)\)其实就是\(A[i]=A[i]+x\),其实就是看改变\(A[i]\)的时候会改变那些\(C[j]\)。跟\(sum(i)\)很像,只不过是反着来。下面是代码。
1
2
3
4
5
void add(int i, int x) {
for (int k = i; k <= n; k += lowbit(k)) {
c[k] += x;
}
}
树状数组就先说这些,下面是树状数组的模版。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static const int n = 20060;
int c[n+1];

int lowbit(int x) {
return x & -x;
}

void add(int x, int flag) {
for (int i = x; i <= n; i += lowbit(i)) {
c[i] += flag;
}
}

int sum(int x) {
int s = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
s += c[i];
}
return s;
}

解题思路

构造一个长度为\(k\)滑动窗口,然后利用树状数组查找这个滑动窗口中小于等于某个值的元素的个数。举个例子。 输入: nums = [1,0,1,1], k = 1, t = 2 输出: true 构造一个长度为1的滑动窗口,把0号加入到滑动窗口,此时滑动窗口为[1],然后处理2号数字,也就是0。因为要满足nums [i] 和 nums [j] 的差的绝对值最大为 t,也即是\(|nums[j]-0|\leq t\),也就是\(-t+0\leq nums[j]\leq t+0\),也就是\(-2\leq nums[j]\leq 2\),然后树状数组登场,查找当前滑动窗口中小于等于\(t+0\)的元素的个数,也就是小于等于2的元素的个数,[1]中小于等于2的元素有1个;然后查找当前滑动窗口中小于等于\((-t+0)-1\)的元素的个数(想想为啥要再减1),也就是小于等于-3的元素的个数,[1]中小于等于-3的元素有0个;所以[1]中大于等于2并且小于等于2的元素有1个。又因为这是在当前滑动窗口里找的,所以可定符合 i 和 j 之间的差的绝对值最大为 ķ。 如果在处理j号元素时,没有找到符合条件的元素,也就是让滑动窗口右移一位,也即是把\(nums[j]\)加入到滑动窗口,并把\(nums[j-k]\)移除滑动窗口。代码就是

1
2
add(nums[j], 1); #表示滑动窗口中多了一个值为nums[j]的元素
add(nums[j-k], -1); #表示滑动窗口中少了一个值为nums[j-k]的元素
前提是\(j-k\geq 0\)。 以上就是答题思路,然而在具体实践中,常遇到的两个问题是 1. 求左右边界时有可能超int范围,要用long类型 2. 对数组进行离散化处理。再利用一个数组a,关键代码如下:
1
2
3
4
5
for (int i = 0; i < nums.size(); i++) {
a[i] = nums[i];
}
sort(a, a+nums.size());#排序
an = unique(a, a + nums.size()) - a; #a的长度
在更新树状数组是,不再直接用\(nums[i]\),然是用\(nums[i]\)在a数组中的下标。 ## 代码实现

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
class Solution {
public:
static const int n = 20060;
int c[n+1];
int a[n+1];

int an;

int lowbit(int x) {
return x & -x;
}

void add(int x, int flag) {
for (int i = x; i <= n; i += lowbit(i)) {
c[i] += flag;
}
}

int sum(int x) {
int s = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
s += c[i];
}
return s;
}

bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
memset(c, 0, sizeof(c));
memset(a, 0, sizeof(a));
for (int i = 0; i < nums.size(); i++) {
a[i] = nums[i];
}
sort(a, a+nums.size());
an = unique(a, a + nums.size()) - a;

for (int j = 0; j < nums.size(); j++) {
long long int left = (long long int )-t + (long long int )nums[j]-1, right = (long long int )t + (long long int )nums[j];
int idx_left = lower_bound(a, a+an, left) - a;
int idx_right = lower_bound(a, a+an, right) - a;

if (a[idx_left] == left) idx_left++;
if (a[idx_right] == right) idx_right++;

if (sum(idx_right) - sum(idx_left) > 0) {
return true;
}

int idx_j = lower_bound(a, a+an, nums[j]) - a + 1;
add(idx_j, 1);

int i = j - k;
if (i >= 0) {
int idx_i = lower_bound(a, a+an, nums[i]) - a + 1;
add(idx_i, -1);
}
}
return false;
}
};

Leetcode 5206. 删除字符串中的所有相邻重复项 II

题目描述

给你一个字符串 s,「k 倍重复项删除操作」将会从 s 中选择 k 个相邻且相等的字母,并删除它们,使被删去的字符串的左侧和右侧连在一起。 你需要对 s 重复进行无限次这样的删除操作,直到无法继续为止。 在执行完所有删除操作后,返回最终得到的字符串。 本题答案保证唯一。

实例1 输入:s = "abcd", k = 2 输出:"abcd" 解释:没有要删除的内容。

实例2 输入:s = "deeedbbcccbdaa", k = 3 输出:"aa" 解释: 先删除 "eee" 和 "ccc",得到 "ddbbbdaa" 再删除 "bbb",得到 "dddaa" 最后删除 "ddd",得到 "aa"

实例3 输入:s = "pbbcggttciiippooaais", k = 2 输出:"ps"

提示 1 <= s.length <= 10^5 2 <= k <= 10^4 s 中只含有小写英文字母。 ## 解题思路 刚看到这道题的时候没有什么思路,想这会不会要用到哪种我还不知道的数据结构或者奇妙的字符串算法,然后没有往下做就去吃饭去了。在去吃饭的路上,突发灵感——栈。这道题不就跟后缀表达式那种题目有异曲同工之妙吗。 ## 代码实现

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
class Solution {
public:
string removeDuplicates(string s, int k) {
deque<pair<char, int>> d;
for (int i = 0; i < s.length(); i++) {
if (d.empty()) {
d.push_back({s[i], 1});
}
else {
if (d.back().first == s[i]) {
d.back().second++;
}
else {
d.push_back({s[i], 1});
}
}
if (d.back().second == k) {
d.pop_back();
}
}

string ans = "";
for (auto & au : d) {
// cout << au.first << " " << au.second << endl;
string tmp(au.second, au.first);
ans += tmp;
}

return ans;
}
};
# Leetcode 5214. 最长定差子序列 ## 题目描述 给你一个整数数组 arr 和一个整数 difference,请你找出 arr 中所有相邻元素之间的差等于给定 difference 的等差子序列,并返回其中最长的等差子序列的长度。 示例 1: 输入:arr = [1,2,3,4], difference = 1 输出:4 解释:最长的等差子序列是 [1,2,3,4]。 ## 解题思路 看到这个题目,第一个想到的就是刚刚做过的最长上升子序列。感觉就是稍微改一改就行了,然后提交发现,超时了。那么现在,就以这道最长定差子序列为例,说明一下我是如何进行优化的。 直接一点吧,本题的状态转移方程:

\[ dp[i]=\{1,dp[j]+1\} \]

其中,\(0\leq j < i\),并且还需要满足\(arr[i]-arr[j]=difference\)。 怎么获得适合的\(j\)呢?朴素的做法就是让\(j\)\([0,i-1]\)这个区间,遍历就是了。这正是可以优化的地方。仔细想想我们真正需要的是什么,不就是单纯的一个满足\(arr[i]-arr[j]=difference\)\(j\)吗,直接用一个map<int, int>来保存储存这些信息不就好了。这对应着代码里的m[arr[i]] = i;。 ## 代码实现 ### 超时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public:
int longestSubsequence(vector<int>& arr, int difference) {
int dp[100005];
dp[0] = 1;
int ans = 1;
for (int i = 1; i < arr.size(); i++) {
dp[i] = 1;
for (int j = i-1; j >= 0; j--) {
if ((arr[i] - arr[j]) == difference) {
dp[i] = max(dp[i], dp[j]+1);
}
}
ans = max(ans, dp[i]);
}
return ans;
}
};
### 通过
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 longestSubsequence(vector<int>& arr, int difference) {
map<int, int> m;
int dp[100005];
dp[0] = 1;
int ans = 1;
m[arr[0]] = 0;
for (int i = 1; i < arr.size(); i++) {
dp[i] = 1;

if (m.find(arr[i]-difference) != m.end()) {
int j = m[arr[i]-difference];
dp[i] = dp[j]+1;
}

m[arr[i]] = i;

ans = max(ans, dp[i]);
}
return ans;
}
};
# Leetcode 309. 最佳买卖股票时机含冷冻期

题目描述

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。 设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。 示例: 输入: [1,2,3,0,2] 输出: 3 解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出] ## 解题思路 依然是动态规划。 用\(dp[i]\)表示到第\(i\)为止,能赚到的最多的钱。状态转移方程为: \[ dp[i]= \left \{ \begin{array}{lr} 0& i\leq 0\\ max\{dp[i-1], prices[i]-prices[j]+dp[j-2]\}& otherwise \end{array} \right . \]

说明:第\(i\)天有两种情况,一是不买不卖,二是卖。不能是买。 如果是情况一,则\(dp[i]=dp[i-1]\); 如果是情况二,则\(dp[i]=max\{prices[i]-prices[j]+dp[j-2]\}\),其中\(0\leq j < i\)。因为如果第\(j\)天买入,则第\(j-1\)天是没法卖的,所以这里是\(dp[j-2]\)

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() <= 1) return 0;
int dp[5050];
dp[0] = 0;
for (int i = 1; i < prices.size(); i++) {
dp[i] = dp[i-1];
for (int j = i-1; j >= 0; j--) {
if (prices[i] - prices[j] > 0) {
int tmp = 0;
if (j-2 >= 0) tmp = dp[j-2];
dp[i] = max(dp[i],
(prices[i]-prices[j]+tmp));
}
}
}
return dp[prices.size()-1];
}
};

Leetcode 1223. 掷骰子模拟

题目描述

有一个骰子模拟器会每次投掷的时候生成一个 1 到 6 的随机数。 不过我们在使用它时有个约束,就是使得投掷骰子时,连续 掷出数字 i 的次数不能超过 rollMax[i](i 从 1 开始编号)。 现在,给你一个整数数组 rollMax 和一个整数 n,请你来计算掷 n 次骰子可得到的不同点数序列的数量。 假如两个序列中至少存在一个元素不同,就认为这两个序列是不同的。由于答案可能很大,所以请返回 模 10^9 + 7 之后的结果。 示例 1: 输入:n = 2, rollMax = [1,1,2,2,2,3] 输出:34 解释:我们掷 2 次骰子,如果没有约束的话,共有 6 6 = 36 种可能的组合。但是根据 rollMax 数组,数字 1 和 2 最多连续出现一次,所以不会出现序列 (1,1) 和 (2,2)。因此,最终答案是 36-2 = 34。* ## 解题思路 设置一个\(f\)矩阵,\(f[i][j]\)表示以\(i\)结尾长度为\(j\)的串的个数,然后根据规则,迭代这个矩阵。 举着例子,假设第一次摇骰子,可能摇到1、2、3、4、5、6,所以就有\(f[1][1]=f[2][1]=f[3][1]=f[4][1]=f[5][1]=f[6][1]=1\),然后第二次摇骰子,假设上一次摇到的是1,这一次又摇到1,则\(f[1][2]=1\),如果这次摇到2,则\(f[2][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
37
38
39
40
41
42
43
44
class Solution {
public:
int dieSimulator(int n, vector<int>& rollMax) {
long long int mat[7][16] = {0};
long long int tmp[7][16];
for (int i = 1; i <= 6; i++) {
mat[i][1] = 1;
}

n--;
while (n--) {
memset(tmp, 0, sizeof(tmp));

for (int i = 1; i <= 6; i++) {
for (int j = 1; j <= 15; j++) {
for (int k = 1; k <= 6; k++) {

if (i == k) {
if (rollMax[i-1] > j) {
tmp[i][j+1] = (tmp[i][j+1] % 1000000007 + mat[i][j] % 1000000007) % 1000000007;
}
}
else {
tmp[k][1] = (tmp[k][1] % 1000000007 + mat[i][j] % 1000000007) % 1000000007;
}
}
}
}
for (int i = 1; i <= 6; i++) {
for (int j = 1; j <= 15; j++) {
mat[i][j] = tmp[i][j];
}
}
}
long long int ans = 0;
for (int i = 1; i <= 6; i++) {
for (int j = 1; j <= 15; j++) {
ans += (mat[i][j] % 1000000007);
ans %= 1000000007;
}
}
return ans;
}
};

Leetcode 401. 二进制手表

题目描述

二进制手表顶部有 4 个 LED 代表小时(0-11),底部的 6 个 LED 代表分钟(0-59)。 每个 LED 代表一个 0 或 1,最低位在右侧。 例如,上面的二进制手表读取 “3:25”。 给定一个非负整数 n 代表当前 LED 亮着的数量,返回所有可能的时间。 案例: 输入: n = 1 返回: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"] 注意事项: 输出的顺序没有要求。 小时不会以零开头,比如 “01:00” 是不允许的,应为 “1:00”。 分钟必须由两位数组成,可能会以零开头,比如 “10:2” 是无效的,应为 “10:02”。 ## 解题思路 重要的是遍历搜索所有可能的亮灯方案,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
74
class Solution {
public:
int a[10];
int n;
vector<string> ans;

string trans(int k) {
if (k == 0) return "0";
string tmp = "";
while (k != 0) {
tmp += string(1, '0' + (k % 10));
k /= 10;
}

return tmp;
}

void rotate(string &s) {
int len = s.length();
for (int i = 0; i < len / 2; i++) {
char t = s[i];
s[i] = s[len-1-i];
s[len-1-i] = t;
}
}

void dfs(int i, int k) {
if (i == n) {
if (k == 0) {

// for (int j = 0; j < n; j++) {
// cout << a[j] << " ";
// }
// cout << endl;

int hour = 0;
int sec = 0;

for (int j = 0; j < 4; j++) {
hour += (int(pow(2, j)) * a[j]);
}
for (int j = 4; j < n; j++) {
sec += (int(pow(2, j-4)) * a[j]);
}


if (hour < 12 && sec < 60) {

string h = trans(hour);
string s = trans(sec);
if (s.length() == 1) s += "0";

rotate(h);
rotate(s);

string tmp = h + ":" + s;
ans.push_back(tmp);
}
}
return;
}
else {
a[i] = 0;
dfs(i+1, k);
a[i] = 1;
dfs(i+1, k-1);
}
}
vector<string> readBinaryWatch(int num) {
n = 10;
dfs(0, num);
return ans;
}
};

文章作者: Ash's Blogs
文章链接: http://yoursite.com/2019/12/01/leecode%E9%A2%98%E7%9B%AE/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hexo