《剑指offer》第1~11题:刷题week1[C++题解]
文章目录
- 1. 找出数组中重复的数字
- 思路一:排序
- 思路二:hash表
- 思路三:原地交换
- 2. 不修改数组找出重复的数字
- 思路:抽屉原理+二分
- 3. 二维数组中的查找
- 思路:思维题(选取右上角的值)
- 4. 替换空格
- 思路:语法题
- 5. 从尾到头打印链表
- 6. 重建二叉树
- 思路
- 7. 二叉树的下一个结点
- 思路
- 8. 用两个栈实现队列
- 思路
- 9. 斐波那契数列
- 思路
- 10. 旋转数组的最小数字
- 思路一:暴力
- 思路二: 二分
- 11. 矩阵中的路径
- 思路
- 总结
1. 找出数组中重复的数字
题目链接:https://www.acwing.com/problem/content/14/
思路一:排序
ac代码:时间复杂度O(n×logn)O(n\times logn)O(n×logn)
思路:这里是把数组排序,然后相邻两个元素比较,如果相等,则直接返回该数。
class Solution {public:int duplicateInArray(vector<int>& nums) {if(nums.empty()) return -1;int n = nums.size();sort(nums.begin(), nums.end());if(nums[0] < 0 || nums[n - 1] > n - 1) return -1;for(int i = 0; i < n - 1; i ++){if(nums[i + 1] == nums[i]) return nums[i];}return -1;}
};
思路二:hash表
使用C++中的容器unordered_map
统计元素个数,如果出现个数大于1元素直接输出即可。
ac代码:时间复杂度O(n×logn)O(n\times logn)O(n×logn),空间复杂度O(1)O(1)O(1),原因是使用了长度为n的哈希表。
class Solution {public:int duplicateInArray(vector<int>& nums) {int n = nums.size();unordered_map<int, int> mp;for(int i = 0; i < n; i ++)if(nums[i] < 0 || nums[i] > n -1) return -1;for(int i = 0; i < n; i ++){mp[nums[i]] ++;if(mp[nums[i]] > 1) return nums[i];}return -1;}
};
思路三:原地交换
时间复杂度O(n)O(n)O(n),空间复杂度O(1)O(1)O(1)。
思路:将x=nums[i]x=nums[i]x=nums[i] 和下标xxx处的值nums[x]nums[x]nums[x]相比较,不同的话,将nums[i] 和nums[x]交换,这样就可以把数值x放到下标x的地方。当下一次出现x,并且发现 x==nums[x]x == nums[x]x==nums[x]时,这时候x就是出现2次的数值。
通俗的解释就是,数值2它的合法位置是下标为2的地方,如果它在别处,就需要将其交换到合法位置(索引为2处),这个过程到什么时候结束呢?直到别的索引位置也出现2,即出现重复;或者是交换n次,但没有重复,算法停止。
比如,如果数值2在索引000处,并且nums[2]==2nums[2] == 2nums[2]==2,此时,即2出现重复,返回2即可。
这里解释的比较好的是,leetcode题解中的“一个萝卜一个坑”的类比。
class Solution {public:int duplicateInArray(vector<int>& nums) {if(nums.empty()) return -1;int n = nums.size();for(int i = 0; i < n; i ++)if(nums[i] < 0 || nums[i] > n - 1) return -1;for(int i = 0; i < n; i ++){while(i != nums[i] && nums[i] != nums[nums[i]]) swap(nums[i], nums[nums[i]]);if(i != nums[i] && nums[i] == nums[nums[i]]) return nums[i];}return -1;}
};
稍微简介一点的写法
class Solution {public:int duplicateInArray(vector<int>& nums) {int n = nums.size();for(auto &x : nums) if(x < 0 || x > n - 1) return -1;for(int i = 0; i < n; i ++){int x = nums[i];while( x != i && x != nums[x]) swap(x, nums[x]);if(x != i && x == nums[x]) return x;}return -1;}
};
2. 不修改数组找出重复的数字
题目链接:https://www.acwing.com/problem/content/15/
思路:抽屉原理+二分
本题要求不能修改原数组
解题思路
这里采用二分来做。由于每个数都是在1~n之间,而共有n+1个数,故至少有一个数重复。这是根据抽屉原理得到的。
抽屉原理:
n+1个物品放到n个抽屉里面,那么至少有一个抽屉会放2个物品。
我们对最大值为n-1的这串数据,将其二分为两段,分别为 a=[1,n2]a=[1, \frac{n}{2}]a=[1,2n] 和 b=[n2+1,n]b=[\frac{n}{2}+1,n]b=[2n+1,n]。请注意,这里是对数值二分,不是对数组下标二分。稍后我们统计的是位于这个区间中的数值个数。
比如最大值n-1 = 7,那么区间[1,7][1,7][1,7]分的区间就是a=[1,4]a=[1,4]a=[1,4]和 b=[5,7]b=[5,7]b=[5,7]。然后遍历这个数组,统计在两个区间中的数的个数,其中,必然有1个区间中数的个数大于区间长度,假设这个区间为 a=[1,4]a=[1, 4]a=[1,4],其 区间长度为 4 -1 +1 = 4,并假设在原数组中统计出来的位于a中的元素个数为5 ,根据抽屉原理,这个区间中必然存在重复的数值。那么我们遍可以将区间[1,7][1,7][1,7]缩小为[1,4][1,4][1,4],然后再重复上述步骤,直到某个区间长度变成1,此时这个值就是答案。
时间复杂度O(nlogn)O(nlogn)O(nlogn): 这是因为每次二分会使数组长度减半,需要lognlognlogn次,每次需要遍历整个数组,因此时间复杂度为nlognnlognnlogn;
空间复杂度O(1)O(1)O(1),这是因为没有用到其他数组或者哈希表等结构。
参考题解acwing
ac代码
class Solution {public:int duplicateInArray(vector<int>& nums) {int l = 1, r = nums.size() - 1;while(l < r){int mid = l + r >> 1; // [l, mid], [mid + 1, r]int s = 0; // 左半边数的个数for(auto x :nums) s += x >= l && x <= mid;if(s > mid - l + 1) r = mid;else l = mid + 1;}return r;}
};
3. 二维数组中的查找
题目链接:https://www.acwing.com/problem/content/16/
思路:思维题(选取右上角的值)
每次选取方格中右上角的数值a,这样的话,该值所在的行和所在的列构成单调序列。如果target < a,则可以舍弃该列;如果target > a,则可以舍弃该行。
AC代码
时间复杂度O(m+n)O(m + n)O(m+n),其中m和n是二维数组的维度,空间复杂度O(1)O(1)O(1)
class Solution {public:bool searchArray(vector<vector<int>> array, int target) {if(array.empty()) return false;int n = array.size(), m = array[0].size();int i = 0, j = m - 1; // 右上角的坐标while(i < n && j >= 0){if(array[i][j] == target) return true;else if(target < array[i][j]) j--; // 删掉最后面一列else i ++; //删掉最上面一行}return false;}
};
4. 替换空格
题目链接:https://www.acwing.com/problem/content/17/
思路:语法题
开一个新的字符串res,遍历str,遇到空格将其替换成"%20"添加到res的末尾,如果不是空格,将其添加到res末尾。
AC代码
时间复杂度O(n)O(n)O(n),空间复杂度O(n)O(n)O(n)
class Solution {public:string replaceSpaces(string &str) {string res;for(auto &x : str){if(x == ' ') res += "%20";else res += x;}return res;}
};
5. 从尾到头打印链表
思路一:直接vector逆置
遍历链表,将其元素依次放入vector(名为res)中,然后vector逆序输出即可。这里的逆序使用的是逆序迭代器return vector<int>(res.rbegin(), res.rend());
时间复杂度O(n)O(n)O(n),空间复杂度O(n)O(n)O(n)
AC代码
/*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {public:vector<int> printListReversingly(ListNode* head) {vector<int> res;while(head){res.push_back(head->val);head = head->next;}return vector<int>(res.rbegin(),res.rend());}
};
思路二:链表逆置
/*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x), next(NULL) {}* };*/
class Solution {public:vector<int> reversePrint(ListNode* head) {// 链表逆置ListNode* pre = nullptr; // 记录前驱结点auto cur = head;while (cur) {auto t = cur->next;cur->next = pre;pre = cur;cur = t;} //逆置后的链表头节点是prevector<int> v;for (auto p = pre; p; p = p->next)v.push_back(p->val);return v;}
};
6. 重建二叉树
题目链接:https://www.acwing.com/problem/content/23/
思路
根据二叉树的前序遍历和中序遍历重建二叉树,主要考察递归建树的过程。
这里使用dfs(pl, pr, il, ir)
pl表示前序遍历的左端点,pr表示前序遍历的右端点;
il表示中序遍历的左端点,ir表示中序遍历的右端点。
返回值是树根。
最后需要root->left = left, root->right = right
添加到总的根节点上,这样就重建出来二叉树。
时间复杂度O(n)O(n)O(n),空间复杂度O(n)O(n)O(n)
AC代码
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {public:unordered_map<int, int> hash;// 哈希表,存储中序遍历数据的位置vector<int> preorder, inorder;TreeNode* buildTree(vector<int>& _preorder, vector<int>& _inorder) {preorder = _preorder, inorder = _inorder;for(int i =0; i < inorder.size(); i ++) hash[inorder[i]] = i;return dfs(0, preorder.size() - 1, 0, inorder.size() - 1);}TreeNode* dfs(int pl, int pr, int il, int ir){if(pl > pr) return nullptr;auto root = new TreeNode(preorder[pl]); int k = hash[root->val]; // 中序遍历中的位置auto left = dfs(pl + 1, k - il + pl, il, k - 1);auto right = dfs(k - il + pl + 1, pr, k + 1, ir);root->left = left, root->right = right;return root;}
};
7. 二叉树的下一个结点
俗称“树的后继”
题目链接:https://www.acwing.com/problem/content/31/
思路
时间复杂度O(H)O(H)O(H),H为树的高度空间复杂度O(1)O(1)O(1)
对于结点source,分为两种情况讨论。
第一种,source结点有右子树,则中序遍历的后继结点就是左子树中的最左边的点。
第二种,source结点没有右子树。此时如何处理呢? 需要沿着父节点不断向上遍历,直到找到第一个该点是其父节点的左儿子,该点记为temp,该点的父节点就是source的后继。
举例子,这里以E结点作为source,它没有右子树,显然是第二种情况,我们向上遍历,source = E ->father, 也就是source变成B,此时,我们发现B是A的左儿子,到达边界条件,A就是E的后继。
ac代码
/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode *father;* TreeNode(int x) : val(x), left(NULL), right(NULL), father(NULL) {}* };*/
class Solution {public:TreeNode* inorderSuccessor(TreeNode* p) {if(p->right){ //p结点有右子树p = p->right;while(p->left) p = p->left;return p;}// p结点没有右子树// 向上遍历,找到第一个当前点是父节点的左儿子while(p->father && p == p->father->right) p = p->father;return p->father;}
};
8. 用两个栈实现队列
思路
使用两个栈来模拟队列的行为: 队列先进先出。
具体操作:
对于队列的push操作,直接stack1压栈即可。
对于队列的pop操作,将stack1元素都弹出到stack2中,然后stack2中top即可。使用完毕后,再将stack2中的元素都压入到stack1中。
空间复杂度O(n)O(n)O(n),时间复杂度:
- push是O(1)O(1)O(1)
- pop是O(n)O(n)O(n)
- peek是O(n)O(n)O(n)
- empty是O(1)O(1)O(1)
ac代码
class MyQueue {public:stack<int> stk, cache;/** Initialize your data structure here. */MyQueue() {}/** Push element x to the back of queue. */void push(int x) {stk.push(x);}void copy(stack<int> &a, stack<int> &b){while(a.size()){b.push(a.top());a.pop();}}/** Removes the element from in front of queue and returns that element. */int pop() {copy(stk, cache);int res = cache.top();cache.pop();copy(cache, stk);return res;}/** Get the front element. */int peek() {copy(stk, cache);int res = cache.top();copy(cache, stk);return res;}/** Returns whether the queue is empty. */bool empty() {return stk.empty();}
};/*** Your MyQueue object will be instantiated and called as such:* MyQueue obj = MyQueue();* obj.push(x);* int param_2 = obj.pop();* int param_3 = obj.peek();* bool param_4 = obj.empty();*/
9. 斐波那契数列
思路
开大数组,保存每个索引的值,遍历递推即可。
时间复杂度O(n)O(n)O(n),空间复杂度O(n)O(n)O(n)
AC代码
class Solution {public:int a[39];int Fibonacci(int n) {a[1] = 1, a[2] = 1;for(int i = 3; i <= 39; i ++) a[i] = a[i - 1] + a[i - 2];return a[n];}
};
10. 旋转数组的最小数字
思路一:暴力
暴力做法:
时间复杂度O(n)O(n)O(n),空间复杂度O(1)O(1)O(1)
ac代码
class Solution {public:int findMin(vector<int>& nums) {if(nums.empty()) return -1;int res = nums[0];for(auto &x : nums) if(x <= res) res = x;return res;}
};
思路二: 二分
整个数组旋转之后,趋势如下图所示。
在前半部分,满足nums[i]≥nums[0]nums[i] \ge nums[0]nums[i]≥nums[0]这个性质,而后半部分(除去最后一段可能存在等于nums[0]nums[0]nums[0]的情况)不满足该性质。分界点,就是数组中最小的值。
可以用二分法来求分界点。具体操作步骤如下:
- 进行预处理,删掉最后一段等于nums[0]nums[0]nums[0]的值。
- 删掉之后,如果最后一个数大于nums[0]nums[0]nums[0],则剩余的为单调递增数列,最小值就是nums[0]nums[0]nums[0]。
- 删掉之后,如果最后的数小于nums[0]nums[0]nums[0],则进行二分找分界点。
二分的时间复杂度O(logn)O(logn)O(logn),删除最后一段最坏的情况是O(n)O(n)O(n),所以总的时间复杂度是O(n)O(n)O(n),空间复杂度O(1)O(1)O(1)。
ac代码
class Solution {public:int findMin(vector<int>& nums) {if(nums.empty()) return -1;int n = nums.size() - 1;int a = nums[0];while(n > 0 && nums[n] == a) n --;if(nums[n] >= a) return a;int l = 0, r = n;while(l < r){int mid = l + r >> 1; // [l, mid] 和 [mid + 1, r]if(nums[mid] < a) r = mid;else l = mid + 1;}return nums[r];}
};
11. 矩阵中的路径
题目链接:https://www.acwing.com/problem/content/21/
思路
深度优先搜索dfs
枚举字符串起点,共n2n^2n2种,然后每个起点dfs是否可以形成字符路径。注意,dfs搜索的时候不能走回头路。
时间复杂度O(n2k3)O(n^2 k^3)O(n2k3),k为str字符个数-1,空间复杂度O(1)O(1)O(1)
class Solution {public:bool hasPath(vector<vector<char>>& matrix, string &str) {if(matrix.empty()) return false;int n = matrix.size(), m = matrix[0].size();for(int i = 0; i < n; i ++)for(int j = 0; j < m; j ++)if(dfs(matrix, str, 0, i, j)) return true;return false;}bool dfs(vector<vector<char>>& matrix, string &str, int u, int x, int y){if(u == str.size()) return true;if(matrix[x][y] != str[u]) return false;//特判 matrix = a,和str = a的情况if(matrix[x][y] == str[u] && u == str.size() - 1) return true;char t = matrix[x][y];matrix[x][y] = '%';int dx[4] ={1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};for(int i = 0; i < 4; i ++){int a = x + dx[i], b = y + dy[i];if(a >= 0 && a < matrix.size() && b >= 0 && b <matrix[0].size()){if(dfs(matrix, str, u + 1, a, b)) return true;}}matrix[x][y] = t; // 恢复现场return false;}
};
总结
当刷了一定量的题目和见了一定量的模型之后,刷题的感觉就培养起来了。这一步几乎是人人都要走的。
《剑指offer》第1~11题:刷题week1[C++题解]相关推荐
- 【LeetCode】《剑指Offer》第Ⅴ篇⊰⊰⊰ 39 - 47题
[LeetCode]<剑指Offer>第Ⅴ篇⊰⊰⊰ 39 - 47题 文章目录 [LeetCode]<剑指Offer>第Ⅴ篇⊰⊰⊰ 39 - 47题 39. 数组中出现次数超过 ...
- 剑指offer——面试题11:数值的整数次方
剑指offer--面试题11:数值的整数次方 Solution1:基本算法 累乘,时间复杂度为O(n) 要考虑全部情况:指数 < 0, == 0 和 > 0. 注意在 if-else if ...
- python数据结构与算法刷题——剑指offer第二版加部分leetcode题
说不清楚,只能看代码理解的用红色标出 查找算法:查找较排序来说较简单,不外乎顺序查找和二分查找.哈希表查找和二叉排序树查找.(很多面试官喜欢让应聘者写出二分查找(如test53)的代码)[注意:二分查 ...
- 关于《剑指offer》的66道编程题的总结(五)
注:2019年9月8日21:57:16 这部分主要是解决与二叉树相关的问题 文章目录 (第四十一题)重建二叉树 (第四十二题)从上往下打印二叉树 (第四十三题)二叉树的深度 (第四十四题)二叉树的镜像 ...
- 剑指offer解题思路锦集11-20题
又来更新剑指offer上的题目思路啦. 11.[二进制中1的个数] 题目:输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. eg:NumberOf1(1)=1 NumberOf1(2) ...
- python36块砖36人搬算法_剑指offer python实现 66道算法题
所有题目均采用牛客网在线编程实现,代码均在github上. 数组 题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组 ...
- [剑指offer][JAVA]面试题第[39]题[数组中出现次数超过一半的数字][HashMap][摩尔投票法]
[问题描述][简单] 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字.你可以假设数组是非空的,并且给定的数组总是存在多数元素.示例 1:输入: [1, 2, 3, 2, 2, 2, 5, ...
- 《剑指offer》第四十九题(丑数)
// 面试题49:丑数 // 题目:我们把只包含因子2.3和5的数称作丑数(Ugly Number).求按从小到 // 大的顺序的第1500个丑数.例如6.8都是丑数,但14不是,因为它包含因子7. ...
- [剑指offer][JAVA]面试题第[34]题[二叉树中和为某一值的路径][回溯]
[问题描述][中等] 输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径.从树的根节点开始往下一直到叶节点所经过的节点形成一条路径.示例: 给定如下二叉树,以及目标和 sum = ...
- [剑指offer][JAVA]面试题第[32-3]题[从上到下打印二叉树 ][BFS]
[问题描述][中等] 请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推. 例如: 给定二叉树: [3 ...
最新文章
- html5 本地批量存储,HTML5本地存储
- 程序开发中那些莫名奇妙的bug
- Customization larbin
- es创建索引库,无法使用InetSocketTransportAddress
- 【简洁+注释】剑指 Offer 32 - II. 从上到下打印二叉树 II
- python 短网址_Python实现短网址ShortUrl的Hash运算实例讲解
- (17)css3新增背景属性
- Python之Pandas库常用函数大全(含注释)
- 结构体中操作c语言,C语言中结构体的操作
- Padavan启用ipv6并允许公网访问内网
- 音视频开发(四十四):M3U8边缓存边播放
- RAID磁盘阵列与配置
- 腾讯入股艺龙,在线旅游市场引发关注
- 最小生成树 Kruskal
- 【优雅解决】:换源后 sudo apt-get update 出现 N: Updating from such a repository can't be done securely……
- 消费者人群画像 python_2019数字中国创新大赛 消费者人群画像 信用智能评分
- c语言循环菜鸟,C语言菜鸟基础教程之for循环
- 纽约峭石之巅观景台:从直入云天的城市之巅眺望纽约全景
- 计算机网络学术期刊,中国计算机学会推荐的计算机网络方向国际学术会议与国际学术期刊...
- MIGO BAPI_GOODSMVT_CREATE创建及增强
热门文章
- 正则化方法:防止过拟合,提高泛化能力
- (笔试题)程序运行时间
- Linux硬件信息查看
- C#面向对象名词解释(四)
- redis缓存java对象_Redis缓存系统-Java-Jedis操作Redis,基本操作以及 实现对象保存...
- sql server 多条记录数据合并为一条_如何利用Python实现SQL自动化?
- 【小项目关键技术】硬件通信三种方式、串口、IIC、SPI
- 【数理知识】《矩阵论》方保镕老师-目录及关于符号的含义
- 2.7 迁移学习-深度学习第三课《结构化机器学习项目》-Stanford吴恩达教授
- 5.1 代价函数-机器学习笔记-斯坦福吴恩达教授