【练习】2021下半年数据结构刷题笔记和总结 (三)栈 队列 链表 枚举算法
題目來自書或者網(wǎng)站。
- 解密QQ 號——隊列
- 回文字符串---棧
- 火柴棍等式
- 輸入數(shù)字n,要求輸出從1~n的全排列
- 【力扣】給你一個包含 n 個整數(shù)的數(shù)組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有和為 0 且不重復(fù)的三元組
- 設(shè)計一個循環(huán)隊列重新排列棧中的元素
- 遞歸反轉(zhuǎn)一個棧
解密QQ 號——隊列
小哈告訴了小哼一串加密過的數(shù)字,同時小哈也告訴了小哼解密規(guī)則。規(guī)則是這樣的:首先將第1 個數(shù)刪除,緊接著將第2 個數(shù)放到這串?dāng)?shù)的末尾,再將第3 個數(shù)刪除并將第4 個數(shù)放到這串?dāng)?shù)的末尾,再將第5 個數(shù)刪除……直到剩下最后一個數(shù),將最后一個數(shù)也刪除。按照剛才刪除的順序,把這些刪除的數(shù)連在一起就是小哈的QQ 啦。現(xiàn)在你來幫幫小哼吧。小哈給小哼加密過的一串?dāng)?shù)是“6 3 1 7 5 8 9 2 4”。
【此題來自啊哈算法,以下是作者的分析】:
首先需要一個數(shù)組來存儲這一串?dāng)?shù)即int q[101],并初始化這個數(shù)組即int q[101]={0,6,3,1,7,5,8,9,2,4};(此處初始化是我多寫了一個0,用來填充q[0],因為我比較喜歡從q[1]開始用,對數(shù)組初始化不是很理解的同學(xué)可以去看一下我的上本書《啊哈C!思考快你一步》)。接下來就是模擬解密的過程了。
解密的第一步是將第一個數(shù)刪除,你可以想一下如何在數(shù)組中刪除一個數(shù)呢。最簡單的方法是將所有后面的數(shù)都往前面挪動一位,但是這樣的做法很耗費時間。
在這里,我將引入兩個整型變量head 和tail。head 用來記錄隊列的隊首(即第一位),tail 用來記錄隊列的隊尾(即最后一位)的下一個位置,你可能會問:為什不直接記錄隊尾,卻要記錄隊尾的下一個位置呢?這是因為當(dāng)隊列中只剩下一個元素時,隊首和隊尾重合會帶來一些麻煩。我們這里規(guī)定隊首和隊尾重合時,隊列為空。
現(xiàn)在有9 個數(shù),9 個數(shù)全部放入隊列之后head=1;tail=10;此時head 和tail 之間的數(shù)就是目前隊列中“有效”的數(shù)。如果要刪除一個數(shù)的話,就將head++就OK 了,這樣仍然可以保持head 和tail 之間的數(shù)為目前隊列中“有效”的數(shù)。這樣做雖然浪費了一個空間,卻節(jié)省了大量的時間,這是非常劃算的。新增加一個數(shù)也很簡單,把需要增加的數(shù)放到隊尾即q[tail]之后再tail++就OK 啦
int main() {int q[102]={0,6,3,1,7,5,8,9,2,4},head,tail; int i; head = 1; tail = 10;//q[0]用0填充 while(head < tail) {cout<<q[head]; head++; q[tail++] = q[head++];}}回文字符串—棧
#include <string.h>int main() { char a[102],s[102]; gets(a); int n = strlen(a); int top = 0;//棧頂 int mid = n/2 - 1;//注意要-1 for(int i = 0; i <= mid;i++) s[++top] = a[i]; int nex; if(n % 2 == 0) nex = mid + 1; if(n % 2 == 1) nex = mid + 2; //尤其注意這個容易錯的地方:奇數(shù)和偶數(shù)的情況不同 如果i直接從mid+1開始遍歷會出錯 for(int i = nex;i < n;i++) {if(s[top] != a[i]) {cout<<"no";break;} else top--; }cout<<"yes";}自己想的不用棧更方便的方法:
int main() { char a[102],s[102]; gets(a); int n = strlen(a); int top = 0;//棧頂 int mid = n/2 - 1;//注意要-1 for(int i = 0,j = n-1;i != (n-1/2); i++,j--) if(a[i] != a[j]) {cout<<"no!"; break;} cout<<"o";火柴棍等式
6.23日
首先分析數(shù)字特征,因為最多24根,減去符號,20根用于拼數(shù)字,1只需要1根,所以最多拼10個1,1111+1=1112 這樣個數(shù)大于了20,所以ABC中最大也不能超過1111,采用枚舉法,只需要對AB進行枚舉即可,不需要再枚舉C,否則會超過時限
輸入數(shù)字n,要求輸出從1~n的全排列
如123的全排列:123 132 321 312 213 231
采用深度優(yōu)先搜索方法
要點:
【力扣】給你一個包含 n 個整數(shù)的數(shù)組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有和為 0 且不重復(fù)的三元組
自己的代碼:
vector<vector<int>> threeSum(vector<int>& nums) {if (nums.size() < 3) return {};vector<vector<int>> res;//關(guān)鍵點:時間復(fù)雜度的優(yōu)化和去重int total = nums.size();int j = total - 1,m;sort(nums.begin(), nums.end());for (int i = 0; i < total; ++i){if (i > 0 && nums[i] == nums[i - 1]) continue;//確保每次枚舉的數(shù)字不一樣int begin = i + 1;int third = total - 1;int c = -nums[i] ;for (int z = begin; z < total; z++){//枚舉每一個不一樣的zif (z > begin && nums[z] == nums[z - 1]) continue;//每一次與上次不一樣while (third > z && (nums[z] + nums[third] - c) > 0) --third;if (third == z) break;//如果重合了,退出循環(huán)if (nums[z] + nums[third] == c) res.push_back({ nums[i],nums[z],nums[third] });}}return res;}這里由于 while (third > z && (nums[z] + nums[third] - c) > 0) --third;
if (third == z) break;//如果重合了,退出循環(huán)
這兩句話的順序顛倒了,錯了好多次也沒看出來!!!!!!
注意點:1.不要重復(fù),如何確保三元組不重復(fù)
2.采取先排序的策略,雙指針,其中中間的指針從第一個指針的下一個開始枚舉。
官方的題解:
任意一個三元組的和都為 00。如果我們直接使用三重循環(huán)枚舉三元組,會得到 O(N^3)O(N
3
) 個滿足題目要求的三元組(其中 NN 是數(shù)組的長度)時間復(fù)雜度至少為 O(N^3)O(N
3
)。在這之后,我們還需要使用哈希表進行去重操作,得到不包含重復(fù)三元組的最終答案,又消耗了大量的空間。這個做法的時間復(fù)雜度和空間復(fù)雜度都很高,因此我們要換一種思路來考慮這個問題。
「不重復(fù)」的本質(zhì)是什么?我們保持三重循環(huán)的大框架不變,只需要保證:
第二重循環(huán)枚舉到的元素不小于當(dāng)前第一重循環(huán)枚舉到的元素;
第三重循環(huán)枚舉到的元素不小于當(dāng)前第二重循環(huán)枚舉到的元素。
也就是說,我們枚舉的三元組 (a, b, c)(a,b,c) 滿足 a \leq b \leq ca≤b≤c,保證了只有 (a, b, c)(a,b,c) 這個順序會被枚舉到,而 (b, a, c)(b,a,c)、(c, b, a)(c,b,a) 等等這些不會,這樣就減少了重復(fù)。要實現(xiàn)這一點,我們可以將數(shù)組中的元素從小到大進行排序,隨后使用普通的三重循環(huán)就可以滿足上面的要求。
同時,對于每一重循環(huán)而言,相鄰兩次枚舉的元素不能相同,否則也會造成重復(fù)。舉個例子,如果排完序的數(shù)組為
[0, 1, 2, 2, 2, 3]
^ ^ ^
我們使用三重循環(huán)枚舉到的第一個三元組為 (0, 1, 2)(0,1,2),如果第三重循環(huán)繼續(xù)枚舉下一個元素,那么仍然是三元組 (0, 1, 2)(0,1,2),產(chǎn)生了重復(fù)。因此我們需要將第三重循環(huán)「跳到」下一個不相同的元素,即數(shù)組中的最后一個元素 33,枚舉三元組 (0, 1, 3)(0,1,3)。
下面給出了改進的方法的偽代碼實現(xiàn):
nums.sort()
for first = 0 … n-1
// 只有和上一次枚舉的元素不相同,我們才會進行枚舉
if first == 0 or nums[first] != nums[first-1] then
for second = first+1 … n-1
if second == first+1 or nums[second] != nums[second-1] then
for third = second+1 … n-1
if third == second+1 or nums[third] != nums[third-1] then
// 判斷是否有 a+b+c==0
check(first, second, third)
這種方法的時間復(fù)雜度仍然為 O(N^3)O(N
3
),畢竟我們還是沒有跳出三重循環(huán)的大框架。然而它是很容易繼續(xù)優(yōu)化的,可以發(fā)現(xiàn),如果我們固定了前兩重循環(huán)枚舉到的元素 aa 和 bb,那么只有唯一的 cc 滿足 a+b+c=0a+b+c=0。當(dāng)?shù)诙匮h(huán)往后枚舉一個元素 b’b
′
時,由于 b’ > bb
′
b,那么滿足 a+b’+c’=0a+b
′
+c
′
=0 的 c’c
′
一定有 c’ < cc
′
<c,即 c’c
′
在數(shù)組中一定出現(xiàn)在 cc 的左側(cè)。也就是說,我們可以從小到大枚舉 bb,同時從大到小枚舉 cc,即第二重循環(huán)和第三重循環(huán)實際上是并列的關(guān)系。
有了這樣的發(fā)現(xiàn),我們就可以保持第二重循環(huán)不變,而將第三重循環(huán)變成一個從數(shù)組最右端開始向左移動的指針,從而得到下面的偽代碼:
nums.sort()
for first = 0 … n-1
if first == 0 or nums[first] != nums[first-1] then
// 第三重循環(huán)對應(yīng)的指針
third = n-1
for second = first+1 … n-1
if second == first+1 or nums[second] != nums[second-1] then
// 向左移動指針,直到 a+b+c 不大于 0
while nums[first]+nums[second]+nums[third] > 0
third = third-1
// 判斷是否有 a+b+c==0
check(first, second, third)
這個方法就是我們常說的「雙指針」,當(dāng)我們需要枚舉數(shù)組中的兩個元素時,如果我們發(fā)現(xiàn)隨著第一個元素的遞增,第二個元素是遞減的,那么就可以使用雙指針的方法,將枚舉的時間復(fù)雜度從 O(N^2)
) 減少至 O(N)。為什么是 O(N) 呢?這是因為在枚舉的過程每一步中,「左指針」會向右移動一個位置(也就是題目中的 bb),而「右指針」會向左移動若干個位置,這個與數(shù)組的元素有關(guān),但我們知道它一共會移動的位置數(shù)為 O(N),均攤下來,每次也向左移動一個位置,因此時間復(fù)雜度為 O(N)。
注意到我們的偽代碼中還有第一重循環(huán),時間復(fù)雜度為 O(N)O(N),因此枚舉的總時間復(fù)雜度為 O(N^2)
)。由于排序的時間復(fù)雜度為 O(N \log N),在漸進意義下小于前者,因此算法的總時間復(fù)雜度為 O(N^2) )。
設(shè)計一個循環(huán)隊列重新排列棧中的元素
一個棧有2n個元素,從棧頂?shù)綏5椎脑匾来问莂2n,a2n-1,…,a1,要求通過一個循環(huán)隊列重新排列棧中的元素,使得從棧頂?shù)綏5滓来问莂1,a3,…,a2n-1,a2,a4,…,a2n
思路:先將棧中全部元素進入一個隊列中,求出棧中元素總數(shù),然后設(shè)一個計數(shù)器i,當(dāng)i為奇數(shù)時進入棧,偶數(shù)時進入隊列
最后一起將隊列的所有元素進棧
遞歸反轉(zhuǎn)一個棧
要求不能申請一個同樣的棧,除了系統(tǒng)棧之外,其他空間復(fù)雜度為o(1)
思路:設(shè)計將元素X從棧底插入的算法,遞歸出棧元素tmp直到棧空,此時進棧x,再依次將原來退棧的元素進棧,如下:
void PushBottom(stack<int>&st,int x) { int tmp; if(st.empty()) st.push(x); else{ tmp = st.top(); st.pop(); PushBottom(st,x); st.push(tmp); }} //現(xiàn)在反轉(zhuǎn)整個棧 void reverseStack(stack<int>&st) { if(st.empty()) return; else { int tmp = st.top(); st.pop(); reverseStack(st); PushBottom(st,tmp); aa } }總結(jié)
以上是生活随笔為你收集整理的【练习】2021下半年数据结构刷题笔记和总结 (三)栈 队列 链表 枚举算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【spring学习笔记】(二)Sprin
- 下一篇: 【学习笔记】springboot的过滤器