剑指 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)
给定两个字符串 s1
和 s2
,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。
思路
直接使用数组来进行统计两个字符串的各个字符个数,然后统一逐字符匹配,看各个字符的数量是否一致。
数组的大小直接用 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)
字符串轮转。给定两个字符串 s1
和 s2
,请编写代码检查 s2
是否为 s1
旋转而成(比如,waterbottle
是 erbottlewat
旋转后的字符串)。
显然,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)
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 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)。
两个思路:
- 单个栈,栈元素为两个值,值本身和截止到目前的最小值;每次添加元素时进行更新即可。
- 两个栈,一个栈存数值,一个栈采用单调栈来保存最小值。
//思路二:单调栈,但是一直判定为数组 越界。目前没有找到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(N∗N) ,其中 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;
}
};