LeetCode 每日一题
2678. 老人的数目
给你一个下标从 0 开始的字符串
details
。details
中每个元素都是一位乘客的信息,信息用长度为15
的字符串表示,表示方式如下:
- 前十个字符是乘客的手机号码。
- 接下来的一个字符是乘客的性别。
- 接下来两个字符是乘客的年龄。
- 最后两个字符是乘客的座位号。
请你返回乘客中年龄 严格大于 60 岁 的人数。
#解题思路和优化思路
这道题没有任何的难点 就是简单的遍历 统计60以上的人数
#Coding
class Solution {
public:
int countSeniors(vector<string>& details) {
int res=0;
for(auto& detail : details)
{
int age=(detail[11]-'0')*10+detail[12]-'0';
if(age>60) res++;
}
return res;
}
};
1155. 掷骰子等于目标和的方法数
这里有
n
个一样的骰子,每个骰子上都有k
个面,分别标号为1
到k
。给定三个整数
n
,k
和target
,返回可能的方式(从总共kn
种方式中)滚动骰子的数量,使正面朝上的数字之和等于target
。答案可能很大,你需要对
10^9 + 7
取模
#解题思路和优化思路
这道题需要用到的知识点是二维的动态规划 通过二维的动态规划来简化整道题的思路 我们设数组nums二维数组 下标 i j 表示 前 i 个数字组成的和为k的方法数 明白了下标的含义 再来推导递推公式 首先明白的一件事就是 每个骰子他的值都是大于等于1的 所以 当 i == j 即骰子数和总和相等时 他只有一种方法 再来看普通情况 每个骰子的值是1~k 即第i个骰子骰到总和为j的方法数为前i+1个骰子的总和是j-x 第i个骰子的数字为x 即 如果这样算他的最差情况就是O(N*N) 所以我们需要用到斜率优化来降低时间复杂度 通过规律 我们可以发现每一个点都是 从上一排的左边一个开始往左数k个 所以当我们算某一排的时候 可以借用到这一排的前一个来计算 当前位置=前一个位置加上前一个位置的上面一排(减去上一个位置的第一个数字如果存在的话)这样就完成了斜率优化了 把时间复杂度降低到了 O(N) 最后返回nums[n][target] 即为答案
#Coding
class Solution {
public:
int numRollsToTarget(int n, int k, int target) {
int mod=1e9+7;
vector<vector<long long>> nums(n+1,vector<long long>(target+1,0));
for(int i=1;i<=min(k,target);++i)
{
nums[1][i]=1;
}
for(int i=2;i<=n;++i)
{
for(int j=i;j<=target;++j)
{
if(j==i) nums[i][j]=1;
else
{
int subIdx=j-k-1;
int subVal=subIdx>=0?nums[i-1][subIdx]:0;
nums[i][j]=((nums[i-1][j-1]+nums[i][j-1]-subVal)+mod)%mod;
}
}
}
return (nums[n][target]+mod)%mod;
}
};
2698. 求一个整数的惩罚数
给你一个正整数
n
,请你返回n
的 惩罚数 。
n
的 惩罚数 定义为所有满足以下条件i
的数的平方和:
1 <= i <= n
i * i
的十进制表示的字符串可以分割成若干连续子字符串,且这些子字符串对应的整数值之和等于i
。
#解题思路和优化思路
这道题看到 题的时候就可以想到是暴力枚举回溯 通过枚举每种情况 最后返回可以符合的答案
#Coding
class Solution {
bool dfs(string &s, int pos, int tot, int target) {
if (pos == s.size()) return tot == target;
int sum = 0;
for (int i = pos; i < s.size(); i++)
{
sum = sum * 10 + s[i] - '0';
if (sum + tot > target) break;
if (dfs(s, i + 1, sum + tot, target)) return true;
}
return false;
}
public:
int punishmentNumber(int n) {
int res=0;
for(int i=1;i<=n;++i)
{
string s=to_string(i*i);
if(dfs(s,0,0,i)) res+=i*i;
}
return res;
}
};
2520. 统计能整除数字的位数
给你一个整数
num
,返回num
中能整除num
的数位的数目。如果满足
nums % val == 0
,则认为整数val
可以整除nums
#解题思路和优化思路
很简单的一道题 注意不要看错问题就好
#Coding
class Solution {
public:
int countDigits(int num) {
int temp=num;
int res=0;
while(num)
{
int d=num%10;
if(temp%d==0) res++;
num/=10;
}
return res;
}
};
1465. 切割后面积最大的蛋糕
矩形蛋糕的高度为
h
且宽度为w
,给你两个整数数组horizontalCuts
和verticalCuts
,其中:
horizontalCuts[i]
是从矩形蛋糕顶部到第i
个水平切口的距离verticalCuts[j]
是从矩形蛋糕的左侧到第j
个竖直切口的距离请你按数组
horizontalCuts
和verticalCuts
中提供的水平和竖直位置切割后,请你找出 面积最大 的那份蛋糕,并返回其 面积 。由于答案可能是一个很大的数字,因此需要将结果 对109 + 7
取余 后返回。
#解题思路和优化思路
这道题的核心思路就是统计每两条线之间的格子长度 所以我们预处理时 需要将第一个格子的长度和最后一个格子的长度计算出来 再通过遍历两个数组计算出来中间格子的长度 最后返回最宽格子*最长格子就是答案
#Coding
class Solution {
public:
int maxArea(int h, int w, vector<int>& horizontalCuts, vector<int>& verticalCuts){
sort(horizontalCuts.begin(),horizontalCuts.end());
sort(verticalCuts.begin(),verticalCuts.end());
horizontalCuts.push_back(h);
verticalCuts.push_back(w);
int maxRowDiff=horizontalCuts[0]-0,maxColDiff=verticalCuts[0]-0;
const int mod=1e9+7;
for(int i=1;i<horizontalCuts.size();++i)
{
maxRowDiff=max(maxRowDiff,horizontalCuts[i]-horizontalCuts[i-1]);
}
for(int i=1;i<verticalCuts.size();++i)
{
maxColDiff=max(maxColDiff,verticalCuts[i]-verticalCuts[i-1]);
}
return (int)(((long long )maxColDiff*maxRowDiff)%mod);
}
};
2558. 从数量最多的堆取走礼物
给你一个整数数组
gifts
,表示各堆礼物的数量。每一秒,你需要执行以下操作:
- 选择礼物数量最多的那一堆。
- 如果不止一堆都符合礼物数量最多,从中选择任一堆即可。
- 选中的那一堆留下平方根数量的礼物(向下取整),取走其他的礼物。
返回在
k
秒后剩下的礼物数量。
#解题思路和优化思路
按照题意模拟过程 由于需要用到最大值 所以为了简化时间复杂度 我们需要使用堆来简化算法 每次将堆顶弹出并且留下(int)sqrt(堆顶值) 剩余全部拿走 模拟k次就是答案
#Coding
class Solution {
public:
long long pickGifts(vector<int>& gifts, int k) {
long long res=0;
priority_queue<int> maxQ;
for(int i=0;i<gifts.size();++i)
{
maxQ.push(gifts[i]);
res+=gifts[i];
}
while(k)
{
int val=maxQ.top();
maxQ.pop();
res-=val-(int)sqrt(val);
maxQ.push((int)sqrt(val));
k--;
}
return res;
}
};
274. H 指数
给你一个整数数组
citations
,其中citations[i]
表示研究者的第i
篇论文被引用的次数。计算并返回该研究者的h
指数。根据维基百科上 h 指数的定义:
h
代表“高引用次数” ,一名科研人员的h
指数 是指他(她)至少发表了h
篇论文,并且每篇论文 至少 被引用h
次。如果h
有多种可能的值,h
指数 是其中最大的那个。
#解题思路和优化思路
这道题是使用二分法来解题 通过二分来快速的计算出符合题目要求的最大值 这道题需要注意的是数组的长度 由于最多有数组长度的值 所以最后需要和 数组长度取一个最小值作为答案返回
#Coding
class Solution {
bool check(int k,vector<int>& citations)
{
int res=0;
for(int i=0;i<citations.size();++i)
{
if(citations[i]>=k) res++;
}
return res>=k;
}
public:
int hIndex(vector<int>& citations) {
int left=*min_element(citations.begin(),citations.end());
int right=*max_element(citations.begin(),citations.end());
while(left<right)
{
int mid=(left+right+1)/2;
if(check(mid,citations)) left=mid;
else right=mid-1;
}
return min(left,(int)citations.size());
}
};
LeetCode周赛题目(第368场周赛)
2908. 元素和最小的山形三元组 I
如果下标三元组
(i, j, k)
满足下述全部条件,则认为它是一个 山形三元组 :
i < j < k
nums[i] < nums[j]
且nums[k] < nums[j]
请你找出
nums
中 元素和最小 的山形三元组,并返回其 元素和 。如果不存在满足条件的三元组,返回-1
。
#解题思路
对于T1这种级别的题目 他要求都会很松跨 所以我们不需要特别的在乎时间复杂度 怎么简单怎么来就好了 T2会有优化的代码呈现出来
#Coding
class Solution {
public:
int minimumSum(vector<int>& nums) {
int res=INT_MAX;
for(int i=0;i<nums.size();++i)
{
for(int j=i+1;j<nums.size();++j)
{
for(int k=j+1;k<nums.size();++k)
{
if(nums[i]<nums[j] && nums[k]<nums[j])
{
res=min(res,nums[i]+nums[j]+nums[k]);
}
}
}
}
return res==INT_MAX?-1:res;
}
};
2910. 合法分组的最少组数
给你一个长度为
n
下标从 0 开始的整数数组nums
。我们想将下标进行分组,使得
[0, n - 1]
内所有下标i
都 恰好 被分到其中一组。如果以下条件成立,我们说这个分组方案是合法的:
- 对于每个组
g
,同一组内所有下标在nums
中对应的数值都相等。- 对于任意两个组
g1
和g2
,两个组中 下标数量 的 差值不超过1
。请你返回一个整数,表示得到一个合法分组方案的 最少 组数。
#解题思路
这道题的基本思路就是统计每一个数字的个数 然后将他们分组 使得符合题意 但是有一个注意的是 例如 [1,1,1,3,3,3,3,4,4,4,4,4] 这个数组 他们的个数分别是 3 4 5 如果按照题意 5会被分成 2 3 或者 1 4 其中 1 和 2 的个数差和4差不止一个 所以违背了题意 我们需要想一个办法 来解决这个问题 我们可以从数组数字出现的最小值 开始枚举一直到1 设这个数字为 k 每个数组可以分成 k 或者 k+1为一组 则这个数组的每个数字的个数可以表达成为 n=k*a+b 其中b=n mod k 如果a>=b 则有 b个组 可以变成 k+1一组 反之则不可能分解成功 所以我们可以枚举k 直到最后可以得到答案就返回
#Coding
class Solution {
public:
int minGroupsForValidAssignment(vector<int>& nums) {
unordered_map<int,int> umap;
int res=0;
umap.clear();
for(int i=0;i<nums.size();++i)
{
umap[nums[i]]++;
}
int minNum=INT_MAX;
for(auto& i :umap)
{
minNum=min(minNum,i.second);
}
cout<<minNum;
for(;;minNum--)
{
res=0;
for(auto& i : umap)
{
if(i.second/minNum < i.second%minNum)
{
res=0;
break;
}
res+=(i.second+minNum)/(minNum+1);
}
if(res) return res;
}
return res;
}
};
2909. 元素和最小的山形三元组 II
给你一个下标从 0 开始的整数数组
nums
。如果下标三元组
(i, j, k)
满足下述全部条件,则认为它是一个 山形三元组 :
i < j < k
nums[i] < nums[j]
且nums[k] < nums[j]
请你找出
nums
中 元素和最小 的山形三元组,并返回其 元素和 。如果不存在满足条件的三元组,返回-1
。
#解题思路
这道题就是T1的优化版本 T2会把时间复杂度卡的很死 所以我们用第一道题的思路就会TLE的 所以我们需要优化一下思路 这道题很像前面周赛碰到的那道题的思路 我们可以用一个数组来统计后缀最小的数 suffix 数组 每一个位置都来记录从当前位置到数组末尾的数组最小值 再用一个pre变量来记录前面的最小值 这样 我们只需要枚举中间值就好了 如果枚举的当前值大于前面的值 并且 大于后面的值 就可以记录当前的和 最后返回最小和就是答案
#Coding
class Solution {
public:
int minimumSum(vector<int>& nums) {
int n=nums.size();
vector<int> suffix(n,0);
suffix[n-1]=nums[n-1];
for(int i=n-2;i>1;--i)
{
suffix[i]=min(suffix[i+1],nums[i]);
}
int res=INT_MAX;
int pre=nums[0];
for(int i=1;i<n-1;++i)
{
if(nums[i]>pre && nums[i]>suffix[i+1])
res=min(res,pre+nums[i]+suffix[i+1]);
pre=min(pre,nums[i]);
}
return res==INT_MAX?-1:res;
}
};
2911. 得到 K 个半回文串的最少修改次数
给你一个字符串
s
和一个整数k
,请你将s
分成k
个 子字符串 ,使得每个 子字符串 变成 半回文串 需要修改的字符数目最少。请你返回一个整数,表示需要修改的 最少 字符数目。
注意:
- 如果一个字符串从左往右和从右往左读是一样的,那么它是一个 回文串 。
- 如果长度为
len
的字符串存在一个满足1 <= d < len
的正整数d
,len % d == 0
成立且所有对d
做除法余数相同的下标对应的字符连起来得到的字符串都是 回文串 ,那么我们说这个字符串是 半回文串 。比方说"aa"
,"aba"
,"adbgad"
和"abab"
都是 半回文串 ,而"a"
,"ab"
和"abca"
不是。- 子字符串 指的是一个字符串中一段连续的字符序列。
#解题思路
T4的难度很大 需要用很多的方法才能到dp的那一步 首先是处理 d 我们可以利用埃及筛素数的方法来加速算法速度 即每个质数每个数乘n 直到他大于max 后面就是解决半回文串的问题 根据题目i的意思 可以取两个值 left 和 right 他们对于 d 是同余数的如果他俩不一项 cnt就需要加1 返回最小的cnt数字作为 left 到 right的最小修改数 后面就是动态规划来取得最小值了 这属于分块dp类型 即 我们可以 把问题一步一步缩小 把[0~n] 的问题一步一步分解 直到k等于0 说明分解完了
#Coding
class Solution {
const int MAX=201;
vector<vector<int>> divisors;
void init()
{
for(int i=1;i<MAX;++i)
{
for(int j=2*i;j<MAX;j+=i)
{
divisors[j].push_back(i);
}
}
}
int getModify(string s)
{
int n=s.size();
int res=n;
for(int d : divisors[n])
{
int cnt=0;
for(int i=0;i<d;++i)
{
for(int left=i,right=n-d+left;left<right;left+=d,right-=d)
{
cnt+=s[left]!=s[right];
}
}
res=min(res,cnt);
}
return res;
}
int dfs(vector<vector<int>>& memo,vector<vector<int>>& modify,string s,int k,int right)
{
if(k==0) return modify[0][right];
int& res=memo[k][right];
if(res<=s.size()) return res;
for(int left=2*k;left<right;++left)
{
res=min(res,dfs(memo,modify,s,k-1,left-1)+modify[left][right]);
}
return res;
}
public:
int minimumChanges(string s, int k) {
int n=s.size();
divisors.resize(MAX);
init();
vector<vector<int>> modify(n-1,vector<int>(n));
for(int i=0;i<n-1;++i)
{
for(int j=i+1;j<n;++j)
{
int len=j-i+1;
modify[i][j]=getModify(s.substr(i,len));
}
}
vector<vector<int>> memo(k,vector<int>(n,n+1));
return dfs(memo,modify,s,k-1,n-1);
}
};
LeetCode专项练习(dp数组)
62. 不同路径
一个机器人位于一个
m x n
网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
#解题思路和优化思路
最经典的二维dp数组 对于网格中的每一个点 都有从上面向下走一步到达到前位置 和 向右走一步到达当前位置 对于第一行和第一列的所有点都只有一种方法 普通点都是2种方法 即递推公式是
最后返回 dp[m-1][n-1] 就是答案
#Coding
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> steps(m,vector<int>(n,0));
for(int i=0;i<m;++i)
{
steps[i][0]=1;
}
for(int i=0;i<n;++i)
{
steps[0][i]=1;
}
for(int i=1;i<m;++i)
{
for(int j=1;j<n;++j)
{
steps[i][j]=steps[i-1][j]+steps[i][j-1];
}
}
return steps[m-1][n-1];
}
};
63. 不同路径 II
一个机器人位于一个
m x n
网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用
1
和0
来表示。
#解题思路和优化思路
上一道题的条件版本 和上一道题几乎一模一样的解法 只需要注意障碍物会拦截机器人的某些路段 导致需要到这个节点的所有路都不通
#Coding
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m=obstacleGrid.size(),n=obstacleGrid[0].size();
vector<vector<int>> dp(m,vector<int>(n,0));
dp[0][0]=obstacleGrid[0][0]==1?0:1;
for(int i=1;i<m;++i)
{
dp[i][0]=obstacleGrid[i][0]==1?0:dp[i-1][0];
}
for(int i=1;i<n;++i)
{
dp[0][i]=obstacleGrid[0][i]==1?0:dp[0][i-1];
}
for(int i=1;i<m;++i)
{
for(int j=1;j<n;++j)
{
dp[i][j]=obstacleGrid[i][j]==1?0:dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
}
};
64. 最小路径和
给定一个包含非负整数的
m x n
网格grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向下或者向右移动一步。
#解题思路和优化思路
还是以机器人为模板的题 每个点需要依赖自己的左边和自己的上面 选择一个较小的和自己位置的值相加作为当前路径的最小值
#Coding
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m=grid.size(),n=grid[0].size();
vector<vector<int>> dp(m,vector<int>(n,0));
dp[0][0]=grid[0][0];
for(int i=1;i<m;++i)
{
dp[i][0]=dp[i-1][0]+grid[i][0];
}
for(int i=1;i<n;++i)
{
dp[0][i]=dp[0][i-1]+grid[0][i];
}
for(int i=1;i<m;++i)
{
for(int j=1;j<n;++j)
{
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[m-1][n-1];
}
};
55. 跳跃游戏
给你一个非负整数数组
nums
,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标,如果可以,返回
true
;否则,返回false
。
#解题思路和优化思路
这道题最开始想到的就是dp数组 但是他不是传统意义的dp数组 他是依赖前面所有的数来判断当前位置是否可以到达 但是这样的时间复杂度太大了 这道题是有贪心解法的 我们计算maxRight 如果来到一个maxRight来不到的位置直接返回false 每个点都计算maxRight的值 如果maxRight的值大于等于最后一个下标 则直接返回正确
#Coding
class Solution {
public:
bool canJump(vector<int>& nums) {
//dp数组
vector<bool> dp(nums.size(),false);
dp[0]=true;
for(int i=0;i<nums.size();++i)
{
for(int j=i-1;j>=0;--j)
{
if(!dp[j]) continue;
if(nums[j]+j>=i)
{
dp[i]=true;
break;
}
}
}
return dp[nums.size()-1];
//贪心优化
int maxRight=0;
for(int i=0;i<nums.size();++i)
{
if(maxRight<i) return false;
maxRight=max(maxRight,i+nums[i]);
if(maxRight>=nums.size()-1) return true;
}
return maxRight>=nums.size()-1;
}
};
45. 跳跃游戏 II
给定一个长度为
n
的 0 索引整数数组nums
。初始位置为nums[0]
。每个元素
nums[i]
表示从索引i
向前跳转的最大长度。换句话说,如果你在nums[i]
处,你可以跳转到任意nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达
nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达nums[n - 1]
。
#解题思路和优化思路
和上面的思路如出一辙
#Coding
class Solution {
public:
int jump(vector<int>& nums) {
vector<int> dp(nums.size(),INT_MAX);
dp[0]=0;
for(int i=0;i<nums.size();++i)
{
for(int j=i-1;j>=0;--j)
{
if(j+nums[j]>=i) dp[i]=min(dp[i],dp[j]+1);
}
}
return dp[nums.size()-1];
int maxPos = 0, n = nums.size(), end = 0, step = 0;
for (int i = 0; i < n - 1; ++i)
{
if (maxPos >= i)
{
maxPos = max(maxPos, i + nums[i]);
if (i == end)
{
end = maxPos;
++step;
}
}
}
return step;
}
};
53. 最大子数组和
给你一个整数数组
nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分
#解题思路和优化思路
这道题被优化的很经典 就是用一个cur 来遍历数组当 cur<0 时 让cur 回到0 这样就可以局部最优达到全局最优解
#Coding
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int cur=0,res=0;
for(int i=0;i<nums.size();++i)
{
cur+=nums[i];
if(cur<0) cur=0;
res=max(res,cur);
}
int maxValue=*max_element(nums.begin(),nums.end());
if(maxValue<=0) return maxValue;
return res;
}
};
22. 括号生成
数字
n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合
#解题思路和优化思路
这道题利用回溯的方法很好解题 判断左括号是否还有以及右括号的存在个数小于左括号的个数来回溯 终止条件就是 长度为2n长度
#Coding
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;
}
};
91. 解码方法
一条包含字母
A-Z
的消息通过以下映射进行了 编码 :'A' -> "1" 'B' -> "2" ... 'Z' -> "26"要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,
"11106"
可以映射为:
"AAJF"
,将消息分组为(1 1 10 6)
"KJF"
,将消息分组为(11 10 6)
注意,消息不能分组为
(1 11 06)
,因为"06"
不能映射为"F"
,这是由于"6"
和"06"
在映射中并不等价。给你一个只含数字的 非空 字符串
s
,请计算并返回 解码 方法的 总数 。题目数据保证答案肯定是一个 32 位 的整数。
#解题思路和优化思路
这道题上来的想法就是回溯 像这周的每日一题一样 但是他的时间复杂度很高 会导致TLE 所以需要dp来优化时间复杂度 根据我们的回溯算法 我们需要反着来完成它一遍 每一个位置会依赖他的前面一个或者两个位置的值 最后写下dp的递推公式就是答案
#Coding
class Solution {
//回溯法
int dfs(string& s,int cur)
{
if(s[cur]=='0') return 0;
if(cur==s.size()) return 1;
if(cur==s.size()-1) return s[cur]>='1';
int curVal=s[cur]-'0';
if(curVal==1) return dfs(s,cur+1)+dfs(s,cur+2);
else if(curVal==2 && s[cur+1]<='6') return dfs(s,cur+1)+dfs(s,cur+2);
else return dfs(s,cur+1);
}
public:
//dp优化
int numDecodings(string s) {
int n=s.size();
vector<int> dp(n,0);
dp[0]=s[0]=='0'?0:1;
for(int i=1;i<n;++i)
{
if(s[i]=='0' && s[i-1]=='0') return 0;
if(s[i]=='0' && s[i-1]>'2') return 0;
dp[i]=dp[i-1];
if(s[i]!='0' && (i<s.size() && s[i+1]!='0') &&
(s[i-1]=='1' || (s[i-1]=='2' && s[i]<='6')))
dp[i]+=i>1?dp[i-2]:1;
}
return dp[n-1];
}
};
120. 三角形最小路径和
给定一个三角形
triangle
,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标
i
,那么下一步可以移动到下一行的下标i
或i + 1
。
#解题思路和优化思路
一颗二叉树可以被看做一个三角形 每一层依赖上一层的同一列和上一列 即 我们可以把二维数组优化成滚动数组 即一维数组 来减少数组的开支 因为每一个位置都依赖自己的左上位置 所以我们可以把数组从右往左进行更新 使其达到正确的目的
#Coding
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int n=triangle.size();
vector<int> dp(n,0);
for(int i=0;i<n;++i)
{
for(int j=i;j>=0;--j)
{
if(i && i==j) dp[j]=dp[j-1]+triangle[i][j];
else if(j) dp[j]=min(dp[j-1],dp[j])+triangle[i][j];
else dp[j]=dp[j]+triangle[i][j];
}
}
return *min_element(dp.begin(),dp.end());
}
};
139. 单词拆分
给你一个字符串
s
和一个字符串列表wordDict
作为字典。请你判断是否可以利用字典中出现的单词拼接出s
。注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
#解题思路和优化思路
这道题就是暴力的取每一个可能性 来计算是否可以成功
#Coding
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet;
for(auto& word : wordDict)
{
wordSet.insert(word);
}
vector<bool> dp(s.size()+1);
dp[0]=true;
for(int i=1;i<=s.size();++i)
{
for(int j=0;j<i;++j)
{
if(dp[j] && wordSet.find(s.substr(j,i-j))!=wordSet.end())
{
dp[i]=true;
break;
}
}
}
return dp[s.size()];
}
};
122. 买卖股票的最佳时机 II
给你一个整数数组
prices
,其中prices[i]
表示某支股票第i
天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
#解题思路和优化思路
这道题就是一道典型的动态规划题目 我们可以构造一个2维数组来记录每一天的情况 每一列有两行 分别表示 持有股 不持有股 两种状态 每一个位置的计算都需要前一天的两个位置 当天没有股票是否购买 或者当天有股票是否卖出 后续可以看出这道题可以使用贪心算法 即每个小段可能存在局部增长函数 即在[l,r] 这一段存在 price[i]>=price[i-1] 我们需要寻找所有的这样的区间 最后让他们求和 就是整个区间的利润值
#Coding
class Solution {
public:
int maxProfit(vector<int>& prices) {
//dynamicProgramming
int n=prices.size();
vector<vector<int>> dp(n,vector<int>(2,0));
dp[0][1]=-prices[0];
for(int i=1;i<n;++i)
{
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
}
return dp[n-1][0];
//nonDynamicProgramming
int res=0;
for(int i=1;i<prices.size();++i)
{
if(prices[i]>=prices[i-1]) res+=prices[i]-prices[i-1];
}
return res;
}
};
96. 不同的二叉搜索树
给你一个整数
n
,求恰由n
个节点组成且节点值从1
到n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
#解题思路和优化思路
我们不需要关注他是不是二叉搜索树 我们只需要看 他的左子树有多少情况 右子树有多少情况 然后相乘就是答案 由于根节点会占据一个节点 所以我们需要令n-1 让左子树的个数从 0~n-1 进行遍历 所以每一个n会依赖前面的位置的数字 即 我们可以使用dp来递推出每一个位置的值
#Coding
class Solution {
public:
int numTrees(int n) {
if(n==1) return 1;
if(n==2) return 2;
vector<int> dp(n+1,0);
dp[0]=1;
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;++i)
{
for(int j=0;j<i;++j)
{
dp[i]+=dp[j]*dp[i-j-1];
}
}
return dp[n];
}
};
97. 交错字符串
给定三个字符串
s1
、s2
、s3
,请你帮忙验证s3
是否是由s1
和s2
交错 组成的。两个字符串
s
和t
交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:
s = s1 + s2 + ... + sn
t = t1 + t2 + ... + tm
|n - m| <= 1
- 交错 是
s1 + t1 + s2 + t2 + s3 + t3 + ...
或者t1 + s1 + t2 + s2 + t3 + s3 + ...
注意:
a + b
意味着字符串a
和b
连接
#解题思路和优化思路
看到这道题 很容易会想到使用动态规划来解题 我们构造一个二维dp数组 其下标 i j 分别表示 s1的前i个字母 和 s2的前j个字母 是否可以构成 s3的 前 i+j 个 字符 假设 s1的第i个字符和s3的i+j个字符一样 整个数组是否相同就需要 s1的前i-1个字母和s2的前j个字母是否可以构成s3的前i+j-1个字母 s2也是相同的 即 dp[i][j]=dp[i-1][j]&&s1[i]==s3[i+j] || dp[i][j-1] && s2[j]==s3[i+j] 递推公式推出来之后 我们就需要计算边界条件 即 dp[0][0] 的值 很容易发现 他一定是 true 其余的只需要根据递推公式 把矩阵填满就好了
#Coding
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
int len1=s1.size(),len2=s2.size(),len3=s3.size();
if(len1+len2!=len3) return false;
vector<vector<int>> dp(len1+1,vector<int>(len2+1,0));
dp[0][0]=1;
for(int i=0;i<=len1;++i)
{
for(int j=0;j<=len2;++j)
{
int pos=i+j-1;
if(i) dp[i][j] |= (dp[i-1][j] && s1[i-1]==s3[pos]);
if(j) dp[i][j] |= (dp[i][j-1] && s2[j-1]==s3[pos]);
}
}
return dp[len1][len2];
}
};
给你一个字符串
s
,请你将s
分割成一些子串,使每个子串都是 回文串 。返回s
所有可能的分割方案。回文串 是正着读和反着读都一样的字符串。
#解题思路和优化思路
这道题的预处理需要用到动态规划 数组的下标 i j 表示从i到j是否是回文串 预处理的时候 从i到j有 i位置和 j 位置的字母相同并且 s 从 i+1 到 j-1 位置也是 回文串 此时 s的i到j位置是回文串 即dp[i][j]=dp[i+1][j-1] && s[i]==s[j] 由于计算的时候需要 dp[i+1][j-1] 我们需要先一步计算出i+1的值 所以我们从下往上 最左往右填表 最后在深度遍历一遍字符串 就能返回答案
#Coding
class Solution {
vector<vector<string>> res;
vector<string> stringArr;
vector<vector<int>> dp;
void dfs(string& s,int idx)
{
if(idx==s.size())
{
res.push_back(stringArr);
return;
}
for(int i=idx;i<s.size();++i)
{
if(dp[idx][i])
{
stringArr.push_back(s.substr(idx,i-idx+1));
dfs(s,i+1);
stringArr.pop_back();
}
}
}
public:
vector<vector<string>> partition(string s) {
int n=s.size();
dp.assign(n,vector<int>(n,true));
for(int i=n-1;i>=0;--i)
{
for(int j=i+1;j<n;++j)
{
dp[i][j]=s[i]==s[j] && dp[i+1][j-1];
}
}
dfs(s,0);
return res;
}
};
2896. 执行操作使两个字符串相等
给你两个下标从 0 开始的二进制字符串
s1
和s2
,两个字符串的长度都是n
,再给你一个正整数x
。你可以对字符串
s1
执行以下操作 任意次 :
- 选择两个下标
i
和j
,将s1[i]
和s1[j]
都反转,操作的代价为x
。- 选择满足
i < n - 1
的下标i
,反转s1[i]
和s1[i + 1]
,操作的代价为1
。请你返回使字符串
s1
和s2
相等的 最小 操作代价之和,如果无法让二者相等,返回-1
。注意 ,反转字符的意思是将
0
变成1
,或者1
变成0
。
#解题思路和优化思路
将不同的位数放入数组中 对于每一个位置 都有两种变化 第一种 和后面的某一位发生变化 所以这一位消耗数目平均下来就是 x/2 第二种 即 和相邻的都变化 消耗位为 diff[i+1]-dff[i] 所以递推方程就是 算到最后即为答案
#Coding
class Solution {
public:
int minOperations(string s1, string s2, int x) {
if(s1==s2) return 0;
vector<int> diffArr;
for(int i=0;i<s1.size();++i)
{
if(s1[i]!=s2[i]) diffArr.push_back(i);
}
int n=diffArr.size();
if(diffArr.size()%2) return -1;
int f0=0,f1=x;
for(int i=0;i<n-1;++i)
{
int f2=min(f1+x,f0+(diffArr[i+1]-diffArr[i])*2);
f0=f1;
f1=f2;
}
return f1/2;
}
};
152. 乘积最大子数组
给你一个整数数组
nums
,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。测试用例的答案是一个 32-位 整数。
子数组 是数组的连续子序列。
#解题思路和优化思路
这道题和连续数组的最大和很像 但是又不是一样的思路 因为如果有偶数个负数 前面是奇数个负数 其值为负数 即不是前面的最优解 无法带到当前位置 我们直到 一个负数 乘 一个越小的数字 其结果越大 所以 我们可以记录两个数值 前面的最小乘积 和 前面的最大乘积 最终返回最优解
#Coding
class Solution {
public:
int maxProduct(vector<int>& nums) {
int maxValue=nums[0],minValue=nums[0];
int res=nums[0];
for(int i=1;i<nums.size();++i)
{
int maxv=maxValue,minv=minValue;
maxValue=max(nums[i],max(maxv*nums[i],minv*nums[i]));
minValue=min(nums[i],min(maxv*nums[i],minv*nums[i]));
res=max(res,maxValue);
}
return res;
}
};
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
#解题思路和优化思路
很经典的dp问题 有点像贪心算法 每一个位置都可以有两种情况 偷 或者 不偷 如果不偷 当前位置的最大值就是 上一个位置的值 如果偷 则 当前位置的最大值就是 上上个位置的值加上当前位置的数字 取最大值就是当前位置的最大值
#Coding
class Solution {
public:
int rob(vector<int>& nums) {
vector<int> dp(nums.size(),0);
if(nums.size()==1) return nums[0];
if(nums.size()==2) return max(nums[0],nums[1]);
dp[0]=nums[0];
dp[1]=max(nums[0],nums[1]);
for(int i=2;i<nums.size();++i)
{
dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[nums.size()-1];
}
};
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额
#解题思路和优化思路
这道题就是上一道题的加强版 我们可以把数组扩大成为2维的 拿了0位置 和 没有拿0位置两个维度 如果拿了0位置 不能再拿最后一个位置的 所以我们返回倒数第二个位置的最大值 没有拿0位置的 正常判断到最后一个位置 两者比大小 返回较大的值为答案
#Coding
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size()==1) return nums[0];
else if(nums.size()==2) return max(nums[0],nums[1]);
vector<vector<int>> dp(nums.size(),vector<int>(2,0));
dp[0][0]=nums[0];
dp[1][0]=nums[0];
dp[1][1]=nums[1];
for(int i=2;i<nums.size();++i)
{
dp[i][0]=max(dp[i-1][0],dp[i-2][0]+nums[i]);
dp[i][1]=max(dp[i-1][1],dp[i-2][1]+nums[i]);
}
return max(dp[nums.size()-2][0],dp[nums.size()-1][1]);
}
};
在一个由
'0'
和'1'
组成的二维矩阵内,找到只包含'1'
的最大正方形,并返回其面积。
#解题思路和优化思路
这道题需要用到动态规划的知识来加速 我们可以从上往下 从左往右的遍历数组 我们考虑每一个点为矩阵的右下角的点 即当前位置的最大矩阵满足min(上边连续1的个数,左边连续1的个数,左上角连续的1的个数)+1 考虑原因 如果左上角有一个矩阵大小为k 且左上角为右下角下标 当前位置作为上一个矩阵的的延申 需要满足当前位置左边的点 和 上面的点 都需要大于等于左上角矩阵的边长 否则 只能够做min(左边连续1的个数 上边连续1的个数)+1 的边长矩阵
#Coding
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
int m=matrix.size(),n=matrix[0].size();
int maxValue=0;
if(m==0 || n==0) return 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(min(dp[i][j-1],dp[i-1][j]),dp[i-1][j-1])+1;
maxValue=max(maxValue,dp[i][j]);
}
}
}
return maxValue*maxValue;
}
};
洛谷算法1-1
P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布
题目描述
石头剪刀布是常见的猜拳游戏:石头胜剪刀,剪刀胜布,布胜石头。如果两个人出拳一样,则不分胜负。在《生活大爆炸》第二季第 8 集中出现了一种石头剪刀布的升级版游戏。
升级版游戏在传统的石头剪刀布游戏的基础上,增加了两个新手势:
斯波克:《星际迷航》主角之一。
蜥蜴人:《星际迷航》中的反面角色。
这五种手势的胜负关系如表一所示,表中列出的是甲对乙的游戏结果。
现在,小 A 和小 B 尝试玩这种升级版的猜拳游戏。已知他们的出拳都是有周期性规律的,但周期长度不一定相等。例如:如果小 A 以“石头-布-石头-剪刀-蜥蜴人-斯波克”长度为 6 的周期出拳,那么他的出拳序列就是“石头-布-石头-剪刀-蜥蜴人-斯波克-石头-布-石头-剪刀-蜥蜴人-斯波克-......”,而如果小 B 以“剪刀-石头-布-斯波克-蜥蜴人”长度为 5 的周期出拳,那么他出拳的序列就是“剪刀-石头-布-斯波克-蜥蜴人-剪刀-石头-布-斯波克-蜥蜴人-......”
已知小 A 和小 B 一共进行 N 次猜拳。每一次赢的人得 1 分,输的得 0 分;平局两人都得 0 分。现请你统计 N 次猜拳结束之后两人的得分。
#解题思路和优化思路
这道题的思路就是根据表格来模拟石头剪刀布的过程 很简单的一道题 只需要确定好每个个体对应的输赢就好了
#Coding
P1328 石头剪刀布
#include<bits/stdc++.h>
using namespace std;
int point1=0,point2=0;
void battleTime(int a,int b)
{
if(a==b) return;
if(a==0)
{
if(b==2 || b==3) point1++;
else point2++;
}
else if(a==1)
{
if(b==0 || b==3) point1++;
else point2++;
}
else if(a==2)
{
if(b==1 || b==4) point1++;
else point2++;
}
else if(a==3)
{
if(b==4 || b==2) point1++;
else point2++;
}
else
{
if(b==0 || b==1) point1++;
else point2++;
}
}
int main()
{
int k,m,n;
cin>>k>>m>>n;
vector<int> nums1(m),nums2(n);
for(int i=0;i<m;++i)
{
cin>>nums1[i];
}
for(int i=0;i<n;++i)
{
cin>>nums2[i];
}
for(int i=0;i<k;++i)
{
int val1=nums1[i%m],val2=nums2[i%n];
battleTime(val1,val2);
}
cout<<point1<<" "<<point2;
return 0;
}
P4924 [1007] 魔法少女小Scarlet
题目描述
Scarlet 最近学会了一个数组魔法,她会在 n\times nn×n 二维数组上将一个奇数阶方阵按照顺时针或者逆时针旋转 90^\circ90∘。
首先,Scarlet 会把 11 到 n^2n2 的正整数按照从左往右,从上至下的顺序填入初始的二维数组中,然后她会施放一些简易的魔法。
Scarlet 既不会什么分块特技,也不会什么 Splay 套 Splay,她现在提供给你她的魔法执行顺序,想让你来告诉她魔法按次执行完毕后的二维数组。
#解题思路和优化思路
这道题就是一个矩阵的旋转应用题 我们选中 2r+1阶的方阵顺时针或者逆时针的旋转 只需要抓住他们之间的几何关系 这道题就很好处理
#Coding
#include<bits/stdc++.h>
using namespace std;
void clockWise(int x,int y,int r,vector<vector<int>>& nums)
{
int n=nums.size();
vector<vector<int>> temp(n,vector<int>(n,INT_MAX));
while(r>0)
{
for(int i=0;i<2*r;++i)
{
temp[x-r][y-r+i]=nums[x-r+i][y+r];
}
for(int i=0;i<2*r;++i)
{
temp[x-r+i][y+r]=nums[x+r][y+r-i];
}
for(int i=0;i<2*r;++i)
{
temp[x+r][y+r-i]=nums[x+r-i][y-r];
}
for(int i=0;i<2*r;++i)
{
temp[x+r-i][y-r]=nums[x-r][y-r+i];
}
r--;
}
for(int i=0;i<n;++i)
{
for(int j=0;j<n;++j)
{
if(temp[i][j]!=INT_MAX) nums[i][j]=temp[i][j];
}
}
}
void antiClockWise(int x,int y,int r,vector<vector<int>>& nums)
{
int n=nums.size();
vector<vector<int>> temp(n,vector<int>(n,INT_MAX));
while(r>0)
{
for(int i=0;i<2*r;++i)
{
temp[x-r+i][y+r]=nums[x-r][y-r+i];
}
for(int i=0;i<2*r;++i)
{
temp[x+r][y+r-i]=nums[x-r+i][y+r];
}
for(int i=0;i<2*r;++i)
{
temp[x+r-i][y-r]=nums[x+r][y+r-i];
}
for(int i=0;i<2*r;++i)
{
temp[x-r][y-r+i]=nums[x+r-i][y-r];
}
r--;
}
for(int i=0;i<n;++i)
{
for(int j=0;j<n;++j)
{
if(temp[i][j]!=INT_MAX) nums[i][j]=temp[i][j];
}
}
}
int main()
{
int n,m;
cin>>n>>m;
vector<vector<int>> nums(n,vector<int>(n));
int cur=1;
for(int i=0;i<n;++i)
{
for(int j=0;j<n;++j)
{
nums[i][j]=cur++;
}
}
for(int i=0;i<m;++i)
{
int x,y,r,z;
cin>>x>>y>>r>>z;
x-=1,y-=1;
if(z==1) clockWise(x,y,r,nums);
else antiClockWise(x,y,r,nums);
}
for(int i=0;i<n;++i)
{
for(int j=0;j<n;++j)
{
if(j) cout<<" ";
cout<<nums[i][j];
}
cout<<endl;
}
return 0;
}
P1009 [NOIP1998 普及组] 阶乘之和
题目描述
用高精度计算出 S = 1! + 2! + 3! + \cdots + n!S=1!+2!+3!+⋯+n!(n \le 50n≤50)。
其中
!
表示阶乘,定义为 n!=n\times (n-1)\times (n-2)\times \cdots \times 1n!=n×(n−1)×(n−2)×⋯×1。例如,5! = 5 \times 4 \times 3 \times 2 \times 1=1205!=5×4×3×2×1=120。
#解题思路和优化思路
接下来的三道题思路都非常的相似 因为数据太过大 所以没有任何数据类型可以把他们存放(c++) 所以我们需要利用数组来存放他们 思路就是将数字转换成为 n 个数字组成的数组 再来计算 最后通过遍历数组来读出答案
#Coding
#include<bits/stdc++.h>
using namespace std;
vector<long long> getArr(int num)
{
vector<long long> nums;
while(num)
{
int d=num%10;
nums.push_back(d);
num/=10;
}
return nums;
}
vector<long long> getFactorial(int n)
{
vector<long long> num1(100,0);
num1[0]=1;
for(int i=2;i<=n;++i)
{
vector<long long> sum(100,0);
vector<long long>num2=getArr(i);
for(int i=0;i<100;++i)
{
for(int j=0;j<num2.size();++j)
{
sum[i+j]+=num1[i]*num2[j];
}
}
num1=sum;
}
int lens=100;
for(int i=0;i<lens;++i)
{
if(num1[i]>9)
{
num1[i+1]+=num1[i]/10;
num1[i]%=10;
}
}
while(lens>1 && num1[lens-1]==0) lens--;
num1.resize(lens);
return num1;
}
int main()
{
int n;
cin>>n;
vector<int> sum(100,0);
for(int i=1;i<=n;++i)
{
vector<long long> arr=getFactorial(i);
for(int i=0;i<arr.size();++i)
{
sum[i]+=arr[i];
}
}
for(int i=0;i<100;++i)
{
if(sum[i]>9)
{
sum[i+1]+=sum[i]/10;
sum[i]%=10;
}
}
int lens=100;
while(lens>1 && sum[lens-1]==0) lens--;
for(int i=lens-1;i>=0;--i)
cout<<sum[i];
return 0;
}
P1303 A*B Problem
题目描述
给出两个非负整数,求它们的乘积。
#解题思路和优化思路
和上面的思路一样
#Coding
#include<bits/stdc++.h>
using namespace std;
int main()
{
char a[2001],b[2001];
int nums1[2001],nums2[2001];
cin>>a>>b;
int lens1=strlen(a),lens2=strlen(b);
for(int i=1;i<=lens1;++i)
{
nums1[i]=a[lens1-i];
}
for(int i=1;i<=lens2;++i)
{
nums2[i]=b[lens2-i];
}
int sum[2001];
for(int i=1;i<=lens1;++i)
{
for(int j=1;j<=lens2;++j)
{
sum[i+j-1]=nums1[i]*nums2[j];
}
}
int lens=lens1+lens2;
for(int i=1;i<=lens;++i)
{
if(sum[i]>9)
{
sum[i+1]+=sum[i]/9;
sum[i]%=9;
}
}
while(sum[lens]=0 && lens>1) lens--;
for(int i=lens;i>=0;--i)
{
cout<<sum[i];
}
return 0;
}
P1601 A+B Problem(高精)
题目描述
高精度加法,相当于 a+b problem,不用考虑负数。
#解题思路和优化思路
和上面的思路一样
#Coding
P1601 A+B(高精)
#include<bits/stdc++.h>
using namespace std;
int main()
{
char a[500],b[500];
cin>>a>>b;
int lens1=strlen(a),lens2=strlen(b);
vector<int> nums1(500,0),nums2(500,0);
for(int i=0;i<lens1;++i)
{
nums1[i]=a[lens1-1-i]-'0';
}
for(int i=0;i<lens2;++i)
{
nums2[i]=b[lens2-1-i]-'0';
}
vector<int> sum(1000);
int lens=lens1+lens2;
for(int i=0;i<max(lens1,lens2);++i)
{
sum[i]=nums1[i]+nums2[i];
}
for(int i=0;i<lens;++i)
{
if(sum[i]>9)
{
sum[i+1]+=sum[i]/10;
sum[i]%=10;
}
}
while(sum[lens-1]==0 && lens>1) lens--;
for(int i=lens-1;i>=0;--i)
{
cout<<sum[i];
}
return 0;
}
P1563 [NOIP2016 提高组] 玩具谜题
题目描述
小南有一套可爱的玩具小人, 它们各有不同的职业。
有一天, 这些玩具小人把小南的眼镜藏了起来。 小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的面朝圈外。如下图:
这时 singer 告诉小南一个谜題: “眼镜藏在我左数第 3个玩具小人的右数第 1 个玩具小人的左数第 2 个玩具小人那里。 ”
小南发现, 这个谜题中玩具小人的朝向非常关键, 因为朝内和朝外的玩具小人的左右方向是相反的: 面朝圈内的玩具小人, 它的左边是顺时针方向, 右边是逆时针方向; 而面向圈外的玩具小人, 它的左边是逆时针方向, 右边是顺时针方向。
小南一边艰难地辨认着玩具小人, 一边数着:
singer 朝内, 左数第 3 个是 archer。
archer 朝外,右数第 1 个是 thinker 。
thinker 朝外, 左数第 2 个是 writer。
所以眼镜藏在 writer 这里!
虽然成功找回了眼镜, 但小南并没有放心。 如果下次有更多的玩具小人藏他的眼镜, 或是谜题的长度更长, 他可能就无法找到眼镜了。所以小南希望你写程序帮他解决类似的谜题。 这样的谜題具体可以描述为:
有 n 个玩具小人围成一圈, 已知它们的职业和朝向。现在第 1 个玩具小人告诉小南一个包含 m 条指令的谜題, 其中第 z 条指令形如“左数/右数第 s个玩具小人”。 你需要输出依次数完这些指令后,到达的玩具小人的职业。
#解题思路和优化思路
题目看着非常的繁琐 我们只需要提取有用的消息 即 数组是逆时针的 同时 0朝向内 1朝向外 我们可以推出 0的左手是顺时针 右手是逆时针 1的左手是逆时针 右手是顺时针 这样就可以通过下标遍历数组来模拟圆圈 逆时针的时候加 顺时针的时候减 最后mod人数就可以到达正确的位置
#Coding
#include<bits/stdc++.h>
using namespace std;
int main()
{
int m,n;
cin>>m>>n;
vector<string> toys(m);
unordered_map<string,int> toysOrient;
for(int i=0;i<m;++i)
{
int orient;
cin>>orient;
cin>>toys[i];
toysOrient[toys[i]]=orient;
}
int cur=0;
for(int i=0;i<n;++i)
{
int orient,count;
count%=m;
cin>>orient>>count;
if(orient==0)
{
if(toysOrient[toys[cur]]==0) cur=(cur+m-count)%m;
else cur=(cur+count)%m;
}
else
{
if(toysOrient[toys[cur]]==1) cur=(cur+m-count)%m;
else cur=(cur+count)%m;
}
}
cout<<toys[cur];
}
P1067 [NOIP2009 普及组] 多项式输出
题目描述
一元 nn 次多项式可用如下的表达式表示:
其中,称为 i 次项,称为 i 次项的系数。给出一个一元多项式各项的次数和系数,请按照如下规定的格式要求输出该多项式:
多项式中自变量为 x,从左到右按照次数递减顺序给出多项式。
多项式中只包含系数不为 0 的项。
如果多项式 n 次项系数为正,则多项式开头不出
+
号,如果多项式 n 次项系数为负,则多项式以-
号开头。对于不是最高次的项,以
+
号或者-
号连接此项与前一项,分别表示此项系数为正或者系数为负。紧跟一个正整数,表示此项系数的绝对值(如果一个高于 0 次的项,其系数的绝对值为 1,则无需输出 1)。如果 x 的指数大于 1,则接下来紧跟的指数部分的形式为“x^b”,其中 b 为 x 的指数;如果 x 的指数为 1,则接下来紧跟的指数部分形式为 x;如果 x 的指数为 0,则仅需输出系数即可。多项式中,多项式的开头、结尾不含多余的空格。
#解题思路和优化思路
没有任何算法上的设计 只需要注意几个点就可以AC掉 1 x等于1和0是不需要输出指数的数字 2前面系数是1的 除了指数为0时 其余时刻都不输出
#Coding
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin>>n;
int cur=n;
bool flag=true;
for(int i=0;i<=n;++i)
{
int x;
cin>>x;
if(x!=0)
{
if(x>0 && flag==false) cout<<"+";
flag=false;
if(x<0) cout<<"-";
if(i==n || (x!=1 && x!=-1)) cout<<abs(x);
if(cur>1) cout<<"x^"<<cur;
else if(cur==1) cout<<"x";
}
cur--;
}
if(flag) cout<<"0";
return 0;
}
P1518 [USACO2.4] 两只塔姆沃斯牛 The Tamworth Two
题目描述
两只牛逃跑到了森林里。Farmer John 开始用他的专家技术追捕这两头牛。你的任务是模拟他们的行为(牛和 John)。
追击在 10×10 的平面网格内进行。一个格子可以是:一个障碍物,两头牛(它们总在一起),或者 Farmer John。两头牛和 Farmer John 可以在同一个格子内(当他们相遇时),但是他们都不能进入有障碍的格子。
一个格子可以是:
.
空地;*
障碍物;C
两头牛;F
Farmer John。这里有一个地图的例子:
*...*..... ......*... ...*...*.. .......... ...*.F.... *.....*... ...*...... ..C......* ...*.*.... .*.*......
牛在地图里以固定的方式游荡。每分钟,它们可以向前移动或是转弯。如果前方无障碍(地图边沿也是障碍),它们会按照原来的方向前进一步。否则它们会用这一分钟顺时针转 90 度。 同时,它们不会离开地图。
Farmer John 深知牛的移动方法,他也这么移动。
每次(每分钟)Farmer John 和两头牛的移动是同时的。如果他们在移动的时候穿过对方,但是没有在同一格相遇,我们不认为他们相遇了。当他们在某分钟末在某格子相遇,那么追捕结束。
读入十行表示地图。每行都只包含 10 个字符,表示的含义和上面所说的相同。保证地图中只有一个
F
和一个C
。F
和C
一开始不会处于同一个格子中。计算 Farmer John 需要多少分钟来抓住他的牛,假设牛和 Farmer John 一开始的行动方向都是正北(即上)。 如果 John 和牛永远不会相遇,输出 0。
#解题思路和优化思路
这道题我也是碰着巧AC的 我一次次试的数据量 大概是1000 这道题就是模拟题 按照题意模拟就好了 为了减少代码量 我自己构建了一个dir数组来统计方向
#Coding
#include<bits/stdc++.h>
using namespace std;
int main()
{
int jx,jy,cowx,cowy;
int jDir=0,cowDir=0;
char map[10][10];
int dir[4][2]={{-1,0},{0,1},{1,0},{0,-1}};
for(int i=0;i<10;++i)
{
for(int j=0;j<10;++j)
{
cin>>map[i][j];
if(map[i][j]=='F') jx=i,jy=j;
if(map[i][j]=='C') cowx=i,cowy=j;
}
}
int time=0;
while(true && time<2000)
{
int x=dir[jDir][0],y=dir[jDir][1];
int curx=jx+x,cury=jy+y;
if(map[curx][cury]=='*' || curx<0 || curx>=10 || cury>=10 || cury<0) jDir=(jDir+1)%4;
else jx=curx,jy=cury;
x=dir[cowDir][0],y=dir[cowDir][1];
curx=cowx+x,cury=cowy+y;
if(map[curx][cury]=='*' || curx<0 || curx>=10 || cury>=10 || cury<0) cowDir=(cowDir+1)%4;
else cowx=curx,cowy=cury;
time++;
if(jx==cowx && jy==cowy) break;
}
if(time==2000) cout<<0;
else cout<<time;
return 0;
}
P1786 帮贡排序
题目描述
目前帮派内共最多有一位帮主,两位副帮主,两位护法,四位长老,七位堂主,二十五名精英,帮众若干。
现在 absi2011 要对帮派内几乎所有人的职位全部调整一番。他发现这是个很难的事情。于是要求你帮他调整。
他给你每个人的以下数据:
他的名字(长度不会超过 3030),他的原来职位,他的帮贡,他的等级。
他要给帮贡最多的护法的职位,其次长老,以此类推。
可是,乐斗的显示并不按帮贡排序而按职位和等级排序。
他要你求出最后乐斗显示的列表(在他调整过职位后):职位第一关键字,等级第二关键字。
注意:absi2011 无权调整帮主、副帮主的职位,包括他自己的(这不是废话么..)
他按原来的顺序给你(所以,等级相同的,原来靠前的现在也要靠前,因为经验高低的原因,但此处为了简单点省去经验。)
#解题思路和优化思路
就是一道基于 堆 和 哈希表 的一道题 通过对帮贡的排序来排序 细节很多 需要扣一扣
#Coding
#include<bits/stdc++.h>
using namespace std;
int main()
{
unordered_map<string,string> jobMap;
unordered_map<string,long long> moneyMap,gradeMap;
string head="";
vector<string> deputy;
priority_queue<pair<long long ,string>> maxQ;
int n;
cin>>n;
for(int i=0;i<n;++i)
{
string name,job;
long long money, grade;
cin>>name>>job>>money>>grade;
moneyMap[name]=money;
gradeMap[name]=grade;
if(job=="BangZhu") head=name;
else if(job=="FuBangZhu") deputy.push_back(name);
else
{
jobMap[name]=job;
maxQ.push(make_pair(money,name));
}
}
if(head!="") cout<<head<<" BangZhu "<<gradeMap[head]<<endl;
for(int i=0;i<deputy.size();++i)
{
cout<<deputy[i]<<" FuBangZhu "<<gradeMap[deputy[i]]<<endl;
}
int jobLeft[4]={2,4,7,25};
string job[4]={"HuFa","ZhangLao","TangZhu","JingYing"};
int cur=0;
while(!maxQ.empty())
{
int money1=maxQ.top().first;
string name1=maxQ.top().second;
maxQ.pop();
if(!maxQ.empty())
{
int money2=maxQ.top().first;
string name2=maxQ.top().second;
maxQ.pop();
if(money1==money2 && jobLeft[cur]==1)
{
string curJob=job[cur];
if(jobMap[name1]==curJob)
{
if(cur<4)
{
cout<<name1<<" "<<job[cur]<<" "<<gradeMap[name1];
jobLeft[cur]--;
if(jobLeft[cur]==0) cur++;
}
else cout<<name1<<" BangZhong "<<gradeMap[name1];
maxQ.push(make_pair(money2,name2));
}
else
{
if(cur<4)
{
cout<<name2<<" "<<job[cur]<<" "<<gradeMap[name2];
jobLeft[cur]--;
if(jobLeft[cur]==0) cur++;
}
else cout<<name2<<" BangZhong "<<gradeMap[name2];
maxQ.push(make_pair(money1,name1));
}
}
else
{
if(cur<4)
{
cout<<name1<<" "<<job[cur]<<" "<<gradeMap[name1];
jobLeft[cur]--;
if(jobLeft[cur]==0) cur++;
}
else cout<<name1<<" BangZhong "<<gradeMap[name1];
maxQ.push(make_pair(money2,name2));
}
}
else
{
if(cur<4)
{
cout<<name1<<" "<<job[cur]<<" "<<gradeMap[name1];
jobLeft[cur]--;
if(jobLeft[cur]==0) cur++;
}
else cout<<name1<<" BangZhong "<<gradeMap[name1];
}
cout<<endl;
}
return 0;
}
P1591 阶乘数码
题目描述
求 n! 中某个数码出现的次数。
#解题思路和优化思路
这道题是一道很好的低精度*高精度的板子题 通过数组来记录每一位的数字 从而避免精度的流失
#Coding
//P1591 阶乘数码
#include<bits/stdc++.h>
using namespace std;
int num[5000];
int main()
{
int t;
cin>>t;
while (t--)
{
memset(num,0,sizeof(num));
num[1]=1;
int n,i,j,k,m;
int bit=1,carry=0;
cin>>n>>m;
for(i=2;i<=n;i++)
{
carry=0;
for(j=1;j<=bit;j++)
{
num[j]=num[j]*i+carry;
carry=num[j]/10;
num[j]=num[j]%10;
}
while(carry>0)
{
num[j]=carry%10;
carry/=10;
j++;
}
bit=j-1;
}
long long sum=0;
for (i=bit;i>=1;i--)
{
if (num[i]==m)
sum++;
}
cout<<sum<<endl;
}
return 0;
}
P1249 最大乘积
题目描述
一个正整数一般可以分为几个互不相同的自然数的和,如 3=1+23=1+2,4=1+34=1+3,5=1+4=2+35=1+4=2+3,6=1+5=2+46=1+5=2+4。
现在你的任务是将指定的正整数 nn 分解成若干个互不相同的自然数的和,且使这些自然数的乘积最大。
#解题思路和优化思路
这道题的思路可以分为2大部分 分解n为k个不同的数字 以及k个数字的乘积 先解决第一个问题 即 如何分解成k个不同的因子 我们可以从2开始遍历他的因子 即 for(int i=2;i<=n;++i) if(n>=i) count++ 利用count来记录他第一次分解的个数 直到他无法再分解假设我们现在分解了 k个 因子 剩下的数 一定不会大于k+2 我们现在有k个因子 所以我们可以从后到前让每一个数字都加1 直到剩余的数被分配完 接下来就是第二个问题 高精度的处理k个数字的乘积问题 我们还是利用数组来解题 通过数组来记录每一位上的数字 最后遍历数组得到答案
#Coding
#include <bits/stdc++.h>
using namespace std;
string multiplication(string a,string b)
{
string s;
int len1=a.size(),len2=b.size();
vector<int> num1(len1,0),num2(len2,0),num3(len1+len2,0);
for(int i=len1-1;i>=0;--i)
{
num1[len1-i-1]=a[i]-'0';
}
for(int i=len2-1;i>=0;--i)
{
num2[len2-i-1]=b[i]-'0';
}
for(int i=0;i<len1;++i)
{
for(int j=0;j<len2;++j)
{
num3[i+j]+=num1[i]*num2[j];
}
}
for(int i=0;i<len1+len2;++i)
{
num3[i+1]+=num3[i]/10;
num3[i]%=10;
}
if(num3[len1+len2-1]) s+=to_string(num3[len1+len2-1]);
for(int i=len1+len2-2;i>=0;--i) s+=to_string(num3[i]);
return s;
}
int main()
{
int n;
cin>>n;
if(n<=4)
{
cout<<n<<endl<<n<<endl;
}
else
{
int bit=0;
string power="1";
vector<int> res(1001,0);
vector<string> s(1001);
for(int i=2;i<=n;++i)
{
if(n>=i)
{
res[bit]=i;
s[bit++]=to_string(i);
n-=i;
}
else break;
}
for(int i=bit-1;i>=0;--i)
{
if(n)
{
res[i]++;
s[i]=to_string(res[i]);
n--;
}
}
if(n)
{
res[bit-1]++;
s[bit-1]=to_string(res[bit-1]);
n--;
}
for(int i=0;i<bit;++i)
{
if(i) cout<<" ";
cout<<res[i];
power=multiplication(s[i],power);
}
cout<<endl<<power;
}
return 0;
}
P1045 [NOIP2003 普及组] 麦森数(50分)
题目描述
形如 2^{P}-12P−1 的素数称为麦森数,这时 PP 一定也是个素数。但反过来不一定,即如果 PP 是个素数,2^{P}-12P−1 不一定也是素数。到 1998 年底,人们已找到了 37 个麦森数。最大的一个是 P=3021377P=3021377,它有 909526 位。麦森数有许多重要应用,它与完全数密切相关。
任务:输入 P(1000<P<3100000)P(1000<P<3100000),计算 2^{P}-12P−1 的位数和最后 500500 位数字(用十进制高精度数表示)
#解题思路和优化思路
这道题只拿了50分 有些算法还是无法达到竞技水平 这道题通过数组来记录高位数字 达到可以实现2的k次方的每一位存储
#Coding
P1045 麦森数(50)
#include<bits/stdc++.h>
using namespace std;
const int Max=1e8;
int main()
{
vector<int> bit(Max);
bit[0]=1;
int p;
cin>>p;
int curBit=0;
for(int i=0;i<=p;++i)
{
for(int j=0;j<curBit;++j)
{
bit[j]*=2;
}
for(int j=0;j<curBit;++j)
{
bit[j+1]+=bit[j]/10;
bit[j]%=10;
}
if(bit[curBit])
{
bit[curBit+1]+=bit[curBit]/10;
bit[curBit]%=10;
curBit++;
}
}
int cur=0;
cout<<curBit<<endl;
bit[0]--;
for(int i=0;i<curBit;++i)
{
if(bit[i]==-1)
{
bit[i+1]--;
if(i<curBit-1) bit[i]=9;
}
}
if(bit[curBit-1]==0) curBit--;
for(int i=499;i>=0;--i)
{
cout<<bit[i];
cur++;
if(cur%50==0) cout<<endl;
}
return 0;
}