4. 寻找两个正序数组的中位数
思路
/* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
* 这里的 "/" 表示整除
* nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
* nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
* 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
* 这样 pivot 本身最大也只能是第 k-1 小的元素
* 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
* 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
* 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
*/
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
代码
class Solution {
public:
int getKthNum(vector<int>& nums1, vector<int>& nums2, int k){
int len1 = nums1.size(), len2 = nums2.size();
int index1 = 0, index2 = 0;
while(true){
if(index1 == len1){
return nums2[index2 + k - 1];
}
if(index2 == len2){
return nums1[index1 + k -1];
}
if(k == 1){
return min(nums1[index1], nums2[index2]);
}
int pos1 = min(index1 + k/2 - 1, len1 - 1);
int pos2 = min(index2 + k/2 - 1, len2 - 1);
if(nums1[pos1] <= nums2[pos2]){
k -= pos1 - index1 + 1;
index1 = pos1 + 1;
}
else{
k -= pos2 - index2 + 1;
index2 = pos2 + 1;
}
}
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int total_len = nums1.size() + nums2.size();
if(total_len % 2 == 1){
return getKthNum(nums1, nums2, (total_len + 1) / 2);
}
else{
return (getKthNum(nums1, nums2, total_len / 2) + getKthNum(nums1, nums2, total_len / 2 + 1)) / 2.0;
}
}
};
10. 正则表达式匹配
10.1 思路
1、动态规划:
dp[i][j] 表示s[0 - i]在p[0 - j]中是否匹配。有两种情况:
(1)如果p[j] != '.',可以把' . '当作一个特殊的字母,则有:
如果s[i - 1] == p[j - 1],则dp[i][j] = dp[ i - 1][j - 1];否则为false。
(2)如果p[j] == '*',则有两种情况:
*前的元素重复使用,则dp[i][j] = dp[i - 1][j],通俗理解为,是否匹配取决于s[0, i -1]和p[0, j]的匹配关系
*前的元素被舍弃,则dp[i][j] = dp[i][j - 2],通俗理解为,是否匹配取决于s[0, i]和p[0, j - 2]的匹配关系
当s[i] == p[j - 1]时,则*前元素可舍弃,可不舍弃,两者取或
当s[i] != p[j - 1]时,则*前元素必须舍弃
class Solution {
public:
bool isMatch(string s, string p) {
s = " " + s;
p = " " + p;//一定要保证第一个数匹配成功
vector<vector<bool>> dp(s.size() + 1, vector<bool>(p.size() + 1, false));
dp[0][0] = true;
for(int i = 1; i <= s.size(); i++){
for(int j = 1; j <= p.size(); j++){
if(p[j - 1] != '*'){
if(s[i - 1] == p[j - 1] || p[j - 1] == '.'){
dp[i][j] = dp[i - 1][j - 1];
}
else{
dp[i][j] = false;
}
}
else{
if(s[i - 1] == p[j - 2] || p[j - 2] == '.'){
dp[i][j] = dp[i - 1][j] || dp[i][j - 2] || dp[i][j - 1];
}
else{
dp[i][j] = dp[i][j - 2];
}
}
cout<<dp[i][j]<<" ";
}
cout<<endl;
}
return dp[s.size()][p.size()];
}
};
10.2 代码
剑指offer 矩阵中的路径
思路
回溯+剪枝(遇到边界直接返回)
代码
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
rows = board.size();
cols = board[0].size();
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
if(dfs(board, word, i, j, 0)) return true;
}
}
return false;
}
private:
int rows, cols;
bool dfs(vector<vector<char>>& board, string word, int i, int j, int k) {
if(i >= rows || i < 0 || j >= cols || j < 0 || board[i][j] != word[k]) return false;
if(k == word.size() - 1) return true;
board[i][j] = '\0'; //做标记,表示已经使用,回溯
bool res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) ||
dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
board[i][j] = word[k]; //还原
return res;
}
};
括号生成
思路
回溯:因为一定是现有左括号,再有右括号,所以如代码所示
代码
class Solution {
void backtrack(vector<string>& ans, string& cur, int open, int close, int n) {
if (cur.size() == n * 2) { //数量够就成功
ans.push_back(cur);
return;
}
if (open < n) { //左括号是小于括号对数
cur.push_back('(');
backtrack(ans, cur, open + 1, close, n);
cur.pop_back();
}
if (close < open) { //右括号必须小于左括号,省去了判断是否合理
cur.push_back(')');
backtrack(ans, cur, open, close + 1, n);
cur.pop_back();
}
}
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
string current;
backtrack(result, current, 0, 0, n);
return result;
}
};
最大矩形
思路
和单调栈中的求最大矩形的思路基本类似:只不过每一层,类似一个最大矩形,逐层求出最大矩形即可。
代码
class Solution {
public:
int maximalRectangle(vector<vector<char>>& matrix) {
vector<int> height(matrix[0].size() + 2, 0); //左右两边设置最低边界
stack<int> stack;
int maxArea = 0;
for(int i = 0; i < matrix.size(); i++){ //逐层找出最大矩形
for(int j = 0; j < matrix[0].size(); j++){
if(matrix[i][j] == '0'){
height[j + 1] = 0;
}
else{
height[j + 1] += 1;
}
}
stack.push(0);
for(int j = 1; j < height.size(); j++){
if(height[j] > height[stack.top()]){
stack.push(j);
}
else if(height[j] == height[stack.top()]){
stack.pop(); //其实替不替换都能成功
stack.push(j);
}
else{
while(!stack.empty() && height[j] < height[stack.top()]){
int mid = stack.top();
stack.pop();
int left = stack.top();
maxArea = max(height[mid] * (j - left - 1), maxArea);
cout << maxArea <<endl;
}
stack.push(j);
}
}
}
return maxArea;
}
};
二叉树中的最大路径和
思路
树状dp,类似于求数组中的最大数组子集和,dp[i]表示以i结尾的数组的子集的最大和,dp[i] = max(dp[i - 1], 0) + val;
所以本题后序遍历,拿到节点的左右子树的dp,分别表示为dp[L]和dp[R],且其小于0时直接取0。并记录最大值。
代码
class Solution {
public:
int result = INT_MIN;
int postTravel(TreeNode* root){
if(root == nullptr){
return 0;
}
int left = postTravel(root->left);
int right = postTravel(root->right);
int dpL = max(left, 0);
int dpR = max(right, 0);
result = max(result, dpL + dpR + root->val);
return max(dpL, dpR) + root->val;
}
int maxPathSum(TreeNode* root) {
postTravel(root);
return result;
}
};
岛屿数量
思路
岛屿类问题,有限使用dfs,深度优先遍历,不断扩充找到连续的岛屿,每一次dfs,岛屿数量+1
代码
class Solution {
public:
void dfs(vector<vector<char>>& grid, int i, int j){
if(i >= grid.size() || j >= grid[0].size() || i < 0 || j < 0){
return;
}
if(grid[i][j] == '0'){
return;
}
grid[i][j] = '0';
dfs(grid, i + 1, j);
dfs(grid, i - 1, j);
dfs(grid, i, j + 1);
dfs(grid, i, j - 1);
}
int numIslands(vector<vector<char>>& grid) {
int num_island = 0;
for(int i = 0; i < grid.size(); i++){
for(int j = 0; j < grid[0].size(); j++){
if(grid[i][j] == '1'){
dfs(grid, i, j);
num_island++;
}
}
}
return num_island;
}
};
课程表
思路
这是一道经典的拓扑排序的问题,我们使用深度有限遍历来实现拓扑排序。
先统计所有节点的入度,对于入度为0的节点就可以分离出来,然后把这个节点指向的节点的入度减一。
一直做改操作,直到所有的节点都被分离出来。
如果最后不存在入度为0的节点,那就说明有环,不存在拓扑排序,也就是很多题目的无解的情况。
代码
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
if(prerequisites.size() == 0){
return true;
}
unordered_map<int, int> indegree;
unordered_map<int, int> x; //判断初始化入度为0的点不重复
queue<int> indegree0;
//初始化入度表
for(int i = 0; i < prerequisites.size(); i++){
indegree[prerequisites[i][0]]++;
}
//初始化入度为0的节点
for(int i = 0; i < prerequisites.size(); i++){
if(indegree.count(prerequisites[i][1]) == 0){
if(x.count(prerequisites[i][1]) == 0){
indegree0.push(prerequisites[i][1]);
x[prerequisites[i][1]]++;
}
}
}
while(!indegree0.empty()){
int course = indegree0.front();
indegree0.pop();
for(int i = 0; i < prerequisites.size(); i++){
if(course == prerequisites[i][1] && indegree.count(prerequisites[i][0]) != 0){
indegree[prerequisites[i][0]]--;
if(indegree[prerequisites[i][0]] == 0){
indegree0.push(prerequisites[i][0]);
indegree.erase(prerequisites[i][0]);
}
}
}
numCourses--;
if(numCourses == 0){
return true;
}
}
if(numCourses > 0 && indegree.size() > 0){
return false;
}
return true;
}
};
最大正方形
思路
1、可以使用单调栈,和最大矩形的做法一样,也可以使用动态规划
2、动态规划:dp[i][j]表示以ij为右下角的正方形的最大边长。
应该为其左边,上边,和左上三个dp的最小值+1。
代码
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
int maxside = 0;
vector<vector<int>> dp(m, vector<int>(n, 0));
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(matrix[i][j] == '1'){
if(i == 0 || j == 0){
dp[i][j] = 1;
}
else{
dp[i][j] = min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}) + 1;
}
maxside = max(maxside, dp[i][j]);
}
}
}
return maxside * maxside;
}
};
除自身以外数组的乘积
思路
不能使用除法:则我们需要将每一个元素的左边累乘和右边累乘相乘即可。
即遍历数组两遍,求出元素的左右累乘的乘积。
代码
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> result(n, 1);
int left = 1, right = 1;
for(int i = 0; i < n; i++){
result[i] *= left ; //左边的累积
left *= nums[i];
result[n - i - 1] *= right; //右边的累积
right *= nums[n - i - 1];
}
return result;
}
};
寻找重复数
思路
本题和找到环的入口一题一样,但是难以想到。将数组的值作为下一个的索引
代码
class Solution {
public:
int findDuplicate(vector<int>& nums) {
//和环形链表题类似,以下标和值作为索引
int low = 0;
int fast = 0;
low = nums[low];
fast = nums[nums[fast]];
while(low != fast){
low = nums[low];
fast = nums[nums[fast]];
}
fast = 0;
while(low != fast){
low = nums[low];
fast = nums[fast];
}
return low;
}
};
二叉树序列化
思路
很多方法,但是优雅的字符串流
先用先序遍历将节点存起来,如果是null就存”#“
使用字节流分割
代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if( root==NULL ) return "#";
//先序遍历,这种遍历很难写的啦
return to_string(root->val) + ' ' + serialize(root->left) + ' ' + serialize(root->right);
}
TreeNode *dfs( istringstream &s ){
string tmp;
//按空格分割
s>>tmp;
if( tmp=="#" ) return nullptr;
TreeNode* node = new TreeNode(stoi(tmp));
node->left = dfs(s);//因为流会读完前一部分,所以后一部分和前一部分的s不同
node->right= dfs(s);
return node;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
istringstream ss(data); //创建字节流
return dfs(ss);
}
};
// Your Codec object will be instantiated and called as such:
// Codec ser, deser;
// TreeNode* ans = deser.deserialize(ser.serialize(root));
戳气球
思路
此题乍一看是01背包问题,其实没那么简单
具体思路见力扣题解
要特别注意动态规划的遍历顺序,因为dp[i][j] = dp[i][k] + dp[k][j] + val,所以需要从每一个小区间慢慢构建为大区间,也即自底向上。
代码
class Solution {
public:
int maxCoins(vector<int>& nums) {
//dp[i][j]表示在开区间(i, j)中的戳破气球的最大值
int n = nums.size();
vector<vector<int>> dp(n + 2, vector<int>(n + 2, 0));
vector<int> val(n + 2, 1);
for(int i = 1; i < n + 1; i++){
val[i] = nums[i - 1];
}
//从最小的区间开始,即区间内只存在最后一个气球,戳破获得的最大价值
for(int i = n - 1; i >= 0; i--){
for(int j = i + 2; j < n + 2; j++){
for(int k = i + 1; k < j; k++){
int sum = val[i] * val[k] *val[j];
sum += dp[i][k] + dp[k][j]; //之后进行分治,大区间由小区间构成
dp[i][j] = max(dp[i][j], sum);
}
}
}
return dp[0][n + 1];
}
};
和为K的子数组
思路
因为存在负数,所以使用双指针是行不通的,应使用前缀和的思路
代码
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
//不能使用双指针,因为存在负数
//需要使用前缀和,遍历得到每一个索引的前缀和,后一个前缀和 - 前一个前缀和 = k则找到
unordered_map<int, int> map;
int sum = 0;
int result = 0;
map[0] = 1; //前缀和为0的节点数为1
for(int num : nums){
sum += num; //该节点的前缀和
result += map[sum - k]; //如果当前前缀和 - k 为之前的前缀和 且存在,则次数可相加
map[sum]++; //更新现在的前缀和
}
return result;
}
};
任务调度器
思路
代码
class Solution {
public:
int leastInterval(vector<char>& tasks, int n) {
int len = tasks.size();
vector<int> task_num(26);
for(char ch : tasks){
task_num[ch - 'A']++; //每个任务的数量
}
sort(task_num.rbegin(), task_num.rend()); //任务数量多的排在前面
int cnt = 1;
while(cnt < task_num.size() && task_num[cnt] == task_num[0]){
cnt++; //求出最后一个桶有多少个任务
}
//要么任务之间没有间隙,则执行时间为任务长度,要么有间隙,则为前桶的体积加最后一个桶的宽度
return max(len, (task_num[0] - 1) * (n + 1) + cnt);
}
};
除法求值
思路
图论的题目,a/b = 2可以理解为a = 2 * b,其中a, b为图中的两个节点,2为节点之间的权值。所以这题的一种思路就是进行搜索,dfs或bfs,搜索指定路径上的节点,比如找a/c,可以找到a/b,再找b/c,遍历的节点顺序为a, b, c,将该路径上的权值之积就出来即可。
所以本题的难点:1、建图,2、深搜的实现。
代码
class Solution {
public:
//定义一个邻接表
unordered_map<string, vector<string>> nodeNeighber;
//定义一个边表
map<pair<string, string>, double> edge;
//定义一个以访问节点的表
set<string> visited;
double dfs(string start, string end){
//左右节点均没有邻居 或 该节点已经访问过,则直接返回
if(visited.count(start) || !nodeNeighber.count(start) || !nodeNeighber.count(end)){
return 0.0;
}
//如果有直接相连的边,直接返回
if(edge.count(make_pair(start, end))) return edge[make_pair(start, end)];
if(edge.count(make_pair(end, start))) return edge[make_pair(end, start)];
//没有直接相连的边
visited.insert(start);
vector<string> neighbers = nodeNeighber[start];
double res = 0.0;
for(string neighber : neighbers){
double temp = dfs(neighber, end);
if(temp > 0){ //如果neighber能到达end,则可以计算两数之积
res = temp * edge[make_pair(start, neighber)];
//生成两个边,方便优化
edge[make_pair(start, end)] = res;
edge[make_pair(end, start)] = 1.0 / res;
break;
}
}
visited.erase(start);
return res;
}
vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
//初始化邻接表和边表
vector<double> result;
int index = 0;
for(vector<string> equation: equations){
nodeNeighber[equation[0]].push_back(equation[1]);
nodeNeighber[equation[1]].push_back(equation[0]);
edge[make_pair(equation[0], equation[1])] = values[index];
edge[make_pair(equation[1], equation[0])] = 1.0 / values[index];
// cout<<edge[make_pair(equation[0], equation[1])]<<" "<<edge[make_pair(equation[1], equation[0])]<<endl;
index++;
}
//开始遍历queries数组
for(vector<string> querie : queries){
double res = dfs(querie[0], querie[1]);
if(res == 0.0){
result.push_back(-1);
}
else{
result.push_back(res);
}
}
return result;
}
};
路径总和III
思路
使用前缀和的思想:每个节点维系从根节点到该节点的前缀和。如果该节点的前缀和为curnum,则如果在前面的前缀和中存在curnum - target,那说明这两个节点之间的差为target,即找到。
需要用hash来存储指定前缀和的数量,回溯之和,要把前缀和减去当前节点值,防止curnum一直累加(因为是一个全局的值)
代码
/**
* 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:
unordered_map<int, int> map;
int result = 0;
void preflexSum(TreeNode* root, int targetSum, int& cursum){
if(root == nullptr){
return;
}
cursum += root->val;
if(map.find(cursum - targetSum) != map.end()){
result += map[cursum - targetSum];
}
map[cursum]++;
preflexSum(root->left, targetSum, cursum);
preflexSum(root->right, targetSum, cursum);
map[cursum]--;
cursum -= root->val;
}
int pathSum(TreeNode* root, int targetSum) {
map[0] = 1;
int cursum = 0;
preflexSum(root, targetSum, cursum);
return result;
}
};