单词拆分(一)
牛客链接:NC181 单词拆分(一)
题解1:DFS
每次从字符串中从头开始选择定长的子字符串去集合中寻找对应目标,如果存在,则继续这么操作寻找后续字符串。如果找到最后,字符串为0了,那么说明字符串集合能够组成这个字符串。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string字符串
* @param dic string字符串vector
* @return bool布尔型
*/
bool wordDiv(string s, vector<string>& dic) {
// write code here
if(s.size() == 0)
return true;
int ans = false;
for(int i =0;i<=s.size();++i){ //递归查找
string temp = s.substr(0,i);
if(find(dic.begin(), dic.end(), temp) != dic.end())
{
ans = wordDiv(s.substr(i), dic);
}
if(ans == true) //截枝
break;
}
return ans;
}
};
题解2:动态规划
设定一个dp数组,初始化为0。其中dp[i]=1表示以i结尾的字符串能够通过集合组合而成。 当前dp[i]可以通过前j个字符串和j-i字符串组合而成,如果dp[j]=1,且i-j的子字符串在集合中,则dp[i] =1;
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string字符串
* @param dic string字符串vector
* @return bool布尔型
*/
bool wordDiv(string s, vector<string>& dic) {
// write code here
unordered_set<string> us; //保所有出现过的字符串
for(string sub:dic)
us.insert(sub);
bitset<501> dp = 0; //比vector更快
dp[0] = 1;
for(int i =1;i<=s.size();++i){ //遍历之后的每一位
for(int j =0;j<i;++j){ //截取字符串长度
if(dp[j] && us.find(s.substr(j,i-j)) != us.end()) //能表示直接退出循环。
{
dp[i] = 1;
break;
}
}
}
return dp[s.size()];
}
};
单词拆分(二)
牛客链接:NC182 单词拆分(二)
解法1:DFS
深度搜索每个可能的同时,加其加入到子答案中。当s剩余0时,子答案加入答案集合,否则不成立,他不是一个子答案。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string字符串
* @param dic string字符串vector
* @return string字符串vector
*/
void dfs(string s, string sub_ans, vector<string>& dic, vector<string> &ans){ //dfs
if(s.size() == 0){
sub_ans.erase(0, 1); //去掉开头的空格
ans.push_back(sub_ans);
return;
}
for(int i = 0;i<=s.size();++i)
{
if(find(dic.begin(),dic.end(),s.substr(0,i))!=dic.end())
{
string temp = sub_ans;
sub_ans = sub_ans +" "+s.substr(0,i);
dfs(s.substr(i), sub_ans, dic, ans);
sub_ans = temp;
}
}
}
vector<string> wordDiv(string s, vector<string>& dic) {
// write code here
vector<string> ans;
dfs(s,"",dic,ans);
return ans;
}
};
最长公共子数组
牛客链接:NC183 最长公共子数组
解法1:动态规划
定义一个二维数组ans[i][j],其中ans[i][j]表示以A中第i个元素和B中第j个元素结尾的最长后缀序列的长度,递推关系如下:
如果A[i-1] == B[j-1],则ans[i][j] = ans[i-1][j-1] + 1; 即在原有的基础后缀基础上加上延伸一个。
否则,后缀不存在,重新计算后缀,ans[i][j] = 0;
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param A int整型vector
* @param B int整型vector
* @return int整型
*/
int longestCommonSubarry(vector<int>& A, vector<int>& B) {
// write code here
if(A.size() == 0 or B.size() == 0)
return 0;
int res = 0;
vector<vector<int>> ans(A.size()+1,vector<int>(B.size()+1,0));
for(int i =1;i<=A.size();++i){
for(int j =1;j<=B.size();++j){
if(A[i-1] == B[j-1])
ans[i][j] = ans[i-1][j-1] + 1; //可以优化
else{
ans[i][j] = 0;
}
res = max(res, ans[i][j]);
}
}
return res;
}
};
这里可以通过使用一维数组滑动更新,实现空间优化。每次都要利用第j-1个信息,因此要先更新j在更新j-1。
每次更新的时候通过扫描前一个字符与当前字符是否相等,来考虑当前是否能够通过前一个最长序列来延续,以此省掉第一个维度。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param A int整型vector
* @param B int整型vector
* @return int整型
*/
int longestCommonSubarry(vector<int>& A, vector<int>& B) {
// write code here
if(A.size() == 0 or B.size() == 0)
return 0;
int res = 0;
vector<int> ans(B.size()+1,0);
for(int i = 1; i<=A.size(); ++i){
for(int j = B.size(); j>=0; --j){
if(A[i-1] == B[j-1])
ans[j] = ans[j-1] + 1;
else{
ans[j] = 0;
}
res = max(res, ans[j]);
}
}
return res;
}
};
压缩字符串(一)
牛客链接:NC101 压缩字符串(一)
解法1:栈
从头到尾遍历,出现不同的元素,统计栈内元素个数,为1则不需要输出,同时清空栈,用于计算下一个元素。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param param string字符串
* @return string字符串
*/
string compressString(string param) {
// write code here
stack<char> s;
string ans = "";
if(param.size() == 0)
return ans;
for(int i =0; i<param.size();++i){
if(s.size()==0 or param[i] == s.top())
s.push(param[i]);
else{
ans+=s.top();
if(s.size()>1)
ans+=to_string(s.size());
while(s.size()>0)
s.pop();
s.push(param[i]);
}
}
ans+=s.top();
if(s.size()>1)
ans+=to_string(s.size());
return ans;
}
};
几步可以从头跳到尾
牛客链接:NC148 几步可以从头跳到尾
解法1
从头到尾遍历,每次确定一个最大能够到达的路径,在这一步内我们都只需要一步,当到达边界时候,我们需要判断这一步中能通过下一步能够达到的最远距离,且步数加一。直到能够到达的位置超过或等于n,则当前步数就是答案。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 最少需要跳跃几次能跳到末尾
* @param n int整型 数组A的长度
* @param A int整型vector 数组A
* @return int整型
*/
int Jump(int n, vector<int>& A) {
// write code here
if(n == 0)
return 0;
if(n>A.size())
return -1;
int step = 1;
int m_len = A[0];
int now_max = A[0];
for(int i =1;i<A.size() && m_len < n-1;++i){
int now_cango = A[i] + i;
if(now_cango > now_max)
now_max = now_cango;
if(i >= m_len)
{
m_len = now_max;
step++;
}
}
return step;
}
};
跳跃游戏(一)
牛客链接:NC197 跳跃游戏(一)
**解法1:暴力DP **
定义一个dp数组,dp[i]表示第i个位置是否可达,其中dp[o]=true;对于目标位置i,向前遍历位置j,如果j可达,且j到i的距离小于等于j的最大跳数,则i也可达,即dp[i] = true;
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型vector
* @return bool布尔型
*/
bool canJump(vector<int>& nums) {
// write code here
if(nums.size()>1 && nums[0] == 0) //当第一个为0,但是目标位置大于0时,无法跳跃。
return false;
int location = nums.size()-1;
vector<bool> ans(nums.size(),false);
ans[0] = true;
for(int i =1;i<nums.size();++i){
for(int j =i-1;j>=0;--j){
if(ans[j] && nums[j] >= i-j){
ans[i] = 1;
break;
}
}
}
return ans[location];
}
};
解法2:计算最远跳跃距离
定义最远跳跃距离变量。每次更新最远跳跃距离,如果能够超越数组长度,则成功,否则失败。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型vector
* @return bool布尔型
*/
bool canJump(vector<int>& nums) {
int n = nums.size();
int farthest = 0;
for (int i = 0; i < n - 1; i++) {
// 不断计算能跳到的最远距离
farthest = max(farthest, i + nums[i]);
// 可能碰到了 0,卡住跳不动了
if (farthest <= i) {
return false;
}
}
return farthest >= n - 1;
}
};
跳跃游戏(二)
牛客链接:NC206 跳跃游戏(二)
解法1:DP(超时)
定义数组nums[],初始化为-1;nums[i]表示能够到达i点后能够获得的最大的分,nums[i] 可以由到之前点的最大的分,以及之前点能够到达当前点的前提下,加上当前点的得分:ans[i] = max(ans[i], ans[j] + nums[i]);。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型vector
* @return int整型
*/
int maxJumpGrade(vector<int>& nums) {
// write code here
if(nums.size()==0 or(nums.size()>0 && nums[0] == 0))
return -1;
vector<int> ans(nums.size(),-1);
ans[0] = nums[0];
for(int i = 1;i<nums.size();++i){
for(int j = 0;j<i;++j){
//能够到达当前点,判断能够获得的最大的分。
if(ans[j] != -1 && nums[j] >= i-j){
ans[i] = max(ans[i], ans[j] + nums[i]);
}
}
}
return ans[nums.size()-1];
}
};
定义dp[],dp[i]表示跳到第i个位置的最大值。对于每个位置i,往后搜索他能到达的最大区域,依次更新后续区域的最大值。
class Solution {
public:
int maxJumpGrade(vector<int>& nums) {
if(nums.empty()){ //数组为空
return -1;
}
int n = nums.size(); //n保存数组的长度
vector<int> dp(nums.size(),-1); //分数数组,值初始化为-1
dp[0] = nums[0]; //初始化dp[0]
for(int i = 0; i < n; i++) { //遍历一遍更新分数数组
for(int j = i+1; j < nums.size() && j < i+nums[i]+1; j ++) { //遍历所有从i可以跳到的位置
dp[j] = max(dp[j],dp[i] + nums[j]);
}
}
return dp[n-1];
}
};
解法2:优化贪心dp
定义dp,dp[i]表示从i到重点能够获得的最大积分。 想要获得从头到尾的最大积分,需要从后往前遍历。初始化dp[pos] = nums[pos],pos = nums.size()-1;。依次往前遍历每个节点i判断是否能够到达pos点,能够到达,更新dp[i],并且在往前遍历的时候,只需要找到之前节点能否到到pos=i即可(能到到i,肯定能到达结尾,包含节点越多,积分越大)。
class Solution {
public:
int maxJumpGrade(vector<int>& nums) {
if(nums.empty()){//数组为空
return -1;
}
int n = nums.size();//n保存数组的长度
vector<int> dp(nums.size(),-1);//分数数组,值初始化为-1
int pos = n-1;
dp[pos] = nums[pos];
for(int i =n-2;i>=0;--i){
if(nums[i] + i >= pos){
dp[i] = dp[pos]+nums[i];
pos = i;
}
}
if(pos == 0)
return dp[0];
else
return -1;
}
};
跳跃游戏(三)
牛客链接:NC205 跳跃游戏(三)
解法1:动态规划
初始化dp数组为最大值,其中dp[i]表示从i到终点的最短跳跃次数。对于每个dp[i],他的最短路径为它可到达的点到达终点的最短路径+1中的最小值。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型vector
* @return int整型
*/
int minJumpStep(vector<int>& nums) {
if(nums.empty()){//数组为空
return -1;
}
int n = nums.size();//n保存数组的长度
vector<int> dp(nums.size(),INT_MAX);//分数数组,值初始化为-1
dp[n-1] = 0;
for(int i =n-2; i>=0; --i){
for(int j = i+nums[i]; j>i;--j){
if(j<nums.size() && dp[j] != INT_MAX){
dp[i] = min(dp[i],dp[j]+1);
}
}
}
if(dp[0] == INT_MAX)
return -1;
else
return dp[0];
}
};
解法2:贪心算法
贪心策略,初始化能够最远到达的最远距离nums[0],需要1步。在到达当前最远距离之前都只需要1步,然后到达一步的最远距离后,将之前统计的能到达的最远距离替换当前的最远距离,此时开始需要2步。一次类推,知道最终能到达的距离大于等于重点,则可以到达,且统输出步数。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型vector
* @return int整型
*/
int minJumpStep(vector<int>& nums) {
if(nums.empty()){ //数组为空
return -1;
}
if(nums.size() == 1) //长度为1时,不用走直接到达。
return 0;
int n = nums.size(); //n保存数组的长度
int pos = nums[0], end = nums[0], step = 1;
for(int i = 1; i < n-1; ++i){ //n-1为重点
if(pos>=i){
pos = max(pos,i+nums[i]); //下一步能够到达的最远距离。
if(end==i){
end = pos,step++; //到达这一步能够到的最远距离,然后下一个点开始,步数需要+1。
}
}
}
if(end >= n-1)
return step;
else
return -1;
}
};
数的划分
牛客链接:NC152 数的划分
解法1:动态规划
设DP[i][j]为将 i 分为 j 份的最优解;
当i=j的时候:dp[i][j]=1;
当i<j的时候:dp[i][j] = 0;
当i>j的时候:先将每一份分配1,剩下 i-j 个数字,可以分配给1份、2份、…、j份,可得 d p [ i ] [ j ] = ∑ p = 1 j d p [ i − j ] [ p ] dp[i][j] = \sum_{p=1}^{j}dp[i-j][p] dp[i][j]=∑p=1jdp[i−j][p];同理 d p [ i − 1 ] [ j − 1 ] = ∑ p = 1 j − 1 d p [ i − j ] [ p ] dp[i-1][j-1] = \sum_{p=1}^{j-1}dp[i-j][p] dp[i−1][j−1]=∑p=1j−1dp[i−j][p];所以两者相减得到递推公式 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + d p [ i − j ] [ j ] dp[i][j] = dp[i-1][j-1] + dp[i-j][j] dp[i][j]=dp[i−1][j−1]+dp[i−j][j];
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param n int 被划分的数
* @param k int 化成k份
* @return int
*/
int M = 1e9+7;
int divideNumber(int n, int k) {
// write code here
vector<vector<int>> dp(n+1,vector<int>(k+1,0));
for(int i =0; i<=k; ++i){
dp[i][i] = 1;
}
for(int i =1; i<=n; ++i){
for(int j = 1;j<=min(k,i);++j){
dp[i][j] = (dp[i-1][j-1]+dp[i-j][j]) % M;
}
}
return dp[n][k];
}
};
信封嵌套问题
牛客链接:NC153 信封嵌套问题
题解1:动态规划
先将数组按照长度升序排列,长度相等则按宽度降序,以便后续操作;
设置dp,其中dp[i]表示以i为最外层信封,能够包裹的最大层数,且由于已经排序过了,因此只需要考虑数组之前比他小的信封的基础上进行再套一层就行了。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param letters intvector<vector<>>
* @return int
*/
static bool cmp(vector<int> &a, vector<int> &b) {
if (a[0] == b[0]) {
return a[1] > b[1];
}
return a[0] < b[0];
}
int maxLetters(vector<vector<int> >& letters) {
int n = letters.size();
vector<int> dp(n,1); //dp数组
sort(letters.begin(),letters.end(),cmp);
for(int i = 0; i < n; ++i){
for(int j = 0; j < i; ++j){
if(letters[i][0]>letters[j][0] && letters[i][1]>letters[j][1] )//当前信封比之前的信封大,则考虑是否更新最大值
{
dp[i] = max(dp[i], dp[j]+1);
}
}
}
return *max_element(dp.begin(), dp.end());
}
};
题解2:二分+维护递增数组
先将数组按照长度递增排序,然后按照相等长度,宽度递减排序;
维护一个递增排序,依次往后遍历,来维护每层包装的宽度,使得在保证宽度超越内层的同时,宽度尽可能的小,这样才可能更多的被包裹。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param letters intvector<vector<>>
* @return int
*/
static bool cmp(vector<int> &a, vector<int> &b) {
if (a[0] == b[0]) {
return a[1] > b[1];
}
return a[0] < b[0];
}
int maxLetters(vector<vector<int> >& letters) {
// write code here
int n = letters.size();
sort(letters.begin(),letters.end(),cmp);
int cnt = 0;
vector<int> ans(n+1,0);
for(int i =0;i<n;++i){
int x = letters[i][1];
//lower_bound:利用二分查找到第一个大于等于给定值的位置。
int idx = lower_bound(ans.begin(), ans.begin()+cnt, x) - ans.begin();
ans[idx] = x;
if(idx == cnt) cnt++;
}
return cnt;
}
};
最长回文子序列
牛客链接:NC154 最长回文子序列
题解1
设定二维数组dp,其中dp[i][j]表示从下标i到下标j之间的最长回文字串长度,最终解为dp[0][n-1],其中n为字符串长度,dp中元素初始化为0;
- i=j时,dp[i][j] = 1;
- i<j时:当s[i] = s[j]的时候,dp[i][j] = dp[i+1][j-1]+2;当s[i] != s[j]的时候,dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string 一个字符串由小写字母构成,长度小于5000
* @return int
*/
int longestPalindromeSubSeq(string s) {
// write code here
int n = s.size();
vector<vector<int>> dp(n,vector<int>(n,0));
for(int i = 0;i<n;++i){
dp[i][i] = 1;
}
int ret = -1;
for(int i=n-1;i >= 0; --i){
for(int j = i+1; j<n; ++j){
if(s[i] == s[j]){
dp[i][j] = dp[i+1][j-1]+2;
}
else{
dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][n-1];
}
};