[leetcode] 剑指 Offer 专题(一)
又开了一个笔记专题的坑,未来一两周希望能把《剑指Offer》的题目刷完️。
03 数组中重复的数字
常见思路:
- 哈希表计数。遍历,把
bool table[n]
中没有遇到过的数字置为 1 。时间复杂度 \(O(n)\), 空间复杂度 \(O(n)\) . - 排序。时间 \(O(nlogn)\),空间 \(O(1)\) .
书中解法:排序后的某个数字 k
必然会在 nums[k]
的位置上。如果输入数组每个数字都是唯一的,那么不会出现 nums[i] == nums[nums[i]]
的情况,如果有,就是所求的重复数字。时间 \(O(n)\),空间 \(O(1)\) .
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
int len = nums.size();
for (int i=0; i<len; i++)
{
while (nums[i] != i)
{
if (nums[i] == nums[nums[i]])
return nums[i];
else
swap(nums[i], nums[nums[i]]);
}
}
return -1;
}
};
04 二维数组中的查找
解法1
对每一行用一次二分查找。
class Solution
{
public:
bool findNumberIn2DArray(vector<vector<int>> &matrix, int target)
{
return method1(matrix, target);
}
bool method1(vector<vector<int>> &m, int t)
{
for (auto &v : m)
{
if (binsearch(v,t))
return true;
}
return false;
}
bool binsearch(vector<int> &v, int t)
{
int len = v.size();
if (len == 0)
return false;
int l = 0, r = len - 1;
while (l <= r)
{
if (l == r)
return v[l] == t;
int m = l + (r - l) / 2;
if (t < v[m])
r = m - 1;
else if (v[m] < t)
l = m + 1;
else
return true;
}
return false;
}
};
解法2
利用每一列也是升序序列的规律,对列也进行二分。
bool method2(vector<vector<int>> &m, int t)
{
if (m.size() == 0 || m[0].size() == 0)
return false;
int rows = m.size();
int cols = m[0].size();
int i = 0, j = cols - 1;
while (i < rows && j >= 0)
{
if (m[i][j] == t)
return true;
else if (t < m[i][j])
j--;
else
i++;
}
return false;
}
05 替换空格
题目: 剑指 Offer 05. 替换空格.
解法1
用 string
的 API. 时间复杂度 \(O(N)\) .
class Solution
{
public:
string replaceSpace(string s)
{
const string target = "%20";
const string src = " ";
size_t pos = s.find(src, 0);
while (pos != string::npos)
{
s.replace(pos, src.length(), target);
pos = pos + target.length();
pos = s.find(src, pos);
}
return s;
}
};
解法2
首先计算空格数目, 然后数组后增加空间, 总长度为 len + spaces * 2
.
开辟如上图所示的 2 个指针, 让 p1
从尾到头扫描.
如果 s[p1]
不是空格, 那么令 s[p2--] = s[p1--]
, 把字符复制到填充空格后的位置.
如果 s[p1]
是空格, 那么令:
s[p2--] = '0';
s[p2--] = '2';
s[p2--] = '%';
扫面的边界条件是: p1>0 && p1<p2
.
06 从尾到头打印链表
解法1
使用栈 stack
. 每次遍历一个节点就压栈, 最后输出栈.
解法2
但是要求返回值是一个 vector
, 所以我们还是直接用 vector
依次记录, 最后调用 reverse
函数.
class Solution
{
public:
vector<int> reversePrint(ListNode *head)
{
vector<int> s;
auto p = head;
while (p!= nullptr)
s.emplace_back(p->val), p = p->next;
reverse(s.begin(), s.end());
return s;
}
};
07 重建二叉树
题目: 剑指 Offer 07. 重建二叉树.
解法
这里是给定先序和中序, 但其原理与给定中序和后序是一样的. 请看: https://www.cnblogs.com/sinkinben/p/11455712.html.
class Solution
{
public:
TreeNode *buildTree(vector<int> &preorder, vector<int> &inorder)
{
int ridx = 0;
return innerBuild(preorder, inorder, ridx, 0, inorder.size() - 1);
}
inline int vfind(vector<int> &v, int val)
{
return find(v.begin(), v.end(), val) - v.begin();
}
TreeNode *innerBuild(vector<int> &preorder, vector<int> &inorder, int &ridx, int l, int r)
{
if (ridx >= (int)preorder.size() || l > r)
return nullptr;
if (l == r)
return new TreeNode(preorder[ridx++]);
int pos = vfind(inorder, preorder[ridx]);
auto p = new TreeNode(preorder[ridx++]);
p->left = innerBuild(preorder, inorder, ridx, l, pos - 1);
p->right = innerBuild(preorder, inorder, ridx, pos + 1, r);
return p;
}
};
08 二叉树的下一个节点
Leetcode 题库缺失本题.
题目要求: 给定二叉树和当中的某个节点, 要求返回该节点在中序遍历中的下一个节点 (指针形式) .
题目 API : TreeNode *getNext(TreeNode *root, TreeNode *node)
.
TreeNode
结构包括: val, left, right, parent
.
在中序遍历中, 是「左 根 右」的顺序遍历.
如果 node
存在右子树, 那么其下一个节点就是右子树的最左节点.
如果没有右子树:
- 如果
node
是左子树, 那么下一个节点是node->parent
. - 如果
node
是右子树, 那么下一个节点是node
的祖先, 并且这个祖先的左子树也是node
的祖先. 即下面这种情况:
root
/
x1
/ \
x2 x3
\
x4
\
x5
/
x6
inorder = [x2, x4, x6, x5, x1, x3, root]
getNext(x5) = x1
代码实现:
TreeNode *min(TreeNode *p)
{
if (p == nullptr) return p;
while (p->left != nullptr)
p = p->left;
return p;
}
TreeNode *getNext(TreeNode *root, TreeNode *node)
{
if (node == nullptr) return nullptr;
if (node->right != nullptr) return min(node->right);
auto x = node, y = node->parent;
while (y != nullptr && y->right == x)
x = y, y = y->parent;
return y;
}
09 用两个栈实现队列
解法1:暴力
把 s1
的序列倒进 s2
,那么 s2
就是对列对应的顺序了。最后把 s2
倒回 s1
保持状态一致性。
class CQueue
{
public:
stack<int> s1, s2;
CQueue()
{
}
void appendTail(int value)
{
s1.push(value);
}
int deleteHead()
{
while (!s1.empty())
s2.push(s1.top()), s1.pop();
if (s2.empty())
return -1;
int t = s2.top();
s2.pop();
while (!s2.empty())
s1.push(s2.top()), s2.pop();
return t;
}
};
解法2:官方解法
把栈变为队列顺序的方法:倒进另外一个栈。
所以我们使用 s1
来保存新加入的元素,s2
来保存准备出队的元素。在删除时,只要对 s2
进行判断:
- 如果为空,那么就
s1
的所有元素倒过来,丢一个出去。 - 如果不为空,那么直接丢一个出去。
一图胜千言。
代码实现:
class CQueue
{
public:
stack<int> s1, s2;
CQueue()
{
}
void appendTail(int value)
{
s1.push(value);
}
int deleteHead()
{
int t = -1;
if (s2.empty())
{
while (!s1.empty())
s2.push(s1.top()), s1.pop();
if (!s2.empty())
t = s2.top(), s2.pop();
}
else
{
t = s2.top(), s2.pop();
}
return t;
}
};
附加题:两个队列实现一个栈
题目:225. 用队列实现栈。
当栈不为空时,两个队列有且只有一个是有元素的,空的队列作为一个 buffer
。当需要获取队列头部元素,就把不空队列的前 n-1 个元素转移到 buffer
,剩下的一个就是栈顶元素。
看图。
代码实现:
class MyStack {
public:
queue<int> q1, q2;
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
if (q1.empty()) q2.push(x);
else if (q2.empty()) q1.push(x);
else assert(0);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
if (!q1.empty() && q2.empty())
{
while (q1.size() > 1) q2.push(q1.front()), q1.pop();
int t = q1.front();
q1.pop();
return t;
}
if (q1.empty() && !q2.empty())
{
while (q2.size() > 1) q1.push(q2.front()), q2.pop();
int t = q2.front();
q2.pop();
return t;
}
return -1;
}
/** Get the top element. */
int top() {
if (!q1.empty() && q2.empty())
{
while (q1.size() > 1) q2.push(q1.front()), q1.pop();
int t = q1.front();
q2.push(t), q1.pop();
return t;
}
if (q1.empty() && !q2.empty())
{
while (q2.size() > 1) q1.push(q2.front()), q2.pop();
int t = q2.front();
q1.push(t), q2.pop();
return t;
}
return -1;
}
/** Returns whether the stack is empty. */
bool empty() {
return q1.empty() && q2.empty();
}
};
10-I 斐波那契数列
class Solution {
public:
int fib(int n) {
const int mod = 1000000007;
if (n==0 || n==1) return n;
int f0=0, f1=1, f2=1;
for (int i=2; i<=n; i++)
f2=(f0+f1)%mod, f0=f1, f1=f2;
return f2;
}
};
10-II 青蛙跳台阶
class Solution {
public:
int numWays(int n) {
const int mod = 1000000007;
if (n==0 || n==1) return 1;
int f0=1, f1=1, f2=2;
for (int i=2; i<=n; i++)
f2=(f0+f1)%mod, f0=f1, f1=f2;
return f2;
}
};
本题扩展:
- 如果 每次可以跳 1,2,3,...,n 个台阶,那么跳上 \(n\) 个台阶的方法数目 \(f(n)=2^{n-1}\) .
可通过数学归纳法证明之。
当 \(n=1\) 或 \(n=2\),\(f(n)=n\) .
当 \(n=k\),设 \(f(k) = 2^{k-1}\) .
当 \(n=k+1\),则有 \(f(k+1) = 1+\sum_{i=1}^{k}f(k) = 2^{k}\) .
为什么呢?有 \(k+1\) 个台阶时, 王子可以:
- 直接走 k+1 步
- 先走 1 步,再走 k 步
- 先走 2 步,再走 k-1 步
- ...
- 先走 k 步,再走 1 步
证毕。
- 用 2x1 的矩形填充 2xN 的矩形,有多少种填充方法?
斐波那契数列 \(f(n)\) 。
如果竖着填充:
|x| | | | ... | |
|x| | | | ... | |
如果横着填充:
|x|x| | | ... | |
|x|x| | | ... | |
所以 \(f(n) = f(n-1)+f(n-2)\) .
11 旋转数组的最小数字
暴力解法 \(O(N)\)。
使用二分查找,时间复杂度 \(O(logn)\) 。请看官方题解。
class Solution
{
public:
int minArray(vector<int> &v)
{
int len = v.size();
if (len == 0) return -1;
if (len == 1) return v.front();
int l = 0, r = len - 1, m = l;
while (l <= r)
{
if (l == r)
break;
m = l + (r - l) / 2;
if (v[m] < v[r])
r = m;
else if (v[m] > v[r])
l = m + 1;
else
r--;
}
return v[l];
}
};
12 矩阵中的路径
显然是 DFS 搜索路径啦。使用 '.'
符号来标记该位置已被搜索。
class Solution
{
public:
int rows = 0, cols = 0;
bool exist(vector<vector<char>> &board, string word)
{
if (board.size() == 0 || board[0].size() == 0)
return word.length() == 0;
rows = board.size(), cols = board[0].size();
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
if (word[0] == board[i][j] && dfs(board, word, 1, i, j))
return true;
}
}
return false;
}
bool dfs(vector<vector<char>> &b, const string &word, int idx, int x, int y)
{
if (idx == (int)word.length())
return true;
char tmp = b[x][y], target = word[idx++];
b[x][y] = '.';
if (x + 1 < rows && b[x + 1][y] == target && dfs(b, word, idx, x + 1, y))
return true;
if (x - 1 >= 0 && b[x - 1][y] == target && dfs(b, word, idx, x - 1, y))
return true;
if (y + 1 < cols && b[x][y + 1] == target && dfs(b, word, idx, x, y + 1))
return true;
if (y - 1 >= 0 && b[x][y - 1] == target && dfs(b, word, idx, x, y - 1))
return true;
b[x][y] = tmp;
return false;
}
};
13 机器人的运动范围
依旧是 DFS 。使用二维数组记录该位置是否被访问。
// #include "leetcode.h"
class Solution
{
public:
int rows = 0, cols = 0, limit = 0;
int ans = 0;
int movingCount(int m, int n, int k)
{
rows = m, cols = n, limit = k;
vector<vector<bool>> visited(rows, vector<bool>(cols, false));
dfs(visited, 0, 0);
return ans;
}
int calculate(int x, int y)
{
int t = 0;
while (x)
t += (x % 10), x /= 10;
while (y)
t += (y % 10), y /= 10;
return t;
}
void dfs(vector<vector<bool>> &visited, int x, int y)
{
if (visited[x][y])
return;
visited[x][y] = true, ans++;
if (x + 1 < rows && calculate(x + 1, y) <= limit)
dfs(visited, x + 1, y);
if (x - 1 >= 0 && calculate(x - 1, y) <= limit)
dfs(visited, x - 1, y);
if (y + 1 < cols && calculate(x, y + 1) <= limit)
dfs(visited, x, y + 1);
if (y - 1 >= 0 && calculate(x, y - 1) <= limit)
dfs(visited, x, y - 1);
}
};
14-I 剪绳子
数学解法
基本不等式:和为定值,积有最大值。当且仅当所有数相等时,取得最大值。
假设分成 \(k\) 份:
并且有:
设将绳子按照长度 \(x\) 等分为 \(a\) 段时,所得的积最大,为 \(x^a = x^{\frac{n}{x}}\) .
令 \(y=f(x)=x^{\frac{n}{x}}\), 其中 \(n\) 为常数,那么有:
令 \(y'=0\) ,得:\(x = e\) . 分析单调性得,\(x = e\) 时,\(y=f(x)\) 取得最大值。
又因为长度 \(x\) 必须为整数,所以取等分长度为 2 或者 3 。
显然,通过某些简单的 n 可以验证等分长度取 3 所得的积才是最大的。例如:n=6, n=10.
还有一个问题时,如果剩余长度不足 3 怎么办?按等分长度 3 分割,剩余长度可以为:0, 1, 2 。
显然,当剩余 1 长度时,我们比较 \(3^a \times 1\) 和 \(3^{a-1} \times 4\) 即可(后者大)。
原本以为是动态规划,其实是道数学题,人晕了 ️ 。
#include <cmath>
class Solution {
public:
int cuttingRope(int n)
{
if (n <= 3) return n-1;
int a = n/3, b=n%3;
if (b==0) return pow(3,a);
else if (b==1) return pow(3,a-1)*4;
else return pow(3,a)*2;
}
};
动态规划解法
状态定义:dp[i]
为分割长度为 i
的绳子所得的最大积。
边界:dp[0] = dp[1] = 0
. 因为不能分割。
转移方程:
解析:当从长度为 \(i\) 的绳子剪出一段 \(j\) ,那么剩下的部分可剪可不剪。如果剪,那就是 \(j \times dp[i-j]\), 如果不剪,那就是 \(j \times (i-j)\) .
int dpMethod(int n)
{
vector<int> dp(n+1,0);
for (int i=2;i<=n;i++)
{
for (int j=1;j<i;j++)
dp[i] = max(dp[i], max(j*(i-j), j*dp[i-j]));
}
return dp[n];
}
14-II 剪绳子 II
只是 n 的范围变大了,需要取模。
class Solution {
public:
int cuttingRope(int n) {
if (n<=3) return n-1;
const int mod = (int)(1e9+7);
int a = n/3;
int b = n%3;
int64_t k = 1;
while (--a) k = (k*3)%mod;
if (b==0) return (k*3)%mod;
else if (b==1) return (k*4) % mod;
else return (k*6)%mod;
}
};
15 二进制中 1 的个数
位操作的经典套路。
class Solution {
public:
int hammingWeight(uint32_t n) {
int t = 0;
while (n)
t++, n&=(n-1);
return t;
}
};
[leetcode] 剑指 Offer 专题(一)的更多相关文章
- LeetCode剑指Offer刷题总结(一)
LeetCode过程中值得反思的细节 以下题号均指LeetCode剑指offer题库中的题号 本文章将每周定期更新,当内容达到10题左右时将会开下一节. 二维数组越界问题04 public stati ...
- Leetcode - 剑指offer 面试题29:数组中出现次数超过一半的数字及其变形(腾讯2015秋招 编程题4)
剑指offer 面试题29:数组中出现次数超过一半的数字 提交网址: http://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163 ...
- LeetCode—剑指 Offer学习计划
第 1 天 栈与队列(简单) 剑指 Offer 09. 用两个栈实现队列 class CQueue { public: CQueue() { } stack<int>s1,s2; void ...
- LeetCode 剑指 Offer 22. 链表中倒数第k个节点
剑指 Offer 22. 链表中倒数第k个节点 题意 输入一个链表,输出该链表中倒数第k个节点.为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点. 例如,一个链表有 6 个 ...
- [LeetCode]剑指 Offer 17. 打印从1到最大的n位数
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数.比如输入 3,则打印出 1.2.3 一直到最大的 3 位数 999. 示例 1: 输入: n = 1 输出: [1,2,3,4,5,6,7, ...
- [LeetCode]剑指 Offer 52. 两个链表的第一个公共节点
题解 nodeA走一个链表A(A独有+公共),再走B独有的长度, nodeB走一个链表B(B独有+公共),再走A独有的长度. 结果:两者相遇点即为交点:若没有交点,两者都走到null,会返回null. ...
- ⛅剑指 Offer 11. 旋转数组的最小数字
20207.22 LeetCode 剑指 Offer 11. 旋转数组的最小数字 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个递增排序的数组的一个旋转,输出旋转数组的最小 ...
- LeetCode:“剑指 Offer”
LeetCode:"剑指 Offer" 刷题小菜鸡,花了几天时间做了一遍 LeetCode 上给出的 "剑指 Offer" 在此做一下记录 LeetCode主页 ...
- C++版 - 剑指offer之面试题37:两个链表的第一个公共结点[LeetCode 160] 解题报告
剑指offer之面试题37 两个链表的第一个公共结点 提交网址: http://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?t ...
随机推荐
- 转载:Redis主从复制与高可用方案
转载自: https://www.cnblogs.com/lizhaojun-ops/p/9447016.html 原文链接:http://gudaoyufu.com/?p=1230 redis主从复 ...
- Java-Collection和Map
创建博客的目的主要帮助自己记忆和复习日常学到和用到的知识:或有纰漏请大家斧正,非常感谢! 之前面试,被问过一个问题:List和Set的区别. 主要区别很明显了,两者都是数组形式存在的,继承了Colle ...
- tcp、http 学习小结
tcp.http 学习小结 前言 最近因为cdn的一个问题,困扰了自己好久.因为需要统计网站访问的成功数,而且要求比较精确.目前的实现不能满足要求,因为没有区别访问成功与否,也没有对超时做处理.期间解 ...
- docker部署Broketrmq集群
部署Broketrmq集群 通过docker-compose形式部署 首先创建 broker 配置文件,配置文件如下: brokerClusterName = DefaultCluster #集群名 ...
- LevelDb 101学习
转自http://www.cnblogs.com/haippy/archive/2011/12/04/2276064.html LevelDb日知录之一:LevelDb 101 说起LevelDb也许 ...
- 9.Lock-锁
- 4.Kafka使用
- python血脉贲张的cosplay小姐姐图片
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. 基本环境配置 python 3.6 pycharm requests 相关模块pip安装即可 ...
- shiro安全框架和spring整合
上干货......... 整合spring的配置文件 <?xml version="1.0" encoding="UTF-8"?><beans ...
- leetcode1558题解【贪心】
leetcode1558.得到目标数组的最少函数调用次数 题目链接 算法 贪心 时间复杂度O(nlogN),N为数组中最大的那个数. 1.题意就是给定一个函数,该函数有两种功能,一种就是将数组中的所有 ...