动态规划(中等)
Q1把数字翻译成字符串
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
我的第一思路是利用递归思想,将问题根据数字数组的长度和前两个数字来划分成子问题:
- 当长度为0或者1时,递归函数直接返回1,因为此时不会有多余情况了
- 当长度大于2时,要根据前两位数进行划分:
- 若第一个数字为0或者二位数大于25,则此时只有一种情况,即将第一个数字翻译成一个字母,返回后参数是 后n-1个数字串 可能的情况类型个数
- 若二位数<25 ,则可以有两种划分 ,即两个数字作为一个字母,或者一个对应一个字母
显然,递归算法会开辟更多的空间,但时间复杂度较好:
class Solution {
public:
int translateNum(int num) {
vector<int> nums;
int n=0;
while(num)
{
nums.insert(nums.begin(),num - (num/10)*10);
num = num/10;
n++;
}
return countNum(nums,0,n);
}
int countNum(vector<int> nums,int a , int n)
{
if(n < 0) return 0;
else if(n == 1 || n == 0) return 1;
else {
if(nums[a]*10+nums[a+1] > 25 || nums[a]==0 ) return countNum(nums,a+1,n-1);
else return countNum(nums,a+1,n-1) + countNum(nums,a+2,n-2);
};
}
};
本题最好的方法还是动态规划的方法,主要思想如下:
-
dp[ i ] 表示以i为结尾的数字串的翻译类型个数
-
转移方程: d p[ i ] = if (i 和 i-1 能组合翻译) dp[ i ] = dp[i-1] + dp[ i - 2]
else : dp[i] = dp [ i-1]
提升性能的两个tips:
- 由于整体顺序是一遍的遍历,因此可以不需要先将数转换为字符串或者一维数组,直接循环取余,自右向左动态规划
- 由于每次运算只涉及dp数组中的第 i i -1 i -2 的元素,因此也不需要开辟dp数组,直接用a b c变量进行代替即可
class Solution {
public:
int translateNum(int num) {
string str = to_string(num);
int len = str.size();
if(len < 2) return len;
int a,b,c; //代表dp i-2/i-1/i
b = 1;
a = 1;
for(int i = 2;i <= len;i++){
if(str[i-2] == '1' || (str[i-2] == '2' && str[i-1] <= '5')) c = a+b;
else c = b;
}
return dp[len];
}
};
Q2 最长不含重复字符的子字符串
从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
我的第一种思路,使用集合的数据结构,根据字符串进行递推,dp[ i ]表示以 i 为结尾的最长的不重复子字符串:
- 当某一字符在当前字符集合已经存在,则清空集合,回溯,构造新的集合,计算dp值
- 当某一字符不存在与当前集合,则加入集合,dp[ i ] = dp[ i-1 ] + 1
缺点:dp和集合 占用空间,回溯浪费大量时间
class Solution {
public:
int lengthOfLongestSubstring(string s) {
set<char> sett;
int n = s.length();
if(n <= 1) return n;
int dp[n];
dp[0] = 1;
sett.insert(s[0]);
int max = 1,j;
for(int i=1;i<n;i++)
{
if(sett.count(s[i])) {
sett.clear();
for( j=i-1;j>=0;j--)
{
sett.insert(s[j]);
if(s[i] == s[j]) break;
}
dp[i] = i - j;
}else{
dp[i] = dp[i-1] + 1;
sett.insert(s[i]);
if(dp[i]>max) max = dp[i];
}
}
return max;
}
};
根据题解,其实不需要使用集合,在第一种情况中,只需要向左找到第一个相同的元素求差即可,当然也可以用哈希表来存贮有关信息,减少了向左搜索的过程。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int len = s.size();
if (len == 0) {
return 0;
}
unordered_map<char, int> hash; //存储每个字符 和 该字符最后出现的位置(每次遍历到相同的字符会更新下标)
int max_len = 0, sub_len = 0;
for(int right=0;right<len;++right){
int left = hash.find(s[right]) != hash.end() ? hash[s[right]] : -1;
hash[s[right]] = right; // 更新哈希表(遍历每个字符,都要更新该字符 最后在字符串s中出现的位置)
sub_len = (sub_len < right - left) ? sub_len + 1 : right - left;
max_len = std::max(max_len, sub_len); // max(dp[j - 1], dp[j])
}
return max_len;
}
};