废话不多说,直接开始!
51.给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素 。不能使用除法。(注意:规定B[0]和B[n-1] = 1)
设有数组大小为5。
对于第一个for循环
第一步:b[0] = 1;
第二步:
第三步:
第四步:
第五步:
然后对于第二个for循环
第一步
第二步
;
第三步
;
第四步
;
由此可以看出从b[4]到b[0]均已经得到正确计算。
class Solution {
public:
vector<int> multiply(const vector<int>& A) {
vector<int> result;
int size=A.size();
if(size==0)
return result;
result.push_back(1);
for(int i=0;i<size-1;i++)
result.push_back(result.back()*A[i]);
int tmp=1;
for(int i=size-1;i>=0;i--) {
result[i]=result[i]*tmp;
tmp=tmp*A[i];
}
return result;
}
};
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个或多个。 这里把这些情况都考虑到:
1.当
匹配0个字符时,str当前字符不变,pattern当前字符后移两位, 跳过这个
符号;
2.当‘*’匹配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 the next character in pattern is not '*'
if (*(pattern+1) != '*') {
if (*str == *pattern || (*str != '\0' && *pattern == '.'))
return match(str+1, pattern+1);
else
return false;
}
//if the next character is '*'
else {
if (*str == *pattern || (*str != '\0' && *pattern == '.'))
return match(str, pattern+2) || match(str+1, pattern);
else
return match(str, pattern+2);
}
}
};
53.请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串 , , , 和 都表示数值。 但是 , , , 和 都不是。
class Solution {
public:
bool isNumeric(char* str) {
// 标记符号、小数点、e是否出现过
bool sign = false, decimal = false, includeE = false;
for (int i = 0; i < strlen(str); i++) {
if (str[i] == 'e' || str[i] == 'E') {
if (i == strlen(str)-1) return false; // e后面一定要接数字
if (includeE) return false; // 不能同时存在两个e
includeE = true;
} else if (str[i] == '+' || str[i] == '-') {
// 第二次出现+-符号,则必须紧接在e之后
if (sign && str[i-1] != 'e' && str[i-1] != 'E') return false;
// 第一次出现+-符号,且不是在字符串开头,则也必须紧接在e之后
if (!sign && i > 0 && str[i-1] != 'e' && str[i-1] != 'E') return false;
sign = true;
} else if (str[i] == '.') {
// e后面不能接小数点,小数点不能出现两次
if (includeE || decimal) return false;
decimal = true;
} else if (str[i] < '0' || str[i] > '9') // 不合法字符
return false;
}
return true;
}
};
54.请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
有大佬提供的一个高效的算法:
思路:时间复杂度O(1),空间复杂度O(n)
1、用一个128大小的数组统计每个字符出现的次数
2、用一个队列,如果第一次遇到ch字符,则插入队列;其他情况不在插入
3、求解第一个出现的字符,判断队首元素是否只出现一次,如果是直接返回,否则删除继续第3步骤
分析:可以看出相同的字符只被插入一次,最多push128个,同时大多数情况会直接返回队首。所以大家不要被里面的while循环迷惑
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)。以下是两个结论证明:
两个结论:
1、设置快慢指针,假如有环,他们最后一定相遇。
2、两个指针分别从链表头和相遇点继续出发,每次走一步,最后一定相遇与环入口。
证明结论1:设置快慢指针fast和low,fast每次走两步,low每次走一步。假如有环,两者一定会相遇(因为low一旦进环,可看作fast在后面追赶low的过程,每次两者都接近一步,最后一定能追上)。
证明结论2:
设:
链表头到环入口长度为–a
环入口到相遇点长度为–b
相遇点到环入口长度为–c
则:相遇时
快指针路程=a+(b+c)k+b ,k>=1 其中b+c为环的长度,k为绕环的圈数(k>=1,即最少一圈,不能是0圈,不然和慢指针走的一样长,矛盾)。
慢指针路程=a+b
快指针走的路程是慢指针的两倍,所以:
(a+b)*2=a+(b+c)k+b
化简可得:
a=(k-1)(b+c)+c 这个式子的意思是: 链表头到环入口的距离=相遇点到环入口的距离+(k-1)圈环长度。其中k>=1,所以k-1>=0圈。所以两个指针分别从链表头和相遇点出发,最后一定相遇于环入口。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
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 = q->next;//当前处理的p要向链表尾部移动
}
else {
pre = p;
p = p->next;
}
}
return pHead;
}
};
57.给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
分析二叉树的下一个节点,一共有以下情况:
1.二叉树为空,则返回空;
2.节点右孩子存在,则设置一个指针从该节点的右孩子出发,一直沿着指向左子结点的指针找到的叶子节点即为下一个节点;
3.节点不是根节点。如果该节点是其父节点的左孩子,则返回父节点;否则继续向上遍历其父节点的父节点,重复之前的判断,返回结果。代码如下:
/*
struct TreeLinkNode {
int val;
struct TreeLinkNode *left;
struct TreeLinkNode *right;
struct TreeLinkNode *next;
TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
}
};
*/
class Solution {
public:
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
if(pNode == nullptr) {
return nullptr;
}
TreeLinkNode* next = nullptr;
//先右子节点的左子节点遍历
if(pNode->right != nullptr) {
TreeLinkNode* rightNode = pNode->right;
while(rightNode->left != nullptr) {
rightNode = rightNode->left;
}
next = rightNode;
}
//向父结点遍历
else if(pNode->next != nullptr) {
TreeLinkNode* parentNode = pNode->next;
TreeLinkNode* currentNode = pNode;
while(parentNode != nullptr && currentNode == parentNode->right) {
currentNode = parentNode;
parentNode = parentNode->next;
}
next = parentNode;
}
return next;
}
};
58.请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
思路:首先根节点以及其左右子树,左子树的左子树和右子树的右子树相同
- 左子树的右子树和右子树的左子树相同即可,采用递归
- 非递归也可,采用栈或队列存取各级子树根节点
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
bool isSymmetrical(TreeNode* pRoot)
{
return isSymmetrical(pRoot,pRoot);
}
bool isSymmetrical(TreeNode* t1,TreeNode* t2)
{
if(t1==nullptr&&t2==nullptr)
return true;
if(t1==nullptr||t2==nullptr)
return false;
if(t1->val!=t2->val)
return false;
return isSymmetrical(t1->left,t2->right)&& isSymmetrical(t1->right,t2->left);
}
};
59.请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int> > result;
if(pRoot == nullptr) return result;
stack<TreeNode*> stackL;
stack<TreeNode*> stackR;
TreeNode* popNode;
vector<int> temp;
temp.push_back(pRoot->val);
result.push_back(temp);
temp.clear();
stackL.push(pRoot);
while(!stackL.empty() || !stackR.empty()) {
while(!stackL.empty()){
popNode = stackL.top();
stackL.pop();
if(popNode->right) {
stackR.push(popNode->right);
temp.push_back(popNode->right->val);
}
if(popNode->left) {
stackR.push(popNode->left);
temp.push_back(popNode->left->val);
}
}
if(!temp.empty()) {
result.push_back(temp);
temp.clear();
}
while(!stackR.empty()) {
popNode = stackR.top();
stackR.pop();
if(popNode->left) {
stackL.push(popNode->left);
temp.push_back(popNode->left->val);
}
if(popNode->right) {
stackL.push(popNode->right);
temp.push_back(popNode->right->val);
}
}
if(!temp.empty()) {
result.push_back(temp);
temp.clear();
}
}
return result;
}
};
60.从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int>> result;
if(pRoot == NULL) return result;
queue<TreeNode*> q;
q.push(pRoot);
while(!q.empty()){
int size = q.size();//读取每一层的元素的数量
vector<int> levelelem;
while(size--){
TreeNode* t = q.front();
q.pop();
levelelem.push_back(t->val);
if(t->left != NULL) q.push(t->left);
if(t->right != NULL) q.push(t->right);
}
result.push_back(levelelem);
}
return result;
}
};