剑指offer 刷题记录(51~60题)

废话不多说,直接开始!

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[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1] 。不能使用除法。(注意:规定B[0]和B[n-1] = 1)

设有数组大小为5。
对于第一个for循环
第一步:b[0] = 1;
第二步: b [ 1 ] = b [ 0 ] a [ 0 ] = a [ 0 ] b[1] = b[0] * a[0] = a[0]
第三步: b [ 2 ] = b [ 1 ] a [ 1 ] = a [ 0 ] a [ 1 ] ; b[2] = b[1] * a[1] = a[0] * a[1];
第四步: b [ 3 ] = b [ 2 ] a [ 2 ] = a [ 0 ] a [ 1 ] a [ 2 ] ; b[3] = b[2] * a[2] = a[0] * a[1] * a[2];
第五步: b [ 4 ] = b [ 3 ] a [ 3 ] = a [ 0 ] a [ 1 ] a [ 2 ] a [ 3 ] ; b[4] = b[3] * a[3] = a[0] * a[1] * a[2] * a[3];
然后对于第二个for循环
第一步
t e m p = a [ 4 ] = a [ 4 ] ; temp *= a[4] = a[4];
b [ 3 ] = b [ 3 ] t e m p = a [ 0 ] a [ 1 ] a [ 2 ] a [ 4 ] ; b[3] = b[3] * temp = a[0] * a[1] * a[2] * a[4];
第二步
t e m p = a [ 3 ] = a [ 4 ] a [ 3 ] temp *= a[3] = a[4] * a[3] ;
b [ 2 ] = b [ 2 ] t e m p = a [ 0 ] a [ 1 ] a [ 4 ] a [ 3 ] ; b[2] = b[2] * temp = a[0] * a[1] * a[4] * a[3];
第三步
t e m p = a [ 2 ] = a [ 4 ] a [ 3 ] a [ 2 ] temp *= a[2] = a[4] * a[3] * a[2] ;
b [ 1 ] = b [ 1 ] t e m p = a [ 0 ] a [ 4 ] a [ 3 ] a [ 2 ] ; b[1] = b[1] * temp = a[0] * a[4] * a[3] * a[2];
第四步
t e m p = a [ 1 ] = a [ 4 ] a [ 3 ] a [ 2 ] a [ 1 ] temp *= a[1] = a[4] * a[3] * a[2] * a[1] ;
b [ 0 ] = b [ 0 ] t e m p = a [ 4 ] a [ 3 ] a [ 2 ] a [ 1 ] ; b[0] = b[0] * temp = a[4] * a[3] * a[2] * a[1];
由此可以看出从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.请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串 " + 100 " "+100" , " 5 e 2 " "5e2" , " 123 " "-123" , " 3.1416 " "3.1416" " 1 E 16 " "-1E-16" 都表示数值。 但是 " 12 e " "12e" , " 1 a 3.14 " "1a3.14" , " 1.2.3 " "1.2.3" , " + 5 " "+-5" " 12 e + 4.3 " "12e+4.3" 都不是。

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;
        }
     
};
发布了36 篇原创文章 · 获赞 8 · 访问量 1567

猜你喜欢

转载自blog.csdn.net/weixin_43619346/article/details/104339861