我在代码随想录|写代码总结篇| 数组 |链表|哈希表|字符串|双指针法|栈与队列|二叉树|回溯算法|贪心算法|动态规划|单调栈
机缘
因为想要成为代码大神所以开始了我代码随想录的刷题过程
收获
- 对于算法的基础软件就是了解算法的基础,建立了自己的知识体系
- 发现算法还是一个积累的过程,对于很多知识点不是只刷往往一个总结的时间收获是以前的几倍
日常
- 创作已经是你生活的一部分了,有限的精力下平衡创作和工作学习
- 刷题 + 文章 + 总结知识点
成就
- 对与算法进入小白阶段, 积累ing
- 养成了文章习惯
- 准备自己的想法
数组
二分查找模版
左开右闭
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0,right=nums.size();
while(left<right){
int mid = (right-left)/2+left;
if(target>nums[mid]){
left=mid+1;
}else if(target<nums[mid]){
right=mid;
}else{
return mid;
}
}
return -1;
}
};
闭区间
class Solution {
public:
int search(vector<int>& nums, int target) {//二分查找找不到这么办?
int i=0,j=nums.size()-1;
int mid=0;
while(i<=j){
mid = (j-i)/2+i;
if(target>nums[mid]){
i=mid+1;
}else if(target<nums[mid]){
j=mid-1;
}else{
return mid;
}
}
return -1;
}
};
另类:
class Solution {
public:
int search(vector<int>& nums, int target) {
if(!binary_search(nums.begin(),nums.end(),target)) {//二分搜索元素是否存在
return -1;
} else {
return lower_bound(nums.begin(),nums.end(),target) - nums.begin();//得到的地址减去首地址 等于下标
}
}
};
在二分查找中学会了查找区间 左开右闭 右闭左开 闭区间
移除元素: 原地移除用类哈希表的方法
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int cnt=0;
for(int i=0;i<nums.size();i++){
if(nums[i]!=val) nums[cnt++]=nums[i];
}
return cnt;
}
};
有序数组的平方: 可能有数组中有负数
长度最小的子数组: 本质就是找数组中长度最小的组合
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int cnt=0,result=INT_MAX;//这里我设置了计数器
for(int j=0,i=0;j<nums.size();j++){ //这里的j是终点也就是快指针
cnt+=nums[j];//这里统计和用于与target进行比较
while(cnt>=target){ //当我们的cnt>=target时也就代表我们的快指针到达终点
result=min(result,j-i+1); //然后每次移动取最小的哪个
cnt-=nums[i++]; //先删除慢指针所指的元素然后慢指针向前移动
}
}
return result == INT_MAX ? 0 : result; //返回值与INT_MAX进行比较
//如果相同者表示没有进while也就没有值
}
};
螺旋矩阵II : 数组的处理对每个数组的处理规则要相同,遍历区间的问题
经典
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> arr(n, vector<int>(n,0));
int j,k,o,p,m=1;
if(n&1){//判断不是偶数是偶数返回0不是返回正数
arr[n/2][n/2]=n*n;//奇数判断我们要将
}
for(int i=0;i<n/2;i++){//圈数大小,如果大于则会越界
//就是起始点大于终点
for(j=i;j<=n-i-2;j++){//向左遍历
arr[i][j]=m++;
}
for(k=i;k<=n-i-2;k++){//向下遍历
arr[k][n-i-1]=m++;
}
for(o=n-i-1;o>=i+1;o--){//o是大于等于n+1
arr[n-i-1][o]=m++;//向左遍历
}
for(p=n-i-1;p>=i+1;p--){//向上遍历
arr[p][i]=m++;
}
}
return arr;
}
};
链表
-
移除链表元素 : 学会了
双指针法
和虚拟头结点
的方法, 不断遍历,链表就是想法会更简单但是实现起来会更难, 要注意结点是否为空,我们是否访问了空结点之类的 -
设计链表: 这个主要完成链表的
增删查改
重要 最好将模版背一背
class MyLinkedList{
public:
//初始化链表
MyLinkedList(){
this->size = 0;
this->head=new ListNode(0);//这个是虚拟结点
}
//获取到第index个结点数值,如果index是非法的数值直接返回-1,注意index是从0开始
int get(int index){
if(index<0||index>=size) return -1;
ListNode*cur = head;
index++;
while(index--) cur = cur->next;
return cur->val;
}
void addAtHead(int val){
addAtIndex(0,val);//既然都是增加结点那么调用我们增加结点的函数就可以
}
void addAtTail(int val){
addAtIndex(size,val);//同理
}
//增加结点
//如果index个节点之前插入新节点,那么新插入的节点为链表的新头节点
//如果index等于链表的长度,则说明是新插入的节点为链表的尾结点
//如果index大于链表的长度,则返回为空
void addAtIndex(int index, int val){
if(index>size) return ;
index = max(0,index);
size++;
ListNode*cur = head;
while(index--) cur = cur->next;
ListNode*pred = new ListNode(val);
pred->next = cur->next;
cur->next = pred;
}
void deleteAtIndex(int index){//删除结点
if(index<0||index>=size) return ;
size--;//长度记得改变!!!!!
ListNode*cur = head;
while(index--) cur = cur -> next;
ListNode*pred = cur->next;
cur->next = pred->next;
delete pred;
}
private://定义一个私有属性为类使用
int size;
ListNode*head;
};
-
反转链表: 理解起来有点难度主要是模拟,还是要记住相关的情况就好了
通过图可以知道pre主要储存的是上一个结点然后将 cur -> next 指向上一个结点即可完成反转
这个思路是告诉完美储存的是上一个结点和双指针的方法,双指针主要还是链表使用最多 -
两两交换链表中的节点: 涉及链表交换,这道题是真的难理解,无论是交换链表还是循环操作(螺旋矩阵) 等等都要操作规则一样, 前面是操作俩结点但是整个时候他是操作3个结点, 题目是储存cur -> next 和 cur -> next -> next;然后对三个结点进行操作
如图:
-
删除链表的倒数第N个节点: 这个只要循环到第n个结点然后删除第n个结点即可,但是注意: 这个N可能为头结点, 使用虚拟头结点会更好
-
链表相交: 这题和循环链表一样,如同诗歌般绚丽,也如同命运般琢磨不透,正如<<我与地坛>>的书评般: 年少时捡到一把枪,因为年少无知,扣动扳机,谁都没有受伤,多年以后,背后隐隐约约有风声,回头,子弹正中眉心, 下面给出代码
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* you= headA;
ListNode* me= headB;
// 在遇到你之前,我们是俩条平行线,以外不会相遇
if (you == nullptr || me == nullptr)
return nullptr;
// 兜兜转转,我们的生命轨迹终将出现交点
while(me!=you){
// 我们携手与共,即使一个人掉队,也会拉上彼此继续前行
you = you == nullptr ? headB : you->next;
me = me == nullptr ? headA : me->next;r
// 往后余生,你就是我的世界
}
return you;
//即使身处两个世界,但只要以相同的速度双向奔赴,就一定会相遇。
//两人同时以相同的速度走自己的路,走完自己的路之后走对方的路,两者有缘则相遇,
//相遇则结束长跑,若无缘则同时走到“空”,跳出感情的死循环。
}
};
- 环形链表II : 只要有环就不会遍历到NULL, 入口确定: 快慢指针.一个移动一个结点,一个移动两个结点,最后会在入口处相遇,
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode*fast = head;
ListNode*slow = head;
//我们彼此相识共同进步
while(fast!=nullptr&&fast->next!=nullptr){
//即使身处两个世界,以不相同的速度双向奔赴,但只要在同一恋爱循环中,就一定会相遇。
slow = slow->next;
fast = fast->next->next;
//在命运的某一时刻我们登入的婚姻的殿堂
if(slow==fast){
//在我们相遇的哪个时刻脑中回忆着过往
ListNode*cnt = slow;
ListNode*cur = head;
while(cur!=cnt){
//从相识到相知彼此断靠近
cur=cur->next;
cnt = cnt->next;
}
//直到回忆道我们的青涩告白返回我们当初最真挚的情感
return cur;
}
}
//尽管我们没有进入恋爱的循环我们都有着彼此的故事
return nullptr;
}
};
哈希表
主要作用我认为是用来去重, C++代表哈希表的主要是 unordered_map<int,int> mp;
和 unordered_set<int,int> st;
主要去记录元素也可以通过一些方法查找记录的元素
字符串
我认为字符串是重要的 常考
- 反转字符串: 最简单的方法是
reverse
也可以用swap()
同样可以用双指针的方法
class Solution {
public:
void reverseString(vector<char>& s) {
int i = 0;
int k = s.size() - 1;
while (i < k) {
s[k],s[i]=s[k],s[i];
i++;
k--;
}
}
};
- 反转字符串II : 特定区域反转字符串, 这个要对字符串的反转要有一定的理解,还有就是区间控制,比如大于2k时取什么,小于2k时取什么,遇到这个特定区间变化的时候一般会用
min()
或者max()
class Solution {
public:
string reverseStr(string s, int k) {
int n = s.size();
for(int i=0;i<n;i+=2*k) {
reverse(s.begin()+i,s.begin()+min(i+k,n));
}
return s;
}
};
- 替换数字: 遇到特定字符转换为指定字符的情况, 如图
核心代码
void solve() {
string s; cin>>s;
for(int i=0;i<s.size();i++){
if(s[i]>='1'&&s[i]<='9'){
s.replace(i,1,"number");
}
}
std::cout<<s;
}
代码函数讲解: replace() 用于交换字符,代码中只要遇到 if(s[i]>='1'&&s[i]<='9')
将这个字符 交换为 number
其中 1
代表被交换字符的大小
- 翻转字符串里的单词: 这个便是区域交换, 交换时要注意空格
class Solution {
public:
void reverse(string& s, int start, int end){ //翻转,区间写法:左闭右闭 []
for (int i = start, j = end; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间添加空格, 快慢指针。
int slow = 0;
for (int i = 0; i < s.size(); ++i) { //
if (s[i] != ' ') { //遇到非空格就处理,即删除所有空格。
if (slow != 0) s[slow++] = ' '; //手动控制空格,给单词之间添加空格。slow != 0说明不是第一个单词,需要在单词前添加空格。
while (i < s.size() && s[i] != ' ') { //补上该单词,遇到空格说明单词结束。
s[slow++] = s[i++];
}
}
}
s.resize(slow); //slow的大小即为去除多余空格后的大小。
}
string reverseWords(string s) {
removeExtraSpaces(s); //去除多余空格,保证单词之间之只有一个空格,且字符串首尾没空格。
reverse(s, 0, s.size() - 1);
int start = 0; //removeExtraSpaces后保证第一个单词的开始下标一定是0。
for (int i = 0; i <= s.size(); ++i) {
if (i == s.size() || s[i] == ' ') { //到达空格或者串尾,说明一个单词结束。进行翻转。
reverse(s, start, i - 1); //翻转,注意是左闭右闭 []的翻转。
start = i + 1; //更新下一个单词的开始下标start
}
}
return s;
}
};
遇到空格怎么样将空格反转?
反转容易 reverse
但是读入
读入可以用
std::scanf(“%[^\n]s”, &s); //可以将空格读入
assign 是std::string类的一个成员函数,用于将一个字符串或字符数组赋值给另一个字符串
// 函数重载
string& assign (const string& str);
string& assign (const string& str, size_t subpos, size_t sublen);
string& assign (const char* s, size_t n);
string& assign (const char* s);
string& assign (size_t n, char c);
- 右旋字符串: 左右旋转要学会复制字符串,然后将字符串相加减去复制后的字符串
void solve() {
int n;
std::string s;
std::cin >> n >> s;
int len = s.size();
// Ensure n is within the bounds of the string length
n = n % len;
// Create a substring of the last n characters
std::string t = s.substr(len - n, n);
// Erase the last n characters from s
s.erase(len - n, n);
// Prepend the substring t to s
s = t + s;
// Output the modified string
std::cout << s;
}
1、strstr() 函数搜索一个字符串在另一个字符串中的第一次出现。
2、找到所搜索的字符串,则该函数返回第一次匹配的字符串的地址;
3、如果未找到所搜索的字符串,则返回NULL。
KMP算法
class Solution {
public:
//KMP
int strStr(string s, string t) {
int n = s.size(),m=t.size();
if(m==0) return 0;
s.insert(s.begin(),' ');
t.insert(t.begin(),' ');
vector<int> next(m+1);
//处理next数组
for(int i=2,j=0;i<=m;i++){
while(j&&t[i]!=t[j+1]) j = next[j];
if(t[i] == t[j+1]) j++;
next[i] = j;
}
//匹配过程
for(int i=1,j=0;i<=n;i++){
while(j&&s[i] != t[j+1]) j = next[j];
if(s[i]==t[j+1]) j++;
if(j==m) return i-m;
}
return -1;
}
};
KMP 可以用C++的 find()
class Solution {
public:
int strStr(string haystack, string needle) {
int x = haystack.find(needle);
return x;
}
};
- 重复的子字符串: 将字符串复制后在新字符串中查找 s 如果找到 就返回 true 否则 false
class Solution {
public:
bool repeatedSubstringPattern(string s) {
string t =s+s;//俩相同字符串相加
t = t.substr(1,t.size()-2);//suberstr用于提取字符串和copy类似
//这个将首尾第一个字符串剔除
int index = t.find(s);//在我们新的字符串中查找s
if(index != string::npos){
return 1;
}else{
return 0;
}
}
};
双指针法
双指针返回即标记俩处位置,用双指针可以减少一定的时间
栈与队列
- 用栈实现队列
class MyQueue {
public:
MyQueue() {
}
void push(int x) {
s.push(x);//将元素入队列
}
int pop() {
if(t.empty()&&s.empty()) return -1;//
if(!t.empty()){//这里s改为t
int result = t.top();
t.pop();
return result;
}else{
while(!s.empty()){
t.push(s.top());
s.pop();
}
int result = t.top();
t.pop();
return result;
}
}
int peek() {
if(t.empty()&&s.empty()) return -1;
if(!t.empty()){
return t.top();
}else{
while(!s.empty()){
t.push(s.top());
s.pop();
}
return t.top();
}
}
bool empty() {
if(t.empty()&&s.empty()) return -1;
else return 0;
}
private:
stack<int> s;
stack<int> t;
};
- 用队列实现栈
class MyStack {
public:
MyStack(){
}
void push(int x){
s.push(x);
}
//问题是怎么样在第一个队列放一个元素
//让元素始终在一个队列中
int pop(){
int size = s.size();//通过队列长度确定->可以让队列剩下一个元素
size--;//留出一个元素空位
while(size--){
t.push(s.front());//这里将元素放在t队列中
s.pop();
}
int result = s.front();
s.pop();//这个时候s为空格
s = t;//为什么要讲t赋值给s=>让元素始终在一个栈中?
//这里让元素位置复原
while(!t.empty()) {
t.pop();
}
return result;
}
int top(){
return s.back();//返回队尾元素
}
bool empty(){
return s.empty();
}
private:
queue<int>s;
queue<int>t;
};
- 有效的括号
class Solution {
public:
bool isValid(string s) {
if(s.size() % 2 != 0) { return false; }
stack<char> t;
for(int i = 0;i < s.size();i++) {
if(s[i] == '(') { t.push(')'); }
else if(s[i] == '[') { t.push(']'); }
else if(s[i] == '{') { t.push('}'); }
else if(t.empty() || s[i] != t.top()) { return 0; }
else { t.pop(); }
}
return t.empty();
}
};
- 删除字符串中的所有相邻重复项
class Solution {
public:
string removeDuplicates(string S) {
stack<char>st;
for(char s : S){
if(st.empty() || s != st.top()) {
st.push(s);
}else{
st.pop();
}
}
string ans = "";
while(!st.empty()) {
ans += st.top();
st.pop();
}
reverse(ans.begin(),ans.end());
return ans;`
}
};
- 逆波兰表达式求值
class Solution {
public:
int evalRPN(vector<string>& s) {
stack<int>st;
for(int i=0;i < s.size();i++) {
if(st.empty() || (s[i].compare("+") && s[i].compare("-") && s[i].compare("*") && s[i].compare("/"))) {
int num = atoi(s[i].c_str());
st.push(num);
} else {
int x = st.top();
st.pop();
int y = st.top();
st.pop();
switch(s[i][0]) {
case '+' : st.push(x+y);break;
case '-' : st.push(y-x);break;
case '*' : st.push(x*y);break;
case '/' : st.push(y/x); break;
}
}
}
return st.top();
}
};
/*错误汇总:
*不能如下:
if(st.empty() || (s[i] != '+' && s[i] != '-' && s[i] != '*' && s[i] != '/')) {
int num = atio(&s[i]);
*也不可以如下:
if(st.empty() || (s[i].compare('+') && s[i].compare('-') && s[i].compare('*') && s[i].compare('/'))) {
int num = atio(&s[i]);
*正确如下:
if(st.empty() || (s[i].compare("+") && s[i].compare("-") && s[i].compare("*") && s[i].compare("/"))) {
int num = atoi(s[i].c_str());
原因: compare 比较的是字符串 ''这样代表字符
atoi()转化的是字符串而且是c语言风格所以要用.c_str()格式化
还有为什么要s[i][0]我也不太清楚
*/
二叉树
二叉树的遍历区别 前序,中序,后序 遍历, 还有就是 层序遍历
,还有就是二叉树的 深度 和 高度
- 层序遍历模版
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
vector<vector<int>> ans;
if(root != nullptr) que.push(root);
while(!que.empty()) {
int size = que.size();
vector<int>vec;
for(int i = 0;i < size;i++) {
TreeNode*node = que.front();
que.pop();
vec.push_back(node->val);
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
ans.push_back(vec);
}
return ans;
}
};
只要去更改根结点转换即可
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
invertTree(root->left); // 左
swap(root->left, root->right); // 中
invertTree(root->left); // 注意 这里依然要遍历左孩子,因为中间节点已经翻转了
return root;
}
};
- 二叉树的所有路径
class Solution {
public:
void traversal(TreeNode*cur,string path,vector<string>&result) {
path += to_string(cur->val);
if(cur->left == NULL && cur->right == NULL) {
result.push_back(path);
return;
}
if(cur->left) traversal(cur->left,path +"->",result);//path+= "->"会影响整层循环
if(cur->right) traversal(cur->right,path +"->",result);
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;string path;
if(root == nullptr) return result;
traversal(root,path,result);
return result;
}
};
二叉树主要是查找,删除,插入 等操作
回溯算法
回溯算法模版
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
回溯算法可以通过 剪枝优化 让算法效率更高
- 组合
class Solution {
private:
vector<int>vec;
vector<vector<int>>v;
void backtracking(int n,int k,int startIndex) {
if(vec.size()==k) {
v.push_back(vec);
return;
}
for(auto i = startIndex;i<=n;i++) {
vec.push_back(i);
backtracking(n,k,i+1);
vec.pop_back();
}
}
public:
vector<vector<int>> combine(int n, int k) {
v.clear();
vec.clear();
backtracking(n,k,1);
return v;
}
};
全排列模版
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<vector<int>>result;
do {
result.push_back(nums);
} while(next_permutation(nums.begin(),nums.end()));
return result;
}
};
贪心算法
寻找最优解的过程,局部最优到全局最优, 比如我要把饼干分个N个孩子,每个饼干重量不同,孩子的胃口不同,我局部最优是不是要用最少重量的饼干去满足最小胃口的孩子,这样孩子的胃口也能饱
贪心基础题目:
- 分发饼干: 贪心基础题
- 摆动序列: 会有点难度, 最主要是下面的细节问题的处理
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if (nums.size() <= 1) return nums.size();
int curDiff = 0,preDiff = 0,result = 1;
for (int i = 0; i < nums.size() - 1; i++) {
curDiff = nums[i+1] - nums[i];
//出现峰值
if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
curDiff = nums[i + 1] - nums[i];
if ((preDiff <= 0 && curDiff > 0) || preDiff >= 0 && curDiff < 0) {
result++;
preDiff = curDiff; //更新摆动变化
}
}
}
return result;
}
};
- 最大子数组和: 应该比较简单,但是对于当前的我来写还是要消耗很多时间的
最主要是其中含有负数的原因, 如果相加和成为了负数,直接清零,这样才能让数据相加最大化
class Solution {
public:
int maxSubArray(vector<int>& nums) {
if (nums.size() == 0) return 0;
vector<int> dp(nums.size());
dp[0] = nums[0];
int result = dp[0];
for (int i = 1; i < nums.size(); i++) {
dp[i] = max(dp[i - 1] + nums[i], nums[i]); // 状态转移公式
if (dp[i] > result) result = dp[i]; // result 保存dp[i]的最大值
}
return result;
}
};
- 跳跃游戏: 和我之前写过的一道ACM的题差不多,都是从当前起点通过步数跳道最后一个位置
class Solution {
public:
bool canJump(vector<int>& nums) {
int k = 0;
for (int i = 0; i < nums.size(); i++) {
if (i > k) return false;
k = max(k, i + nums[i]);
}
return true;
}
};
如果某一个作为 起跳点 的格子可以跳跃的距离是 3,那么表示后面 3 个格子都可以作为 起跳点
可以对每一个能作为 起跳点 的格子都尝试跳一次,把 能跳到最远的距离 不断更新
如果可以一直跳到最后,就成功了
官方题解:
class Solution {
public:
bool canJump(vector<int>& nums) {
int n = nums.size();
int rightmost = 0;
for (int i = 0; i < n; ++i) {
if (i <= rightmost) {
rightmost = max(rightmost, i + nums[i]);
if (rightmost >= n - 1) {
return true;
}
}
}
return false;
}
};
- 跳跃游戏 II : 这个除了能跳道对面还要找到最小的步数
class Solution {
public:
int jump(vector<int>& nums) {
int cnt = 0;
int ans = 0;
int next = 0;
if (nums.size() == 1) return 0;
for (int i = 0; i < nums.size() - 1; i++) {
cnt = cnt > i + nums[i] ? cnt : i + nums[i];
if(i == next) {
next = cnt;
ans++;
}
}
return ans;
}
};
- K次取反后最大化的数组和 : 只要取反后将最小的取反即可
动态规划
动态规划是有规律的和题型的我对于动态规划的理解目前可能不深,但是只要我们不断总结总会理解
下面是当前我对动态规划的总结
- 斐波那契数: dp 入门基础题, 有就是动态规划数组常常为dp 或者 f 命名
class Solution {
public:
int fib(int n) {
int a[n+5];
a[0]=0;
a[1]=1;
for(int i=2;i<=n;i++){
a[i]=a[i-1]+a[i-2];
}
return a[n];
}
};
- 爬楼梯: 和斐波那契数列一样爬楼梯是当前位置是由上一个文章的次数 和 上上个位置和组合而成的
class Solution {
public:
int climbStairs(int n) {
long long int a[n+5];
a[0]=1;
a[1]=1;
a[2]=2;
for(int i=2;i<n+2;i++){
a[i]=a[i-1]+a[i-2];
}
return a[n];
}
};
- 使用最小花费爬楼梯: 与爬楼梯一样 ,可是加了条件 便是要最小花费, 可以用贪心 + 动规 的写法
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
vector<int> dp(cost.size() + 1);
dp[0] = 0; // 默认第一步都是不花费体力的
dp[1] = 0;
for (int i = 2; i <= cost.size(); i++) {
dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
}
return dp[cost.size()];
}
};
贪心
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
// i = 0, 所站的平地,还没有迈步往上
int min0 = 0;
// i = 1, 往上爬一个台阶,因为题目可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯,所以这里也是0
int min1 = 0;
int minTop = 0;
// i 表示阶梯顶部
for(int i = 2; i <= n ; i++){
// 要么一次迈两阶,要么先迈一阶再迈一阶
minTop = min(min0 + cost[i - 2], min1 + cost[i - 1]);
// 等同于min1变成了平地min0,minNext变成了min1,继而往上爬
min0 = min1;
min1 = minTop;
}
return minTop;
}
};
动规解决路径问题
- 不同路径: 总结数学规律直接算的
class Solution {
public:
int uniquePaths(int m, int n) {
long long numerator = 1; // 分子
int denominator = m - 1; // 分母
int count = m - 1;
int t = m + n - 2;
while (count--) {
numerator *= (t--);
while (denominator != 0 && numerator % denominator == 0) {
numerator /= denominator;
denominator--;
}
}
return numerator;
}
};
易理解版
class Solution {
public:
int uniquePaths(int m, int n) {
int dp[m][n];
dp[0][0] = 0;
for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 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 - 1][n - 1];
}
};
- 不同路径 II : 路径上有障碍物,直接跳过
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int l,r;
int m =obstacleGrid.size();
int n = obstacleGrid[0].size();
int dp[m][n];
if (obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1){
return 0;
}
memset(dp,0,sizeof(dp));
for (int i = 0; i < m && !obstacleGrid[i][0]; i++) dp[i][0] = 1;
for (int j = 0; j < n && !obstacleGrid[0][j]; j++) dp[0][j] = 1;
for (int i = 1;i < m; i++) {
for (int j = 1; j < n; j++) {
if (obstacleGrid[i][j] == 1) {continue;}
dp[i][j] = dp[i-1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
};
- 背包问题 : 每种背包类型都要记下模板,没有模板很难做
- 01背包: 物品只有一个要求最大背包价值
基础模版
#include<bits/stdc++.h>
#define Run 0
#define endl "\n"
#define n 105
#define m 1010
using ll = long long;
using namespace std;
ll dp[n][m];
void slove() {
ll N,V;cin >> N >> V; //这个是物品数 N 背包体积V
for (int i = 1 ; i <= N; i++) {
ll w,v;cin >> w >> v; //每个物品的 重量 和 价值
for (int j = 0; j <= V; j++) {
if (j >= w) dp[i][j] = max(dp[i-1][j],dp[i - 1][j - w] + v); //递推公式(动态转移方程)
else dp[i][j] = dp[i - 1][j];
}
}
cout << dp[N][V] << endl;
}
int main() {
cin.tie(0) -> ios::sync_with_stdio(0);
cout.tie(0) -> ios::sync_with_stdio(0);
#if Run
int _;cin>>_;while(_--) slove();
#else
slove();
#endif
return 0;
}
优化版本
include<bits/stdc++.h>
#define Run 0
#define endl "\n"
#define n 105
#define m 1010
using ll = long long;
using namespace std;
ll dp[n][m];
void slove() {
ll N,V;cin >> N >> V; //这个是物品数 N 背包体积V
for (int i = 1 ; i <= N; i++) {
ll w,v;cin >> w >> v; //每个物品的 重量 和 价值
for (int j = 0; j <= V; j++) {
if (j >= w) dp[i][j] = max(dp[i-1][j],dp[i - 1][j - w] + v); //递推公式(动态转移方程)
else dp[i][j] = dp[i - 1][j];
}
}
cout << dp[N][V] << endl;
}
int main() {
cin.tie(0) -> ios::sync_with_stdio(0);
cout.tie(0) -> ios::sync_with_stdio(0);
#if Run
int _;cin>>_;while(_--) slove();
#else
slove();
#endif
return 0;
}
- 完全背包
模版
#include<bits/stdc++.h>
#define Run 0
#define endl "\n"
#define n 1010
#define m 1010
using ll = long long;
using namespace std;
ll dp[n];
void slove() {
ll N,V;cin >> N >> V; //这个是物品数 N 背包体积V
memset(dp,0,sizeof dp);
for (int i = 0; i < N; i++) {
ll w,v; cin >> w >> v;
for (int j = w; j <= V; j++) {
dp[j] = dp[j] > dp[j - w] + v ? dp[j] : dp[j - w] + v;
}
}
cout << dp[V] << endl;
}
int main() {
cin.tie(0) -> ios::sync_with_stdio(0);
cout.tie(0) -> ios::sync_with_stdio(0);
#if Run
int _;cin>>_;while(_--) slove();
#else
slove();
#endif
return 0;
}
- 打家劫舍类问题: 都是举一反三的问题,通过一个问题然后加条件让解题代码完全不同,目前掌握不熟练,因为涉及到我当前没有掌握的区间变换问题,后续还得加油
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 0) return 0;
if (nums.size() == 1) return nums[0];
vector<int> dp(nums.size());
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for (int i = 2; i < nums.size(); i++) {
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[nums.size() - 1];
}
};
- 买卖股票的最佳时机类问题: 完全没有搞懂,只是将代码抄了一遍,这个也要加油,我认为这个应该算是一个模块,要以段时间去学习,可能我是赶的着急,这个还得要花时间
// 版本二
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
vector<vector<int>> dp(2, vector<int>(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < len; i++) {
dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]);
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
}
return dp[(len - 1) % 2][1];
}
};
- 序列模块
- 其他模块
`
单调栈
单调栈对栈的运用,单调即栈内元素单调,如果不单调则进行一系列操作
1.每日温度
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& T) {
//单调增栈
stack<int> st;
vector<int> result(T.size(),0);
st.push(0);
for (int i = 1; i < T.size(); i++) {
if (T[i] < T[st.top()]) {
st.push(i);
} else if (T[i] == T[st.top()]) {
st.push(i);
} else {
while(!st.empty() && T[i] > T[st.top()]) {
result[st.top()] = i - st.top();
st.pop();
}
st.push(i);
}
}
return result;
}
};
遇到不相同的便弹出并遍历后面的直到下一个大于他的元素
憧憬
- 成为大佬
- 拿下理想offer
- 技术之上,拿下计划软件
Tips
努力方得积累, 有想法才有希望,有希望就得更加拼
本文作者:2c237c6
本文链接:https://www.cnblogs.com/27dCnc/p/18568632
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通