《剑指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)
思路:這里是把數組排序,然后相鄰兩個元素比較,如果相等,則直接返回該數。
思路二: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++题解]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python批量给文件命名为001,00
- 下一篇: win10操作系统vscode如何配置c