设计题
有限状态机
有限状态自动机(FSM "Finite State Machine" 或者 FSA "Finite State Automaton")解决一个输入序列,经过 FSM,最终停留在什么状态这样一个问题。构建 FSM 产出这样一个字典:在 x 状态下,输入 a,就会得到 y 状态。
8-字符串转换整数(atoi)
将字符串转换成整数。"42"、" -42"、"4193 with words"、"words and 987"、"-91283472332"……
每一位的字符,可能有 4 种情况:数字、+/-号、字母、空格。再加上考虑边界情况的处理,如果用 if...else... 结构,代码会写得比较臃肿。因此考虑用有限状态机。
class Automator {
public:
Automator() {
um["start"] = {"start", "sign", "number", "end"};
um["sign"] = {"end", "end", "number", "end"};
um["number"] = {"end", "end", "number", "end"};
um["end"] = {"end", "end", "end", "end"};
}
int columnOf(char input) {
if (isspace(input)) {
return 0;
} else if (input == '+' || input == '-') {
return 1;
} else if (isdigit(input)) {
return 2;
} else {
return 3;
}
}
void input(char c) {
state = um[state][columnOf(c)];
if (state == "number") {
res = res * 10 + (c - '0');
res = sign == 1 ? min(res, (long long)INT_MAX) : min(res, -(long long)INT_MIN);
} else if (state == "sign") {
sign = c == '+' ? 1 : -1;
}
}
string state = "start"; // 当前状态
int sign = 1; // 正负号
long long res = 0; // 结果
private:
unordered_map<string, vector<string>> um; // 数组 0,1,2,3 分别代表空格、符号、数字、字符
};
class Solution {
public:
int myAtoi(string s) {
Automator autom = Automator();
for (char c : s) {
autom.input(c);
if (autom.state == "end") {
break;
}
}
int res = static_cast<int>(autom.sign * autom.res);
return res;
}
};
65-有效数字
393-UTF-8 编码验证
数据结构
146-LRU Cache
设计一个 LRU 缓存容器,支持以下接口:
LRUCache(int capacity)
初始化时传入容器的容量。
int get(int key)
如果 key 存在于容器中则返回 value,否则返回 -1。
void put(int key, int value)
如果 key 存在则更新其 value;否则插入键值对。当超出容量时,移除最久未使用的值。
实现本题的两种操作,需要用到一个哈希表和一个双向链表。
哈希表存储键到双向链表中的节点的映射。
链表要存储头节点;双向链表要存储头、尾两个节点。靠近头部的键值对是最近使用的,靠近尾部的键值对是最久未使用的。
#include <unordered_map>
using std::unordered_map;
#include <memory>
using std::shared_ptr;
using std::make_shared;
struct Node {
Node(int k, int v) : key(k), val(v) {}
int key;
int val;
shared_ptr<Node> pre = nullptr;
shared_ptr<Node> next = nullptr;
};
class LRUCache {
public:
LRUCache(int capacity) : capacity(capacity) {
// 伪头部和尾部节点,不实际存储键值对
head->next = tail;
tail->pre = head;
}
int get(int key) {
if (um.find(key) != um.end()) {
shared_ptr<Node> node = um[key];
moveToHead(node);
return node->val;
} else {
return -1;
}
}
void put(int key, int value) {
if (um.find(key) != um.end()) {
shared_ptr<Node> node = um[key];
node->val = value;
moveToHead(node);
} else {
shared_ptr<Node> node = make_shared<Node>(key, value);
um[key] = node;
addToHead(node);
++size;
if (size > capacity) {
shared_ptr<Node> tail = removeTail();
um.erase(tail->key);
--size;
}
}
}
private:
int capacity; // 容量
int size = 0; // 容器当前大小
unordered_map<int, shared_ptr<Node>> um; // key -> 节点
shared_ptr<Node> head = make_shared<Node>(0, 0); // 头部是最近使用的
shared_ptr<Node> tail = make_shared<Node>(0, 0); // 尾部是最久未使用的
void moveToHead(shared_ptr<Node> node) {
removeNode(node);
addToHead(node);
}
void addToHead(shared_ptr<Node> node) {
node->pre = head;
node->next = head->next;
head->next->pre = node;
head->next = node;
}
void removeNode(shared_ptr<Node> node) {
node->next->pre = node->pre;
node->pre->next = node->next;
}
shared_ptr<Node> removeTail() {
shared_ptr<Node> tmp = tail->pre; // 注意,这才是真正的尾巴!
removeNode(tmp);
return tmp;
}
};
155-最小栈
设计一个支持 push, pop, top 操作,并能在常数时间内检索到最小元素的栈。
解决这个问题要用到一个辅助栈,每次 push 操作,将数压入普通栈、同时将当前最小值压入辅助栈,使他们同进同出、一一对应。
class MinStack {
public:
void push(int x) {
x_stack.push(x);
if (min_stack.empty()) {
min_stack.push(x);
} else {
min_stack.push(min(min_stack.top(), x));
}
}
void pop() {
x_stack.pop();
min_stack.pop();
}
int top() {
return x_stack.top();
}
int getMin() {
return min_stack.top();
}
private:
stack<int> x_stack;
stack<int> min_stack;
};
225-用队列实现栈
插入后,将队列的前 N 个元素依次出队并入队。插入操作是 O(N),其余是 O(1)。
class MyStack {
public:
void push(int x) {
int size = q.size();
q.push(x);
for (int i = 0; i < size; ++i) {
q.push(q.front());
q.pop();
}
}
int pop() {
int front = q.front();
q.pop();
return front;
}
int top() {
return q.front();
}
bool empty() {
return q.empty();
}
private:
queue<int> q;
};
232-用栈实现队列
class MyQueue {
public:
void push(int x) {
int size = stk1.size();
for (int i = 0; i < size; ++i) {
stk2.push(stk1.top());
stk1.pop();
}
stk1.push(x);
for (int i = 0; i < size; ++i) {
stk1.push(stk2.top());
stk2.pop();
}
}
int pop() {
int top = stk1.top();
stk1.pop();
return top;
}
int peek() {
return stk1.top();
}
bool empty() {
return stk1.empty();
}
private:
stack<int> stk1;
stack<int> stk2;
};
895-最大频率栈
这题使用到的数据结构比较巧妙,除了常规的数字到出现频次的字典;还需要一个频次到栈的字典。
class FreqStack {
public:
void push(int val) {
freq[val] += 1;
int f = freq[val];
maxFreq = max(f, maxFreq);
group[f].push(val);
}
int pop() {
int res = group[maxFreq].top();
group[maxFreq].pop();
if (group[maxFreq].empty()) {
maxFreq -= 1;
}
freq[res] -= 1;
return res;
}
private:
unordered_map<int, int> freq; // 数 -> 频次
unordered_map<int, stack<int>> group; // 频次 -> 该频次下的数
int maxFreq; // 当前最大频次
};
分析代码执行过程可以得知,频次是从 1 开始的连续数字,因此,group
这个字典也可以用数组代替。