51. 构建乘积数组
构建乘积数组_牛客题霸_牛客网 (nowcoder.com)
第一印象是求数组全部元素的乘积,然后每个位置都除以自己即可。但题目要求无法使用除法,只能另寻他法。
法一
啥也不管了,先暴力吧。
class Solution {
public:
vector<int> multiply(vector<int>& A) {
vector<int> ans(A.size(),0);
for(int i = 0;i<A.size();i++){
int mul = 1;
for(int j = 0;j<A.size();j++){
if(j != i) mul *= A[j];
}
ans[i] = mul;
}
return ans;
}
};
法二
两次遍历。
乍看好像毫无头绪,但是细细琢磨发现b[i]
等于a中除a[i]
外所有元素的乘积,即b[i] = i左边的元素乘积 * i右边的元素乘积
。而在一次遍历过程中左边或右边单独的乘积是可以累计的。
故,
- 首先将b数组全部初始化为1。
- 第一次遍历,用
temp
记录遍历过程中从左开始的a中元素的累积,并将结果与b[i]
相乘。 - 第二次遍历,依然用
temp
记录从右开始的a中元素的累积,并将结果与b[i]
相乘。 - 得到最终结果。
class Solution {
public:
vector<int> multiply(vector<int>& A) {
int len = A.size();
vector<int> ans(len,1);
int temp = 1;
for(int i = 1;i<len;i++){
temp *= A[i - 1];
ans[i] *= temp;
}
temp = 1;
for(int i = len - 2;i >= 0;i--){
temp *= A[i + 1];
ans[i] *= temp;
}
return ans;
}
};
52. 正则表达式匹配
正则表达式匹配_牛客题霸_牛客网 (nowcoder.com)
看起来就是实现正则表达式里的.
和*
。想不到怎么搞。
法一
C++11支持正则表达式了,但是牛客这里必须自己引入头文件,应该是后台的万能头文件不包括正则吧。
#include<regex>
class Solution {
public:
bool match(string str, string pattern) {
return regex_match(str,regex(pattern));
}
};
法二
首先,.
就代表一个万能字符做特殊判定即可,困难的是*
。
假设,abc与c*abc进行匹配,在从左向右匹配的过程中,首先a与c不匹配,发现不同但并不能做出“不匹配的判断”因为c后面还有一个*
,代表字符可以出现任意次。所以,是否匹配还和当前字符后面跟着的符号有关。还需要去分情况讨论,很是困难。
但是,如果反过来看。从右边往左边匹配困难就少很多了。
假设str = aab
,pattern = c*a*b
。每次匹配过程,结果只和当前匹配的字符和左边的字符 有关,不需要考虑右边是否还跟着其他字符。需要讨论的情况就少了很多,而且问题可以转换为子问题——当前字符是否匹配 + 之前的串是否匹配。可以考虑动规。
dp[i][j]
:长度为i
的str
与长度为j
的pattern
是否匹配。
之后需要分情况讨论(分类依据是p中当前匹配字符是否是*
,因为如果其是普通字符或者.
可以直接比较):
p[i-1]!= '*'
- 当前字符匹配成功,即
s[i-1]==p[j-1] || p[j -1] == '*'
,此时dp[i][j] = dp[i-1][j-1]
(当前字符匹配成功,问题转换为之前的字符串是否匹配成功) - 匹配不成功,
dp[i][j] = false;
- 当前字符匹配成功,即
p[i-1]== '*'
比较s[i-1]
与p[j -2]
(*
前的字符)- 不匹配,即
s[i - 1] != p[j - 2] && p[j - 2] != '.'
,此时dp[i][j] = dp[i][j - 2];
(跳过p中不匹配的部分继续匹配) - 匹配,又有三种情况
- 假设匹配0次,即匹配到了假装没匹配到直接跳过。此时
dp[i][j] = dp[i][j - 2];
- 假设匹配1次,此时
dp[i][j] = dp[i][j - 1];
此时相当于p中多了一个*
,其余可以直接匹配,故直接跳过*
即可。 - 假设匹配多次,此时
dp[i][j] = dp[i-1][j];
相当于p中有a*,代表aaa*。匹配过程中假设s[i-1]
为可以匹配到的最后一个a,故直接跳过s[i-1]
继续去比。
- 假设匹配0次,即匹配到了假装没匹配到直接跳过。此时
- 不匹配,即
class Solution {
public:
bool match(string s, string p) {
int n = s.size();
int m = p.size();
vector<vector<bool>> dp(n + 1,vector<bool>(m + 1,false));
dp[0][0] = true;
dp[0][1] = false;
for(int j = 2;j<=m;j++){
if(p[j - 1] == '*') dp[0][j] = dp[0][j - 2];
}
for(int i = 1;i<=n;i++){
for(int j = 1;j<=m;j++){
if(p[j - 1] != '*'){
if(s[i - 1] == p[j - 1] || p[j - 1] == '.') dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = false;
}else{ //p[j - 1] == '*'
if(s[i - 1] != p[j - 2] && p[j - 2] != '.') dp[i][j] = dp[i][j - 2];
else dp[i][j] = dp[i][j - 2] || dp[i][j - 1] || dp[i - 1][j];
}
}
}
return dp[n][m];
}
};
- 代码中,凡是出现在dp后面的
i
和j
都表示字符串长度,凡是s
和p
后面的则都表示索引。
53. 表示数值的字符串
看不懂,直接开抄。
有效数字(按顺序)可以分成以下几个部分:
- 若干空格
- 一个 小数 或者 整数
- (可选)一个
'e'
或'E'
,后面跟着一个 整数 - 若干空格
将所有能正确表示数值的字符串的形势总结如下:
- 空格只能出现在开头和结尾。
.
出现的正确情况: 只出现一次,且出现在e
的前面。e
出现的正确情况:只出现一次,且出现前有数字。+-
出现的正确情况:只能在开头或e后一位。
class Solution {
public:
bool validNumber(string s) {
// 去掉首尾空格
int start = s.find_first_not_of(' ');
if (start == string::npos) return false;
int end = s.find_last_not_of(' ');
s = s.substr(start, end - start + 1);
bool numFlag = false;
bool dotFlag = false;
bool eFlag = false;
for (int i = 0; i < s.size(); i++) {
if (isdigit(s[i])) {
numFlag = true;
}
//判断'.'的出现是否合法
else if (s[i] == '.' && !dotFlag && !eFlag) {
dotFlag = true;
}
// 判断'e'的出现是否合法
else if ((s[i] == 'e' || s[i] == 'E') && !eFlag && numFlag) {
eFlag = true;
numFlag = false; // 'e'后面必须跟着整数
}
// 判断正负号出现是否合法
else if ((s[i] == '+' || s[i] == '-') && (i == 0 || s[i - 1] == 'e' || s[i - 1] == 'E')) {
// 正确的位置不做处理
}
// 其他情况都不合法
else {
return false;
}
}
// 确保'e'或'E'后有数字
return numFlag;
}
};
54. 字符流中第一个不重复的字符
字符流中第一个不重复的字符_牛客题霸_牛客网 (nowcoder.com)
初看懵逼的很,不明白insert
这个函数有啥好用。后来才发现,这个是把字符串当作字符流。用insert
在遍历字符串的同时,不断显示第一个不重复字符。
这就好说了。
法一
巧用库函数
class Solution
{
public:
//Insert one char from stringstream
void Insert(char ch) {
vec.push_back(ch);
}
//return the first appearence once char in current stringstream
char FirstAppearingOnce() {
if(vec.empty()) return '#';
for(auto& ch : vec){
if(count(vec.begin(),vec.end(),ch) == 1) return ch;
}
return '#';
}
private:
vector<int> vec;
};
法二
不用库函数,用哈希表自己数。空间复杂度会高一点,但应该会稍微快一点。
class Solution
{
public:
//Insert one char from stringstream
void Insert(char ch) {
vec.push_back(ch);
mp[ch]++;
}
//return the first appearence once char in current stringstream
char FirstAppearingOnce() {
if(vec.empty()) return '#';
for(auto& ch : vec){
if(mp[ch] == 1) return ch;
}
return '#';
}
private:
vector<int> vec;
unordered_map<char, int> mp;
};
55. 链表中环的入口结点
LCR 022. 环形链表 II - 力扣(LeetCode)
直接开抄。代码随想录 (programmercarl.com)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
if(slow == fast){
ListNode* index1 = fast;
ListNode* index2 = head;
while(index1 != index2){
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return nullptr;
}
};
56. 删除链表中重复的节点
82. 删除排序链表中的重复元素 II - 力扣(LeetCode)
法一:
与83. 删除排序链表中的重复元素 - 力扣(LeetCode)很像。只不过本题重复的节点一个也不保留。这时候需要考虑第一个元素的处理问题。因为如果保留一个重复节点的话,第一个元素即使是重复的也需要保留,就不需要考虑了。但如果一个也不保留的话,第一个节点也有需要删除的可能性。
这时候,只要在原来代码的基础上新增加一个虚拟头节点,这样第一个节点就成了第二个,两个问题就统一起来了。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;
while(cur->next && cur->next->next){
if(cur->next->val == cur->next->next->val){
int temp = cur->next->val;
while( cur->next && cur->next->val == temp){
cur->next = cur->next->next;
}
}else{
cur = cur->next;
}
}
return dummyHead->next;
}
};
法二:
迭代法。将问题分解为当前节点的去留问题和后继链表的去除重复节点问题。
- 递归出口:当前节点为空或没有后继节点。
- 递归问题:
- 当前节点值与后继节点值不一致:当前节点需要保留,故
head->next = deleteDuplicates(head->next);
然后返回。 - 当前节点值与后继节点值一样:当前节点不需要保留,需要找到与当前节点值不一样的某个后继节点,从该节点开始继续迭代
deleteDuplicates(temp)
。
- 当前节点值与后继节点值不一致:当前节点需要保留,故
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == nullptr || head->next == nullptr) return head;
if(head->val != head->next->val){
head->next = deleteDuplicates(head->next);
return head;
}else{
ListNode* temp = head->next;
while( temp && temp->val == head->val){
temp = temp->next;
}
return deleteDuplicates(temp);
}
return nullptr;
}
};
57. 二叉树的下一个结点
法一:
很直观的一种写法。找中序遍历的下一个节点,那就中序遍历一次,把遍历顺序存在一个数组里,之后遍历一次数组就找到下一个了。
class Solution {
public:
TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
inorder(root);
for(int i = 0;i<nodes.size();i++){
if(nodes[i] == p && i + 1 < nodes.size()) return nodes[i + 1];
}
return nullptr;
}
void inorder(TreeNode* root){
if(root == nullptr) return ;
inorder(root->left);
nodes.push_back(root);
inorder(root->right);
}
private:
vector<TreeNode*> nodes;
};
法二:
法一好像有点浪费空间,其实不需要把所有的遍历结果都存一遍,只要记录两个就行了。pre和cur。若pre == p
,那么cur就是后继。
class Solution {
public:
TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
stack<TreeNode*> st;
TreeNode* pre = nullptr;
TreeNode* cur = root;
while(cur != nullptr || !st.empty()){
if(cur != nullptr){
st.push(cur);
cur = cur->left;
}else{
cur = st.top();
st.pop();
if(pre == p) return cur;
pre = cur;
cur = cur->right;
}
}
return nullptr;
}
};
二叉树的下一个结点_牛客题霸_牛客网 (nowcoder.com)
牛课上这个差不多的题目给出的形式还不一样。牛客只给了一个节点,没有给出根节点所以不能直接遍历。但牛客上的树有一个next指针指向父亲节点,所以也好办。直接分情况讨论即可:
- p是空节点:返回nullptr
- p有右孩子:中序遍历,所以有右孩子的话,下一个节点就是从右节点作为基准,开始找最左边的孩子。
- p没有右孩子:
- 若p的父亲节点是根节点的话:直接返回其父亲节点即可。
- 若p的父亲节点不是根节点的话:此时,按照左根右的遍历顺序来看,p的父亲节点一定是已经遍历过了。需要找到当前节点是其父节点的左子节点的那个父节点。(左根右体遍历顺序中,假设父亲节点为pre,当前节点为p。只要p是pre的右孩子,pre就一定已经遍历过了。当p是pre的左孩子时,p的下一个就是pre。)
class Solution {
public:
TreeLinkNode* GetNext(TreeLinkNode* p) {
if( p == nullptr) return p;
if(p->right != nullptr){
TreeLinkNode* cur = p->right;
while(cur->left)
cur = cur->left;
return cur;
}
while(p->next != nullptr){
TreeLinkNode* pre = p->next;
if(pre->left == p) return pre;
p = p->next;
}
return nullptr;
}
};
58. 判断对称的二叉树
LCR 145. 判断对称二叉树 - 力扣(LeetCode)
比较简单的一道题。注意对称判定的时候是左孩子的左子树和右孩子的右子树判定(其他类推)即可。
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right){
if(left == nullptr && right == nullptr) return true;
else if(left == nullptr || right == nullptr) return false;
else if(left->val != right->val) return false;
else{
return compare(left->left,right->right) && compare(left->right,right->left);
}
}
bool checkSymmetricTree(TreeNode* root) {
if(root == nullptr) return true;
return compare(root->left,root->right);
}
};
59. 按之字形顺序打印二叉树
按之字形顺序打印二叉树_牛客题霸_牛客网 (nowcoder.com)
法一
第一印象,直接层序遍历,然后将偶数位置的数组翻转一下。
class Solution {
public:
vector<vector<int> > Print(TreeNode* root) {
vector<vector<int>> ans;
queue<TreeNode*> que;
if(root) que.push(root);
while(!que.empty()){
int size = que.size();
vector<int> vec;
for(int i = 0;i<size;i++){
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
ans.push_back(vec);
}
for(int i = 1;i<ans.size();i+=2){
reverse(ans[i].begin(),ans[i].end());
}
return ans;
}
};
法二
也可以用一个标志位,在插入时就直接改变插入方向。这里就用odd作为奇数行标志位
class Solution {
public:
vector<vector<int> > Print(TreeNode* root) {
vector<vector<int>> ans;
queue<TreeNode*> que;
if(root) que.push(root);
bool odd = true;
while(!que.empty()){
int size = que.size();
vector<int> vec;
for(int i = 0;i<size;i++){
TreeNode* node = que.front();
que.pop();
if(odd)
vec.push_back(node->val);
else
vec.insert(vec.begin(), node->val);
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
odd = !odd; // 遍历完一行了,标志一下方向变了
ans.push_back(vec);
}
return ans;
}
};
60. 把二叉树打印成多行
把二叉树打印成多行_牛客题霸_牛客网 (nowcoder.com)
这不是一个标标准准的层序遍历吗
class Solution {
public:
vector<vector<int> > Print(TreeNode* root) {
vector<vector<int>> ans;
queue<TreeNode*> que;
if(root) que.push(root);
while(!que.empty()){
int size = que.size();
vector<int> vec;
for(int i = 0;i<size;i++){
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
ans.push_back(vec);
}
return ans;
}
};