《剑指offer》第1~11题:刷题week1[C++题解]
文章目錄
- 1. 找出數(shù)組中重復(fù)的數(shù)字
- 思路一:排序
- 思路二:hash表
- 思路三:原地交換
- 2. 不修改數(shù)組找出重復(fù)的數(shù)字
- 思路:抽屜原理+二分
- 3. 二維數(shù)組中的查找
- 思路:思維題(選取右上角的值)
- 4. 替換空格
- 思路:語法題
- 5. 從尾到頭打印鏈表
- 6. 重建二叉樹
- 思路
- 7. 二叉樹的下一個結(jié)點(diǎn)
- 思路
- 8. 用兩個棧實(shí)現(xiàn)隊(duì)列
- 思路
- 9. 斐波那契數(shù)列
- 思路
- 10. 旋轉(zhuǎn)數(shù)組的最小數(shù)字
- 思路一:暴力
- 思路二: 二分
- 11. 矩陣中的路徑
- 思路
- 總結(jié)
1. 找出數(shù)組中重復(fù)的數(shù)字
題目鏈接:https://www.acwing.com/problem/content/14/
思路一:排序
ac代碼:時間復(fù)雜度O(n×logn)O(n\times logn)O(n×logn)
思路:這里是把數(shù)組排序,然后相鄰兩個元素比較,如果相等,則直接返回該數(shù)。
思路二:hash表
使用C++中的容器unordered_map統(tǒng)計(jì)元素個數(shù),如果出現(xiàn)個數(shù)大于1元素直接輸出即可。
ac代碼:時間復(fù)雜度O(n×logn)O(n\times logn)O(n×logn),空間復(fù)雜度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;} };思路三:原地交換
時間復(fù)雜度O(n)O(n)O(n),空間復(fù)雜度O(1)O(1)O(1)。
思路:將x=nums[i]x=nums[i]x=nums[i] 和下標(biāo)xxx處的值nums[x]nums[x]nums[x]相比較,不同的話,將nums[i] 和nums[x]交換,這樣就可以把數(shù)值x放到下標(biāo)x的地方。當(dāng)下一次出現(xiàn)x,并且發(fā)現(xiàn) x==nums[x]x == nums[x]x==nums[x]時,這時候x就是出現(xiàn)2次的數(shù)值。
通俗的解釋就是,數(shù)值2它的合法位置是下標(biāo)為2的地方,如果它在別處,就需要將其交換到合法位置(索引為2處),這個過程到什么時候結(jié)束呢?直到別的索引位置也出現(xiàn)2,即出現(xiàn)重復(fù);或者是交換n次,但沒有重復(fù),算法停止。
比如,如果數(shù)值2在索引000處,并且nums[2]==2nums[2] == 2nums[2]==2,此時,即2出現(xiàn)重復(fù),返回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;} };稍微簡介一點(diǎn)的寫法
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. 不修改數(shù)組找出重復(fù)的數(shù)字
題目鏈接:https://www.acwing.com/problem/content/15/
思路:抽屜原理+二分
本題要求不能修改原數(shù)組
解題思路
這里采用二分來做。由于每個數(shù)都是在1~n之間,而共有n+1個數(shù),故至少有一個數(shù)重復(fù)。這是根據(jù)抽屜原理得到的。
抽屜原理:
n+1個物品放到n個抽屜里面,那么至少有一個抽屜會放2個物品。
我們對最大值為n-1的這串?dāng)?shù)據(jù),將其二分為兩段,分別為 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]。請注意,這里是對數(shù)值二分,不是對數(shù)組下標(biāo)二分。稍后我們統(tǒng)計(jì)的是位于這個區(qū)間中的數(shù)值個數(shù)。
比如最大值n-1 = 7,那么區(qū)間[1,7][1,7][1,7]分的區(qū)間就是a=[1,4]a=[1,4]a=[1,4]和 b=[5,7]b=[5,7]b=[5,7]。然后遍歷這個數(shù)組,統(tǒng)計(jì)在兩個區(qū)間中的數(shù)的個數(shù),其中,必然有1個區(qū)間中數(shù)的個數(shù)大于區(qū)間長度,假設(shè)這個區(qū)間為 a=[1,4]a=[1, 4]a=[1,4],其 區(qū)間長度為 4 -1 +1 = 4,并假設(shè)在原數(shù)組中統(tǒng)計(jì)出來的位于a中的元素個數(shù)為5 ,根據(jù)抽屜原理,這個區(qū)間中必然存在重復(fù)的數(shù)值。那么我們遍可以將區(qū)間[1,7][1,7][1,7]縮小為[1,4][1,4][1,4],然后再重復(fù)上述步驟,直到某個區(qū)間長度變成1,此時這個值就是答案。
-
時間復(fù)雜度O(nlogn)O(nlogn)O(nlogn): 這是因?yàn)槊看味謺箶?shù)組長度減半,需要lognlognlogn次,每次需要遍歷整個數(shù)組,因此時間復(fù)雜度為nlognnlognnlogn;
-
空間復(fù)雜度O(1)O(1)O(1),這是因?yàn)闆]有用到其他數(shù)組或者哈希表等結(jié)構(gòu)。
參考題解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; // 左半邊數(shù)的個數(shù)for(auto x :nums) s += x >= l && x <= mid;if(s > mid - l + 1) r = mid;else l = mid + 1;}return r;} };3. 二維數(shù)組中的查找
題目鏈接:https://www.acwing.com/problem/content/16/
思路:思維題(選取右上角的值)
每次選取方格中右上角的數(shù)值a,這樣的話,該值所在的行和所在的列構(gòu)成單調(diào)序列。如果target < a,則可以舍棄該列;如果target > a,則可以舍棄該行。
AC代碼
時間復(fù)雜度O(m+n)O(m + n)O(m+n),其中m和n是二維數(shù)組的維度,空間復(fù)雜度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; // 右上角的坐標(biāo)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代碼
時間復(fù)雜度O(n)O(n)O(n),空間復(fù)雜度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());
時間復(fù)雜度O(n)O(n)O(n),空間復(fù)雜度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; // 記錄前驅(qū)結(jié)點(diǎn)auto cur = head;while (cur) {auto t = cur->next;cur->next = pre;pre = cur;cur = t;} //逆置后的鏈表頭節(jié)點(diǎn)是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/
思路
根據(jù)二叉樹的前序遍歷和中序遍歷重建二叉樹,主要考察遞歸建樹的過程。
這里使用dfs(pl, pr, il, ir)
pl表示前序遍歷的左端點(diǎn),pr表示前序遍歷的右端點(diǎn);
il表示中序遍歷的左端點(diǎn),ir表示中序遍歷的右端點(diǎn)。
返回值是樹根。
最后需要root->left = left, root->right = right添加到總的根節(jié)點(diǎn)上,這樣就重建出來二叉樹。
時間復(fù)雜度O(n)O(n)O(n),空間復(fù)雜度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;// 哈希表,存儲中序遍歷數(shù)據(jù)的位置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. 二叉樹的下一個結(jié)點(diǎn)
俗稱“樹的后繼”
題目鏈接:https://www.acwing.com/problem/content/31/
思路
時間復(fù)雜度O(H)O(H)O(H),H為樹的高度空間復(fù)雜度O(1)O(1)O(1)
對于結(jié)點(diǎn)source,分為兩種情況討論。
第一種,source結(jié)點(diǎn)有右子樹,則中序遍歷的后繼結(jié)點(diǎn)就是左子樹中的最左邊的點(diǎn)。
第二種,source結(jié)點(diǎn)沒有右子樹。此時如何處理呢? 需要沿著父節(jié)點(diǎn)不斷向上遍歷,直到找到第一個該點(diǎn)是其父節(jié)點(diǎn)的左兒子,該點(diǎn)記為temp,該點(diǎn)的父節(jié)點(diǎn)就是source的后繼。
舉例子,這里以E結(jié)點(diǎn)作為source,它沒有右子樹,顯然是第二種情況,我們向上遍歷,source = E ->father, 也就是source變成B,此時,我們發(fā)現(xiàn)B是A的左兒子,到達(dá)邊界條件,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結(jié)點(diǎn)有右子樹p = p->right;while(p->left) p = p->left;return p;}// p結(jié)點(diǎn)沒有右子樹// 向上遍歷,找到第一個當(dāng)前點(diǎn)是父節(jié)點(diǎn)的左兒子while(p->father && p == p->father->right) p = p->father;return p->father;} };8. 用兩個棧實(shí)現(xiàn)隊(duì)列
思路
使用兩個棧來模擬隊(duì)列的行為: 隊(duì)列先進(jìn)先出。
具體操作:
對于隊(duì)列的push操作,直接stack1壓棧即可。
對于隊(duì)列的pop操作,將stack1元素都彈出到stack2中,然后stack2中top即可。使用完畢后,再將stack2中的元素都壓入到stack1中。
空間復(fù)雜度O(n)O(n)O(n),時間復(fù)雜度:
- 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. 斐波那契數(shù)列
思路
開大數(shù)組,保存每個索引的值,遍歷遞推即可。
時間復(fù)雜度O(n)O(n)O(n),空間復(fù)雜度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. 旋轉(zhuǎn)數(shù)組的最小數(shù)字
思路一:暴力
暴力做法:
時間復(fù)雜度O(n)O(n)O(n),空間復(fù)雜度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;} };思路二: 二分
整個數(shù)組旋轉(zhuǎn)之后,趨勢如下圖所示。
在前半部分,滿足nums[i]≥nums[0]nums[i] \ge nums[0]nums[i]≥nums[0]這個性質(zhì),而后半部分(除去最后一段可能存在等于nums[0]nums[0]nums[0]的情況)不滿足該性質(zhì)。分界點(diǎn),就是數(shù)組中最小的值。
可以用二分法來求分界點(diǎn)。具體操作步驟如下:
- 進(jìn)行預(yù)處理,刪掉最后一段等于nums[0]nums[0]nums[0]的值。
- 刪掉之后,如果最后一個數(shù)大于nums[0]nums[0]nums[0],則剩余的為單調(diào)遞增數(shù)列,最小值就是nums[0]nums[0]nums[0]。
- 刪掉之后,如果最后的數(shù)小于nums[0]nums[0]nums[0],則進(jìn)行二分找分界點(diǎn)。
二分的時間復(fù)雜度O(logn)O(logn)O(logn),刪除最后一段最壞的情況是O(n)O(n)O(n),所以總的時間復(fù)雜度是O(n)O(n)O(n),空間復(fù)雜度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/
思路
深度優(yōu)先搜索dfs
枚舉字符串起點(diǎn),共n2n^2n2種,然后每個起點(diǎn)dfs是否可以形成字符路徑。注意,dfs搜索的時候不能走回頭路。
時間復(fù)雜度O(n2k3)O(n^2 k^3)O(n2k3),k為str字符個數(shù)-1,空間復(fù)雜度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; // 恢復(fù)現(xiàn)場return false;} };總結(jié)
當(dāng)刷了一定量的題目和見了一定量的模型之后,刷題的感覺就培養(yǎng)起來了。這一步幾乎是人人都要走的。
總結(jié)
以上是生活随笔為你收集整理的《剑指offer》第1~11题:刷题week1[C++题解]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python批量给文件命名为001,00
- 下一篇: win10操作系统vscode如何配置c