LeetCode 练习随笔
做题环境 C++
中等题很值,收获挺多的
不会的题看题解,一道题卡1 h +,多来几道,时间上耗不起。
力扣上的题目和 OJ题目相比不同之处?
一开始上手力扣不习惯,OJ 的题目提交的是完整代码,力扣上的C++只提交目标函数代码,比如某个题目你只需要完成topKFrequent(nums,k)
这个函数。
class Solution {
vector<int> topKFrequent(vector<int>& nums, int k) {
}
};
这也就意味着程序设计的输入不需要自己额外设计了,同时限制了你数据的输入和返回内容的格式。
多亏了oj许多来自力扣的题并补全了完整代码(目标函数部分空缺),渐渐习惯了编写目标函数的答题习惯。
定义问题
-
在哪定义结构体?
有两个位置可写:
1)类里面,讲究的话写在private中,这里就不讲究了。
class Solution { public: struct node { int data; }; vector<int> topKFrequent(vector<int>& nums, int k) { } };
2)顶部区域
struct node { int data; }; class Solution { public: struct node { int data; }; vector<int> topKFrequent(vector<int>& nums, int k) { } };
-
在哪定义全局变量?
1)顶部区域
#include<algorithm> int a =3; class Solution { public: vector<int> topKFrequent(vector<int>& nums, int k) { vector<int>v; v.push_back(a); return v; } };
2)换个方式,传参方式改为引用
很多全局变量是可以被替代的,比如希望值被修改并返回到原来作用域,但返回值位置紧张。
暴力时,全局变量开大数组还是有点用处的。
-
在哪定义头文件?
一般是全面的,多虑了。
下面这种做题区域顶部写头文件试过,编译通过。
#include<algorithm> class Solution { public: vector<int> topKFrequent(vector<int>& nums, int k) { } };
排序问题
-
vector 存入结构体怎么自定义比较规则?
1)自定义规则为结构体形式(优先队列也可用)
class Solution { public: typedef struct node { int data; int sum; } ll; // static bool cmp(ll a, ll b) // { // return a.sum > b.sum; // } struct CompareBySumDesc { bool operator()( ll a, ll b) { return a.sum > b.sum; } }; vector<int> topKFrequent(vector <int> &nums,int k) { vector<ll>v; //... //vector排序 sort(v.begin(),v.end(),CompareBySumDesc()); // sort(v.begin(),v.end(),cmp); //... return res; } };
2)自定义规则为静态函数(简单)
class Solution { public: typedef struct node { int data; int sum; } ll; static bool cmp(ll a, ll b) { return a.sum > b.sum; } // struct CompareBySumDesc // { // bool operator()(const ll a, const ll b) const // { // return a.sum > b.sum; // } // }; vector<int> topKFrequent(vector <int> &nums,int k) { vector<ll>v; //... //vector排序 // sort(v.begin(),v.end(),CompareBySumDesc()); sort(v.begin(),v.end(),cmp); //... return res; } };
-
vector 存入结构体怎么逆置?
用 stack。
-
存入结构体的 priority_queue 怎么自定义比较规则?
给 ll 按照 sum 排序
class Solution { public: typedef struct node { int data; int sum; } ll; //试过了,static 函数行不通,只能 struct cmp { bool operator()( ll a, ll b) { return a.sum > b.sum; } }; void f() { priority_queue<ll, vector<ll>, cmp> pq; }; int main() { Solution().f(); return 0; }
//默认降序 //没有结构体,升序 priority_queue <int,vector<int>,greater<int> > q;
-
使用 priority_queue 实现堆排序?
优先队列本身是堆实现的。只需维护好优先队列的容量 k,超过pop掉。
统计问题
map 统计的神。遍历别忘了迭代器初始化。
注意事项
-
vector<vector<int > >v;
尖括号嵌套尽可能规范地隔开,priority_queue <int,vector<int>,greater<int>> pq;
这种尖括号紧贴一起依稀记得编译没通过。 -
unordered_map
,unordered_set
遍历别忘了迭代器初始化。
玄学
可能不对,原理没有细究,欢迎留言纠正。
- 字符内容 的最大公约数的长度 == 两字符长度的最大公约数。(若字符内容最大公约数存在为前提)
新 get!
新思路
新要求
新问题
1-单调栈
单调栈(Monotonic Stack)是一种常用的数据结构,用于解决一些关于单调性的问题。它通常用于处理需要维护一个递增或递减的元素序列的场景。单调栈的主要思想是保持栈内元素的单调性。
单调递增栈:栈顶元素是栈中最小的元素,当要入栈的元素小于等于栈顶元素时,将栈顶元素弹出,直到栈为空或栈顶元素小于新元素。这样,栈内的元素呈现递增顺序。
单调递减栈:栈顶元素是栈中最大的元素,当要入栈的元素大于等于栈顶元素时,将栈顶元素弹出,直到栈为空或栈顶元素大于新元素。这样,栈内的元素呈现递减顺序。
单调栈在解决一些问题时具有很好的效率,特别是涉及连续区间的问题。例如,在找到数组中每个元素的下一个更大元素、下一个更小元素,或者找到满足特定条件的最大连续子数组等问题中,单调栈都可以派上用场。
example 1
#include <iostream>
#include <stack>
#include <vector>
using namespace std;
//单调递减栈的特性,通过遍历输入数组,找出每个元素的下一个更大元素,并将结果保存在 result 向量中。
vector<int> nextGreaterElement(const vector<int>& nums) {
vector<int> result(nums.size(), -1); // Initialize result vector with -1
stack<int> monoStack; // Monotonic decreasing stack of indices
for (int i = 0; i < nums.size(); ++i) {
while (!monoStack.empty() && nums[i] > nums[monoStack.top()]) {
result[monoStack.top()] = nums[i];
monoStack.pop();
}
monoStack.push(i);
}
return result;
}
int main() {
vector<int> nums = {4, 3, 7, 1, 8, 5, 2, 6};
vector<int> nextGreater = nextGreaterElement(nums);
cout << "Next Greater Elements: ";
for (int val : nextGreater) {
cout << val << " ";
}
cout << endl;
return 0;
}
2-滑动窗口?
连续子序列和问题,优先考虑滑动窗口。
3-auto
应用【c++11 ,STL】
用了auto
,代码太简洁了。
example 1
vector<vector<int> >v;
遍历v中的整型,我的常规做法:
for(int i=0;i<v.size();i++)
{
for(int j=0;j<v[i].size();j++)
{
cout<<v[i][j]<<" ";
}
}
用auto遍历v中的整型:
for(auto items:v)
{
for(auto item:items)
{
cout<<item<<" ";
}
}
example 2
string s;
遍历string,我的常规做法:
for(int i=0;i<s.size();i++)
cout<<s[i]<<" ";
auto遍历string更简洁:
for(auto c:s)
cout<<c<<" ";
example 3
函数参数声明(复杂类型):
void f(vector<vector<int> >a,vector<vector<int> >b)
{
//...
}
auto简化参数声明:
void f(auto a,auto b)
{
//...
}
example 4
#include<set>
set<int>s;
STL容器使用迭代器遍历:
for(set<int>::iterator it = s.begin();it != s.end();++it)
cout<<*it<<" ";
auto简化迭代器:
for(auto it = s.begin();it != s.end();++it)
cout<<*it<<" ";
4-sort()
内嵌式规则
vector<int>v;
通常:
sort(v.begin(),v.end());
bool cmp(int a,int b)
{
return a > b;
}
sort(v.begin(),v.end(),cmp);
内嵌式书写排序规则:
//sort(BEGIN,END,[](a,b){...});
sort(v.begin(),v.end(),[](const auto &a, const auto &b){return a > b;})
5-实现无删遍历queue
不删除元素遍历queue
queue<int>q;
int len =q.size();
//原理:模拟循环
while(len--)
{
int qf = q.front();
//qf
q.pop();
q.push(qf);
}
6-存储二叉树层次遍历的每一层
连这三道题都用到了,记录一下当板子。
vector<vector<int> >zigzagLevelOrder(TreeNode*root)
{
if(!root)return {};//不空才有必要继续。
vector<vector<int> >v;
//pre 先存入漏掉的root,后面都是存新的一层,root永远都不属于新的一层,这里补上。
vector<int>temp;
temp.push_back(root->val);
v.push_back(temp);
//queue operation
queue<TreeNode*>q;
q.push(root);
TreeNode *t ;
int ct=1,nex_ct=0;//关键 借助次数标记旧层全无,全是新层的时刻
while(!q.empty())
{
t=q.front();
q.pop();
ct--;
//新层进
if(t->left)
{
q.push(t->left);
nex_ct++;
}
if(t->right)
{
q.push(t->right);
nex_ct++;
}
//
if(ct==0)//全是最新的一层
{
vector<int>item;
int len = q.size();
while(len--)//无删遍历queue
{
TreeNode*qf = q.front();
item.push_back(qf->val);
q.pop();
q.push(qf);
}
if(!item.empty())v.push_back(item);//力扣空的都不给过。oj里没事
//next
ct= nex_ct;
nex_ct=0;
}
}
return v;
}
7-删除 vector 任意位置元素
vector<int>v;
//删除 v[i]
swap( v[i],v[v.size()-1]);
v.pop_back();
其实只是 v.size()
减少,内存不释放。
vector<int >v;
v.push_back(11);
v.push_back(22);
v.push_back(33);
v.pop_back();
v.pop_back();
v.pop_back();
cout<<v.size()<<endl;
v.push_back(44);
cout<<v[0]<<endl;
cout<<v[1]<<endl;
cout<<v[2]<<endl;
这里存入三个元素,之后全部删除,v.size() 结果是 0,是预期的结果,
但此时通过下标访问v[0]~v[2],原来的值仍然可以访问到,删除时内存没有释放掉。
之后再加入新的元素44,v[0] 内容被覆盖了。
8-手写实现dijkstra
const int INF = 0x3f3f3f3f;
vector<vector<int> >pojects; //[ [1,2,3],[1,2,3],[1,2,3] ] [from,to,cost]
int dijkstra(auto &pojects, int n, int k)//from to k
{
int vis[n];
memset(vis,0,sizeof(vis));
//int temp[n+1];
//memset(temp,INF,sizeof(temp));
vector<int>temp(n+1,INF);//用vector 定义temp数组只需要一行。因为下标要用 1-n,定义n+1更合适。
for(auto it:pojects)
{
if(it[0] == k)
{
int to = it[1];
int dis = it[2];
temp[to] = min(temp[to],dis);
}
temp[k]=0;
}
int tot =n;
int v=k;
while(tot--)
{
int mi_len = INF;
for(int i=1; i<=n; i++)//这里顶点范围[1,n]
{
if(vis[i] == 0 && temp[i] < mi_len)
{
mi_len =temp[i];
v = i;
}
}
if(mi_len == INF)return -1;//没有邻边了,存在不可达点
vis[v]=1;
for(auto it:pojects)
{
if(it[0] == v)
{
int to = it[1];
int dis = it[2];
temp[to] = min(temp[to],temp[v] + dis);
}
temp[k]=0;
}
}
}
9-最大值写法
忘了,记录一下。
const int INF = 0x3f3f3f3f;
10-vector
初始化
vector<int>v(n,0);
容量 n,初始值 0。
打个形状为 nxm 的 0矩阵
vector<vector<int>> v(n,vector<int>(m,0));
其实默认初始化值为0,可以再省一步:
vector<int>v(n);
vector<vector<int>> v(n,vector<int>(m));
11-binary_search
【c++11 ,STL】
使用STL内置binary_search
#include<algorithm>
vector<int>v;
bool res = binary_search(v.begin(),v.end(),target);
cout<<(res?"true":"false")<<endl;
binary_search(startaddress, endaddress, valuetofind);
startaddress: 列表起始地址
endaddress: 列表末尾地址
valuetofind: 查找的目标值
12-unordered_map
【c++11 ,STL】
#include<unordered_map>
using namespace std;//注意需要使用命名空间
unordered_map
和 map
相比实现原理、特点和应用?
insert | search | 底层原理 | 限制 | |
---|---|---|---|---|
unordered_map | O(logn) | O(1) | hash表 | 键值对只能是基本类型 |
map | O(logn) | O(logn) | 红黑树 | 无 |
想用键值对,优先考虑
unordered_map
。
13-unordered_set
【c++11 ,STL】
#include<unordered_set>
using namespace std;//注意需要使用命名空间
想用集合,优先考虑
unordered_set
。
unordered_set
和 set
相比实现原理和特点?
set
底层实现是二叉树【红黑树】,适合频繁插入删除,且保证元素有序【默认升序】。unordered_set
底层实现是 hash表,常数级查询速度。
14-__gcd(len1,len2)【c++11,STL】
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int num1 = 48;
int num2 = 18;
int gcd_result = __gcd(num1, num2);
cout << gcd_result << endl;//最大公约数
return 0;
}
14-stirng.find
查找不到返回什么?
string a;
a ="abcd";
cout<<a.find("ab")<<endl;
cout<<a.find("aa")<<endl;
cout<<a.find("abb")<<endl;
0
4294967295
4294967295
返回值:
找到:[0,a.size())
找不到:4294967295,换句话说,范围之外的数。
15-INT_MAX
c++中,最大int常量。
INT_MAX 与 0x3f3f3f3f 大小比较。
INT_MAX 更大。
INT_MAX - 0x3f3f3f3f = 1086374080
16-快慢指针
以移动0到末尾为例,要求不能复制数组,在原数组上操作:
class Solution
{
public:
void moveZeroes(vector<int>& nums)
{
int slowIndex = 0;
for(int fastIndex=0;fastIndex<nums.size();fastIndex++)
{
if(nums[fastIndex] != 0//匹配到目标数组
{
swap(nums[fastIndex],nums[slowIndex++]);//slowIndex就是用来向后扩展目标数组,并指定下一个位置
}
}
}
};
找字符子序列也可以这么做,思路如下:
把目标子序列abc
类比成目标数组,在提供的字符串ahbkc
中,用快慢指针凑出来这个目标abc
,凑成了【比如这里的abchk
】就是存在abc
。
class Solution {
public:
bool isSubsequence(string s, string t) {
if(s.size()> t.size())return false;
if(s.size() == t.size())return s==t;
int pos = 0;//s[pos]
int slow = 0;
for(int fast = 0;fast< t.size();fast++)
{
if(t[fast] ==s[pos])
{
swap(t[fast],t[slow++]);
++pos;
if(pos >=s.size())break;
}
}
if(pos >=s.size())return true;
return false;
}
};