这个系列就记录下我做Leetcode的过程,保持点手感。
最近备考,不一定更新了。
题目目录
- Leetcode61. 旋转链表
- Leetcode62. 不同路径
- Leetcode63. 不同路径 II
- Leetcode64. 最小路径和
- Leetcode65. 有效数字
- Leetcode66. 加一
- Leetcode67. 二进制求和
- Leetcode68. 文本左右对齐
- Leetcode69. x 的平方根
- Leetcode70. 爬楼梯
- Leetcode71. 简化路径
- Leetcode72. 编辑距离
- Leetcode73. 矩阵置零
- Leetcode74. 搜索二维矩阵
- Leetcode75. 颜色分类
- Leetcode76. 最小覆盖子串
- Leetcode77. 组合
- Leetcode78. 子集
- Leetcode79. 单词搜索
- Leetcode80. 删除有序数组中的重复项 II
Leetcode61. 旋转链表
思路:
题目相当于让你把头部k个节点链接到尾部。
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(head == nullptr || !k) return head;
int len = 1;
ListNode* tmp = head, *st;
while(tmp->next != nullptr) ++len, tmp = tmp->next;
tmp->next = head;
k %= len;
for(int i = 0;i < len - k;i++) tmp = tmp->next;
st = tmp->next;
tmp->next = nullptr;
return st;
}
};
Leetcode62. 不同路径
思路: 二维dp,当前位置路径数量决定于左侧点路径数量加上上方点路径数量。
代码:
class Solution {
public:
int dp[101][101];
int uniquePaths(int m, int n) {
dp[1][0] = 1;
for(int i = 1;i <= m;i++){
for(int j = 1;j <= n;j++) dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
return dp[m][n];
}
};
Leetcode63. 不同路径 II
思路: 二维dp。当前位置路径数量决定于左侧点路径数量加上上方点路径数量。如果当前位置为石头,则当前位置路径数量为0。
代码:
class Solution {
public:
int dp[101][101] = {0, 1};
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size(), n = obstacleGrid[0].size();
for(int i = 1;i <= m;i++){
for(int j = 1;j <= n;j++) dp[i][j] = (1 - obstacleGrid[i - 1][j - 1]) * (dp[i - 1][j] + dp[i][j - 1]);
}
return dp[m][n];
}
};
Leetcode64. 最小路径和
思路: 二维dp。当前位置路径和取决于左侧和上方最小路径和,我们只要每次比较获得最小值即可。
代码:
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int n = grid.size(), m = grid[0].size();
for(int i = 0;i < n;i++){
for(int j = 0;j < m;j++) grid[i][j] += (!i && !j) ? 0 : min(i ? grid[i - 1][j] : 1e9, j ? grid[i][j - 1] : 1e9);
}
return grid[n - 1][m - 1];
}
};
Leetcode65. 有效数字
思路: 模拟。可以将问题分解为查看一个整数串中数字的长度(如果不为整数串返回-1),查看一个串是否为浮点数。然后对于各种情况处理。
代码:
class Solution {
public:
int integerLength(string s){
if(s.length() <= 0) return 0;
int st = 0;
// 处理符号开头的情况 st为数字串开始下标
if(s[0] == '+' || s[0] == '-') st = 1;
// 处理存在非数字的情况
for(int i = st;i < s.length();i++) if(s[i] < '0' || s[i] > '9') return -1;
return s.length() - st;
}
bool isFloat(string s){
// 小数点指针
int point = -1;
for(int i = 0;i < s.length();i++){
if(s[i] == '.'){
point = i;
break;
}
}
// 若不存在小数点
if(point < 0){
// 判断当前字符串是否为整数
if(integerLength(s) > 0) return true;
else return false;
}
// l为小数点左侧整数串长度 r为小数点右侧整数串长度
int l = integerLength(s.substr(0, point)), r = integerLength(s.substr(point + 1, s.length() - point - 1));
// 左/右侧不为整数串
if(l < 0 || r < 0) return false;
// 左右侧长度都为0(仅有一个.的情况)
if(l == 0 && r == 0) return false;
// 小数点右侧跟着符号的情况
if(point + 1 < s.length() && (s[point + 1] == '-' || s[point + 1] == '+')) return false;
return true;
}
bool isNumber(string s) {
// e的位置
int e = -1;
for(int i = 0;i < s.length();i++){
if(s[i] == 'e' || s[i] == 'E'){
e = i;
break;
}
}
// 如果没有e
if(e < 0){
if(isFloat(s)) return true;
return false;
}
// 如果e左侧不为小数(包括整数)
if(!isFloat(s.substr(0, e))) return false;
// 如果e右侧不为小数
if(integerLength(s.substr(e + 1, s.length() - e - 1)) <= 0) return false;
return true;
}
};
Leetcode66. 加一
思路: 从后往前模拟即可。若当前位置大于9,取模,更改进位。注意判断最终有进位的情况。
代码:
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
digits[digits.size() - 1]++;
int tmp = 0, add = 0;
for(int i = digits.size() - 1;i >= 0;i--) tmp = digits[i] + add, digits[i] = tmp % 10, add = tmp / 10;
if(add) digits.insert(digits.begin(), add);
return digits;
}
};
Leetcode67. 二进制求和
思路: 从低位到高位模拟即可。为了方便,我将两个字符串反转后进行计算。
代码:
class Solution {
public:
string addBinary(string a, string b) {
std::reverse(a.begin(), a.end());
cout << a << '\n';
std::reverse(b.begin(), b.end());
cout << b << '\n';
int add = 0, pl = 0, tmp = 0;
string ans;
for(;pl < a.length() && pl < b.length();pl++){
tmp = add + a[pl] - '0' + b[pl] - '0';
add = tmp >> 1;
ans.append(1, (char)(tmp % 2 + '0'));
}
for(;pl < a.length();pl++){
tmp = add + a[pl] - '0';
add = tmp >> 1;
ans.append(1, (char)(tmp % 2 + '0'));
}
for(;pl < b.length();pl++){
tmp = add + b[pl] - '0';
add = tmp >> 1;
ans.append(1, (char)(tmp % 2 + '0'));
}
while(add) ans.append(1, (char)(add % 2 + '0')), add >>= 1;
std::reverse(ans.begin(), ans.end());
return ans;
}
};
Leetcode68. 文本左右对齐
思路: 按要求模拟级即可。
代码:
class Solution {
public:
vector<string> fullJustify(vector<string>& words, int maxWidth) {
vector<string> ans;
string tmpStr;
int l = 0, r = 0, sum, avg, add, tmp;
while(l < words.size()){
// 计算已统计的单词总长度
sum = words[l].size();
// 当前遍历到的单词下标
r = l + 1;
// sum += 1 + words[r].size()其中的 1 是为了给每个单词之间初始化一个空格
while(r < words.size() && sum + words[r].size() + 1 <= maxWidth) sum += 1 + words[r].size(), ++r;
// 当前单词串
tmpStr = words[l];
tmp = maxWidth - sum + (r - l - 1);
// 最后一行左对齐
if(r >= words.size()){
for(int i = l + 1;i < r;i++) tmpStr += " " + words[i];
tmpStr.append(maxWidth - tmpStr.length(), ' ');
}else if(r - l == 1) tmpStr.append(tmp, ' '); // 如果一行只有一个单词则左对齐
else{
// 平均空格数
avg = tmp / (r - l - 1);
// 前add个需要多加一个空格
add = tmp - avg * (r - l - 1);
for(int i = l + 1;i < r;i++){
// 给单词补上前面的空格
tmpStr.append(avg + (add > 0 ? 1 : 0), ' ');
tmpStr += words[i];
--add;
}
}
ans.push_back(tmpStr);
l = r;
}
return ans;
}
};
Leetcode69. x 的平方根
思路: 二分答案,找到最大符合条件的答案。
代码:
class Solution {
public:
int mySqrt(int x) {
int ans = 0, l = 0, r = 46340, mid;
while(l <= r){
mid = (l + r) >> 1;
if(mid * mid <= x) ans = mid, l = mid + 1;
else r = mid - 1;
}
return ans;
}
};
Leetcode70. 爬楼梯
思路: dp。对于每一阶楼梯而言,对于下一阶以及下下阶的贡献等于自身的方案数。
代码:
class Solution {
public:
int dp[47] = {1};
int climbStairs(int n) {
for(int i = 0;i < n;i++){
dp[i + 2] += dp[i];
dp[i + 1] += dp[i];
}
return dp[n];
}
};
Leetcode71. 简化路径
思路: 模拟。
代码:
class Solution {
public:
string ans[2000];
int cnt = -1;
string simplifyPath(string path) {
int p = 0, len = path.length();
string out = ""; // 存储答案
string tmp;
while(p < len){
// 将单词前/去除
while(p < len && path[p] == '/') ++p;
tmp = "";
// 取出当前单词(暂且将..以及.看做单词取出)
while(p < len && path[p] != '/') tmp = tmp + path[p++];
// 如果当前单词为空或者用.指向当前目录, 则不变
if(tmp.length() == 0 || (tmp.length() == 1 && tmp[0] == '.')) continue;
// 若用..返回上级目录则返回
if(tmp.length() == 2 && tmp[0] == tmp[1] && tmp[0] == '.'){
if(cnt >= 0) --cnt;
}else ans[++cnt] = tmp;
}
// 输出
if(cnt < 0) out = "/";
else for(int i = 0;i <= cnt;i++) out += "/" + ans[i];
return out;
}
};
Leetcode72. 编辑距离
思路: dp。dp[i][j]
代表word1.substr(0,i)
与word2.substr(0,j)
匹配的最小花费。每次遍历到word1[i]
与word2[j]
有四种处理情况:
word1[i] == word2[j]
:那么相当于word1.substr(0,i-1)
与word2.substr(0,j-1)
匹配的最小花费,即dp[i][j]==dp[i-1][j-1]
;- 更改
word1[i]
使得word1[i] == word2[j]
:那么与情况1相同,dp[i][j]==dp[i-1][j-1]
; - 删除
word1[i]
:那么相当于word1.substr(0,i-1)
与word2.substr(0,j)
匹配的最小花费,即dp[i][j]==dp[i-1][j]
; - 插入一个字符作为
word1[i]
:那么相当于使word2删除最后一个字符word2[j]
,变成了word1.substr(0,i)
与word2.substr(0,j-1)
匹配的最小花费,即dp[i][j]==dp[i][j-1]
需要注意下,需要处理纯纯插入或者纯纯删除的情况,即dp[i][0]
与dp[0][j]
(这两种对于每增加一个字符长度,仅多需要处理一次,即dp[i][0]=i
,dp[0][j]=j
)。
class Solution {
public:
int dp[501][501];
int minDistance(string word1, string word2) {
for(int i = 0;i <= word1.length();i++) dp[i][0] = i;
for(int i = 0;i <= word2.length();i++) dp[0][i] = i;
for(int i = 1;i <= word1.length();i++){
for(int j = 1;j <= word2.length();j++){
if(word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
}
}
return dp[word1.length()][word2.length()];
}
};
Leetcode73. 矩阵置零
思路: 使用了两个变量存储第一行中每列(col)第一列中每行(row)是否存在0。先将第一行第一列处理完,然后处理matrix[1:-1, 1:-1]
中每行每列是否存在0,如果存在,直接将对应行第一列、列第一行的数置为0(作为标记存在)。然后如果第一列中某一行为0,则置所有当前行的数字为0;如果第一行中某一列为0,则置所有当前列的数字为0。然后用col、row特殊处理下第一行第一列即可。
代码:
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
// 第一行中每列(col)第一列中每行(row)是否存在0
bool row = false, col = false;
for(int i = 0;i < matrix.size() && !row;i++) if(!matrix[i][0]) row = true;
for(int i = 0;i < matrix[0].size() && !col;i++) if(!matrix[0][i]) col = true;
// 处理matrix[1:-1, 1:-1]中每行每列是否存在0
for(int i = 1;i < matrix.size();i++){
for(int j = 1;j < matrix[0].size();j++) if(!matrix[i][j]) matrix[i][0] = matrix[0][j] = 0;
}
for(int i = 1;i < matrix.size();i++){
if(!matrix[i][0]) for(int j = 1;j < matrix[0].size();j++) matrix[i][j] = 0;
}
for(int i = 1;i < matrix[0].size();i++){
if(!matrix[0][i]) for(int j = 1;j < matrix.size();j++) matrix[j][i] = 0;
}
// 用col/row特殊处理下第一行第一列
if(row) for(int i = 0;i < matrix.size();i++) matrix[i][0] = 0;
if(col) for(int i = 0;i < matrix[0].size();i++) matrix[0][i] = 0;
}
};
Leetcode74. 搜索二维矩阵
思路: 二分。由于矩阵每行有序,每列有序,并且下一行值必定大于上一行,所以先根据行首值二分,获得target可能行号ans,然后在行ans中查看是否存在target。
代码:
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
// ans用于存放可能得行号
int l = 0, r = matrix.size() - 1, mid, ans = 0;
// 二分获取行号
while(l <= r){
mid = (l + r) >> 1;
if(matrix[mid][0] <= target) ans = mid, l = mid + 1;
else r = mid - 1;
}
// 二分判断行中是否有target
l = 0, r = matrix[ans].size() - 1;
while(l <= r){
mid = (l + r) >> 1;
if(matrix[ans][mid] == target) return true;
else if(matrix[ans][mid] < target) l = mid + 1;
else r = mid - 1;
}
return false;
}
};
Leetcode75. 颜色分类
思路: 要求仅使用常数空间一趟扫描,那我们可以使用双指针。指针l指向已经排好的0的最后一个位置,指针r指向已经排好的2的第一个位置。然后遍历,发现0的时候添加到左边,发现2添加到右边。
代码:
class Solution {
public:
void sortColors(vector<int>& nums) {
int l = -1,r = nums.size();
for(int i = 0;i < r;i++){
// 如果是0直接放到前面, 由于前面必为排好的0, 所以不用考虑移动后当前位置值为2的情况以及需要移动左指针的情况
if(nums[i] == 0) swap(nums[++l], nums[i]);
// 处理为2的情况
if(nums[i] == 2){
// 由于后面的没处理过, 所以需要先处理连续2的情况
while(r > i + 1 && nums[r - 1] == 2) --r;
swap(nums[--r], nums[i]);
// 如果换过来啦一个0, 那么当前位置还要和前面换
if(nums[i] == 0) swap(nums[++l], nums[i]);
}
}
}
};
Leetcode76. 最小覆盖子串
思路: 滑动窗口。预先处理出来t
字符串各类字母的个数,存储在timesT
中,由于
A
A
AAscII编码为
65
65
65,所以我们减去
65
65
65作为基值(可以略微减小点空间)。在s
上维护一个窗口,用timesS
记录窗口中各个字符的数量(同样是减去
65
65
65),遍历s
的时候将当前遍历到的字符加入窗口中进行统计(遍历到的位置i
作为窗口右端点),此时更新窗口左端点l
,如果左侧端点的字符在窗口中出现次数大于在t
字符串中出现的次数,那么将其删除。将窗口缩小后判断当前窗口是否满足所有字符串条件,满足就对最小长度minLength
以及最小长度窗口左位置minL
进行更新。
代码:
class Solution {
public:
// 最小窗口长度minLength 最小长度窗口左位置minL
int minLength = 1e6, minL = -1;
// 窗口中各类字母的个数timesS t中各类字母的个数timesT
int timesS[60], timesT[60];
string minWindow(string s, string t) {
// 记得初始化
memset(timesS, 0, sizeof (timesS));
memset(timesT, 0, sizeof (timesT));
// 当前左窗口位置l f用于判断当前窗口内字母是否符合t要求
int l = 0, f;
// 统计t字符情况
for(int i = 0;i < t.length();i++) timesT[t[i] - 65]++;
for(int i = 0;i < s.length();i++){
// 扩展维护窗口
timesS[s[i] - 65]++;
// 缩减左窗口. 如果左侧端点的字符在窗口中出现次数大于在t字符串中出现的次数,那么将其删除
while(l <= i && timesS[s[l] - 65] > timesT[s[l] - 65]) timesS[s[l] - 65]--, l++;
f = 1;
for(int j = 0;j < 60 && f;j++){
if(timesT[j] > timesS[j]) f = 0;
}
// 符合标准加入答案
if(f && i - l + 1 < minLength) minLength = i - l + 1, minL = l;
}
if(minL == -1) return "";
return s.substr(minL, minLength);
}
};
Leetcode77. 组合
思路: 递归。(我还以为是所有排列组合,交上去才发现是无逆序对形式的组合)。直接看代码吧。
代码:
class Solution {
public:
// 存储当前排列
vector<int> tmpV;
// 存储答案
vector<vector<int>> ans;
// 递归获得当前排列下一个数的所有可能组合
// n为能取的最大的数 k为当前排列还需要添加几个数 st为当前位置的数字的最小值
void nextVector(int n, int k, int st){
// i为当前取值
for(int i = st;i <= n;i++){
tmpV.push_back(i);
// 如果存入的是最后一个数字, 那么加入答案序列
if(k == 1) ans.push_back(tmpV);
// 如果还能填入数字, 那么继续递归填入数字(其实这一步可以剪枝的, 判断剩下的数字是否足以填满k-1个格子, 但我懒得写了:))
else if(st < n) nextVector(n, k - 1, i + 1);
// 回溯
tmpV.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
tmpV.clear();
ans.clear();
nextVector(n, k, 1);
return ans;
}
};
Leetcode78. 子集
思路: 递归。和上一题其实差不多。
代码:
class Solution {
public:
// 用于存储传入的nums变量
vector<int> first;
// 当前的序列
vector<int> tmpV;
// 答案
vector<vector<int>> ans;
// 处理传入序列中从下标为st开始的值
void addVector(int st){
// 从st开始的每个值都可添加到当前位置
for(int i = st;i < first.size();i++){
tmpV.push_back(first[i]);
ans.push_back(tmpV);
// 如果当前遍历到的不是最后一个下标, 那么可以继续递归
if(i < first.size() - 1) addVector(i + 1);
// 回溯
tmpV.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
// 记得初始化
first = nums;
tmpV.clear();
ans.clear();
ans.emplace_back();
addVector(0);
return ans;
}
};
Leetcode79. 单词搜索
思路: dfs+回溯嗯跑。用dfs判断当前点位周围是否存在可以满足board[i][j]==word[p]
的位置,如果有,则到那个位置跑下一个字母的搜索。
代码:
class Solution {
public:
// 设置了个全局变量免得一直传参
vector<vector<char>> initBoard;
// 设置了个全局变量免得一直传参
string initWord;
// 判断位置(i,j)是否被访问过, vis[i][j]==0未访问, vis[i][j]==1已访问
int vis[8][8];
// 方向, dir[i][0]是当前位置x变化量, dir[i][1]是当前位置y变化量
int dir[4][2] = {1, 0, -1, 0, 0, 1, 0, -1};
// dfs x为当前x坐标, y为当前y坐标, p为当前搜索word中下标为p的字符
int find(int x, int y, int p){
// 满足条件就返回
if(p >= initWord.length()) return 1;
// 四个方向去寻找
for(int i = 0;i < 4;i++){
// 下一个单元格位置(xx,yy)
int xx = x + dir[i][0], yy = y + dir[i][1];
// 判断是否越界
if(xx < 0 || xx >= initBoard.size() || yy < 0 || yy >= initBoard[0].size()) continue;
// 判断是否被访问过 是否为word[p]
if(!vis[xx][yy] && initBoard[xx][yy] == initWord[p]){
// 标记已访问
vis[xx][yy] = 1;
// 找到了直接返回
if(find(xx, yy, p + 1)) return 1;
// 回溯
vis[xx][yy] = 0;
}
}
return 0;
}
bool exist(vector<vector<char>>& board, string word) {
// 初始化
initBoard = board;
initWord = word;
memset(vis, 0, sizeof(vis));
// 将每个格子作为初始点进行寻找
for(int i = 0;i < board.size();i++){
for(int j = 0;j < board[0].size();j++){
if(board[i][j] == word[0]){
// 标记已访问
vis[i][j] = 1;
// 找到了直接返回
if(find(i, j, 1)) return true;
// 回溯
vis[i][j] = 0;
}
}
}
return false;
}
};
Leetcode80. 删除有序数组中的重复项 II
思路: 原数组有序(单调不减),相当于给你这么的一个数组,将其按序添加到栈中,使栈顶三个数字相同的时候将顶部数字出栈。
代码:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
// 当前栈的大小为ed+1
int ed = -1;
// 将数组中的数字按序添加到栈中
for(int i = 0;i < nums.size();i++){
// 入栈
nums[++ed] = nums[i];
// 如果顶部三个相同则出栈
if(ed > 1 && nums[ed - 2] == nums[ed]) --ed;
}
return ed + 1;
}
};