每日算法练习,千锤百炼,静待花开。
Leetcode的专栏会持续更,因为在跟着英雄哥做知识星球的事儿:在lc被欺负的这些年;
对英雄哥的知识星球有兴趣的可以看看他这篇文章喔: 英雄算法联盟 | 31天让你的算法与众不同
但是英雄哥这儿了,五月不再招人啦,有想法的小伙伴浅等六月嗷~
单片机是会持续更的,但是硬件看到人比较少吧,或者我理解它不投策,这个更得慢:十四天学会51单片机;至于现在这个专栏只会记录全部是每日的算法题:知识星球每天的习题,以及在咱高校算法学习社区中,泡泡和p佬普及组和提高组的题目,一般是当天写当天更吧。现在优先写泡泡的题,p佬的有点小把握不住
好朋友执梗正在带新星计划了,有想法的小伙伴不要犹豫嗷
点击查看详情
第一题 P1095 [NOIP2007 普及组] 守望者的逃离
线性动态规划
题目描述
解题报告
注意题目中的信息,让咱们找到最短时间或者能够跑的最大距离。说人话就是 让咱们
找最优解
。再看看这个数据范围,暴力是没法暴力了,老老实实动态规划吧。
这个题可以不用闫式DP分析了吧。因为状态转移比较明显了,但还是浅用一下吧。
闫式DP是从集合的角度来分析动态规划的,主要有状态表示
和状态计算
两个环节。
状态表示
状态表示主要是对用于描述问题的这个集合f
进行定义,以及对这个集合f
最终存放的数据,也就是属性
进行确定。
——集合:f[i]表示在i这个时间内,可以跑的距离
——属性:最大值
状态计算
状态计算对应的是集合划分的过程,通常的玩法是选取最后一个不同点,其实也就是看上个状态转移到当前状态有没有不同的地方对于本题而言,本题是线性DP,当前状态和上个状态分别使用
f[i]
和f[i-1]
表示。
从f[i-1]
到f[i]
有三种情况
——① 剩余魔法值大于等于10,可使用技能,f[i] = f[i-1] + 60
——② 剩余魔法值小于10,不可使用技能,原地休息,此时没有位移,f[i] = f[i-1]
——③老老实实跑步,f[i] = f[i-1] + 17
将上述文字提炼一下,得到这张闫式DP分析图:
参考代码(C++版本)
#include
using namespace std;
typedef long long LL;
const int N = 3e5 + 10;
int f[N];//f[i]表示用i的时间,最多可以跑多远。
int m,s,t;//初始魔法值、到逃生口距离,所剩时间
int main()
{
scanf("%d %d %d",&m,&s,&t);
//优先考虑全部放技能闪烁
for(int i =1;i <= t;i++)
{
//放一次技能十点魔法值
if(m >= 10)
{
m -= 10;
f[i] = f[i-1] + 60;
}else//魔法值不够,原地休息
{
m += 4;
f[i] = f[i-1];
}
}
//将只是跑步的玩法揉进去,找个每个递推的最大值
for(int i = 1;i <= t;i++) f[i] = max(f[i],f[i-1]+17);
bool isSuccess = false;
for(int i = 0; i <= t;i++)
{
if(f[i] >= s)
{
isSuccess = true;
puts("Yes");
printf("%d\n",i);
break;
}
}
//用完时间了,都没有出去,输出NO
if(!isSuccess)
{
puts("No");
printf("%d\n",f[t]);
}
return 0;
}
第二题 1290. 二进制链表转整数
题目描述
解题报告
采用的常规模拟的思路,先求出当前链表的长度,再逐一的去取链表中每个结点的数据,把取出来的这位数据,转换成十进制数,累加起来,就是需要的结果。
思路挺简单的,效率差了一点。参考代码(C++版本)
/**
* 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:
int getDecimalValue(ListNode* head) {
//先扫一遍。统计一下链表的长度吧
ListNode* temp = head;
int n = 0;
while(temp)
{
temp = temp->next;
n ++;
}
//将链表中没一个结点的信息取出来,转成十进制
int ans = 0;
temp = head;
for(int i = n-1;i >= 0; i--)
{
if(temp != nullptr)
{
ans += (temp->val) * pow(2,i);
temp = temp->next;
}
}
return ans;
}
};
第三题 237. 删除链表中的节点
覆盖
题目描述
解题报告
不是常规思路,这种使用覆盖来实现删除的想法可以浅积累一下。
补充一下常规思路:
常见的玩法是定位到待删除
结点的上一个结点,比如它叫做prenode
,通过修改这个prenode
的next
指针,指向待删除结点的下一个结点,比如叫做nenode
,通过这种方式实现删除。
本题了,传入的参数是要被删除的结点,无法定位到上一个结点。因此不能使用常规的方法来实现了。
现在我们有的是node
结点,我们是可以通过next
指针来使用node
结点之后的数据的,也可以删除node
结点之后的数据。
我们可以删除node
结点的下一个结点,为了出现node被删除的效果
,可以让node
存储的数据变成其下一个结点存储的数据。参考代码(C++版本)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
//不是常规玩法了
node->val = node->next->val;
node->next = node->next->next;
}
};
第四题 剑指 Offer II 024. 反转链表
题目描述
解题报告
按照下面的小字吧,提供两种解法思路,一种是常规的
迭代法
,一种是递归法
,其中迭代的效率要高一点。
迭代法可以画图解,但是今天暂时没有时间,后续补上。参考代码(C++版本)
解法一:迭代法
/**
* 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* reverseList(ListNode* head) {
//特判
if(head == nullptr) return head;
//获取头指针的下一个结点
ListNode* move = head->next;
//断开头指针和该结点的连接
head->next = nullptr;
while(move)
{
//存储当前move结点的下一个结点的信息
ListNode* tmp = move->next;
//让move翻转指向其前一个结点
move->next = head;
//让头指针移动到move指针的位置
head = move;
//让move移动到其后面一个结点的位置
move = tmp;
}
return head;
}
};
解法二:递归法
/**
* 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* reverseList(ListNode* head) {
if(!head) return head;
//设置递归结束的条件
//也就是找到尾结点了
if(head->next == nullptr) return head;
ListNode* ans = reverseList(head->next);
head->next->next = head;//让身为尾结点的head->next指向其前一个结点head,构建出原本尾结点翻转指向前一个结点的新链表
//让新链表的尾结点指向null
head->next = nullptr;
//返回形成新链表
return ans;
}
};
第五题 1019. 链表中的下一个更大节点
题目描述
解题报告
因为直接操作链表也不太方便,就考虑将数据存储到数组中,直接操作数值吧。
这个题是使用的栈的想法,让当前栈从栈顶到栈底是严格单调递减
的,也就是假如新的元素一旦大于栈顶,就把当前的栈顶元素弹出。参考代码(C++版本)
/**
* 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:
vector<int> nextLargerNodes(ListNode* head) {
vector<int> ans;
ListNode* curr = head;
//把链表中的时间
while (curr != nullptr)
{
ans.push_back(curr->val);
curr = curr->next;
}
// 返回结果的数组,默认是0
int n = ans.size();
vector<int> res(n, 0);
// 单调栈中保存的是数组的索引,数值是递减,
stack<int> stk;
for (int i = 0; i < n; i++)
{
if (stk.empty()) stk.push(i);
else
{
while (!stk.empty() && ans[stk.top()] < ans[i])
{
// 更新更大的数值
res[stk.top()] = ans[i];
stk.pop();
}
stk.push(i);
}
}
return res;
}
};
①积累动态规划,因为动归了,有点吃自己的经验
② 今天链表的题大多数是可以根据题目意思进行模拟的,需要注意空链表的情况