目录
palindrome-partitioning-ii(困难,动态规划)
add-binary(字符串,简单,二进位)
题目
给定两个二进制字符串,返回他们的和(用二进制表示)。输入为非空字符串且只包含数字 1 和 0。
示例 1:
输入: a = "11", b = "1"
输出: "100"
示例 2:
输入: a = "1010", b = "1011"
输出: "10101"
思路
关键点:
1.int 和string之间的转换,ASC码48就是'0',也就是说'0'的值是48,而后依次是'1'到'9'。 这样正好是char型减去48就是它对应的int值。这么就很巧妙了,代码中的例子:
a[j-1] = a[j-1] + 1;这个好像特殊似的,可以直接加一,但实际上呢,a[j-1]的确是字符串里面的值不假,不是整形,但当asc码加上1,也的确是所要的结果,所以,就不需要再减去‘0’了。
还有这个,a[j] = a[j] - '0' + b[j];看起来两个字符串执行加减,需要都转换为两个int类型,其实只需要转换一个,只不过是将原来的:(a-48(string转int) + b-48(string转int) )+ 48(将得到的int值再转为string) 直接变成了:((a-48)+ b)取得的效果是一样的。
所以,要理解string和int是转为asc码之后的底层运算的原理,才可以。
2.最后一步,要单独处理,所以for循环中,j>0而不是j>=0。
3.string要是想在开头插入一个字符,直接插就行了: a = '0' + a;
4.while循环里面,是前++,不是后++,说明,是先加1再循环。结果一样吧。
class Solution {
public:
string addBinary(string a, string b) {
int al = a.size();
int bl = b.size();
while(al < bl) //让两个字符串等长,若不等长,在短的字符串前补零,否则之后的操作会超出索引
{
a = '0' + a;
++ al;
}
while(al > bl)
{
b = '0' + b;
++ bl;
}
for(int j = a.size() - 1; j > 0; -- j) //从后到前遍历所有的位数,同位相加
{
a[j] = a[j] - '0' + b[j];
if(a[j] >= '2') //若大于等于字符‘2’,需要进一
{
a[j] = (a[j] - '0') % 2 + '0';
a[j-1] = a[j-1] + 1;
}
}
//最后一步单独处理
a[0] = a[0] - '0' + b[0]; //将ab的第0位相加
if(a[0] >= '2') //若大于等于2,需要进一
{
a[0] = (a[0] - '0') % 2 + '0';
a = '1' + a;
}
return a;
}
};
multiply-strings(中等,字符串,二进位)
题目
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
示例 1:
输入: num1 = "2", num2 = "3"
输出: "6"
示例 2:
输入: num1 = "123", num2 = "456"
输出: "56088"
说明:
num1 和 num2 的长度小于110。
num1 和 num2 只包含数字 0-9。
num1 和 num2 均不以零开头,除非是数字 0 本身。
不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。
思路
这个上面这道思路一样,只不过分成了先位与位相乘,再相加,加完考虑进位。一定要明白,每一步是位与位的关系,而不是乘数的一位对应下个整个乘数的关系。
最后,用了res.substr(i),很是巧妙,意思是从res的第i位起开始输出。用 这种方法,防止得数的开头出现零。
class Solution {
public:
string multiply(string num1, string num2) {
int n1 = num1.size();
int n2 = num2.size();
string res(n1 + n2, '0');
for (int i = n2 - 1; i >= 0; i--) {
for (int j = n1 - 1; j >= 0; j--) {
int temp = (res[i + j + 1] - '0') + (num1[j] - '0')*(num2[i] - '0');
res[i + j + 1] = temp % 10 + '0';//当前位
res[i + j] += temp / 10; //前一位加上进位,res[i+j]已经初始化为'0',加上int类型自动转化为char,所以此处不加'0'
}
}
//去除首位'0',只是去除首位的
for (int i = 0; i < n1 + n2; i++) {
if (res[i] != '0')
return res.substr(i);//获得字符串s中从第i位开始的字符串
}
return "0";
}
};
Count-and-say(简单,字符串)
题目
报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:
1. 1
2. 11
3. 21
4. 1211
5. 111221
1 被读作 "one 1" ("一个一") , 即 11。
11 被读作 "two 1s" ("两个一"), 即 21。
21 被读作 "one 2", "one 1" ("一个二" , "一个一") , 即 1211。
给定一个正整数 n(1 ≤ n ≤ 30),输出报数序列的第 n 项。注意:整数顺序将表示为一个字符串。
示例 1:
输入: 1
输出: "1"
示例 2:
输入: 4
输出: "1211"
思路
1.用上一次的结果作为这一次的输入,这个代码通过递归做到了,反复将处理完成的上一次的结果,传过来做这一次的输入。
2.to_string将int转为字符串string.
3.最后的res,是每一次都更新的,只存第n项,而不是,所有项。
class Solution {
public:
string countAndSay(int n) {
if(n==1) return "1";
string strlast=countAndSay(n-1);
int count = 1;//计数
string res;//存放结果
for(int i=0;i<strlast.size();i++)
{
if(strlast[i]==strlast[i+1])//计算有多少个相同数字
{
count++;
}
else
{
res+=to_string(count)+strlast[i];
count=1;
}
}
return res;
}
};
gas-station(中等,贪心算法)
题目描述
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
说明:
如果题目有解,该答案即为唯一答案。输入数组均为非空数组,且长度相同。输入数组中的元素均为非负数。
示例 1:
输入:
gas = [1,2,3,4,5]
cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。
示例 2:
输入:
gas = [2,3,4]
cost = [3,4,3]
输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。
思路
1.非常困扰的问题,会不会正确的起点,出现在tank加和的过程中呢?如果,tank第一次加和大于零,第二次是正确的起点,然后,然后,后面的某一部归到了负数,那么正确的起点不就被湮没了吗?我一直在困扰这种情况,但想通了之后,就明白了,这种情况是不存在的,假设当前点是正确起点,那自起点往后根本不会出现加和为负的情况(如果出现,那当前点肯定不是),如果这个正确起点前面也出现了tank为正的数,这数也肯定是正确起点(正确起点不唯一),你想想,如果当前点所产生的余量都够走一圈了,那上一个点的余量再加上当前点的余量不是更可以走一圈了吗?所以,不存在把正确点给湮没的情况,而且,如果tank累加为负了,那走过的所有点都不可能是正确节点了。
class Solution {
public:
int canCompleteCircuit(vector<int> &gas, vector<int> &cost) {
int start(0),total(0),tank(0);
for(int i=0;i<gas.size();i++){
if((tank=tank+gas[i]-cost[i])<0){
start=i+1;
total+=tank;
tank=0;
}
}
return (total+tank<0)?-1:start;
}
};
palindrome-partitioning-ii(困难,动态规划)
题目描述
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回符合要求的最少分割次数。
示例:
输入: "aab" 输出: 1
解释: 进行一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。
思路
要切割最少的回文串,就要清楚回文串的定义,如果以某个字符为中心(字符数量为奇数),它前面的和它后面的相等,那就是回文串;如果(字符数量为偶数),它后面的和它自身相等,也是回文串。
接下来就开始动态规划,得字符数量为l,设置一个list,长度为l+1,list[i]代表前i个字符可以被划分几次,初始值设成list[i]=i-1;表示,前i个字符串至多可以被划分为i-1个。
接下来进入循环,遍历每个字符,并以当前遍历的字符为中心,分奇数和偶数两种情况找回文,如果找到了一个回文序列,就
list[end+1] = min(list[end+1], list[start]+1);假设“aab”这个序列,start=0,end=1时候,发现回文了,那么list[2]就存放的是list[0]+1,因为原先list[2]存放2,现在发现因为发现了回文,只需要切一次就行了,存放1,(list[0]+1),很巧妙!。这算是一个规律吧。然后,保存起来,因为是统计拢共的回文串切分,所以,需要在每次循环的时候,都要把切分数更新一下(list[i+1] = min(list[i+1], list[i] + 1); ),将上一次获得的结果给继承下来。
这里还有一个边界问题比较巧妙。当循环的时候设置的list的长度是(l+1),但实际的主for循环里面的上限是l,这是因为list[0]是个没用的量,真正有意义的是从list[1]开始,表示一个字符的时候切分数为0,而s还是从0开始计算,真正的末尾是l-1,所以,上限是l这个设定是没问题的。另外,也是对于S而言的。l-1确实就是对应最后一个元素了。而list对应的最后一个元素应该是list[l]。
if(i == l-1){ // 最后一个了没必要找了
break;
}
最后,地方:
// 如果整个串都是回文串,那么就中断
if(list[l] == 0){
return 0;
}
其实是多余的,不加这个也行。
class Solution {
public:
int minCut(string s) {
int l = s.length();
vector<int> list(l+1); // list[i]代表前i个字符需要划几次,特别地,list[0]=-1
for(int i = 0;i < l+1;++i){ // 初始化[-1, 0, 1, 2, 3...]
list[i] = i - 1;
}
for(int i = 0;i < l;++i){ // 以每个字符为中心找最长回文子串
list[i+1] = min(list[i+1], list[i] + 1); // 初始化,最坏情况下就比左边的多划一次
if(i == l-1){ // 最后一个了没必要找了
break;
}
// 先找偶数个的
int start = i, end = i+1;
while(s[start] == s[end]){
list[end+1] = min(list[end+1], list[start]+1);
if(end == l-1 || start == 0){
break;
}
--start, ++end;
}
// 再找奇数个的
start = i-1, end = i+1;
if(start < 0){
continue;
}
while(s[start] == s[end]){
list[end+1] = min(list[end+1], list[start]+1);
if(end == l-1 || start == 0){
break;
}
--start, ++end;
}
// 如果整个串都是回文串,那么就中断
if(list[l] == 0){
return 0;
}
}
return list[l];
}
};