名企高频笔试题目(2)
最小编辑代价
题目描述
给定两个字符串str1和str2,再给定三个整数ic,dc和rc,分别代表插入、删除和替换一个字符的代价,请输出将str1编辑成str2的最小代价。
示例1
输入
"abc","adc",5,3,2
输出
2
示例2
输入
"abc","adc",5,3,100
输出
8
int minEditCost(string str1, string str2, int ic, int dc, int rc) {
// write code here
int len1 = str1.length() + 1, len2 = str2.length() + 1;
vector<vector<int> > dp(len1, vector<int>(len2, 0)); //str1是行,str2是列
for (int i = 1; i < len1; ++i) dp[i][0] = dc * i; //从i到i-1对str1是delete
for (int j = 1; j < len2; ++j) dp[0][j] = ic * j; //从j到j-1对str1是insert
for (int i = 1; i < len1; ++i)
for (int j = 1; j < len2; ++j)
if (str1[i - 1] == str2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = min(dp[i - 1][j] + dc, min(dp[i][j - 1] + ic, dp[i - 1][j - 1] + rc));
return dp[len1 - 1][len2 - 1];
}
找到字符串的最长无重复字符子串
题目描述
给定一个数组arr,返回arr的最长无的重复子串的长度(无重复指的是所有数字都不相同)。
示例1
输入
[2,3,4,5]
输出
4
示例2
输入
[2,2,3,4,3]
输出
3
分析:
哈希+双指针,检测到重复值则滑动窗口,记录最大的长度,时间复杂度O(N)
int maxLength(vector<int>& arr) {
unordered_map<int, int> hash_map; //哈希表
int maxLen = 0;
for (int l = 0, r = 0; r < arr.size(); ++r) {
if (hash_map.find(arr[r]) != hash_map.end() && hash_map[arr[r]] >= l) { //滑动窗口
l = hash_map[arr[r]] + 1;
}
hash_map[arr[r]] = r; //建立映射
maxLen = max(maxLen, r - l + 1);
}
return maxLen;
}
用两个栈实现队列
题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
class Solution
{
public:
void push(int node) {
stack1.push(node);
}
int pop() {
if (stack1.empty()) return -1; //异常处理
while (!stack1.empty()) { //主栈到辅栈,倒置
stack2.push(stack1.top());
stack1.pop();
}
int res = stack2.top();
stack2.pop(); //删除"队首"元素
while (!stack2.empty()) { //辅栈到主栈,还原
stack1.push(stack2.top());
stack2.pop();
}
return res; //返回被删除元素
}
private:
stack<int> stack1; //主栈
stack<int> stack2;//辅栈
};
在二叉树中找到两个节点额最近公共祖先节点
题目描述
给定一棵二叉树以及这棵树上的两个节点 o1 和 o2,请找到 o1 和 o2 的最近公共祖先节点。
示例1
输入
[3,5,1,6,2,0,8,#,#,7,4],5,1
输出
3
分析:
递归,边界条件是到达叶子节点或者找到目标节点。当满足同时覆盖包含两个节点时返回。
int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
if(root == nullptr) return 0; //达到叶子
if(o1 == root->val || o2 == root->val) return root->val; //找到目标节点
int l = lowestCommonAncestor(root->left, o1, o2); //左子树,若不包含返回0
int r = lowestCommonAncestor(root->right, o1, o2); //右子树,若包含返回val
if(l && r) return root->val; //若左右子树都包含返回该节点
return l ? l : r; //若左子树包含返回左,否则返回右
}
这个写法在查找元素0的时候会有bug,但测试用例似乎没有这一项。
进制转化
题目描述
给定一个十进制数M,以及需要转换的进制数N。将十进制数M转化为N进制数
示例1
输入
7,2
输出
"111"
备注:
M是32位整数,2<=N<=16.
测试用例有负数,没法子直接只用_itos_s()
函数,所以得自己写。
string decTo(int dec, int base) { //十进制转其他进制v1.0
string res = "";
bool negative = false;
if (dec < 0) {
negative = true;
dec = -dec;
}
do {
int t = dec % base;
if (t >= 0 && t <= 9) res += '0' + t;
else res += t - 10 + 'a';
dec = dec / base;
} while (dec);
if (negative) res += '-';
reverse(res.begin(), res.end());
return res;
}
只有正数的情况下,以下做法亦可。
string intTo(int dec, int base) { //十进制转其他进制v2.0
char buffer[20];
_itoa_s(dec, buffer, base);
return string(buffer);
}
int toInt(string str, int base) { //其他进制转10进制
char* buffer = const_cast<char*>(str.c_str());
char *res;
return strtol(buffer, &res, base);
}
重建二叉树
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
分析:
人生苦短,有时候纠结cpp的指针,下标,不如Python快乐。
class Solution:
def reConstructBinaryTree(self, pre, tin):
if not pre or not tin:
return None
root = TreeNode(pre.pop(0))
index = tin.index(root.val)
root.left = self.reConstructBinaryTree(pre, tin[:index])
root.right = self.reConstructBinaryTree(pre, tin[index + 1:])
return root
最长递增子序列
题目描述
给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中字典序最小的)
示例1
输入
[2,1,5,3,6,4,8,9,7]
输出
[1,3,4,8,9]
示例2
输入
[1,2,8,6,4]
输出
[1,2,4]
说明
其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4)其中第三个字典序最小,故答案为(1,2,4)
分析:
LIS求法见最长递增子序列,本题的不同之处:为了获取字典序最小,需要从后往前遍历,取每个位置上最后被替换的元素。
vector<int> LIS(vector<int>& arr) {
// write code here
int len = arr.size();
vector<int> res;
vector<int> temp; //每个位置的LIS长度
res.emplace_back(arr.front());
temp.emplace_back(0);
for (unsigned int i = 1; i < len; ++i)
if (arr[i] > res.back())
{
res.emplace_back(arr[i]);
temp.emplace_back(res.size() - 1);
}
else {
int pos = lower_bound(res.begin(), res.end(), arr[i]) - res.begin();
res[pos] = arr[i];
temp.emplace_back(pos);
}
for (int i = len - 1, k = res.size() - 1; k >= 0; --i)
if (temp[i] == k)
{
res[k] = arr[i];
--k;
}
return res;
}
岛屿数量
题目描述
给一个01矩阵,1代表是陆地,0代表海洋, 如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。
岛屿: 相邻陆地可以组成一个岛屿(相邻:上下左右) 判断岛屿个数。
示例1
输入
[[1,1,0,0,0],[0,1,0,1,1],[0,0,0,1,1],[0,0,0,0,0],[0,0,1,1,1]]
输出
3
void dfs(vector<vector<char> >& grid, int x, int y) {
if (x < 0 || x >= grid.size() || y < 0 || y >= grid[0].size() || grid[x][y] == '0')
return;
int dx[4] = { -1,1,0,0 }, dy[4] = { 0,0,1,-1 };
grid[x][y] = '0';
for (int i = 0; i < 4; i++) {
dfs(grid, x + dx[i], y + dy[i]);
}
}
int solve(vector<vector<char> >& grid) {
int ans = 0;
for (int i = 0; i < grid.size(); i++)
for (int j = 0; j < grid[0].size(); j++)
if (grid[i][j] == '1') {
ans++;
dfs(grid, i, j);
}
return ans;
}
判断t1树与t2树是否有拓扑结构相同的子树
题目描述
给定彼此独立的两棵二叉树,判断 t1 树是否有与 t2 树拓扑结构完全相同的子树。
设 t1 树的边集为 E1,t2 树的边集为 E2,若 E2 等于 E1 ,则表示 t1 树和t2 树的拓扑结构完全相同。
示例1
输入
{1,2,3,4,5,6,7,#,8,9},{2,4,5,#,8,9}
输出
true
分析
bool isContains(TreeNode* root1, TreeNode* root2) {
// write code here
if (root1 == nullptr) return false;
if (check(root1, root2)) return true; //检查当前为根时是否满足
return isContains(root1->left, root2) || isContains(root1->right, root2); //递归检查左右子树
}
bool check(TreeNode* root1, TreeNode* root2) {
if (root1 == nullptr && root2 == nullptr) return true; //到达叶子节点
else if (root1 == nullptr || root2 == nullptr) return false;
else if (root1->val != root2->val) return false;
return check(root1->left, root2->left) && check(root1->right, root2->right); //检查是否一致
}
在两个长度相等的排序数组中找上中位数
题目描述
给定两个有序数组arr1和arr2,已知两个数组的长度都为N,求两个数组中所有数的上中位数。
上中位数:假设递增序列长度为n,若n为奇数,则上中位数为第n/2+1个数;否则为第n个数
[要求]
时间复杂度为O(logN)O(log**N),额外空间复杂度为O(1)O(1)
示例1
输入
[1,2,3,4],[3,4,5,6]
输出
3
说明
总共有8个数,上中位数是第4小的数,所以返回3。
示例2
输入
[0,1,2],[3,4,5]
输出
2
说明
总共有6个数,那么上中位数是第3小的数,所以返回2
class Solution {
public:
/**
* find median in two sorted array
* @param arr1 int整型vector the array1
* @param arr2 int整型vector the array2
* @return int整型
*/
int findMedianinTwoSortedAray(vector<int>& arr1, vector<int>& arr2) {
// write code here
int i = -1, j = -1, k = arr1.size() - 1;
bool flag = true;
while (true) {
if (arr1[i + 1] < arr2[j + 1]) {
++i;
if (!flag) flag = true; //在左
}
else {
++j;
if (flag) flag = false; //在右
}
if (i == -1 && j == k || j == -1 && i == k || i + j == k - 1) break; //全在左,全在右,左右均有
}
if (flag) return arr1[i];
else return arr2[j];
}
};
检测环的入口
题目描述
对于一个给定的链表,返回环的入口节点,如果没有环,返回null
拓展:
你能给出不利用额外空间的解法么?
ListNode *detectCycle(ListNode *head) {
ListNode *fast = head;
ListNode *slow = head;
while(fast && fast->next) //链表无环时长度可奇可偶
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow) break; //检测环的存在
}
if(head == nullptr || head->next == nullptr|| fast != slow) return nullptr; //空链表,单节点无环链表,多节点无环链表
ListNode *del = head;
while(del->next)
{
ListNode *temp = del;
del = del->next;
temp->next = nullptr; //入口有两个next指针指向它
}
return del;
}
删除链表倒数第k个节点
题目描述
给定一个链表,删除链表的倒数第n个节点并返回链表的头指针
例如,
给出的链表为:1->2->3->4->5, n= 2.
删除了链表的倒数第n个节点之后,链表变为1->2->3->5.
备注:
题目保证n一定是有效的
请给出请给出时间复杂度为\ O(n) O(n)的算法
示例1
输入
{1,2},2
输出
{2}
分析
双指针,尤其要注意删除节点是第一个节点时的操作。
ListNode* removeNthFromEnd(ListNode* head, int n) {
// write code here
if (head == nullptr) return nullptr;
ListNode* preDel = head; //删除节点前一个位置
ListNode* sentry = head; //哨兵
while (n--) sentry = sentry->next;
while (sentry && sentry->next) //删除第一个时sentry ==nullptr
{
preDel = preDel->next;
sentry = sentry->next;
}
if (preDel == head && sentry == nullptr) return head->next; //第一个指针
preDel->next = preDel->next->next; //其他情况
return head;
}
二叉树最大路径和
题目描述
给定一个二叉树,请计算节点值之和最大的路径的节点值之和是多少。
这个路径的开始节点和结束节点可以是二叉树中的任意节点
例如:
给出以下的二叉树,
返回的结果为6
示例1
输入
{-2,1}
输出
1
示例2
输入
{-2,#,-3}
输出
-2
分析
int maxValue = INT_MIN;
int maxPathSum(TreeNode* root) {
// write code here
pathSum(root);
return maxValue;
}
int pathSum(TreeNode* root) {
if (root == nullptr) return 0; //叶子节点
int left = max(0, pathSum(root->left)); //左子树的最大路径,筛除负数
int right = max(0, pathSum(root->right)); //右子树的最大路径,筛除负数
maxValue = max(maxValue, root->val + left + right); //以当前节点为根的最长路径
return max(left, right) + root->val; //路径中只有一个节点能左右子树都选
}
合并两个有序的数组
题目描述
给出两个有序的整数数组和 ,请将数组 合并到数组 中,变成一个有序的数组
注意:
可以假设 数组有足够的空间存放 数组的元素, 和 中初始的元素数目分别为 和
分析:
不必新开一个空间,可以在A数组上原地操作。从前往后处理有可能破坏未合并的数据,所以从后往前处理
void merge(int A[], int m, int B[], int n) {
int i = m - 1, j = n - 1, k = m + n - 1;
while (i >= 0 || j >= 0)
{
if (i >= 0 && A[i] >= B[j] || j < 0) A[k--] = A[i--];
else A[k--] = B[j--];
// if (j >= 0 && A[i] <= B[j] || i < 0) A[k--] = B[j--];
// else A[k--] = A[i--];
}
return;
}
二叉搜索树的第K个节点
题目描述
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
分析一:二叉排序树满足左子树 < 根 < 右子树的性质,可以中序遍历,在找到第k小的节点后终止遍历。
TreeNode* KthNode(TreeNode* pRoot, int k)
{
TreeNode* res = nullptr;
int cnt = 0;
inOrder(pRoot,res, k, cnt);
return res;
}
void inOrder(TreeNode* pRoot,TreeNode* &res,int k,int &count)
{
if(pRoot == nullptr || k == count) return;
inOrder(pRoot->left,res,k,count);
count++;
if(count == k) res = pRoot;
inOrder(pRoot->right,res,k,count);
}
分析二:根据每个节点的位置选择向左子树找还是向右子树找
TreeNode* KthNode(TreeNode* pRoot, int k)
{
if(pRoot == nullptr) return nullptr;
int cur = countNode(pRoot->left);
if(k <= cur) return KthNode(pRoot->left, k); //在左子树
if(k == cur + 1) return pRoot; //当前节点
return KthNode(pRoot->right,k - cur - 1); //在右子树,左子树cur,根1
}
int countNode(TreeNode* pRoot) //当前结点为根的子树的结点数目
{
if(pRoot == nullptr) return 0;
return countNode(pRoot->left)+countNode(pRoot->right)+1;
}