1.剑指 Offer 46. 把数字翻译成字符串
题目描述:
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
0 <= num < 2^31
示例:
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
解答描述:
该题由于一个数字可能有多个翻译,可以单个数字地翻译,也可以把0-25的数字看做一个整体翻译,要求所有的翻译方法,采用动态规划遍历所有情况。
首先,将十进制的数字的各数位拆开,放到一个动态数组vector中,注意,当数字为0时,无法拆开,直接返回只有一种翻译情况。
其次,令dp[i]表示前i个数位中最多有的翻译情况,初始化dp[0]=1,dp[1]=2(当前两个数位可以看做整体翻译时)/dp[1]=1(当只能一个一个翻译时)
然后动态规划递推,dp[i]=dp[i-1]+dp[i-2](当i,i-1可以看做整体翻译时)/dp[i]=dp[i-1](当只能一个一个翻译时)。
注意:看做整体时,0x是不符合要求的。
代码:
class Solution {
public:
int translateNum(int num) {
if(num==0)
{
return 1;
}
vector<int> x;//暂存num的各个数位
while(num>0)
{
int temp=num%10;//个位
x.insert(x.begin(),temp);
num=num/10;
}
int n=x.size();
if(n==1)//只有一个数位时,只有一种翻译方式
{
return 1;
}
int *dp=new int[n];//dp[i]表示前i个数最多有多少种不同的翻译方法
dp[0]=1;//初始化
if((x[0]*10+x[1])<=25 && x[0]!=0)
{
dp[1]=2;
}
else
{
dp[1]=1;
}
for(int i=2;i<n;i++)
{
if( (x[i-1]*10+x[i])<=25 && x[i-1]!=0 )
{
dp[i]=dp[i-1]+dp[i-2];//可以单个翻译,也可以在i-2之后把两个数位看做一个数字翻译
}
else
{
dp[i]=dp[i-1];//只能单个翻译
}
}
return dp[n-1];
}
};
2.剑指 Offer 48. 最长不含重复字符的子字
题目描述:
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例:
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
解答描述:
该题要找出最长的不包含重复字符的子字符串,最直观的,首先定义一个可以去重的数据结构unordered_map(char,int),存放char类型的字符和字符对应的位置。
然后,令dp[i]表示以i对应字符结尾的最长不重复子串的长度,初始化dp[0]=1,依次判断整个字符串
1)当s[i]没有出现过时,直接加入不重复子串,dp[i]=dp[i-1]+1
2)当s[i]出现过时,分两种情况:(这是难点!!!)
2.1)当两个重复的字符之间间隔j-i大于dp[i-1]时,说明前面那个重复的字符已被排除出最长范围内,直接令dp[i]=dp[i-1]+1;
2.2)当两个重复的字符之间间隔j-i小于等于dp[i-1]时,说明前面那个重复的字符之前被计算在了最长范围内,需要先排除它,令dp[i]=j-i;
代码:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
if(s=="")
{
return 0;
}
int n=s.size();
if(n==1)
{
return 1;
}
unordered_map<char,int> x;//用于判断是否含有重复字符
int *dp=new int[n];//dp[i]表示第i个位置之前的最长字符串长度
dp[0]=1;
x.emplace(s[0],0);
int max_len=dp[0];
for(int i=1;i<n;i++)
{
if(x.count(s[i])==0)//没有重复,长度直接加1
{
dp[i]=dp[i-1]+1;
printf("%d %d\n",i,dp[i]);
x.emplace(s[i],i);
}
else//有重复,从前一个相同的字符重新开始
{
int index=x[s[i]];
if(i-index>dp[i-1])//如果前一个相同的字符已经超过了上一个最大的字符的范围,那么就不管它,还使用上一个最大字符******
{
dp[i]=dp[i-1]+1;
}
else//否则,去除上一个相同的字符
{
dp[i]=i-index;
}
printf("%d %d\n",i,dp[i]);
x.erase(s[i]);//旧的相同字符出集合
x.emplace(s[i],i);//新的相同字符入队,更新最后出现的位置
}
max_len=max(max_len,dp[i]);
}
return max_len;
}
};
3.70. 爬楼梯
题目描述:
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例:
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
解答描述:
最简单的DP,令dp[i]表示走到第i级台阶的方案数,要么走一步,要么走两步,所以dp[i]=dp[i-1]+dp[i-2]
注意:初始化的时候dp[0]=1;这样dp[2]=dp[0]+dp[1]得到的才是正确的。
代码:
class Solution {
public:
int climbStairs(int n) {
if(n==1)
{
return 1;
}
int *dp=new int[n+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
4.198. 打家劫舍
题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例:
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
解答描述:
也很简单,关键在于题干中的选择的两个房屋要不相邻。
令dp[i]表示到第i个房屋获得的最高金额,要么前一个房屋选了,那么这个房屋不能再选;要么前一个房屋没选,那么这个房屋还可以再选。 dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
注意:初始化的时候,前两个房屋需要单独定义,dp[0]=nums[0](只能选第一个房屋)
dp[1]=max(dp[0],dp[1])(前两个中要么选第一个,要么选第二个)
最后返回dp中的最大值即可。
代码:
class Solution {
public:
int rob(vector<int>& nums) {
int n=nums.size();
if(n==1)
{
return nums[0];
}
int *dp=new int[n];//dp[i]表示到i为止的最高金额
dp[0]=nums[0];
dp[1]=max(dp[0],nums[1]);//前2个中要么选第一个,要么选第二个
int max_money=max(dp[0],dp[1]);
for(int i=2;i<n;i++)
{
dp[i]=max(dp[i-2]+nums[i],dp[i-1]);//前一个选了就不能再选,前一个没选还能再选
max_money=max(max_money,dp[i]);
}
return max_money;
}
};
5.120. 三角形最小路径和
题目描述:
给定一个三角形 triangle ,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。
示例:
示例 1:
输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:
2
3 4
6 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
示例 2:
输入:triangle = [[-10]]
输出:-10
解答描述:
每个节点可以又它的上一层的正上方得到,或者由上一层的左边得到,多种方案求最优,考虑DP.
直译题目要求令dp[i][j]表示走到i行j列时的最小路径和,递推公式为: dp[i][j]=min(dp[i-1][j]+triangle[i][j],dp[i-1][j-1]+triangle[i][j]);
注意处理第一列和每一行的最后一列,这两种情况的节点都只能由一个方向得到,需要单独讨论。
最后返回到最后一行的dp的最小值即可。
代码:
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int rows=triangle.size();
if(rows==1)//只有一行
{
return triangle[0][0];
}
int dp[rows][rows];
memset(dp,0,sizeof(dp));
dp[0][0]=triangle[0][0];//第一行只能走自己
for(int i=1;i<rows;i++)//第一列只能由上一行的相同下标得
{
dp[i][0]=dp[i-1][0]+triangle[i][0];
}
for(int i=1;i<rows;i++)//每一行的最后一列只能由上一行的左边一列下标得
{
dp[i][i]=dp[i-1][i-1]+triangle[i][i];
}
for(int i=1;i<rows;i++)
{
for(int j=1;j<i;j++)//要么又上一行的同列得,要么由上一行的左边一列得
{
dp[i][j]=min(dp[i-1][j]+triangle[i][j],dp[i-1][j-1]+triangle[i][j]);
}
}
int min_length=INT_MAX;
for(int i=0;i<rows;i++)//到达最后一行中的最小值
{
min_length=min(min_length,dp[rows-1][i]);
}
return min_length;
}
};