不定期更新的leetcode刷题记录
2020年7月23日更新,博主现在在按照标签刷题了QAQ
题目链接 题目难度:困难
虽然这是一个动态规划的题目,但是我首先想到的是用堆栈的方法来匹配左括号和右括号。例如
()(()
1 2 3 4
题目链接 题目难度:中等
由于题目中的路线已经确定(必须是从左上角走到右下角),又因为格子中的数是非负数,即不可能一个格子走两次,故此,构造一个dp,记录走到当前位置的最小的和即可,其中dp[i][j]的值可以由它左边和上边的格子的bp值来确定:
d p [ i ] [ j ] = { m i n ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + g r i d [ i ] [ j ] i ≠ 0 , j ≠ 0 d p [ i ] [ j − 1 ] + g r i d [ i ] [ j ] i = 0 , j ≠ 0 d p [ i − 1 ] [ j ] + g r i d [ i ] [ j ] j = 0 , i ≠ 0 g r i d [ 0 ] [ 0 ] j = 0 , i = 0 dp[i][j]=\left\{ \begin{array}{rcl} min(dp[i-1][j],dp[i][j-1])+grid[i][j] && {i\not=0 , j\not=0}\\ dp[i][j-1]+grid[i][j] &&{i=0 ,j\not=0}\\ dp[i-1][j]+grid[i][j] && {j=0 , i\not=0}\\ grid[0][0] && {j=0 , i=0} \end{array} \right. dp[i][j]=⎩⎪⎪⎨⎪⎪⎧min(dp[i−1][j],dp[i][j−1])+grid[i][j]dp[i][j−1]+grid[i][j]dp[i−1][j]+grid[i][j]grid[0][0]i=0,j=0i=0,j=0j=0,i=0j=0,i=0
代码部分
// 返回值为最终结果
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
int dp[m][n];
for(int i = 0; i < m; i++){
for(int j = 0 ; j < n ; j++){
if(i == 0 && j == 0){
dp[0][0] = grid[0][0];
continue;
}
if(i == 0){
dp[i][j] = dp[i][j - 1] + grid[i][j];
continue;
}
if(j == 0){
dp[i][j] = dp[i - 1][j] + grid[i][j];
continue;
}
dp[i][j] = ((dp[i - 1][j] < dp[i][j - 1])? dp[i - 1][j]:dp[i][j - 1]) + grid[i][j];
}
}
return dp[m - 1][n - 1] ;
}
题目链接 题目难度:简单
(我并没有很好的判断这个问题可以归属于哪一类,说它是动态规划吧,私以为也不是不可以;在这里给出官方答案和我的答案的差异)
我的思路:这个题乍一眼看就可以是一个通过递归来完成的内容,只要判断了有一种获胜的方法就可以返回true,故此只需要穷举当前可以选择的数字,再向前递归,求出下一种情况能否成功即可。
注意点:一定要注意当前是谁在取数字,取数的人不同,可能导致的结果千差万别。
优点:容易理解且代码执行用时和内存消耗都不大;
缺点:代码相对于官方代码稍微复杂一些
class Solution {
public:
bool divisor(int N , bool alice){
if(N == 1){
if(!alice) return true;
else return false;
}
if(N == 2){
if(alice) return true;
else return false;
}
bool flag = alice ? true : false;
for(int i = 1; i < sqrt(N); i++){
if( N % i == 0 ){
return divisor( N - i , !flag);
}
}
return flag;
}
bool divisorGame(int N) {
return divisor( N , 1 );
}
};
官方题解:
博弈类的问题常常让我们摸不着头脑。当我们没有解题思路的时候,不妨试着写几项试试:
N = 1 的时候,区间 (0, 1) 中没有整数是 n 的因数,所以此时 Alice 败。
N = 2 的时候,Alice 只能拿 1,N 变成 1,Bob 无法继续操作,故 Alice 胜。
N = 3 的时候,Alice 只能拿 11,N 变成 2,根据 N = 2 的结论,我们知道此时 Bob 会获胜,Alice 败。
N = 4 的时候,Alice 能拿 1 或 2,如果 Alice 拿 1,根据 N = 3 的结论,Bob 会失败,Alice 会获胜。
N = 5 的时候,Alice 只能拿 1,根据 N = 4 的结论,Alice 会失败。
…
写到这里,也许你有了一些猜想。没关系,请大胆地猜想,在这种情况下大胆地猜想是 AC 的第一步。也许你会发现这样一个现象:N 为奇数的时候 Alice(先手)必败,N 为偶数的时候 Alice 必胜。 这个猜想是否正确呢?下面我们来想办法证明它。
证明
N = 1 和 N=2 时结论成立。
N > 2 时,假设 N≤k 时该结论成立,则 N = k + 1 时:
如果 k 为偶数,则 k + 1 为奇数,x 是 k+1 的因数,只可能是奇数,而奇数减去奇数等于偶数,且 k+1−x≤k,故轮到 Bob 的时候都是偶数。而根据我们的猜想假设 N≤k 的时候偶数的时候先手必胜,故此时无论 Alice 拿走什么,Bob 都会处于必胜态,所以 Alice 处于必败态。
如果 k 为奇数,则 k + 1 为偶数,x 可以是奇数也可以是偶数,若 Alice 减去一个奇数,那么 k + 1 - x 是一个小于等于 k 的奇数,此时 Bob 占有它,处于必败态,则 Alice 处于必胜态。
综上所述,这个猜想是正确的。
class Solution {
public:
bool divisorGame(int N) {
return N % 2 == 0;
}
};
题目链接 题目难度:简单
我在这一个题目遇到的最大的问题是vector数组的使用(一个特别简单易懂的题目我居然能做错三次5555),对于各种容器我还是需要进行学习。给自己挖个坑总结下各种容器的使用方法吧,链接在这里【此处需要有一个链接】。
代码部分
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
bool flag = true;
if(digits[digits.size() - 1] != 9){
digits[digits.size() - 1] += 1;
return digits;
}
for(int i = digits.size()-1;i >= 0; i--){
if( i == 0 && digits[i] == 9 && flag){
digits[0] = 1;
digits.push_back(0);
return digits;
}
if(digits[i] == 9 && flag){
digits[i] = 0;
}
else{
digits[i] += 1;
return digits;
}
}
return digits;
}
};
题目链接 题目难度:简单
一般处理这种遍历数组/字符串数组的题目时,我往往都是用从array.size()-1开始逐渐下标减一的方法来解决的,不得不说,reverse这个函数是我之前没有接触到的好工具,算是给应用者提供了一个很好的借口来简洁的处理需要从低位开始计算的问题(我并不清楚它的效率如何,但是就是一个倒转数组元素位置的函数,应该也不会消耗太多的时间和内存),注意此函数没有返回值。
本题目就是简单的相加,看看是否有进位即可,最后再将计算得到的结果反转即可。
代码部分
class Solution {
public:
string addBinary(string a, string b) {
string ans;
reverse(a.begin(), a.end());
reverse(b.begin(), b.end());
int n = max(a.size(), b.size()), carry = 0;
//此处carry表示进位
for (size_t i = 0; i < n; ++i) {
carry += i < a.size() ? (a.at(i) == '1') : 0;
carry += i < b.size() ? (b.at(i) == '1') : 0;
ans.push_back((carry % 2) ? '1' : '0');
carry /= 2;
}
if (carry) {
ans.push_back('1');
}
reverse(ans.begin(), ans.end());
return ans;
}
};
题目链接 题目难度:中等
题目要求把一棵树转换成一个链表,其实首先可以根据题目已经给出的例子看出,转换的过程可以看作是一个先序遍历的过程,这样一解释就会让整个问题变得简单很多了,(其实我本身就不是算法不好,我是语言不好我哭),这个题目得难度可能还是在如何去写代码上。
首先构建一个可以代表链表的树节点集合l,其次是一个先序遍历的函数,像加入队列一样,谁先被访问到,谁先进入队列,和先序遍历的原理不谋而合,当先序遍历函数运行结束后,就可以根据实际情况进行对数据结构的设置了。
class Solution {
public:
void flatten(TreeNode* root) {
vector<TreeNode*> l;
preorderTraversal(root, l);
int n = l.size();
for (int i = 1; i < n; i++) {
TreeNode *prev = l.at(i - 1), *curr = l.at(i);
prev->left = nullptr;
prev->right = curr;
}
}
void preorderTraversal(TreeNode* root, vector<TreeNode*> &l) {
if (root != NULL) {
l.push_back(root);
preorderTraversal(root->left, l);
preorderTraversal(root->right, l);
}
}
};
题目链接 题目难度:困难
(此处为转载的内容,详细请见leetcode题解)
这个题目乍一看到,并没有什么思路,在题解里面发现了用编译原理的内容来解释题目,深受感触,现把具体的内容搬运过来。
本题可以采用《编译原理》里面的确定的有限状态机(DFA)解决。构造一个DFA并实现,构造方法可以先写正则表达式,然后转为 DFA,也可以直接写,我就是直接写的,虽然大概率不会是最简结构(具体请参考《编译器原理》图灵出版社),不过不影响解题。DFA 作为确定的有限状态机,比 NFA 更加实用,因为对于每一个状态接收的下一个字符,DFA 能确定唯一一条转换路径,所以使用简单的表驱动的一些方法就可以实现,并且只需要读一遍输入流,比起 NFA 需要回读在速度上会有所提升。
构建出来的状态机如封面图片所示(红色为 终止状态,蓝色为 中间状态)。根据《编译原理》的解释,DFA 从状态 0 接受串 s 作为输入。当s耗尽的时候如果当前状态处于中间状态,则拒绝;如果到达终止状态,则接受。
然后,根据 DFA 列出如下的状态跳转表,之后我们就可以采用 表驱动法 进行编程实现了。需要注意的是,这里面多了一个状态 8,是用于处理串后面的若干个多余空格的。所以,所有的终止态都要跟上一个状态 8。其中,有一些状态标识为-1,是表示遇到了一些意外的字符,可以直接停止后续的计算。
状态图
状态跳转表
blank | +/- | 0-9 | . | e | other |
---|---|---|---|---|---|
0 | 0 | 1 | 6 | 2 | -1 |
1 | -1 | -1 | 6 | 2 | -1 |
2 | -1 | -1 | 3 | -1 | -1 |
3 | 8 | -1 | 3 | -1 | -1 |
4 | -1 | 7 | 5 | -1 | 4 |
5 | 8 | -1 | 5 | -1 | -1 |
6 | 8 | -1 | 6 | 3 | 4 |
7 | -1 | -1 | 5 | -1 | -1 |
8 | 8 | -1 | -1 | -1 | -1 |
代码部分
class Solution {
public:
bool isNumber(string s) {
if(s.empty()) return false;
int n = s.size();
int state = 0;
vector<bool> finals({0, 0, 0, 1, 0, 1, 1, 0, 1}); // 合法的终止状态
vector<vector<int> > transfer({
{0, 1, 6, 2, -1, -1},
{-1, -1, 6, 2, -1, -1},
{-1, -1, 3, -1, -1, -1},
{8, -1, 3, -1, 4, -1},
{-1, 7, 5, -1, -1, -1},
{8, -1, 5, -1, -1, -1},
{8, -1, 6, 3, 4, -1},
{-1, -1, 5, -1, -1, -1},
{8, -1, -1, -1, -1, -1},
});
for(int i = 0; i < n; ++i){
state = transfer[state][_make(s[i])];
if(state < 0) return false;
}
return finals[state];
}
private:
int _make(const char& c){
switch(c){
case ' ': return 0;
case '+': return 1;
case '-': return 1;
case '.': return 3;
case 'e': return 4;
default: return _number(c);
}
}
int _number(const char& c){
if(c >= '0' && c <= '9')
return 2;
else
return 5;
}
};
未完待续