目录
1.两数之和
1)用双指针解决,首先进行稳定排序。
2)因为返回的是原数组中的下标,应该用结构体保存数值和下标的对应关系
注意:如果nodes用的是在外部定义vector就会导致超出内存限制,改为内部大小确定的数组即可解决。
struct Node{
int index;
int value;
};
bool cmp(const Node &a,const Node &b)
{
return a.value<b.value;
}
//vector<Node> nodes;
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int len=nums.size();
int left=0;
int right=len-1;
vector<int> ans;
Node nodes[len];
for(int i=left;i<=right;i++)
{
Node tmp;
tmp.value=nums[i];
tmp.index=i;
//nodes.push_back(tmp);
nodes[i]=tmp;
}
//stable_sort(nodes.begin(),nodes.end(),cmp);
stable_sort(nodes,nodes+len,cmp);
while(left<right)
{
if(nodes[left].value+nodes[right].value==target)
{
ans.push_back(nodes[left].index);
ans.push_back(nodes[right].index);
return ans;
}
else if(nodes[left].value+nodes[right].value<target)
{
left++;
}
else right--;
}
return ans;
}
};
2.两数相加
题目:给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
注意:逆序是指链表的头部存低位,尾部存高位,直接从头到尾遍历链表即可。
返回的ans_l不能只设置为一个结点指针ListNode*,它必须是一个指向确定结点的指针,否则其没有next值。但是这个结点的值无所谓,最后返回时会跳过该结点。相当于创建了一个任意值的头结点。
这里的链表是不带头结点的链表,但是返回的是链表首结点的指针。
判断结果是否可能以0作为最高位,只要判断最后carry是否为0即可。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
/*我的代码*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
//stack<int> s1,s2,ans;
ListNode* p1=l1;
ListNode* p2=l2;
ListNode* ans_l=new ListNode(0);//这个结点的值是什么无所谓,只是为了创建一个next为NULL的结点
ListNode* p=ans_l;
int carry=0;
while(p1!=NULL&&p2!=NULL)
{
int tmp=p1->val+p2->val+carry;
if(tmp>=10)
{
carry=tmp/10;
tmp=tmp%10;
}
else carry=0;
p->next=new ListNode(tmp);
p=p->next;
p1=p1->next;
p2=p2->next;
}
while(p1!=NULL)
{
int tmp=p1->val+carry;
if(tmp>=10)
{
carry=tmp/10;
tmp=tmp%10;
}
else carry=0;
p->next=new ListNode(tmp);
p=p->next;
p1=p1->next;
}
while(p2!=NULL)
{
int tmp=p2->val+carry;
if(tmp>=10)
{
carry=tmp/10;
tmp=tmp%10;
}
else carry=0;
p->next=new ListNode(tmp);
p=p->next;
p2=p2->next;
}
if(carry!=0)
{
p->next=new ListNode(carry);
}
return ans_l->next;
}
};
/*评论中更简洁的代码,只要一个循环*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(0);
ListNode* curr = dummy;
int carry = 0;
while (l1 || l2) {
int val1 = l1 ? l1->val : 0;
int val2 = l2 ? l2->val : 0;
int sum = val1 + val2 + carry;
carry = sum / 10;
curr->next = new ListNode(sum % 10);
curr = curr->next;
if (l1) l1 = l1->next;
if (l2) l2 = l2->next;
}
if (carry) curr->next = new ListNode(carry);
return dummy->next;
}
};
3.无重复字符的最长子串
关键点:滑动窗口+Hashmap
建立一个字符串到索引的映射,而不是使用集合来判断一个字符是否存在。 当我们找到重复的字符时,我们可以立即跳过该窗口,下一次从下一个字符开始。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
map<char,int> mp;
int len=s.length();
int maxlen=0,nowlen=0;
for(int i=0;i<len;i++)
{
if(mp.find(s[i])==mp.end()) //没有重复的元素
{
mp[s[i]]=i;
nowlen++;
if(nowlen>maxlen)
maxlen=nowlen;
}
else //如果有重复的,将重复元素之后的元素再次加入map
{
int j=mp[s[i]];
nowlen=0;
mp.clear();
for(int k=j+1;k<=i;k++)
{
mp[s[k]]=k;
nowlen++;
}
if(nowlen>maxlen)
maxlen=nowlen;
}
}
return maxlen;
}
};
5.最长回文子串
法一:中心拓展法
回文中心的两侧互为镜像。因此,回文可以从他的中心展开,并且只有 2n-1 个这样的中心(一个元素为中心的情况有 n 个,两个元素为中心的情况有 n-1 个)
从左向右枚举回文中心,分奇回文和偶回文。
注意:1. 要特判空字符和单个字符的情况(直接返回) “”和“ ”不一样,长度分别为0和1
2. C++ string中substr()用法:
string substr(int pos,int len) ;
int find1(string s,int len,int mid,int& left,int& right)
{
int i=mid,j=mid;
while(i>=0&&j<=len-1&&s[i]==s[j])
{
left=i;
right=j;
i--;
j++;
}
return right-left+1;
}
int find2(string s,int len,int mid,int& left,int& right)
{
int i=mid,j=mid+1;
while(i>=0&&j<=len-1&&s[i]==s[j])
{
left=i;
right=j;
i--;
j++;
}
return right-left+1;
}
class Solution {
public:
string longestPalindrome(string s) {
int len=s.length();
if(len==0||len==1) return s;
int maxlen=0;
int left,right;
int ans_left,ans_right;
for(int i=0;i<len;i++) //所有奇数情况
{
int cnt1=find1(s,len,i,left,right);
if(cnt1>maxlen)
{
ans_left=left;
ans_right=right;
maxlen=cnt1;
}
int cnt2=find2(s,len,i,left,right);
if(cnt2>maxlen)
{
ans_left=left;
ans_right=right;
maxlen=cnt2;
}
}
return s.substr(ans_left,maxlen);
}
};
法二:动态规划法
首先从头遍历一遍字符串,设置初始状态:dp[i][i]=1 dp[i][i+1]=2(if s[i]=s[i+1])
然后子串长度递增的方式,依次填充dp数组。
int dp[1010][1010];
class Solution {
public:
string longestPalindrome(string s) {
int len=s.length();
if(len==0||len==1) return s;
memset(dp,0,sizeof(dp));
int maxcnt=0;
int left=0,right=0;
for(int i=0;i<len;i++)
{
dp[i][i]=1;
if(i!=len-1&&s[i]==s[i+1])
{
dp[i][i+1]=2;
left=i;
right=i+1;
}
}
for(int r=3;r<=len;r++)
{
for(int i=0;i<=len-r;i++)
{
int j=i+r-1;
if(s[i]==s[j]&&dp[i+1][j-1]!=0) //且其内部串必须是回文串
{
dp[i][j]=dp[i+1][j-1]+2;
if(dp[i][j]>maxcnt)
{
maxcnt=dp[i][j];
left=i;
right=j;
}
}
}
}
return s.substr(left,right-left+1);
}
};
4.寻找两个有序数组的中位数
参考:LeetCode题解
1.中位数:将一个集合划分成长度相等的两个子集,其中一个子集中的元素总是大于另一个子集中的元素。
2.割: 通过切一刀,能够把有序数组分成左右两个部分,切的那一刀就被称为割(Cut
),割(Cut
)的左右会有两个元素,分别是左边最大值和右边最小值。割可以割在两个数中间,也可以割在1个数上,如果割在一个数上,那么这个数即属于左边,也属于右边。
对于两个数组,设Ci
为第i
个数组的割(下标值),满足题意的割法应满足以下条件:
LMaxi为第i个数组割后的左元素.
RMini为第i个数组割后的右元素。
首先,LMax1<=RMin1,LMax2<=RMin2 这是肯定的,因为数组是有序的,左边肯定小于右边!,而如果割(Cut)在某个数上,则左右相等。
其次,LMax1<=RMin2,LMax2<=RMin1
如果左半边全小于右半边,如果左边的元素个数相加刚好等于k, 那么第k个元素就是Max(LMax1, LMax2),这个比较好理解的,因为Max(LMax1, LMax2)肯定是左边k个元素的最大值,因为合并后的数组是有序,第k个元素肯定前面k个元素中最大的那个。
那么如果 LMax1>RMin2,说明数组1的左边元素太大(多),我们把C1减小,C2=k-C1也就相应的增大。LMax2>RMin1同理,把C2减小,C1=k-C2也就相应的增大。
3.两个数组的最大问题是,它们合并后,m+n总数可能为奇, 也可能为偶,所以我们得想法让m+n总是为偶数
通过虚拟加入‘#’,我们让m转换成2m+1 ,n转换成2n+1, 两数之和就变成了2m+2n+2,恒为偶数。
注意是虚拟加,其实根本没这一步,通过下面的转换,我们可以保证虚拟加后每个元素跟原来的元素一一对应
注:割在‘#’上Ci为偶数,而偶数直接除以2和减1之后再除2结果是不同的,所以刚好表示Lmaxi和Rmini是两个不同的元素。
而割在数字上Ci为奇数, 而奇数直接除以2和减1之后再除2结果是相同的,所以刚好表示Lmaxi和Rmini是两个相同的元素。
4.最快的割(Cut)是使用二分法,
有2个数组,我们对哪个做二分呢? 根据之前的分析,我们知道了,只要C1或C2确定,另外一个也就确定了。这里,为了效率,我们肯定是选长度较短的做二分,假设为C1。
LMax1>RMin2,把C1减小,C2增大。—> C1向左二分
LMax2>RMin1,把C1增大,C2减小。—> C1向右二分
如果C1或C2已经到头了怎么办?
这种情况出现在:如果有个数组完全小于或大于中值。假定n<m, 可能有4种情况:
C1 = 0 —— 数组1整体都在右边了,所以都比中值大,中值在数组2中,简单的说就是数组1割后的左边是空了,所以我们可以假定LMax1 = INT_MIN
C1 =2n —— 数组1整体都在左边了,所以都比中值小,中值在数组2中 ,简单的说就是数组1割后的右边是空了,所以我们可以假定RMin1= INT_MAX,来保证LMax2<RMin1恒成立
C2 = 0—— 数组2整体在右边了,所以都比中值大,中值在数组1中 ,简单的说就是数组2割后的左边是空了,所以我们可以假定LMax2 = INT_MIN
C2 = 2m—— 数组2整体在左边了,所以都比中值小,中值在数组1中, 简单的说就是数组2割后的右边是空了,为了让LMax1 < RMin2恒成立,我们可以假定RMin2 = INT_MAX
#include <stdio.h>
#include <vector>
using namespace std;
#define max(a,b) (((a) > (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b))
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
if (n > m) //保证数组1一定最短
{
return findMedianSortedArrays(nums2, nums1);
}
// Ci 为第i个数组的割,比如C1为2时表示第1个数组只有2个元素。LMaxi为第i个数组割后的左元素。RMini为第i个数组割后的右元素。
int LMax1, LMax2, RMin1, RMin2, c1, c2, lo = 0, hi = 2 * n; //我们目前是虚拟加了'#'所以数组1是2*n长度
while (lo <= hi) //二分
{
c1 = (lo + hi) / 2; //c1是二分的结果
c2 = m + n - c1;
LMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2];
RMin1 = (c1 == 2 * n) ? INT_MAX : nums1[c1 / 2];
LMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2];
RMin2 = (c2 == 2 * m) ? INT_MAX : nums2[c2 / 2];
if (LMax1 > RMin2)
hi = c1 - 1;
else if (LMax2 > RMin1)
lo = c1 + 1;
else
break;
}
return (max(LMax1, LMax2) + min(RMin1, RMin2)) / 2.0;
}
};
int main(int argc, char *argv[])
{
vector<int> nums1 = { 2,3, 5 };
vector<int> nums2 = { 1,4,7, 9 };
Solution solution;
double ret = solution.findMedianSortedArrays(nums1, nums2);
return 0;
}
以下是我自己的代码,已经尽可能优化了,能够求解小数据,大数据会超时。
思路是合并两个数组,在找到满足要求后的元素个数后提前终止。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
vector<int> ans;
int len1=nums1.size();
int len2=nums2.size();
int i=0,j=0;
int m=len1+len2;
bool flag=false;
if(m&1==1) //数量之和为奇数
{
m=m>>1+1;
flag=true;
}
else m=m>>1; //数量之和为偶数
while(i<len1&&j<len2)
{
if(nums1[i]<nums2[j])
ans.push_back(nums1[i++]);
else ans.push_back(nums2[j++]);
if(flag&&ans.size()==m) //达到奇数个要求
return ans[m-1]*1.0;
if(flag==false&&ans.size()==m+1)
return 0.5*(ans[m-1]+ans[m]);
}
while(i<len1)
{
ans.push_back(nums1[i++]);
if(flag&&ans.size()==m) //达到奇数个要求
return ans[m-1]*1.0;
if(flag==false&&ans.size()==m+1)
return 0.5*(ans[m-1]+ans[m]);
}
while(j<len2)
{
ans.push_back(nums2[j++]);
if(flag&&ans.size()==m) //达到奇数个要求
return ans[m-1]*1.0;
if(flag==false&&ans.size()==m+1)
return 0.5*(ans[m-1]+ans[m]);
}
if(flag)
return ans[m-1]*1.0;
else return 0.5*(ans[m-1]+ans[m]);
}
};
11.盛最多水的容器
设置双指针i,j,指向容器两端,逐渐向中间收缩并记录最大值。
每次选定围成水槽两板height[i]
, height[j]
中较小的对应指针,向中间收缩,这是因为
- 水槽的高度由两板中的短板决定,每次收缩,都会导致水槽
底边宽度-1
, -
因此,若想找到比当前最大值更大的水槽,那么水槽的短板高必须要高于上一个水槽短板高,而只有向内移动短板,有可能达成这一条件(若移动长板,下个水槽的面积一定小于当前水槽面积)
为什么一定能找到最优解:
如图所示 height[0]<height[8],选择了左分支1->8,但是由于左右分支有很多重叠部分,真正被放弃的只有①部分。
①部分的特点: 均是从0出发的区间。因为1->8中,height[0]<height[8],水槽的高度已经是0的最大高度height[0],之后无论是0->7、0->6、0->5...高度最大也不会超过height[0],但是宽度一定比0->8小,所以水槽容量不可能再增加,所以可以舍弃。
在第二层,height[8]<height[1],所以选择了右分支1->7,但真正被放弃的也只有②部分。
②部分的特点: 终点均是8,而在1->8中,水槽的高度已经是8的最大高度height[8],之后无论是2->8、3->8....高度最大也不会超过height[8],但是宽度一定比1->8小,所以水槽容量不可能再增加,所以可以舍弃。
class Solution {
public:
int maxArea(vector<int>& height) {
int len=height.size();
int i=0,j=len-1;
int ans=0;
while(i<j)
{
int h=height[i]<height[j]?height[i]:height[j];
int now=(j-i)*h;
if(now>ans)
ans=now;
if(height[i]<height[j])
i++;
else j--;
}
return ans;
}
};
15.三数之和
还是双指针的思想,先将数组排序,每次选择一个非正数,对它左边的序列用双指针遍历。
mid是排序后数组中最后一个非正数。
要注意去重,如果指针移动后的元素和之前一个元素相同则要继续移动指针。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int len=nums.size();
sort(nums.begin(),nums.end());
vector<vector<int> >ans;
vector<int> tmp;
/*找到最大的负数或者是0的下标*/
int mid=len-1;
for(int i=0;i<len-1;i++)
if(nums[i]<=0&&nums[i+1]>0)
mid=i;
//cout<<mid<<endl;
for(int k=0;k<=mid;k++)
{
if(k!=0&&nums[k]==nums[k-1]) continue; //可能会导致重复解
int i=k+1,j=len-1;
while(i<j)
{
if(nums[i]+nums[j]+nums[k]==0)
{
tmp.clear();
tmp.push_back(nums[k]);
tmp.push_back(nums[i]);
tmp.push_back(nums[j]);
ans.push_back(tmp);
i++;
j--;
while(i<j&&nums[i]==nums[i-1]) i++;
while(i<j&&nums[j]==nums[j+1]) j--;
}
else if(nums[i]+nums[j]+nums[k]<0)
{
i++;
}
else
{
j--;
}
}
}
return ans;
}
};
20.有效的括号
map<char,int> mp;
class Solution {
public:
bool isValid(string s) {
int len=s.length();
if(len==0) return true;
if(len==1) return false;
stack<char> st;
mp['(']=1;
mp[')']=1;
mp['{']=2;
mp['}']=2;
mp['[']=3;
mp[']']=3;
for(int i=0;i<len;i++)
{
if(s[i]=='(' || s[i]=='{' || s[i]=='[')
st.push(mp[s[i]]);
else
{
if(st.empty()) return false;
int t1=st.top();
int t2=mp[s[i]];
if(t1!=t2)
return false;
else st.pop();
}
}
if(!st.empty())
return false;
else return true;
}
};
17.电话号码的组合
主要是用DFS进行递归,我自己用的方法有点暴力,主要是2-5都只有三个字符,7、8、9要单独讨论一下,代码有些冗余。可以直接用map映射。
注意:向string字符串追加字符c 只能用ans=ans+c,不可以用ans+=c!
/*我的代码*/
void DFS(vector<string> &res,string digits,string ans,int k,int len)
{
if(k==len)
{
res.push_back(ans);
return;
}
if(digits[k]=='7')
{
ans=ans+'p';
DFS(res,digits,ans,k+1,len);
ans.erase(ans.begin()+k);
ans=ans+'q';
DFS(res,digits,ans,k+1,len);
ans.erase(ans.begin()+k);
ans=ans+'r';
DFS(res,digits,ans,k+1,len);
ans.erase(ans.begin()+k);
ans=ans+'s';
DFS(res,digits,ans,k+1,len);
ans.erase(ans.begin()+k);
}
else if(digits[k]=='8')
{
ans=ans+'t';
DFS(res,digits,ans,k+1,len);
ans.erase(ans.begin()+k);
ans=ans+'u';
DFS(res,digits,ans,k+1,len);
ans.erase(ans.begin()+k);
ans=ans+'v';
DFS(res,digits,ans,k+1,len);
ans.erase(ans.begin()+k);
}
else if(digits[k]=='9')
{
ans=ans+'w';
DFS(res,digits,ans,k+1,len);
ans.erase(ans.begin()+k);
ans=ans+'x';
DFS(res,digits,ans,k+1,len);
ans.erase(ans.begin()+k);
ans=ans+'y';
DFS(res,digits,ans,k+1,len);
ans.erase(ans.begin()+k);
ans=ans+'z';
DFS(res,digits,ans,k+1,len);
ans.erase(ans.begin()+k);
}
else
{
for(int t=0;t<3;t++)
{
//ans[k]='a'+(digits[k]-2)*3+t;
char c='a'+(digits[k]-'2')*3+t;
//char c='*';
ans=ans+c;
DFS(res,digits,ans,k+1,len);
ans.erase(ans.begin()+k);
}
}
}
class Solution {
public:
vector<string> letterCombinations(string digits) {
int len=digits.length();
string ans;
vector<string> res;
if(len==0) return res;
DFS(res,digits,ans,0,len);
return res;
}
};
/*看题解之后改的代码*/
class Solution {
public:
vector<string> res;
map<char,string> mp;
vector<string> letterCombinations(string digits) {
mp['2']="abc";
mp['3']="def";
mp['4']="ghi";
mp['5']="jkl";
mp['6']="mno";
mp['7']="pqrs";
mp['8']="tuv";
mp['9']="wxyz";
int len=digits.length();
string ans;
res.clear();
if(len==0) return res;
DFS(digits,ans,0,len);
return res;
}
void DFS(string digits,string ans,int k,int len)
{
if(k==len)
{
res.push_back(ans);
return;
}
string s=mp[digits[k]];
for(int i=0;i<s.length();i++)
{
ans=ans+s[i];
DFS(digits,ans,k+1,len);
ans.erase(ans.begin()+k);
}
}
};
别人更简洁的代码:
注意map的赋值方式,并且需要在函数内部赋值。
string now放在参数位置,就可以减少添加和删去的步骤。
class Solution {
public:
unordered_map<int, string> mp;
vector<string> letterCombinations(string digits) {
mp = {{2, "abc"}, {3, "def"}, {4, "ghi"}, {5, "jkl"},
{6, "mno"}, {7, "pqrs"}, {8, "tuv"}, {9, "wxyz"}};
vector<string> res;
if (digits == "")
return res;
helper(digits, 0, "", res);
return res;
}
void helper(string digits, int cur, string now, vector<string>& res)
{
if (cur == digits.size())
res.push_back(now);
string s = mp[digits[cur] - '0'];
for (int i = 0; i < s.size(); ++i)
helper(digits, cur+1, now+s.substr(i, 1), res);
}
};
19.删除链表的倒数第N个结点
细节:一定要判断每个可能的NULL情况。题目参数中的head是指向头结点的,这里的链表是带头结点的。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head==NULL||head->next==NULL) return NULL;
ListNode* p1=head;
ListNode* p2=head;
for(int i=0;i<n;i++) //p2多走一步,p1就会停在待删除结点的前一个结点
{
p2=p2->next;
}
if(p2==NULL) return head->next;
while(p2->next!=NULL) //注意此时p1指向的是待删除结点的前一个结点
{
p1=p1->next;
p2=p2->next;
}
p1->next=p1->next->next;//把p1之后一个结点删除
return head;
}
};
21.合并两个有序链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1==NULL) return l2;
if(l2==NULL) return l1;
ListNode*p1=l1,*p2=l2;
ListNode*ans=new ListNode(0);
ListNode*p=ans;
while(p1!=NULL&&p2!=NULL)
{
if(p1->val<p2->val)
{
p->next=p1;
p=p->next;
p1=p1->next;
}
else
{
p->next=p2;
p=p->next;
p2=p2->next;
}
}
/* 直接把后面链表接上就好了..
while(p1!=NULL)
{
p->next=p1;
p=p->next;
p1=p1->next;
}
while(p2!=NULL)
{
p->next=p2;
p=p->next;
p2=p2->next;
}
*/
p->next=(p1==NULL)?p2:p1;
return ans->next;
}
};
10.正则表达式匹配
参考题解:思路——leetcode某题解 代码——leetcode另一题解
vector<vector<bool>>flag(len1 + 1, vector<bool>(len2 + 1, false));
flag[0][0] = true;边界情况
这里flag[i][j]表示pattern[0~j-1]是否可以匹配str[0~i-1],(注意flag[i][j] 对于pattern[j - 1] str[i - 1])
首先列出动态转移方程
第一种情况:
pattern[j - 1] == str[i - 1] || pattern[j - 1] == '.' //元素相等或为通配符‘.’
有flag[i][j] = flag[i - 1][j - 1];
第二种情况:
pattern[j - 1] == '*' //p串的当前元素为‘*’
第二种情况的第一个分支
str[i - 1] == pattern[j - 2] || pattern[j - 2] == '.' //(1)s中当前元素的*之前一个元素相等或为通配符‘.’
有flag[i][j] = (flag[i][j - 2] || flag[i][j - 1] || flag[i - 1][j]);(三种情况分别对应:*号让前面字符出现0次、出现1次、出现2次及以上)
出现0次:直接将 ‘a*’这个整体跳过,考虑子串是否匹配
出现1次:将‘*’跳过,将其变成一个常规字符来考虑是否匹配
出现多次:‘a*’整体保留,s中指针往前移动一个,考虑子串是否匹配
第二种情况第二个分支 //(2)若*前元素和s当前元素不相等,则只能看成出现0次
flag[i][j] = flag[i][j - 2];(*号只能让前面的字符出现0次)
第三种情况:
flag[i][j] = false; //该算法是为了尽可能找到所有可能为true的情况,其余情况均为false
其次在动态规划前需要考虑好初始条件
for (int i = 1; i <= len2; ++i) { //s串为空串时
if (pattern[i - 1] == '*')
flag[0][i] = flag[0][i - 1] || flag[0][i - 2]; 这是为了解决类似 "" 和 ".*.*a*a*b*"这种情况 因为这种也是符合的
// flag[0][i] = flag[0][i - 1] => ""和“.*”匹配
// flag[0][i] =flag[0][i - 2] =>""和“a*”匹配
代码明天补上~~
22.括号生成
使用递归解决,left表示已经有的左括号的数目,right表示已经有的右括号的数目,依次向当前字符串中插入左括号和右括号。
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> ans;
Traceback("",0,0,n,ans);
return ans;
}
void Traceback(string now,int left,int right,int n,vector<string> &ans)
{
if(left<right) return;
if(left>n||right>n) return;
if(left==n&&right==n) ans.push_back(now);
Traceback(now+'(',left+1,right,n,ans);
Traceback(now+')',left,right+1,n,ans);
}
};
31.下一个排列
思路:从后往前遍历,找到第一个i,满足nums[i]>nums[i-1],说明从i到末尾这已经是一个最大的数了,此时应该从后面选择一个比nums[i-1]大的数中最小的一个,和nums[i-1],说明将i-1位前进了一步(更新成了稍大的一个数),再将i及i之后的数进行排序,变成一个最小的数,即可得到全排列。
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int len=nums.size();
int i;
int mark;
for( i=len-1;i>0;i--)
{
if(nums[i]>nums[i-1])
{
int min=i;
for(int j=i;j<len;j++)
{
if(nums[j]>nums[i-1]&&nums[j]<nums[min]) min=j;
}
int t=nums[min];
nums[min]=nums[i-1];
nums[i-1]=t;
sort(nums.begin()+i,nums.end());
break;
}
}
if(i==0)
sort(nums.begin(),nums.end());
}
};
33.搜索旋转排序数组
思路:直接进行二分法,分以下情况讨论:
(1)mid直接等于target,直接返回
(2)在左半边递增区域:
①target在left和mid之间
②target不在之间
(2)在右半边递增区域:
①target在mid和right之间
②target不在之间
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0,right=nums.size()-1;
while(left<=right)
{
int mid=(left+right)/2;
if(nums[mid]==target)
return mid;
if(nums[mid]>=nums[left]) //左部分较长,mid在左半边
{
if(target>=nums[left]&&target<nums[mid])
right=mid-1;
else left=mid+1;
}
else //右半边较长,mid在右半边
{
if(target>nums[mid]&&target<=nums[right])
left=mid+1;
else right=mid-1;
}
}
return -1;
}
};
155.最小栈
即如何在常数时间内获得栈内的最小元素?
方法一:辅助栈
另有一个栈min,存放当前栈内最小的元素。若min栈空或入栈元素比min栈顶元素小,则入min栈。出栈时要判断s栈栈顶元素是否和min栈栈顶元素相等,若相等,min栈栈顶元素也要出栈。
class MinStack {
public:
stack<int> s;//数据栈
stack<int> min;//辅助栈
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
s.push(x);
if(min.empty()||x<=min.top())
{
min.push(x);
}
}
void pop() {
if(s.top()==min.top())
min.pop();
s.pop();
}
int top() {
return s.top();
}
int getMin() {
return min.top();
}
};
方法二:每次入栈两个元素——当前元素和栈内当前最小元素
class MinStack {
public:
/** initialize your data structure here. */
stack<int> s;
MinStack() {
}
void push(int x) {
if(s.empty())
{
s.push(x);
s.push(x);
}
else
{
int temp=s.top();
s.push(x);
if(x<temp)
{
s.push(x);
}
else
{
s.push(temp);
}
}
}
void pop() {
s.pop();
s.pop();
}
int top() {
int temp=s.top();
s.pop();
int top=s.top();
s.push(temp);
return top;
}
int getMin() {
return s.top();
}
};
34.在排序数组中查找元素的第一个和最后一个位置
思路:即查找有序数组中第一个大于等于target的元素 和 第一个大于target的元素(下标减1即可)
数组从左到右一定是先不满足,后开始满足的。如果找不到target,返回的是应该插入target的位置。
PS:这时候需要对upper_bound进行判断,可以直接写一个函数寻找最后一个满足某条件的元素。
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> ans;
ans.push_back(-1);
ans.push_back(-1);
int len=nums.size();
if(len==0) return ans;
int ans_left=binarySearch_1(nums,target,0,len-1);
int ans_right=binarySearch_2(nums,target,0,len-1);
if(nums[ans_left]==target)
{
ans[0]=ans_left;
if(nums[ans_right]==target)
ans[1]=ans_right;
else ans[1]=ans_right-1;
}
return ans;
}
/*寻找第一个大于等于target的数*/
int binarySearch_1(vector<int>& nums,int target,int left,int right)
{
while(left<right)
{
int mid=(left+right)/2;
if(nums[mid]>=target)
right=mid;
else left=mid+1;
}
return left;
}
/*寻找第一个大于target的数*/
int binarySearch_2(vector<int>& nums,int target,int left,int right)
{
while(left<right)
{
int mid=(left+right)/2;
if(nums[mid]>target)
right=mid;
else left=mid+1;
}
return left;
}
/*寻找最后一个小于等于target的数*/
int binarySearch_2(vector<int>& nums,int target,int left,int right)
{
while(left<right)
{
int mid=(left+right)/2+1; //避免死循环mid始终和left相等
if(nums[mid]<=target)
left=mid;
else right=mid-1;
}
return left;
}
};
23.合并K个排序链表
思路:循环遍历每个链表的第一个结点,找到其中最小的那个,记录下来。
优化1:用容量为K的最小堆优先队列,把链表的头结点都放进去,然后出队当前优先队列中最小的,挂上链表,,然后让出队的那个节点的下一个入队,再出队当前优先队列中最小的,直到优先队列为空。
优化2:分治法
分解:将K个链表分成两部分,前半部分和后半部分。将两部分各合并成一个链表l1、l2。
合并:将l1和l2两个链表合并成一个链表。
注意:合并两个链表用到了递归法的思想!!即寻找两个链表头结点中较小的那个作为头结点,然后减小问题规模,继续递归。边界是到达链表尾部,直接返回NULL。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution { //暴力解法
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
int K=lists.size();
ListNode* head=new ListNode(0); //头指针
ListNode* p=head;
while(1)
{
int min=INT_MAX;
int i,min_i=-1;
int cnt_null=0; //统计空指针数目
for(i=0;i<K;i++)
{
if(lists[i]!=NULL&&lists[i]->val<min)
{
min_i=i;
min=lists[i]->val;
}
if(lists[i]==NULL) cnt_null++;
}
if(cnt_null==K) break;
//ListNode tmp=ListNode(min);
//p->next=&tmp;
//cout<<min_i<<endl;
p->next=lists[min_i];
p=p->next;
lists[min_i]=lists[min_i]->next;
}
return head->next;
}
};
class Solution { //分治解法
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
int K=lists.size();
if(K==0) return NULL;
ListNode*head = merge(lists,0,K-1);
return head;
};
/*将下标从left到right的所有链表合并成一个链表*/
ListNode* merge(vector<ListNode*>& lists,int left,int right)
{
if(left>right) return NULL;
if(left==right) return lists[left];
int mid=left+(right-left)/2;
//cout<<mid<<endl;
ListNode* l1 = merge(lists,left,mid); //合并左半部分链表为一个链表
ListNode* l2 = merge(lists,mid+1,right); //合并右半部分链表为一个链表
return mergeTwoList(l1,l2); //将两条链表合并成一个链表
}
ListNode* mergeTwoList(ListNode*l1,ListNode*l2)
{
if(l1==NULL) return l2;
if(l2==NULL) return l1;
if(l1->val<l2->val)
{
l1->next=mergeTwoList(l1->next,l2);
return l1;
}
else{
l2->next=mergeTwoList(l1,l2->next);
return l2;
}
}
};
39.组合总和(子集选取问题 DFS)
要点:①原数组无重复元素 ②数组内某个元素可以重复选取 ③解集不能包含重复的组合。
思路:采用DFS深度搜索,每个结点的两个分支是当前结点选还是不选,由于可以重复选,所以在左分支深度仍然为k。
分析:解集不能包含重复的组合 ——> 要对原数组进行排序
数组内某个元素可以重复选取
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> tmp;
sort(candidates.begin(),candidates.end());
DFS(candidates,target,res,0,tmp,0);
return res;
}
void DFS(vector<int>& candidates,int target,vector<vector<int>>& res,int sum,vector<int>& tmp,int k)
{
if(sum>target||k>=candidates.size())
{
return;
}
if(sum==target)
{
res.push_back(tmp);
return;
}
tmp.push_back(candidates[k]);
DFS(candidates,target,res,sum+candidates[k],tmp,k); //选
tmp.pop_back();
DFS(candidates,target,res,sum,tmp,k+1); //不选
}
};
40.组合总和2
题目要点:①原数组内可能存在重复元素 ②原数组内某个元素不能重复选取(但是相同的数字可能重复出现,仔细体会一下题目表达) ③解集不能包含重复的组合。
分析:本题不同于上题,不再是一个子集选取问题,变成了为变长数组tmp的每个位置选组合适的元素,总的原则是tmp数组的相同位置不能选重复元素(因为tmp数组中元素也是有序的,两个tmp数组相同位置出现相同元素则一定是重复解),tmp数组的不同位置元素可以重复!!!!!
要理清两个概念,在同一个DFS函数中,for循环中的每个点相对于tmp数组来说是同一个位置的,而为了“解集不能包含重复的组合”,必须保证同一个for循环中,指针i在遍历的时候,candidates[i] != candidates[i-1] 。
另外,对于每个当前结点的子结点,也就是tmp数组的下一个位置,元素是可以重复的(如[1,1,6]这样的解),所以要讨论的是指针k后面的一个元素。
对于候选数组排序的目的在于便于判断,tmp的相同位置是否选择了相同的元素!
以上两道题的区别:39题是数组内某个元素可以重复选取,40题是原数组内可能存在重复元素但原数组内某个元素不能重复选取。看起来似乎都可能出现[1,1,6]这样的解,但是仔细思考一下本质是不同的!
39题某个重复选取的次数是任意的(只要sum<target),但是40题最多选取次数就是候选数组中某个元素存在的次数。
所以40题需要一个指针k来指向原候选数组的每个元素!!
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> tmp;
sort(candidates.begin(),candidates.end());
DFS(candidates,res,tmp,target,0,0);
return res;
}
void DFS(vector<int>& candidates,vector<vector<int>>& res,vector<int>& tmp,int target,int sum,int k)
{
if(sum>target)
{
return;
}
if(sum==target)
{
res.push_back(tmp);
return;
}
for(int i=k;i<candidates.size();i++)
{
if(i>k&&candidates[i]==candidates[i-1]) //同一个位置不能有重复元素
continue;
if(candidates[i]>target) continue;
tmp.push_back(candidates[i]);
DFS(candidates,res,tmp,target,sum+candidates[i],i+1);
tmp.pop_back();
}
}
};
46.全排列
题目要求:没有重复元素的数字序列,返回所有全排列。
分析:这是排列问题,要求tmp各个位置元素不能相同,考虑使用mark数组标记该元素之前是否已经出现过。
tips:可以用vector的构造函数,快速构造标记数组
vector<bool> mark(nums.size(),false);
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
vector<int> tmp;
vector<bool> mark;
int len=nums.size();
for(int i=0;i<len;i++)
mark.push_back(false);
DFS(nums,mark,res,tmp,len,0);
return res;
}
void DFS(vector<int>& nums,vector<bool>& mark,vector<vector<int>>& res,vector<int>& tmp,int len,int k)
{
if(k==len)
{
res.push_back(tmp);
return;
}
for(int i=0;i<len;i++)
{
if(!mark[i])
{
tmp.push_back(nums[i]);
mark[i]=true;
DFS(nums,mark,res,tmp,len,k+1);
tmp.pop_back();
mark[i]=false;
}
}
}
};
47.全排列2
题目要求:给定一个可包含重复数字的序列,返回所有不重复的全排列。
思路:可包含重复元素,所以tmp不同位置的结点可以重复。
不重复的排列,所以tmp相同位置的结点不能重复,也就是同一个for循环的兄弟结点不能重复,我用的笨办法是用flag来记录一下for循环中左兄弟结点是什么,为了保证相同元素出现在一起,先要对nums进行一下排序。
由于是全排列问题,所以原数列中的每个元素一定会在tmp中出现,所以每次for循环从nums数组的下标0开始遍历(m叉树、图的m着色问题),选中一个一个元素就对它mark[i]=true,退出这个结点就还原mark[i]=false。
39、40题是寻找一个满足条件的子集,且40题要求原数组中每个元素最多出现一次,所以可以有一个指向原数组的下标k,每次都选定一个i之后,将下标k定位到i,之后从k之后的元素中考虑.
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> res;
vector<int> tmp;
int len=nums.size();
vector<bool> mark(len,false);
sort(nums.begin(),nums.end());
DFS(nums,mark,res,tmp,len,0);
return res;
}
void DFS(vector<int> nums,vector<bool>& mark,vector<vector<int>>& res,vector<int>& tmp,int len,int k)
{
if(k==len)
{
res.push_back(tmp);
return;
}
int flag=INT_MAX;
for(int i=0;i<len;i++)
{
if(mark[i]==true||nums[i]==flag) continue;
flag=nums[i];
tmp.push_back(nums[i]);
mark[i]=true;
DFS(nums,mark,res,tmp,len,k+1);
tmp.pop_back();
mark[i]=false;
}
}
};
78.子集
题目要求:不含重复元素的数组,返回该数组所有可能的子集集合,解集不能包含重复的子集。
思路:典型的选取子集问题,讨论原数组的每个元素,选还是不选。(二分支)
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> res;
vector<int> tmp;
DFS(nums,res,tmp,nums.size(),0);
return res;
}
void DFS(vector<int>& nums,vector<vector<int>>& res,vector<int>& tmp,int len,int k)
{
if(k==len) //已经
{
res.push_back(tmp);
return;
}
tmp.push_back(nums[k]);
DFS(nums,res,tmp,len,k+1); //选
tmp.pop_back();
DFS(nums,res,tmp,len,k+1); //不选
}
};
90.子集2
题目要求:给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。
思路:按照上一题的思路对原数组的每一个元素进行选择,但是在最后加入res的时候,借助map讨论该数组之前是否出现过。
由于对每一种可能情况都讨论了一下,所以复杂度较高,比较暴力。。
优化:参考题解,类似于40题,也有一个指针k在原数组中滑动,但是区别是该题是求子集所以从根结点到每个结点的路径都会放到res中,而40题是寻找一个满足条件的子集(元素之和等于target),所以只有满足了特定条件才放到res。
/*较暴力解法*/
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<vector<int>> res;
vector<int> tmp;
map<vector<int>,int> mp;
sort(nums.begin(),nums.end());
DFS(nums,mp,res,tmp,nums.size(),0);
return res;
}
void DFS(vector<int>& nums,map<vector<int>,int>& mp,vector<vector<int>>& res,vector<int>& tmp,int len,int k)
{
if(k==len)
{
map<vector<int>,int>::iterator it=mp.find(tmp);
if(it==mp.end())
{
mp[tmp]=1;
res.push_back(tmp);
}
return;
}
tmp.push_back(nums[k]);
DFS(nums,mp,res,tmp,len,k+1);//选
tmp.pop_back();
DFS(nums,mp,res,tmp,len,k+1);//不选
map<vector<int>,int>::iterator it=mp.find(tmp);
}
};
/*优化解法*/
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<vector<int>> res;
vector<int> tmp;
sort(nums.begin(),nums.end());
DFS(nums,res,tmp,nums.size(),0);
return res;
}
void DFS(vector<int>& nums,vector<vector<int>>& res,vector<int>& tmp,int len,int k)
{
res.push_back(tmp);
for(int i=k;i<len;i++)
{
if(i>k&&nums[i]==nums[i-1])
continue;
tmp.push_back(nums[i]);
DFS(nums,res,tmp,len,i+1);
tmp.pop_back();
}
}
};
42.接雨水
题解思路:首先找到每个柱子左侧柱子的最大值,和右侧柱子的最大值
依次讨论每个柱子的贡献:对于每个柱子而言,只有和比它高的柱子联合在一起才有可能对总water做出贡献,所以要 找到每个柱子left[i]和right[i]中较小的那个进行联合。
为什么不找left[i]和right[i]中较大的那个?短板原理,每个柱子能产生的贡献是由较小的那个决定的,比如第5个柱子。
class Solution {
public:
int trap(vector<int>& height) {
int n=height.size();
vector<int> left(n),right(n);
for(int i=1;i<n;i++)
left[i]=max(left[i-1],height[i-1]); //left[i]表示i左边的最大值
for(int i=n-2;i>=0;i--)
right[i]=max(right[i+1],height[i+1]); //right[i]表示i右边的最大值
int water=0;
for(int i=0;i<n;i++) //讨论每个柱子的贡献
{
int level=min(left[i],right[i]); //寻找左右侧最大高度中较小的那个
water+=max(0,level-height[i]); // 如果自己高度比level高,则这个柱子没有做出贡献
}
return water;
}
};
152.乘积最大子序列
思路:使用了两个dp数组
dp_min[ i ] :截止到i的子序列积的最小负数(最小负积)
dp_max[ i ] :截止到i的子序列积的最大正数 (最大正积)
初始值:根据nums[0]的来设置
nums[0]>0 : dp_max[0] = nums[0] dp_min[0] = 0 (0用来表示不存在,不可能)
nums[0]<0 : dp_max[0] = 0 dp_min[0] =nums[0]
状态转移方程:根据nums[ i ]的正负分别讨论
nums[ i ]>0 :
dp_max[ i ] = dp_max[ i-1]>0 ? dp_max[ i-1]*nums[i] :nums[ i]
dp_min[ i ] =dp_min [ i-1] *nums[ i ]
//区别在于若 i 之前的最大正积不存在,nums[ i ]可以站出来充当最大正积,但若是最小正积不存在,nums[ i]是个正数,它也无能为力
nums[ i ]<0 :
dp_max [ i ] = dp_min[ i-1] *nums[ i ]
dp_min[ i ] = dp_max[ i-1]>0 ? dp_max[ i-1]*nums[ i]: nums[ i]
//注意负数的最大正积要乘上上一个最小负积来更新
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n=nums.size();
vector<int> dp_min(n); //截止到i的子序列积的最小负数
vector<int> dp_max(n); //截止到i的子序列积的最大正数
/*设置初始值*/
if(nums[0]>0)
{
dp_min[0]=0; //0表示不存在
dp_max[0]=nums[0];
}
else
{
dp_min[0]=nums[0];
dp_max[0]=0;
}
int max=nums[0];
for(int i=1;i<n;i++)
{
if(nums[i]>0)
{
dp_min[i]=dp_min[i-1]*nums[i];
dp_max[i]=dp_max[i-1]>0 ? dp_max[i-1]*nums[i]:nums[i]; //如果i之前最大正积不存在,nums[i]为正,则nums[i]为当前最大正积
}
else
{
dp_min[i]=dp_max[i-1]>0 ? dp_max[i-1]*nums[i]:nums[i];
dp_max[i]=dp_min[i-1]*nums[i];
}
if(dp_max[i]>max)
max=dp_max[i]; //更新最大值
}
return max;
}
};
55.跳跃游戏
题意:给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
思路:从后往前递归,对于当前的dest,若能找到前面的一个i使得nums[i]+i > dest,则再将 i 作为dest继续递归。超时。。
题解思路:
- 如果所有元素都不为0, 那么一定可以跳到最后;(至少能走一步)
- 从后往前遍历,如果遇到nums[i] = 0,就找i前面的元素j,使得nums[j] > i - j。如果找不到,则不可能跳跃到num[i+1],返回false
class Solution { //我的递归思路
public:
bool canJump(vector<int>& nums) {
int dest=nums.size()-1;
return Traceback(nums,dest);
}
bool Traceback(vector<int>& nums,int dest)
{
if(dest==0) return true; //第一个点总是可以达到
bool flag=false;
for(int i=dest-1;i>=0;i--) //寻找是否存在路径到达当前的dest
{
if(nums[i]+i>=dest)
flag=Traceback(nums,i);
if(flag==true)
return flag;
}
return false; //所有路径都失败,则最终失败
}
};
class Solution { //题解思路
public:
bool canJump(vector<int>& nums) {
for(int i=nums.size()-2;i>=0;i--)
{
if(nums[i]==0)
{
int j;
for( j=i-1;j>=0;j--)
if(nums[j]+j>i) break;
if(j<0) return false;
}
}
return true;
}
};
56.合并区间
思路:从前往后找能够包含的集合,将其包含!!我找反了。。
注意下面几组测试样例:
[[0,0],[1,2],[5,5],[2,4],[3,3],[5,6],[5,6],[4,6],[0,0],[1,2],[0,2],[4,5]]
[[2,3],[4,5],[6,7],[8,9],[1,10]]
[[1,2],[4,5],[6,7],[3,8],[9,10]]
[[1,4],[0,0]]
[[201,204],[207,210],[203,212]] !!
class Solution {
public:
//先排个序,然后扫一遍即可
static int cmp(const vector<int> &a,const vector<int> &b){
if(a[0]==b[0])
return a[1]<b[1];
return a[0]<b[0];
}
vector<vector<int>> merge(vector<vector<int>>& inter) {
sort(inter.begin(),inter.end(),cmp);
int i,j;
vector<vector<int>> res;
i=0;
while(i<inter.size()){
int s=inter[i][0],t=inter[i][1];
j=i+1;
while(j<inter.size() && inter[j][0]<=t){
t=max(t,inter[j][1]);j++;
}
res.push_back(vector<int>{s,t});
if(j>=inter.size())
break;
i=j;
}
return res;
}
};
24.两两交换链表中的结点
思路:典型的递归。可以发现函数返回的是完成交换后的链表的头结点,所以可以递归完成。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head==NULL) return NULL; //无结点
if(head->next==NULL) return head; //只有一个结点
ListNode* p =head;
ListNode* q =head->next;
p->next=swapPairs(q->next);
q->next=p;
return q;
}
};
6.Z字型变换
思路:为每一行建立一个字符串。从左到右迭代 s,将每个字符添加到合适的行。
用bool变量goingDown标志当前的方向,只有当我们向上移动到最上面的行或向下移动到最下面的行时,当前方向才会发生改变。
class Solution {
public:
string convert(string s, int numRows) {
if(numRows==1) return s;
int len=s.length();
int size=numRows<len?numRows:len;
vector<string> res;
//vector<string> res(min(numRows, int(s.size())));
for(int i=0;i<size;i++)
res.push_back("");
bool goingDown=false;
int curRow=0;
for(int i=0;i<len;i++)
{
res[curRow]=res[curRow]+s[i];
if(curRow==0||curRow==numRows-1)
goingDown=!goingDown;
if(goingDown==true) curRow++;
else curRow--;
}
string ans;
for(int i=0;i<res.size();i++)
ans=ans+res[i];
return ans;
}
};
7.整数反转
题目特别要求:假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
int的最大值:int max = 0x7fffffff 或 INT_MAX (2e31 -1)
int的最小值:int min = 0x80000000 或 INT_MIN (-2e31)
class Solution {
public:
int reverse(int x) {
int max = 0x7fffffff, min = 0x80000000;//int的最大值最小值
queue<int> q;
int cnt=0;
while(x!=0)
{
q.push(x%10);
x=x/10;
cnt++;
}
long long int ans=0;
while(!q.empty())
{
int num=q.front();
q.pop();
ans=ans*10+num;
}
if(ans>max||ans<min) return 0;
return ans;
}
};
8.字符串转换整数
注意的是,因为输入是字符串形式,长度任意,所以可能出现超出32位有符号整数的情况,所以要在累加的过程中及时判断,一旦超过就及时退出循环。
class Solution {
public:
int myAtoi(string str) {
int len=str.length();
bool minus=false;
long long int ans=0;
int i=0;
while(str[i]==' ') i++;
if(i==len) return 0; //说明空字符串
if(str[i]=='-')
{
minus=true; i++;
}
else if(str[i]=='+')
{
minus=false; i++;
}
while(i<len&&str[i]>='0'&&str[i]<='9')
{
ans=ans*10+str[i]-'0';
if(minus&&(-ans)<INT_MIN) return INT_MIN;
if(!minus&&ans>INT_MAX) return INT_MAX;
i++;
}
if(!minus) return ans;
else return -1*ans;
}
};
9.回文数
class Solution {
public:
bool isPalindrome(int x) {
if(x<0) return false;
if(x==0) return true;
vector<int> v;
while(x!=0)
{
v.push_back(x%10);
x=x/10;
}
int len=v.size();
int i=0,j=len-1;
while(i<=j&&v[i]==v[j])
{
i++;
j--;
}
if(i>j) return true;
else return false;
}
};
12.整数转罗马数字
分析:可以像硬币找零那样,由贪心算法进行划分。每次9、90、900和4、40、400的情况要特别注意怎么处理。
class Solution {
public:
string intToRoman(int num) {
if(num==4)
{
string ans="IV";
return ans;
}
if(num==40)
{
string ans="XL";
return ans;
}
if(num==400)
{
string ans="CD";
return ans;
}
int nums[7]={1000,500,100,50,10,5,1};
char sign[7]={'M','D','C','L','X','V','I'};
vector<char> v;
int cnt=0;
for(int i=0;i<7;i++)
{
if(num==0) break;
int t=num/nums[i];
if(t==0) continue;
if(t==4)
{
/*
if(cnt==0)
{
v.push_back(sign[i]);
v.push_back(sign[i-1]);
cnt+=2;
}
*/
//if(cnt>=1&&(v[cnt-1]=='D'||v[cnt-1]=='L'||v[cnt-1]=='V')) //处理900、90、9的情况
if(cnt>=1&&v[cnt-1]==sign[i-1]) //处理900、90、9的情况
{
v.pop_back();
v.push_back(sign[i]);
v.push_back(sign[i-2]);
cnt++;
}
else{
v.push_back(sign[i]);
v.push_back(sign[i-1]);
cnt+=2;
}
}
else{
for(int j=0;j<t;j++)
{
v.push_back(sign[i]);
cnt++;
}
}
num-=nums[i]*t;
}
string ans;
for(int i=0;i<cnt;i++)
ans=ans+v[i];
return ans;
}
};
这是评论中的代码 不要太简洁啊...
class Solution {
public:
string intToRoman(int num) {
int values[]={1000,900,500,400,100,90,50,40,10,9,5,4,1};
string reps[]={"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
string res;
for(int i=0; i<13; i++){
while(num>=values[i]){
num -= values[i];
res += reps[i];
}
}
return res;
}
};
13.罗马数字转整数
看一下我代码巨长的愚蠢办法..
class Solution {
public:
int romanToInt(string s) {
int len=s.length();
int i=len-1;
int ans=0;
while(i>=0)
{
if(s[i]=='V') //5
{
if(i>0&&s[i-1]=='I')
{
ans+=4;
i-=2;
}
else
{
ans+=5;
i--;
}
}
else if(s[i]=='I') //1
{
while(s[i]=='I')
{
ans+=1;
i--;
}
}
else if(s[i]=='X') //10
{
if(i>0&&s[i-1]=='I')
{
ans+=9;
i-=2;
}
else
{
while(s[i]=='X')
{
ans+=10;
i--;
}
}
}
else if(s[i]=='L') //50
{
if(i>0&&s[i-1]=='X')
{
ans+=40;
i-=2;
}
else
{
ans+=50;
i--;
}
}
else if(s[i]=='C') //100
{
if(i>0&&s[i-1]=='X')
{
ans+=90;
i-=2;
}
else
{
while(s[i]=='C')
{
ans+=100;
i--;
}
}
}
else if(s[i]=='D') //500
{
if(i>0&&s[i-1]=='C')
{
ans+=400;
i-=2;
}
else
{
ans+=500;
i--;
}
}
else if(s[i]=='M')
{
if(i>0&&s[i-1]=='C')
{
ans+=900;
i-=2;
}
else
{
while(s[i]=='M')
{
ans+=1000;
i--;
}
}
}
}
return ans;
}
};
评论中的思路:这题懂了就非常简单。首先建立一个HashMap来映射符号和值,然后对字符串从左到右来,如果当前字符代表的值不小于其右边,就加上该值;否则就减去该值。以此类推到最左边的数,最终得到的结果即是答案 妙啊!!