51、构建乘积数组
题目描述
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。
解题思路
最后要求的数组B如下图所示:
使用两个辅助数组,分别求每一行的上三角和下三角的乘积,最后将两者相乘得最后的结果。
class Solution {
public:
vector<int> multiply(const vector<int>& A) {
int length = A.size();
vector<int> up(length,1),down(length,1),res(length,1);
if(length == 0)
return res;
for(int i = length-2;i >= 0;--i){
up[i] = up[i+1]*A[i+1];
}
for(int i = 1;i < length;++i){
down[i] = down[i-1]*A[i-1];
}
for(int i = 0;i < length;++i){
res[i] = up[i]*down[i];
}
return res;
}
};
52、正则表达式匹配
题目描述
请实现一个函数用来匹配包括’.‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配
解题思路
按照自己的做法,写了一大堆判断条件,在牛客上发现一个很好的:
解这题需要把题意仔细研究清楚,反正我试了好多次才明白的。
首先,考虑特殊情况:
1>两个字符串都为空,返回true
2>当第一个字符串不空,而第二个字符串空了,返回false(因为这样,就无法
匹配成功了,而如果第一个字符串空了,第二个字符串非空,还是可能匹配成
功的,比如第二个字符串是“aaaa”,由于‘’之前的元素可以出现0次,
所以有可能匹配成功)
之后就开始匹配第一个字符,这里有两种可能:匹配成功或匹配失败。但考虑到pattern
下一个字符可能是‘’, 这里我们分两种情况讨论:pattern下一个字符为‘’或
不为‘’:
1>pattern下一个字符不为‘’:这种情况比较简单,直接匹配当前字符。如果
匹配成功,继续匹配下一个;如果匹配失败,直接返回false。注意这里的
“匹配成功”,除了两个字符相同的情况外,还有一种情况,就是pattern的
当前字符为‘.’,同时str的当前字符不为‘\0’。
2>pattern下一个字符为‘’时,稍微复杂一些,因为‘’可以代表0个或多个。
这里把这些情况都考虑到:
a>当‘’匹配0个字符时,str当前字符不变,pattern当前字符后移两位,
跳过这个‘’符号;
b>当‘’匹配1个或多个时,str当前字符移向下一个,pattern当前字符
不变。(这里匹配1个或多个可以看成一种情况,因为:当匹配一个时,
由于str移到了下一个字符,而pattern字符不变,就回到了上边的情况a;
当匹配多于一个字符时,相当于从str的下一个字符继续开始匹配)
之后再写代码就很简单了。
其中在匹配一个或多个的判断时要考虑".*"这个结构,我的理解是这个结构可以匹配任意序列。
class Solution {
public:
bool match(char* str, char* pattern)
{
if (*str == '\0' && *pattern == '\0')
return true;
if (*str != '\0' && *pattern == '\0')
return false;
if(*(pattern + 1) != '*'){
if(*str == *pattern || (*str != '\0' && *pattern == '.')){
return match(str+1, pattern+1);
}else
return false;
}else{
if(*str == *pattern || (*str != '\0' && *pattern == '.'))
return match(str+1, pattern) || match(str, pattern+2);
else
return match(str,pattern+2);
}
}
};
53、表示数值的字符串
题目描述
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。
解题思路:
扣各种条件,见注释:
class Solution {
public:
bool isNumeric(char* string)
{
bool sign=false,point=false,e=false;//正负号、小数点、e出现的标志
int i = 0;
for(;i < strlen(string);i++){
if(string[i] == 'e' ||string[i] == 'E'){//e出现的时候,不能有过e,且后续要有数
if(e) return false;
if(string[i+1] == '\0') return false;
e=true;
}
else if(string[i] == '+'||string[i] == '-'){//正负号,曾出现过则必须紧接e,未出现过但不在第一位也必须紧跟e
if(sign && string[i-1]!= 'e' && string[i-1]!= 'E') return false;
if(!sign && i>0 && string[i-1]!= 'e' && string[i-1]!= 'E') return false;
sign=true;
}
else if(string[i] == '.'){//小数点只能出现一次,且不能在e后
if(e || point) return false;
point = true;
}
else if(string[i] < '0' || string[i] > '9'){
return false;
}
}
return true;
}
};
54、字符流中第一个不重复的数字
题目描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。
解题思路:
利用一个vector来保存流的字符和出现次数,返回时查询
class Solution
{
public:
vector<pair<char,int>> help;
//Insert one char from stringstream
void Insert(char ch)
{
bool hasch=false;
for(int i = 0;i<help.size();++i){
if(help[i].first == ch){
help[i].second++;
hasch= true;
}
}
if(!hasch)
help.push_back(make_pair(ch,1));
}
//return the first appearence once char in current stringstream
char FirstAppearingOnce()
{
for(int i = 0;i<help.size();++i){
if(help[i].second == 1)
return help[i].first;
}
return '#';
}
};
这个是方法比较好想但是空间复杂度高,下面是牛客网上的好方法
提供一个高效的算法:
思路:时间复杂度O(1),空间复杂度O(n)
1、用一个128大小的数组统计每个字符出现的次数
2、用一个队列,如果第一次遇到ch字符,则插入队列;其他情况不在插入
3、求解第一个出现的字符,判断队首元素是否只出现一次,如果是直接返回,否则删除继续第3步骤
class Solution
{
public:
//Insert one char from stringstream
void Insert(char ch)
{
++cnt[ch - '\0'];
if(cnt[ch - '\0'] == 1)
data.push(ch);
}
//return the first appearence once char in current stringstream
char FirstAppearingOnce()
{
while(!data.empty() && cnt[data.front()] >= 2) data.pop();
if(data.empty()) return '#';
return data.front();
}
Solution()
{
memset(cnt, 0, sizeof(cnt));
}
private:
queue<char> data;
unsigned cnt[128];
};
55、链表环入口节点
题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
解题思路
设置快慢指针,都从链表头出发,快指针每次走两步,慢指针一次走一步,假如有环,一定相遇于环中某点(结论1)。接着让两个指针分别从相遇点和链表头出发,两者都改为每次走一步,最终相遇于环入口(结论2)。
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode*fast=pHead,*low=pHead;
while(fast&&fast->next){
fast=fast->next->next;
low=low->next;
if(fast==low)
break;
}
if(!fast||!fast->next)return NULL;
low=pHead;//low从链表头出发
while(fast!=low){//fast从相遇点出发
fast=fast->next;
low=low->next;
}
return low;
}
};
56、删除链表中的重复节点
题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
解题思路
见注释
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if( pHead == NULL ) return pHead;
ListNode *pre = NULL; //指向前面最晚访问过的不重复结点
ListNode *p = pHead; //指向当前处理的结点
ListNode *q = NULL; //指向当前处理结点后面结点
while( p != NULL )
{
//当前结点p,(其实是p指向当前结点),与它下一个结点p->next的val相同,说明要删掉有这个val的所有结点
if( p->next != NULL && p->next->val == p->val )
{
q = p->next;
//找到q,它指向最后一个与p val相同的结点,那p 到 q (包含) 都是要删除的
while( q != NULL && q->next != NULL && q->next->val == p->val )
{
q = q->next;
}
//如果p指向链表中第一个元素,p -> ... -> q ->... , 要删除p到q, 将指向链表第一个元素的指针pHead指向q->next。
if( p == pHead )
{
pHead = q->next;
}
else//如果p不指向链表中第一个元素,pre -> p ->...->q ->... ,要删除p到q,即pre->next = q->next
{
pre->next = q->next;
}
//当前处理的p要向链表尾部移动
p = q->next;
}
else
{
pre = p;
p = p->next;
}
}
return pHead;
}
};
57、二叉树的下一个节点
题目描述
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
解题思路
见注释
class Solution {
public:
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
if(pNode == NULL)
return pNode;
TreeLinkNode* help;
//有右子树 返回右孩子的最下层的左节点
if(pNode -> right != NULL){
help = pNode -> right;
while(help -> left != NULL){
help = help -> left;
}
return help;
}//无右字数 分两种 是父节点的右孩子 或 是父节点的左孩子
while(pNode -> next != NULL){
if(pNode -> next -> left == pNode)//左孩子直接返回父节点
return pNode -> next;
pNode = pNode -> next;//否则继续向上寻找父节点 直到找到是父节点的左孩子的情况
}
return NULL;//若一直未找到,表示找到了根节点,则是树的最右下角的子节点,下一个节点为空
}
};
58、对称二叉树
题目描述
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
解题思路
对于一个根节点,若左子树的左孩子等于右子树右孩子,且左子树右孩子等于右子树左孩子,则对称。将这个根节点向下移动,分别放到根节点的左孩子和右孩子上,递归此过程即可。
class Solution {
public:
bool help(TreeNode* p1, TreeNode* p2){
if(p1 == nullptr && p2 == nullptr)
return true;
if(p1 == nullptr || p2 == nullptr)
return false;
if(p1 -> val != p2 -> val)
return false;
return help(p1 -> left, p2 -> right) && help(p2 -> left, p1 -> right);
}
bool isSymmetrical(TreeNode* pRoot)
{
return help (pRoot, pRoot);
}
};
59、之字形打印二叉树
题目描述
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
解题思路
在层次遍历的基础上修改,在每一层遍历的时候进行vector的构造,加入一个布尔变量判断是奇数行还是偶数行,如果为奇数行则使用pushback,如果是偶数行则使用insert前向插入元素。
code:
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int>> res;
if(pRoot == nullptr)
return res;
queue<TreeNode*> qt;
qt.push(pRoot);
bool isOdd = true;
while(!qt.empty()){
vector<int> temp;
const int length = qt.size();
for(int i = 0;i<length; ++i){
TreeNode* help = qt.front();
qt.pop();
if(isOdd)
temp.push_back(help->val);
else
temp.insert(temp.begin(),help->val);
if(help->left != nullptr)
qt.push(help->left);
if(help->right != nullptr)
qt.push(help->right);
}
res.push_back(temp);
isOdd = !isOdd;
}
return res;
}
};
60、二叉树打印成多行
题目描述
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
题目解析
感觉是上一题的简化版。。。层次遍历输出即可
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int> > res;
if(pRoot == nullptr){
return res;
}
queue<TreeNode*> qt;
qt.push(pRoot);
while(!qt.empty()){
vector<int> temp;
const int length = qt.size();
for(int i = 0;i<length;i++){
TreeNode* help = qt.front();
qt.pop();
temp.push_back(help->val);
if(help->left != nullptr)
qt.push(help->left);
if(help->right != nullptr)
qt.push(help->right);
}
res.push_back(temp);
}
return res;
}
};
61、序列化二叉树
62、二叉搜索树第k小的节点
题目描述
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
解题思路
利用二叉搜索树中序遍历是依次升序的特点,遍历到第k个即可
code
class Solution {
public:
TreeNode* KthNode(TreeNode* pRoot, int k)
{
stack<TreeNode*> mid;
int count = 0;
while(pRoot != nullptr || !mid.empty()){
if (pRoot != nullptr) {
mid.push(pRoot);
pRoot = pRoot->left;
} else { //pNode == null && !stack.isEmpty()
TreeNode* node = mid.top();
mid.pop();
count++;
if(count == k)
return node;
pRoot = node->right;
}
}
return nullptr;
}
};
63、数据流中的中位数
题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
解题思路
建立一个大根堆和一个小根堆(C++中有优先级队列结构就是大根堆),将较小的一半数放入大根堆,较大的一半数放入小根堆,最后如果两个堆数量相等就取两个堆顶的中位数,否则返回数量较大的堆顶。
code
class Solution {
priority_queue<int, vector<int>, less<int> > p;//大根堆 通过修改元素比较方式的参数,第三个参数来改变排序规则
priority_queue<int, vector<int>, greater<int> > q;//小根堆
public:
void Insert(int num){
if(p.empty() || num <= p.top()) p.push(num);//较小数放入大根堆
else q.push(num);//较大数放入小根堆
if(p.size() == q.size() + 2){
q.push(p.top());
p.pop();
} //调整使两者的数量相等,且大根堆的数量永远比小根堆大或等
if(p.size() + 1 == q.size()){
p.push(q.top());
q.pop();
}
}
double GetMedian(){
return p.size() == q.size() ? (p.top() + q.top()) / 2.0 : p.top();
}
};
64、滑动窗口最大值
题目描述
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
解题思路
code
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> res;
deque<int> qi;
for(unsigned int i = 0; i < num.size();++i){
while(!qi.empty()&&(num[qi.back()] <= num[i]))
qi.pop_back();
while(qi.size() && i-qi.front()+1 > size)
qi.pop_front();
qi.push_back(i);
if(size&&i+1>=size)
res.push_back(num[qi.front()]);
}
return res;
}