【LeetCode 程序员面试金典(第 6 版)】第1-4章题目 01.01 ~ 04.10

剑指 offer

面试题 01.01. 判定字符是否唯一

实现一个算法,确定一个字符串 s 的所有字符是否全都不同。

思路

因为题目并没有明确说明是否为 ‘a’-‘z’,所以这里直接暴力枚举进行判断。

如果能够知道输入的字符串大小,则可以使用位运算来进行标记是否出现过。

//1. 暴力枚举判定
class Solution {
public:
    bool isUnique(string astr) {
        for(int i = 0; i < astr.size(); ++i){
            for(int j = i + 1; j < astr.size(); ++j){
                if(astr.at(i) == astr.at(j)) 
                    return false;
            }
        }
        return true;
    }
};

//2. 通过位运算,这里假定字符范围为 'a'-'z' 
class Solution {
public:
    bool isUnique(string astr) {
        int flag = 0;// 用来存储 是否占用 状态
        for (auto c : astr){
            c -= 'a'; // 'a' 应该对应 位置 0 
            if(flag >> c & 1) 
                return false;
            flag |= 1 << c;
        }
        return true;
    }
};

面试题 01.02. 判定是否互为字符重排 - 力扣(LeetCode) (leetcode-cn.com)

给定两个字符串 s1s2,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。

思路

直接使用数组来进行统计两个字符串的各个字符个数,然后统一逐字符匹配,看各个字符的数量是否一致。

数组的大小直接用 26 即可(这里默认字符仅仅包括小写字符),同时每个数组元素的大小用 8bit,1B 即可。

class Solution {
public:
    enum{SIZE = 26};
    bool CheckPermutation(string s1, string s2) {
        int8_t cnt1[SIZE] = {0}, cnt2[SIZE] = {0};
        for_each(s1.begin(), s1.end(), [&cnt1](char c){++cnt1[c - 'a'];}); 
        for_each(s2.begin(), s2.end(), [&cnt2](char c){++cnt2[c - 'a'];}); 
	// 逐字符进行判断 
        for(int i = 0; i < SIZE; ++i){
            if(cnt1[i] != cnt2[i]) 
                return false;
        }
        return true;
    }
}; 

面试题 01.03. URL 化 - 力扣(LeetCode) (leetcode-cn.com)

URL 化。编写一种方法,将字符串中的空格全部替换为 %20。假定该字符串尾部有足够的空间存放新增字符,并且知道字符串的“真实”长度。

字符串长度在 [0, 500000] 范围内。

首先,按照题目的说法,我们需要针对每个空格进行插入三个字符,由于存储字符串的为数组,不断的插入,最坏的时间复杂度为 o ( n 2 ) o(n^2) o(n2),所以这里还不如重新开辟一个新空间,相当于一直在新的字符串的尾部一直插入,虽然耗费了一些空间。

class Solution {
public:
    string replaceSpaces(string S, int length) {
        string ans("");
        string inter("%20");
        for(int i = 0; i < length; ++i){
            char c = S.at(i);
            if(' ' == c) 
                ans += inter;
            else
                ans += c;
        }
        return ans; 
    }
};

由于忽视了题目中的 假定该字符串尾部有足够的空间存放新增字符 ,如果深入了解该特点,我们就可以得到另一个知识点,插入可以不从头开始,我们可以从尾部开始遍历,将新的字符串放在尾部。但是需要注意,处理之后,字符串前可能会有空,这需要进行处理。

class Solution {
public:
    string replaceSpaces(string S, int length) {
        int rear  = S.size();
        for(int i = length - 1; i >= 0; --i){
            if(S[i] == ' ') {
                S[--rear] = '0'; S[--rear] = '2'; S[--rear] = '%';
            }else 
                S[--rear] = S[i];
        } 
	//去掉前方存在的多余的元素
        S = S.substr(rear);
        return S;
    }
};

面试题 01.04. 回文排列 - 力扣(LeetCode) (leetcode-cn.com)

给定一个字符串,编写一个函数判定其是否为某个回文串的排列之一。

回文串是指正反两个方向都一样的单词或短语。排列是指字母的重新排列。

回文串不一定是字典当中的单词。

题目中并没有说明 字符的范围,所以直接利用 map 来进行字符的统计。

如果字符串长度为奇数,则各个字符的个数中只能够有一个是奇数。

class Solution {
public:
    enum{SIZE = 26};
    bool canPermutePalindrome(string s) {
        map<char, int>cCnt;
        for(int i = 0; i < s.size(); ++i){
            ++cCnt[s[i]];
        }
        bool odd = s.size() & 1;
        int oddCCnt = 0;
        for(auto v : cCnt){
            oddCCnt += v.second & 1;
        }
        //cout << odd <<" " << oddCCnt <<"\n";
        return odd == oddCCnt;
    }
};

对于字符,尤其是可见字符,则一定在 128 之内。

可以使用 128 位的整数或者 bitset 来进行维护,尤其是这个题目,只需要考虑各个位上次数的奇偶性,所以 0、1 二进制已经进行表示了

class Solution {
public:
    enum{SIZE = 128};
    bool canPermutePalindrome(string s) {
        bitset<SIZE>bst; 
        bst.reset();
        for(int i = 0; i < s.size(); ++i){
                bst.set(s[i], 1 ^ bst.test(s[i]));
        }
        int oddCCnt = 0;
        for(int i = 0; i < SIZE; ++i){
            oddCCnt += bst.test(i);
        }
        // cout << odd <<" " << oddCCnt <<"\n";
        return (s.size() & 1) == oddCCnt;
    }
};

面试题 01.05. 一次编辑 - 力扣(LeetCode) (leetcode-cn.com)

字符串有三种编辑操作:插入一个字符、删除一个字符或者替换一个字符。 给定两个字符串,编写一个函数判定它们是否只需要一次(或者零次)编辑。

题目要求从三种编辑方式中选择一种或零种进行字符串编辑,使其与另一特定字符串相等。

对题目分析后,可将问题拆分为以下四个并列的判定条件,满足其中任意一个即是一种解决方案:

  • 不使用字符串编辑:要求两个字符串本身相等;
  • 仅使用插入编辑方式:需要满足两个条件,第一,要求两个字符串长度相差 1;第二,对于从左向右第一次不匹配的位置,为了使该位置匹配成功,显然需要在短字符串处插入,使得两个完整字符串匹配成功;
  • 仅使用删除编辑方式:事实上与插入编辑方式本质相同,所以需要满足的条件与上一个相同;比如:长的字符串删减字符和短的字符串添加字符,这是互逆的操作。
  • 仅使用替换编辑方式:需要满足两个条件,第一,两个字符串长度相等;第二,只有一处字符不匹配。
class Solution {
public:
    bool oneEditAway(string first, string second) {
        if(first == second) return true;
        if(first.size() > second.size()) swap(first, second);
        bool can = false;
        int  i, j;
        for(i = 0, j = 0; i < first.size() && j < second.size(); ++i, ++j){
            if(first[i] != second[j]) {
                //delete or insert 
                {
                    can |= (first.substr(i) == second.substr(j + 1));
                }
                //  substitute 
                {
                    can |= (first.substr(i + 1) == second.substr(j + 1));
                }
                break;
            }
        }
        can |= i == first.size() ? first.size() + 1  == second.size() : false; 
        return can ;
    }
};

面试题 01.06. 字符串压缩 - 力扣(LeetCode) (leetcode-cn.com)

字符串压缩。利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能。比如,字符串 aabcccccaaa 会变为 a2b1c5a3。若“压缩”后的字符串没有变短,则返回原先的字符串。你可以假设字符串中只包含大小写英文字母(a 至 z)。

按照题目要求编码即可, 末尾判定是否真的“压缩”成功。

本题需要注意 C++ 中 string 类的特点。举例说明:str = str + c 与 str += c 在时间和空间上差距巨大。这是由于 str = str + c 会额外构造 string 对象。而 str += c 直接在 str 对象的末尾添加字符,并不会额外构造对象,避免构造和析构对象的时间。

class Solution {
public:
    string compressString(string S) {
        string zipS("");
        if(0 == S.size()) return S;
        S += " ";
        char ch = S[0];
        int occ = 1;
        for(int i = 1; i < S.size(); ++i){
            if(S[i] != ch){
                zipS += ch;
                zipS += to_string(occ);
                ch = S[i];
                occ = 1;
            }
            else 
                ++occ;
        }
        S.pop_back();
        return zipS.size() >= S.size() ? S : zipS;
    }
};

面试题 01.07. 旋转矩阵 - 力扣(LeetCode) (leetcode-cn.com)

给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。不占用额外内存空间能否做到?

面试题 01.08. 零矩阵 - 力扣(LeetCode) (leetcode-cn.com)

编写一种算法,若 M × N 矩阵中某个元素为 0,则将其所在的行与列清零。

最直观的思路:采用两个标记数组,一个标记对应行是否需要清零,另一个标记对应列是否需要清零

时间复杂度 O ( N + M ) O(N+M) O(N+M),空间复杂度 O ( N + M ) O(N+M) O(N+M)

**注意:**标记数组的值仅包括 0 和 1,故采用 bool 变量即可

class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        int M = matrix.size(), N = matrix[0].size();
        vector<bool>row(M, 0), col(N, 0);
        for(int i = 0; i < M; ++i){
            for(int j = 0; j < N; ++j){
                if(0 == matrix[i][j]) {
                    row[i] = col[j] = true;
                }
            }
        } 
        for(int i = 0; i < M; ++i){
            for(int j = 0; j < N; ++j){
                if(row[i] || col[j]) 
                    matrix[i][j] = 0;
            }
        }
    }
};

为减少空间复杂度,有时需考虑:是否可利用数据本身存储额外信息,必要时再将数据还原,如此空间复杂度可降为 O ( 1 ) O(1) O(1)

应用上述思想于本题,有如下解法:

复用矩阵的第一列与第一行,分别作为行标记与列标记。

以矩阵的第一列为例进行说明,第一列标记所有行是否需清零。假如第 i 行包括零值,则第一列的第 i 个值置为 0(即使未复用第一列,该值也应设为 0,不过此时该值包括额外的标记含义)。如果第一列包括 0 值,根据题意第一列需清零,但如此做将破坏第一列的标记,所以在此情况下,只需延后第一列的清零即可。

列标记同理。

时间复杂度 O ( N + M ) O(N+M) O(N+M),空间复杂度 O ( 1 ) O(1) O(1)

class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        int M = matrix.size(), N = matrix[0].size();
        bool row_0 = false, col_0 = false;
        for(int i = 0; i < N; ++i) row_0 |= matrix[0][i] == 0; 
        for(int i = 0; i < M; ++i) col_0 |= matrix[i][0] == 0; 
        for(int i = 0; i < M; ++i){
            for(int j = 0; j < N; ++j){
                matrix[i][0] = matrix[i][j] == 0 ? 0 : matrix[i][0];
                matrix[0][j] = matrix[i][j] == 0 ? 0 : matrix[0][j];
            }
        }
        for(int i = 1; i < M; ++i){
            for(int j = 1; j < N; ++j){
                if(0 == matrix[i][0] || 0 == matrix[0][j]) 
                    matrix[i][j] = 0;
            }
        }
        if(row_0) {
            for(int i = 0; i < N; ++i)  matrix[0][i] = 0; 
        }
        if(col_0){
            for(int i = 0; i < M; ++i)  matrix[i][0] = 0; 
        }
    }
};

面试题 01.09. 字符串轮转 - 力扣(LeetCode) (leetcode-cn.com)

字符串轮转。给定两个字符串 s1s2,请编写代码检查 s2 是否为 s1 旋转而成(比如,waterbottleerbottlewat 旋转后的字符串)。

显然,s2 应为 s1 s1 的子串

class Solution {
public:
    bool isFlipedString(string s1, string s2) {
        if(s1.size() != s2.size()) return false;
        s1 += s1;
        return -1 != s1.find(s2);
    }
};

面试题 02.01. 移除重复节点 - 力扣(LeetCode) (leetcode-cn.com)

编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。

该代码中没有额外新建节点

时间复杂度 O ( N 2 ) O(N^2) O(N2),空间复杂度 O ( 1 ) O(1) O(1)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeDuplicateNodes(ListNode* head) {
        if(nullptr == head) return head;
        ListNode* newHead = head, *rear = head;
  
        for(head = head->next; head; head = head->next){
            rear->next = nullptr;
            bool noInsert = false;
            for(auto tmp = newHead; tmp; tmp = tmp->next){
                if(head->val == tmp->val){
                    noInsert = true;
                    break;
                }
            }
            if(!noInsert){
                rear->next = head;
                rear = rear->next;
            }
        }
        rear->next = nullptr;
        return newHead;
    }
};

面试题 02.02. 返回倒数第 k 个节点 - 力扣(LeetCode) (leetcode-cn.com)

实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。

注意: 本题相对原题稍作改动

常规遍历链表到尾部,可以访问到链表中最后一个节点。如果有个懒惰的遍历指针,延后 k 次 next 操作,然后在。

class Solution {
public:
    int kthToLast(ListNode* head, int k) {
        ListNode* lastK = head;
        for(head ; head; head = head->next){
            if(--k < 0){
                lastK = lastK->next;
            }
        }
        return lastK->val;
    }
};

面试题 02.03. 删除中间节点 - 力扣(LeetCode) (leetcode-cn.com)

若链表中的某个节点,既不是链表头节点,也不是链表尾节点,则称其为该链表的「中间节点」。

假定已知链表的某一个中间节点,请实现一种算法,将该节点从链表中删除。

例如,传入节点 c(位于单向链表 a->b->c->d->e->f 中),将其删除后,剩余链表为 a->b->d->e->f

读题后,直观感觉题目出错。在单链表中删除节点是需要拥有待删除节点的前驱节点。显然题目中既不是双向链表,也没有头结点。

这时,显然本题无法采用常规方法。通过举例尝试,可以发现删除节点,实际上是值的左移。只需将待删除节点后整体向左移动一位即可,这和顺序表的删除方法类似。

class Solution {
public:
    void deleteNode(ListNode* node) {
        for(node; ; node = node->next){
            node->val = node->next->val;
            if(nullptr == node->next->next){
                node->next = nullptr;
                break;
            }
        }
    }
};

面试题 02.04. 分割链表 - 力扣(LeetCode) (leetcode-cn.com)

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你不需要 保留 每个分区中各节点的初始相对位置。

直观的思路:采用两个单向链表,分别维护比 x 小的节点和其余节点,最后将两者进行连接即可。

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        ListNode* ltHead = new ListNode(-1);
        ListNode* betHead = new ListNode(-1);
        ListNode * tmp;
        for( ; head; ){
            tmp = head->next;
            if(head->val < x) {
                head->next = ltHead->next;
                ltHead->next = head;
            }else{
                head->next = betHead->next;
                betHead->next = head;
            }
            head = tmp;
        }
        for(ListNode* rear = ltHead; rear; rear = rear->next){
            if(rear->next == nullptr){
                rear->next = betHead->next;
                break;
            }
        }
        return ltHead->next;
    }
};

面试题 02.05. 链表求和 - 力扣(LeetCode) (leetcode-cn.com)

给定两个用链表表示的整数,每个节点包含一个数位。

这些数位是反向存放的,也就是个位排在链表首部。

编写函数对这两个整数求和,并用链表形式返回结果。

目标:利用链表模拟两数加法。

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int carry = 0;
        ListNode* ansHead = nullptr, *rear = nullptr;
        int allValue;
        for(l1; l1 && l2 ; l1 = l1->next, l2 = l2->next){
            allValue = l1->val + l2->val + carry;
            carry = allValue / 10;
            if(nullptr == ansHead) {
                ansHead = new ListNode(allValue % 10);
                rear = ansHead;
            }else{
                rear->next = new ListNode(allValue % 10);
                rear = rear->next;
            }
        }
        if(nullptr != l2) {l1 = l2;}
        if(nullptr != l1) {
            for(l1; l1; l1 = l1->next){
                allValue = carry + l1->val;
                carry = allValue / 10;
                rear->next = new ListNode(allValue % 10);
                rear = rear->next;
            }   
        } 
        if(0 != carry ) { 
            rear->next = new ListNode(carry); 
        }
        return ansHead;
    }
};

面试题 02.06. 回文链表 - 力扣(LeetCode) (leetcode-cn.com)

判定一个链表是否是回文

最直接的方法:复制链表的值到数组中,从而在数组上判定回文。

题目要求一种:时间复杂度为 O ( N ) O(N) O(N),空间复杂度为 O(1)的方法。

找到符合要求的解法较难。

做法将链表的一半进行翻转,从而按照顺序访问来依次匹配回文。
注意翻转后匹配完,需要翻转回来,业务上应该需满足不更改链表的值顺序。但是这将影响并发效率,因为该函数需对链表修改,所以调用该函数时,需对整个链表加锁,这必将减弱并发性。

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        int length = 0;
        for(auto p = head; p; p = p->next) ++length;
        int midPos = length / 2 - 1;
        auto curNode = head;
        auto oneHead = new ListNode(-1);
        int curPos = 0;
        for(curNode; curNode; ){
            if(curPos++ <= midPos){
                // head insert the current node. reverse the first part of whole LinkList.
                auto tmpNode = curNode->next;
                curNode->next = oneHead->next;
                oneHead->next = curNode;
                curNode = tmpNode;
            }else
                break;
        }
        if(length & 1) curNode = curNode->next;
        // compare tow parts.
        for(curNode, oneHead = oneHead->next; curNode; curNode = curNode->next, oneHead = oneHead->next){
            if(oneHead->val != curNode->val)
                return false;
        }
        return true;
    }
};

面试题 02.07. 链表相交 - 力扣(LeetCode) (leetcode-cn.com)

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

从右向左看,与树是一致的结构,所以问题转化为 LCA 最近公共祖先的求解:先保持相同的高度,随后一同向上跳跃。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int lenA = 0, lenB = 0;
        for(auto p = headA; p; p = p->next) ++lenA;
        for(auto p = headB; p; p = p->next) ++lenB;
        if(lenA > lenB) {
            swap(headA, headB);
            swap(lenA, lenB);
        }
        for(int i = 0; i < lenB - lenA; ++i) headB = headB->next;
        for(headA, headB; headA && headB; headA = headA->next, headB = headB->next){
            if(headA == headB) {
                break;
            }
        }
        return headB == headB ? headA : nullptr;
    }
};

面试题 02.08. 环路检测 - 力扣(LeetCode) (leetcode-cn.com)

给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点。若环不存在,请返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

示例 1:

快慢指针。

面试题 03.01. 三合一 - 力扣(LeetCode) (leetcode-cn.com)

三合一。描述如何只用一个数组来实现三个栈。

你应该实现 push(stackNum, value)、pop(stackNum)、isEmpty(stackNum)、peek(stackNum)方法。stackNum 表示栈下标,value 表示压入的值。

构造函数会传入一个 stackSize 参数,代表每个栈的大小。

由题意可知:三个栈有确定的最大空间,故可将一维数组分为三部分,分别执行三个栈的操作即可。

class TripleInOne {
    vector<int>* stack;
    int base[3], top[3], size;
public:
    TripleInOne(int stackSize) {
        stack = new vector<int>(stackSize * 3);
        for(int i = 0; i < 3; ++i) {
            base[i] = top[i] = i * stackSize;
        }
        size = stackSize;
    }
  
    void push(int stackNum, int value) {
        if((stackNum + 1)* size == top[stackNum]) return;
        // cout << "push" <<"\n";
        (*stack)[top[stackNum]++] = value;
    }
  
    int pop(int stackNum) {
        if(base[stackNum] == top[stackNum]) return -1;
        return (*stack)[--top[stackNum]];
    }
  
    int peek(int stackNum) {
        if(base[stackNum] == top[stackNum]) return -1;
        return (*stack)[top[stackNum] - 1];
    }
  
    bool isEmpty(int stackNum) {
        return base[stackNum] == top[stackNum];
    }
};

面试题 03.02. 栈的最小值 - 力扣(LeetCode) (leetcode-cn.com)

请设计一个栈,除了常规栈支持的 pop 与 push 函数以外,还支持 min 函数,该函数返回栈元素中的最小值。执行 push、pop 和 min 操作的时间复杂度必须为 O(1)。

两个思路:

  1. 单个栈,栈元素为两个值,值本身和截止到目前的最小值;每次添加元素时进行更新即可。
  2. 两个栈,一个栈存数值,一个栈采用单调栈来保存最小值。
//思路二:单调栈,但是一直判定为数组 越界。目前没有找到bug。

class MinStack {
    vector<int> minStack, stack;
public:
    /** initialize your data structure here. */
    MinStack() {

    }
    void display(){
        cout << "minstack" << "\n";
        for_each(minStack.begin(), minStack.end(), [](int x){cout << x << " ";});
        cout << "\n";
        cout << "stack" << "\n";
        for_each(stack.begin(), stack.end(), [](int x){cout << x <<  " ";});
        cout<<"\n";
  
    }
    void push(int x) {
        stack.push_back(x);
        if(minStack.size() == 0) minStack.push_back(stack.size());
        else 
            if(x <= stack[minStack.back() - 1]) 
                minStack.push_back(stack.size());
        display();
    }
  
    void pop() {
        while(minStack.back() >= stack.size() ) minStack.pop_back();
        stack.pop_back();
        display();
    }
  
    int top() {
        display();
        return stack.back();
    }
  
    int getMin() {
        display();
        return stack[minStack.back() - 1];
    }
};
// 思路一 
class MinStack {
    vector<pair<int,int>> stack;
public:
    /** initialize your data structure here. */
    MinStack() {

    }
  
    void push(int x) {
        stack.push_back( pair<int,int>(x,  stack.size() ? min(stack.back().second, x) : x) );
    }
  
    void pop() {
        stack.pop_back();
    }
  
    int top() {
        return stack.back().first;
    }
  
    int getMin() {
        return stack.back().second;
    }
};

面试题 03.03. 堆盘子 - 力扣(LeetCode) (leetcode-cn.com)

堆盘子。设想有一堆盘子,堆太高可能会倒下来。因此,在现实生活中,盘子堆到一定高度时,我们就会另外堆一堆盘子。请实现数据结构 SetOfStacks,模拟这种行为。SetOfStacks 应该由多个栈组成,并且在前一个栈填满时新建一个栈。此外,SetOfStacks.push()和 SetOfStacks.pop()应该与普通栈的操作方法相同(也就是说,pop()返回的值,应该跟只有一个栈时的情况一样)。 进阶:实现一个 popAt(int index)方法,根据指定的子栈,执行 pop 操作。

当某个栈为空时,应当删除该栈。当栈中没有元素或不存在该栈时,pop,popAt 应返回 -1.

利用 嵌套的 vector 来模拟题目描述。

有一个点需要考虑:

  • popAt 想要 O ( 1 ) O(1) O(1) 的访问到特定的栈,需要采用顺序表,但是当因为 popAt 而把某个栈弹空时,需要将该栈移除,此时需要 O ( N ) O(N) O(N) 的复杂度,N 表示最大栈的个数。

  • popAt 想要移除栈时 O ( 1 ) O(1) O(1),需要采用链表,但是此时只能够 O ( N ) O(N) O(N) 访问到需要弹出的栈。

//利用 顺序表 来实现
class StackOfPlates {
typedef vector<int> vi;

vector<vi*> setOfStack;
int _cap;
public:
    StackOfPlates(int cap) {
        _cap = cap;
    }
    void push(int val) {
        if(0 == setOfStack.size() || _cap == (*setOfStack.back()).size()) {
            setOfStack.push_back(new vi());
        }
        (*setOfStack.back()).push_back(val);
    }

    bool isEmpty(){
        return 0 == setOfStack.size() || 0 == _cap;
    }
    int pop() {
        if(isEmpty()) return -1;
        int val;
        val = (*setOfStack.back()).back();
        (*setOfStack.back()).pop_back();
        if(0 == (*setOfStack.back()).size()) {
            setOfStack.pop_back();
        }
        return val;
    }
    int popAt(int index) {
        if(isEmpty()) return -1;
        if(index >= setOfStack.size()) {
            return -1;
        }
        int val = (*setOfStack[index]).back();
        (*setOfStack[index]).pop_back();
        if(0 == (*setOfStack[index]).size()) {
            setOfStack.erase(setOfStack.begin() + index);
        }
        return val;
    }
}; 

面试题 03.04. 化栈为队 - 力扣(LeetCode) (leetcode-cn.com)

实现一个 MyQueue 类,该类用两个栈来实现一个队列。

class MyQueue {
    stack<int>mStack, aStack;
public:
    /** Initialize your data structure here. */
    MyQueue() {

    }
  
    /** Push element x to the back of queue. */
    void push(int x) {
        while(!aStack.empty()){
            mStack.push(aStack.top()); aStack.pop();
        }
        mStack.push(x);
    }
  
    /** Removes the element from in front of queue and returns that element. */
    int pop() {
        if(empty()) return -1;
        while(!mStack.empty()){
            aStack.push(mStack.top()); mStack.pop();
        }
        int val = aStack.top();
        aStack.pop();
        return val;
    }
  
    /** Get the front element. */
    int peek() {
        if(empty()) return -1;
        while(!mStack.empty()){
            aStack.push(mStack.top()); mStack.pop();
        }
        return aStack.top();
    }
  
    /** Returns whether the queue is empty. */
    bool empty() {  
        return mStack.empty( ) && aStack.empty();
    }
};

面试题 03.05. 栈排序 - 力扣(LeetCode) (leetcode-cn.com)

栈排序。 编写程序,对栈进行排序使最小元素位于栈顶。最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。该栈支持如下操作:push、pop、peek 和 isEmpty。当栈为空时,peek 返回 -1。

看代码吧,不好说明。

class SortedStack {
    typedef pair<int,int>pii;
    vector<pii> stack;
    vector<int> tmpStack;
public:
    SortedStack() {

    }
  
    void push(int val) {
        stack.push_back(pii(val, stack.size() ? min(val, stack.back().second) : val));
    }
  
    void pop() {
        if(isEmpty()) return;
        int mnValue = stack.back().second;
        while(stack.size() && stack.back().first != mnValue) {
            tmpStack.push_back(stack.back().first);
            stack.pop_back();
        }
        stack.pop_back();
        while(tmpStack.size() ){
            push(tmpStack.back());
            tmpStack.pop_back();
        }
    }
  
    int peek() {  
        if(isEmpty()) return -1;
        return stack.back().second;
    }
  
    bool isEmpty() {
        return 0 == stack.size();
    }
};

C++ 两种辅助栈解法 - 栈排序 - 力扣(LeetCode) (leetcode-cn.com)

此处参考上述的解法。

思路一 :利用辅助栈,帮助主栈维护一个单调的序列(与顺序表的有序维护原理一致)。这样,仅需在 push 时进行复杂的调整即可,调整时间复杂度也为 o ( N ∗ N ) o(N*N) o(NN) ,其中 N N N 为栈中元素的个数。

class SortedStack {
    stack<int>mStack, aStack;
public:
    SortedStack() {

    }
  
    void push(int val) {
        while(!mStack.empty() && mStack.top() < val){
            aStack.push(mStack.top()); mStack.pop();
        }
        mStack.push(val);
        while(!aStack.empty()){
            mStack.push(aStack.top()); aStack.pop();
        }
    }
  
    void pop() {
        if(isEmpty()) return;
        mStack.pop();
    }
  
    int peek() {
        if(isEmpty()) return -1;
        return mStack.top();
    }
  
    bool isEmpty() {
        return mStack.empty();
    }
};

思路二:第一种思路的惰性策略。在维护主栈的有序时,需要两个栈互相传递,很耗时。维护过程中部分操作,事实上延迟到 peek 和 pop 执行也是毫无问题。

思路一、思路二的核心区别即为惰性,可以根据实际场景来调整算法。如果 push 操作频繁,思路二更优,因为维护有序的部分操作延迟到 peek 或 pop 才执行。如果 peek 和 pop 操作频繁,显然思路一更优。

class SortedStack {
    // mstack is main stack, aStack is assistant stack.
    stack<int>mStack, aStack;
public:
    SortedStack() {

    }
  
    void push(int val) {

        while(!mStack.empty() && mStack.top() < val) {
            aStack.push(mStack.top()); mStack.pop();
        }
        while(!aStack.empty() && aStack.top() > val) {
            mStack.push(aStack.top()); aStack.pop();
        }
	// the above two steps is to find the right position.
        mStack.push(val);
    }

    // lazy to update the main stack.
    void update(){
        while(!aStack.empty()){
            mStack.push(aStack.top()); aStack.pop();
        }
    }

    void pop() {
        if(isEmpty())  return;
        update();
        mStack.pop();
    }
  
    int peek() {
        if(isEmpty()) return -1;
        update();
        return mStack.top();
    }
  
    bool isEmpty() {
        return mStack.empty() && aStack.empty();
    }
};

面试题 03.06. 动物收容所 - 力扣(LeetCode) (leetcode-cn.com)

动物收容所。有家动物收容所只收容狗与猫,且严格遵守“先进先出”的原则。在收养该收容所的动物时,收养人只能收养所有动物中“最老”(由其进入收容所的时间长短而定)的动物,或者可以挑选猫或狗(同时必须收养此类动物中“最老”的)。换言之,收养人不能自由挑选想收养的对象。请创建适用于这个系统的数据结构,实现各种操作方法,比如 enqueue、dequeueAny、dequeueDog 和 dequeueCat。允许使用 Java 内置的 LinkedList 数据结构。

enqueue 方法有一个 animal 参数,animal[0]代表动物编号,animal[1]代表动物种类,其中 0 代表猫,1 代表狗。

dequeue*方法返回一个列表[动物编号, 动物种类],若没有可以收养的动物,则返回[-1,-1]。

采用两个队列分别模拟即可。由于题目并没有确定说编号是不断递增,所以这里额外定义了时间戳来衡量动物的收容时间。

class AnimalShelf {
    typedef pair<int, int>pii;
    typedef vector<int>vi;
    queue<pii> animals[2];
    enum{DOG=1, CAT=0};
    int _timestamp = 1;
public:
    AnimalShelf() {

    }
  
    void enqueue(vector<int> animal) {
        animals[animal[1]].push(pii(animal[0], _timestamp++));
    }
  
    vector<int> dequeueAny() {
        if(isEmpty(DOG) && isEmpty(CAT)) return vi{-1, -1};
        if(isEmpty(DOG)) return dequeueCat();
        if(isEmpty(CAT)) return dequeueDog();
        return animals[DOG].front().second < animals[CAT].front().second ? dequeueDog() : dequeueCat();
    }
  
    vector<int> dequeueDog() { 
        if(isEmpty(DOG)) return vi{-1,-1};
        auto val = vi{animals[DOG].front().first, DOG};
        animals[DOG].pop();
        return val;
    }
  
    vector<int> dequeueCat() {
        if(isEmpty(CAT)) return vi{-1,-1};
        auto val = vi{animals[CAT].front().first, CAT};
        animals[CAT].pop();
        return val;
    }
    bool isEmpty(int kind){
        return animals[kind].empty();
    }
};

面试题 04.01. 节点间通路 - 力扣(LeetCode) (leetcode-cn.com)

节点间通路。给定有向图,设计一个算法,找出两个节点之间是否存在一条路径。

BFS 或者 DFS 即可

class Solution {
    enum {N = 100000};
    bool vis[N];
public:
    bool findWhetherExistsPath(int n, vector<vector<int>>& graph, int start, int target) {
        vector<vector<int>> G(n, vector<int>());
        for(auto edge : graph){
            G[edge[0]].push_back(edge[1]);
        }
        queue<int>que;
        que.push(start); vis[start] = true;
        while(!que.empty()){
            int u = que.front(); que.pop();
            if(target == u) return true;
            for(auto v : G[u]){
                if(!vis[v]) {
                    que.push(v); vis[v] = true;
                }
            }
        }
        return false;
    }
};

面试题 04.02. 最小高度树 - 力扣(LeetCode) (leetcode-cn.com)

给定一个有序整数数组,元素各不相同且按升序排列,编写一个算法,创建一棵高度最小的二叉搜索树。

折半查找的搜索树一定是个平衡二叉树

class Solution {
public:
    TreeNode* split(int L, int R, const vector<int>& nums){
        if(L > R) return nullptr;
        int mid = (L + R) >> 1;
        TreeNode* curNode = new TreeNode(nums[mid]); 

        if(L <= mid - 1) curNode->left = split(L, mid - 1, nums);
        if(mid + 1 <= R) curNode->right = split(mid + 1, R, nums);
        return curNode;
    }
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        return split(0, nums.size() - 1, nums);
    }
};

面试题 04.03. 特定深度节点链表 - 力扣(LeetCode) (leetcode-cn.com)

给定一棵二叉树,设计一个算法,创建含有某一深度上所有节点的链表(比如,若一棵树的深度为 D,则会创建出 D 个链表)。返回一个包含所有深度的链表的数组。

BFS 层次遍历,每个节点需保存深度,以便来控制该层的链表。

注意 vector.size()返回的是一个无符号数,需减一时尤其注意。

class Solution {
    typedef pair<TreeNode*, int>pti;
public:
    vector<ListNode*> listOfDepth(TreeNode* tree) {
        vector<ListNode*> ans;
        ListNode* curLayListRear = nullptr;
        queue<pti> que; 
        if(tree) que.push(pti(tree, 0));
        while(!que.empty()){
            auto rt = que.front(); que.pop();
            if(ans.size() <= rt.second) {
                ans.push_back(new ListNode(rt.first->val));
                curLayListRear = ans.back();
            }else{
                curLayListRear->next = new ListNode(rt.first->val);
                curLayListRear = curLayListRear->next;
            }
            if(rt.first->left) 
                que.push(pti(rt.first->left, rt.second + 1));
            if(rt.first->right) 
                que.push(pti(rt.first->right, rt.second + 1));
        }
        return ans;
    }
};

面试题 04.04. 检查平衡性 - 力扣(LeetCode) (leetcode-cn.com)

实现一个函数,检查二叉树是否平衡。在这个问题中,平衡树的定义如下:任意一个节点,其两棵子树的高度差不超过 1。

递归遍历,每个节点判断左子树和右子树的高度的差即可。

class Solution {
    bool _isBanlan = true;
public:
    int getHeight(TreeNode* &rt){
        if(nullptr == rt || false == _isBanlan) return 0;
        int leftHeight = getHeight(rt->left), rightHeight =getHeight(rt->right) ;
        // cout << leftHeight << " " << rightHeight <<"\n"; 
        if(abs(leftHeight - rightHeight) > 1)
            _isBanlan = false;
        return 1 + max(leftHeight, rightHeight);
    }
    bool isBalanced(TreeNode* root) {
        getHeight(root);
        return _isBanlan;
    }
};

面试题 04.06. 后继者 - 力扣(LeetCode) (leetcode-cn.com)

设计一个算法,找出二叉搜索树中指定节点的“下一个”节点(也即中序后继)。

如果指定节点没有对应的“下一个”节点,则返回 null

中序遍历下,第一次找到指定节点,后遇到的第一个节点即为所求。

class Solution {
    bool find = false;
    TreeNode* _p;
    TreeNode* nextP = nullptr;
public:
    void inorder(TreeNode* &rt){
        if(!rt || nextP) return ;
        inorder(rt->left);
	//如果已经找到 找到指定节点 
        if(find){
            nextP = rt;
	    //置标记没有找到
            find = false;
        }
	// 第一次找到P,标记
        if(_p == rt) {
            find = true;
        }
        inorder(rt->right);
    }
    TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
        _p = p; 
        inorder(root);
        return nextP;
    }
};

面试题 04.08. 首个共同祖先 - 力扣(LeetCode) (leetcode-cn.com)

设计并实现一个算法,找出二叉树中某两个节点的第一个共同祖先。不得将其他的节点存储在另外的数据结构中。注意:这不一定是二叉搜索树。

在二叉树中,某个子树的根节点是公共祖先,一共包括两种情况:

  • 左子树和右子树分别找到一个结点
  • 某子树的根节点和(左子树或右子树)分别找到一个
class Solution {
    TreeNode* ans = nullptr;
public:
    int find(TreeNode* &rt, TreeNode* &p, TreeNode* &q){
        // rt is nullptr or the ans has been finded. 
        if(!rt || nullptr != ans) return 0;

        // continue to visit lson and rson  
        int findLeft = find(rt->left, p, q);
        int findRight = find(rt->right, p, q);

        // common pre node include two situations. 
        if(1 == findLeft && 1 == findRight) {
            //1. both leftTree and rightTree have a wait finded node.
            ans = rt;
        }else if(findLeft || findRight){
            if(rt == p || rt == q){
                //2. root node and (leftTree or rightTree) have a wait finded node.
                ans = rt;
            }
        }
        //find a wait node.
        if(rt == p || rt == q) return 1;
        // recurion 
        return findLeft + findRight;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        find(root, p, q);
        return ans;
    }
};

面试题 04.10. 检查子树 - 力扣(LeetCode) (leetcode-cn.com)

检查子树。你有两棵非常大的二叉树:T1,有几万个节点;T2,有几万个节点。设计一个算法,判断 T2 是否为 T1 的子树。

如果 T1 有这么一个节点 n,其子树与 T2 一模一样,则 T2 为 T1 的子树,也就是说,从节点 n 处把树砍断,得到的树与 T2 完全相同。

注意:此题相对书上原题略有改动。

遍历 t1 过程中,依次判断是否与 t2 完全相等。

有个样例需要注意:

[ 2,1,3]

[2]

这个样例应该是不相等的,因为 t2 并不是 t1 的一个子树,只是 t1 的一部分。

class Solution {
    bool finded = false;
public:
    // compare the sub t1 and sub t2.
    bool EUQ(TreeNode* &t1, TreeNode* &t2){
        if(!t2 && !t1) return true;
        else if(!t2 || !t1) 
            return false;
        else {
            // t1 and t2 is not empty 
            if(t1->val == t2->val){
                // both left sub tree and right sub tree is needed equal.
                if(EUQ(t1->left, t2->left) && EUQ(t1->right, t2->right)){
                    return true;
                }else
                    return false;
            }else 
                return false;
        }
    }
    // tranverse the t1 tree.
    void dfs_1(TreeNode* &rt, TreeNode* &t){
        // rt is empty or finded. 
        if(!rt || finded) return ;
        if(EUQ(rt, t)) {
            finded = true;
            return;
        }
        dfs_1(rt->left, t);
        dfs_1(rt->right, t);

    }
    bool checkSubTree(TreeNode* t1, TreeNode* t2) {
        dfs_1(t1, t2);
        return finded;
    }
};


leetcode 上有个思路,很容易找到 bug,还很多人用。

C++ 双 DFS 解法 ,观察到很多单递归代码都是错误的 - 检查子树 - 力扣(LeetCode) (leetcode-cn.com)

这个题目应该是无法利用单个 DFS 来求解的

class Solution {
public:
    bool checkSubTree(TreeNode* t1, TreeNode* t2) {
        if(!t2) return true;
        if(!t1) return false;
        if(t1->val == t2->val){
            return checkSubTree(t1->left, t2->left) && checkSubTree(t1->right, t2->right);
        }else
            return checkSubTree(t1->left, t2) || checkSubTree(t1->right, t2);
    }
};

bug 样例:
[2,3,5,4]
[2,4] 

纯 C 击败 100% 树哈希 0 冲突 时间复杂度 O(N+M) 新手写题解多有不足,还请谅解. - 检查子树 - 力扣(LeetCode) (leetcode-cn.com)
这个解法还是可以学习的。

将树通过 hash 的方式用一个整数来进行表示。

class Solution {
    enum{MOD = (int)1e9 + 7};
    bool finded = false;
public:
    int find(TreeNode* rt, int hashval = -1){
        if(!rt || finded) return 0;
        int curHash =  (rt->val + find(rt->left, hashval) * 1LL * 31 + find(rt->right, hashval) * 1LL * 131) % MOD ;
        if(hashval != -1 && curHash == hashval){
            finded = true;
            return 0;
        }
        // cout <<rt->val <<" " << curHash <<"\n";
        return curHash ;
    }
    bool checkSubTree(TreeNode* t1, TreeNode* t2) {
        int hashval = find(t2);
        find(t1, hashval);
        return finded;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值