简化路径
解题思路,看到题目要求,很容易的就想到了利用栈来解决问题,使用栈存储地址路径,然后通过遍历路径获得每一层地址的字符串,然后根据地址字符串判断操作,如果是..
,则当栈不为空的情况出栈,如果是.
或者空则不操作,否则入栈。
AC代码:
class Solution {
public:
string simplifyPath(string path) {
if(path.size() == 0) return "/";
if(path[path.size() -1] != '/') path = path.append("/");
stack<string> name;
auto l = path.begin();
for(auto r = path.begin(); r != path.end(); ++r) {
if((*l == '/' && *r != '/') || (l == r)) continue;
else {
string tmp(l, r);
cout << l - path.begin() << " " << r - path.begin() << " ";
cout << tmp << endl;
if(tmp == "/..") {
if(!name.empty()) name.pop();
} else if(!(tmp == "/" || tmp == "/.")) {
name.push(tmp);
}
l = r;
}
}
string res;
while(!name.empty()) {
res = name.top() + res;
name.pop();
}
return res.size() == 0? "/" : res;
}
};
编辑距离
看到这道题的时候脑子一片空白,然后想到的是先求出最长公共字符串,可惜可惜啊,都想到最长公共字符串了,都和动态规划沾上关系了,我这竟然还没有想到就直接用动态规划解决,毕竟三种操作都放在那里,告诉你如何状态转移了,默哀三秒。
步入正题,这道题确实是有点意思的,对于动态规划来说,说不明显但是在用完之后怎么看怎么明显。首先明确状态为何?
定义状态数组dp[i][j]
表示word1的第i个字符和word2的第j个字符的编辑距离。
初始化:当word1为0的时候,编辑距离就是word2的长度;同理当word2为0的时候,编辑距离就是word1的长度。
状态转移:dp[i][j]
有三种方式可以到达,分别为word1插入,word1删除,word1替换。这里会有奇怪的地方,word1删除,岂不是不能做到,毕竟在走到dp[i][j]
的时候是不知道dp[i+1][j]
的值。但是word1删除等价于word2插入。所以三种操作代表三种转移方案。
状态转移方程:dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
。不过还是有需要注意的地方,就是替换的时候,如果两个字符串的第i-1个字符和第j-1个字符是相等的,那么我是不需要进行操作的。所以word1替换的操作需要根据字符是否相等来决定是否+1操作。
AC代码:
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.size(), m = word2.size();
if(n * m == 0) return n + m;
int dp[n+1][m+1];
//default
memset(dp, 0, sizeof(dp));
for(int i = 0; i < m+1; ++i) dp[0][i] = i;
for(int i = 0; i < n+1; ++i) dp[i][0] = i;
//State transition
for(int i = 1; i < n+1; ++i) {
for(int j = 1; j < m+1; ++j) {
int inert = dp[i-1][j] + 1;
int delet = dp[i][j-1] + 1;
int replace = dp[i-1][j-1];
if(word1[i-1] != word2[j-1]) replace += 1;
dp[i][j] = min(inert, min(delet, replace));
}
}
return dp[n][m];
}
};
矩阵置零
第一反应就是利用两个set分别保存将要被置为0的行和列,然后看到题目要求:
进阶:
一个直接的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案。
一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。
你能想出一个常数空间的解决方案吗?
不错,第一反应就是一个简单的改进方案,囧。
然后苦苦思索常数空间,想到的一个方案就是在找到0之后先不置为0,用一个别的数字先代替,当然第一反应是-1,但是在写代码的时候又考虑到没说数组中不存在-1的数字,随推翻。然后再次思索,随失败。不甘心的参考了解题,神奇。
最后是通过以第一行第一列作为标记,然后单独考虑第一行第一列,最后虽然额外空间是常数空间,但是怎么都觉得这个代码比较臃肿。
AC代码:
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
bool fr = false, fc = false;
int m = matrix.size();
int n = (*matrix.begin()).size();
//判断第一行是否存在0
for(int i = 0; i < n; ++i) {
if(matrix[0][i] == 0) {
fr = true;
break;
}
}
//判断第一列是否存在0
for(int j = 0; j < m; ++j) {
if(matrix[j][0] == 0) {
fc = true;
break;
}
}
//除了第一行第一列,遍历数组,如果存在0,则将此位置对应的首个行和列置为0
for(int i = 1; i < m; ++i) {
for(int j = 1; j < n; ++j) {
if(matrix[i][j] == 0) {
matrix[0][j] = 0;
matrix[i][0] = 0;
}
}
}
//判断该位置的首个行和列是否为0,是则修改此位置为0
for(int i = 1; i < m; ++i) {
for(int j = 1; j < n; ++j) {
if(matrix[0][j] == 0 || matrix[i][0] == 0) {
matrix[i][j] = 0;
}
}
}
//单独考虑的第一行第一列
if(fr) {
for(int i = 0; i < n; ++i) {
matrix[0][i] = 0;
}
}
if(fc) {
for(int j = 0; j < m; ++j) {
matrix[j][0] = 0;
}
}
}
};
搜索二维矩阵
看到排序数组,而且查找值,提高效率。不用思考就是二分查找,矩阵又如何,两次二分查找就可以解决了,先找行,再找列,找到返回true,否则返回false。
AC代码:
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m = matrix.size();
if(m == 0) return false;
int n = (*matrix.begin()).size() - 1;
if(n == -1) return false;
int targetm = 0, targetn = 0;
while(targetm < m) {
int midm = targetm + (m - targetm) / 2;
if(matrix[midm][0] == target) return true;
else if(matrix[midm][0] < target) {
targetm = midm + 1;
}else {
m = midm;
}
}
targetm = targetm == 0 ? 0 : targetm-1;
while(targetn <= n) {
int midn = targetn + (n - targetn) / 2;
if(matrix[targetm][midn] == target) return true;
else if(matrix[targetm][midn] < target) {
targetn = midn + 1;
}else {
n = midn - 1;
}
}
return false;
}
};
PS,二维数组其实也是一维数组,所以这道题二分查找可以更简洁,left = 0;right = m*n-1;mid = left + (right - left) / 2
,对应的值是matrix[mid/n][mid%n]
。
颜色分类
解题思路,这道题不就是排序吗,可以利用快速排序的思想,以1为基准,小于1的放左边,大于1的放右边。这样的话我们可以维护1的左右边界,遍历一次数组即可完成。
AC代码:
class Solution {
public:
void sortColors(vector<int>& nums) {
if(nums.size() < 2) return;
//left记录1的左边界,right记录1的右边界
int left = 0, right = nums.size() - 1;
for(int i = 0; i <= right; ++i) {
if(nums[i] == 0) {
swap(nums[left++], nums[i]);
}
if(nums[i] == 2) {
//i--保证从后面交换过来的数字再次判断是否为0
swap(nums[i--], nums[right--]);
}
}
}
};
最小覆盖子串
解题思路,这道题明显的是利用滑动窗口来解决,但是奈何思考半天不知道如何操作,大概的思路了解到位,但是在写的时候磕磕绊绊,最终还是参考借鉴了一番,继续加油把~
AC代码:
class Solution {
public:
string minWindow(string s, string t) {
if(s.size() * t.size() == 0) return "";
unordered_map<char, int> need, window;
for (auto c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
// 记录最小覆盖子串的起始索引及长度
int start = 0, len = INT_MAX;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
if (need.count(c)) {
window[c]++;
if (window[c] == need[c])
valid++;
}
// 判断左侧窗口是否要收缩
while (valid == need.size()) {
// 在这里更新最小覆盖子串
if (right - left < len) {
start = left;
len = right - left;
}
// d 是将移出窗口的字符
char d = s[left];
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
if (need.count(d)) {
if (window[d] == need[d])
valid--;
window[d]--;
}
}
}
// 返回最小覆盖子串
return len == INT_MAX ?
"" : s.substr(start, len);
}
};
组合
这道题和全排列很像,只不过是规定了数字上限以及数组个数的,所以不难想到,这道题可以利用回溯法来解决。
这里需要注意的是,循环的终止条件。
i <= n - (k - path.size()) + 1;(k - path.size())表示数组中还需要填写的数字个数。例如当n=10,k=5,且此时path.size()=2;那么i能取到的最大值并非是10,而是8。因为数组还需要填写三个数字,所以最大是8。
AC代码:
class Solution {
public:
vector<vector<int>> res;
void dfs(int n, int k, int start, vector<int>& path) {
if(path.size() == k) {
res.push_back(path);
return;
}
for(int i = start; i <= n - (k - path.size()) + 1; ++i) {
path.push_back(i);
dfs(n, k, i + 1, path);
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
if(n <= 0 || k <= 0 || n < k) return res;
vector<int> path;
dfs(n, k, 1, path);
return res;
}
};
子集
解题思路,因为上一道是用的回溯。所以在看到这道题的时候,不难会想到使用多次的回溯就可以解决,所以解题方案是使用回溯法。
AC代码:
class Solution {
public:
vector<vector<int>> res;
void dfs(int size, int start, vector<int>& nums, vector<int>& path) {
if(path.size() == size) {
res.push_back(path);
return;
}
for(int i = start; i < nums.size() - (size - path.size()) + 1; ++i) {
path.push_back(nums[i]);
dfs(size, i+1, nums, path);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
res.push_back({});
vector<int> path;
for(int i = 1; i <= nums.size(); ++i) {
dfs(i, 0, nums, path);
}
return res;
}
};
单词搜索
这道题的思路和之前的n皇后问题类似,需要对某一个位置上下左右进行搜索。所以不难想到需要用回溯的方法解决,最后还需要注意的是,同一个单元格内的字母不允许被重复使用,所以在回溯的时候需要对数组操作标记。
AC代码:
class Solution {
public:
bool dfs(int x, int y, int index, vector<vector<char>>& board, string& word) {
if(index == word.size()) return true;
//cout << x << " " << y << " " << index << " " << word[index] << endl;
char tmp = board[x][y];
board[x][y] = '0';
//上
if(x - 1 >= 0 && board[x-1][y] == word[index])
if(dfs(x-1, y, index+1, board, word))
return true;
//下
if(x + 1 < board.size() && board[x+1][y] == word[index])
if (dfs(x+1, y, index+1, board, word))
return true;
//左
if(y - 1 >= 0 && board[x][y-1] == word[index])
if (dfs(x, y-1, index+1, board, word))
return true;
//右
if(y + 1 < (*board.begin()).size() && board[x][y+1] == word[index])
if (dfs(x, y+1, index+1, board, word))
return true;
board[x][y] = tmp;
return false;
}
bool exist(vector<vector<char>>& board, string word) {
int m =board.size(), n = (*board.begin()).size();
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j) {
if(board[i][j] == word[0]) {
if(dfs(i, j, 1, board, word)) return true;
}
}
}
return false;
}
};
删除排序数组中的重复项 II
解题思路,想到的是双指针,前面的指针搜索每个数字出现的次数,后面的指针重新写数组。
AC代码
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int ope = 0, scan = 0;
int times = 1, pre = -1;
while(scan < nums.size()) {
pre = nums[scan];
++scan;
while(scan < nums.size() && pre == nums[scan]) {
++scan;
++times;
}
// cout << pre << " " << times << " " << scan << endl;
int n = times > 2 ? 2 : times;
while(n > 0) {
nums[ope++] = pre;
n--;
}
// cout << "res: " << ope << endl;
// for(int i = 0; i < ope; ++i) cout << nums[i] << " ";
// cout << endl;
times = 1;
}
return ope;
}
};