微網(wǎng)站左側(cè)隱藏導(dǎo)航菜單鄭州網(wǎng)絡(luò)營銷策劃
在算法中,模擬是一種通過計算機(jī)程序來模擬現(xiàn)實世界中的過程或系統(tǒng)行為的方法。它的核心思想是根據(jù)題目給定的規(guī)則和邏輯,按照步驟細(xì)致地重現(xiàn)事件的發(fā)展流程,從而獲得最終結(jié)果。
解題時如何使用模擬算法:
- 理解題目規(guī)則:仔細(xì)分析題目給出的規(guī)則和邏輯,確保理解全面。
- 設(shè)計模擬流程:在動手寫代碼之前,先在草紙上畫出模擬的流程圖,明確每一步的操作。
- 模塊化實現(xiàn):將模擬過程分解為多個模塊,分別實現(xiàn),這樣可以減少錯誤并便于調(diào)試。
- 處理特殊情況:注意邊界條件和特殊情況的處理,這是模擬算法中容易出錯的地方。
- 分塊調(diào)試:在實現(xiàn)過程中,可以先單獨測試每個模塊,確保其正確性。
以下是一個簡單的模擬算法題目及其解題思路: 題目:一只長度不計的蠕蟲位于n英寸深的井的底部。它每次向上爬u英寸,但休息時會滑落d英寸。求蠕蟲爬出井口需要的最少爬行次數(shù)。
解題思路:
- 使用循環(huán)模擬蠕蟲的爬行過程。
- 每次循環(huán)中,蠕蟲向上爬u英寸,然后滑落d英寸。
- 當(dāng)蠕蟲的總爬行高度超過井的深度時,結(jié)束循環(huán)。
代碼實現(xiàn):
#include <cstdio>
int main() {int n, u, d; // 井的深度、每次爬升高度、每次滑落高度scanf("%d %d %d", &n, &u, &d);int height = 0, steps = 0;while (height < n) {height += u;steps++;if (height >= n) break; // 如果已經(jīng)爬出井口,結(jié)束循環(huán)height -= d;}printf("%d\n", steps);return 0;
}
通過上述步驟和示例,可以看到模擬算法的核心在于“照葫蘆畫瓢”,按照題目要求逐步實現(xiàn)。
1.替換所有的問號
題目描述:
給你一個僅包含小寫英文字母和
'?'
字符的字符串s
,請你將所有的'?'
轉(zhuǎn)換為若干小寫字母,使最終的字符串不包含任何 連續(xù)重復(fù) 的字符。注意:你 不能 修改非
'?'
字符。題目測試用例保證 除
'?'
字符 之外,不存在連續(xù)重復(fù)的字符。在完成所有轉(zhuǎn)換(可能無需轉(zhuǎn)換)后返回最終的字符串。如果有多個解決方案,請返回其中任何一個。可以證明,在給定的約束條件下,答案總是存在的。
示例 1:
輸入:s = "?zs" 輸出:"azs" 解釋:該示例共有 25 種解決方案,從 "azs" 到 "yzs" 都是符合題目要求的。只有 "z" 是無效的修改,因為字符串 "zzs" 中有連續(xù)重復(fù)的兩個 'z' 。
示例 2:
輸入:s = "ubv?w" 輸出:"ubvaw" 解釋:該示例共有 24 種解決方案,只有替換成 "v" 和 "w" 不符合題目要求。因為 "ubvvw" 和 "ubvww" 都包含連續(xù)重復(fù)的字符。
提示:
1 <= s.length <= 100
s
僅包含小寫英文字母和'?'
字符
算法思路:
- 從前往后遍歷整個字符串,找問號。
- 找到問號之后,就用 a ~ z 的每?個字符去嘗試替換。
- 最終的字符串不包含任何連續(xù)重復(fù)的字符,說明替換的字符與它自身前或后的位置的字符都不重復(fù)。
代碼實現(xiàn):
class Solution
{
public:string modifyString(string s) {int n=s.size();for(int i=0;i<n;i++){if(s[i]=='?')//替換{for(char ch='a';ch<='z';ch++){if((i==0||ch!=s[i-1])&&(i==n-1||ch!=s[i+1])){s[i]=ch;break;}}}}return s;}
};
2.提莫攻擊
題目描述:
在《英雄聯(lián)盟》的世界中,有一個叫 “提莫” 的英雄。他的攻擊可以讓敵方英雄艾希(編者注:寒冰射手)進(jìn)入中毒狀態(tài)。
當(dāng)提莫攻擊艾希,艾希的中毒狀態(tài)正好持續(xù)
duration
秒。正式地講,提莫在
t
發(fā)起攻擊意味著艾希在時間區(qū)間[t, t + duration - 1]
(含t
和t + duration - 1
)處于中毒狀態(tài)。如果提莫在中毒影響結(jié)束 前 再次攻擊,中毒狀態(tài)計時器將會 重置 ,在新的攻擊之后,中毒影響將會在duration
秒后結(jié)束。給你一個 非遞減 的整數(shù)數(shù)組
timeSeries
,其中timeSeries[i]
表示提莫在timeSeries[i]
秒時對艾希發(fā)起攻擊,以及一個表示中毒持續(xù)時間的整數(shù)duration
。返回艾希處于中毒狀態(tài)的 總 秒數(shù)。
示例 1:
輸入:timeSeries = [1,4], duration = 2 輸出:4 解釋:提莫攻擊對艾希的影響如下: - 第 1 秒,提莫攻擊艾希并使其立即中毒。中毒狀態(tài)會維持 2 秒,即第 1 秒和第 2 秒。 - 第 4 秒,提莫再次攻擊艾希,艾希中毒狀態(tài)又持續(xù) 2 秒,即第 4 秒和第 5 秒。 艾希在第 1、2、4、5 秒處于中毒狀態(tài),所以總中毒秒數(shù)是 4 。
示例 2:
輸入:timeSeries = [1,2], duration = 2 輸出:3 解釋:提莫攻擊對艾希的影響如下: - 第 1 秒,提莫攻擊艾希并使其立即中毒。中毒狀態(tài)會維持 2 秒,即第 1 秒和第 2 秒。 - 第 2 秒,提莫再次攻擊艾希,并重置中毒計時器,艾希中毒狀態(tài)需要持續(xù) 2 秒,即第 2 秒和第 3 秒。 艾希在第 1、2、3 秒處于中毒狀態(tài),所以總中毒秒數(shù)是 3 。
提示:
1 <= timeSeries.length <= 104
0 <= timeSeries[i], duration <= 107
timeSeries
按 非遞減 順序排列
算法思路:
計算相鄰兩個時間點的差值:
i. 如果差值大于等于中毒時間,說明上次中毒可以持續(xù) duration 秒;
ii. 如果差值小于中毒時間,那么上次的中毒只能持續(xù)兩者的差值。
最后的攻擊也會持續(xù)duration 秒。
代碼實現(xiàn):
class Solution
{
public:int findPoisonedDuration(vector<int>& timeSeries, int duration) {int ret=0;for(int i=1;i<timeSeries.size();i++){int x=timeSeries[i]-timeSeries[i-1];if(x>=duration)ret+=duration;elseret+=x;}return ret+duration;}
};
3.Z字形變換
題目描述:
將一個給定字符串
s
根據(jù)給定的行數(shù)numRows
,以從上往下、從左到右進(jìn)行 Z 字形排列。比如輸入字符串為
"PAYPALISHIRING"
行數(shù)為3
時,排列如下:P A H N A P L S I I G Y I R
之后,你的輸出需要從左往右逐行讀取,產(chǎn)生出一個新的字符串,比如:
"PAHNAPLSIIGYIR"
。請你實現(xiàn)這個將字符串進(jìn)行指定行數(shù)變換的函數(shù):
string convert(string s, int numRows);
示例 1:
輸入:s = "PAYPALISHIRING", numRows = 3 輸出:"PAHNAPLSIIGYIR"
示例 2:
輸入:s = "PAYPALISHIRING", numRows = 4 輸出:"PINALSIGYAHRPI" 解釋: P I N A L S I G Y A H R P I
示例 3:
輸入:s = "A", numRows = 1 輸出:"A"
提示:
1 <= s.length <= 1000
s
由英文字母(小寫和大寫)、','
和'.'
組成1 <= numRows <= 1000
算法思路:
找規(guī)律,? row 代替行數(shù),row = 4 時畫出的 N 字形如下:
0 2row-2 4row-41 2row-3 2row-1 4row-5 4row-32 2row-4 2row 4row-6 4row-23 2row+1 4row-1
不難發(fā)現(xiàn),數(shù)據(jù)是以 2row - 2 為一個周期進(jìn)行規(guī)律變換的。將所有數(shù)替換成用周期來表示的變量:
第一行的數(shù)是:0, 2row - 2, 4row - 4;
第二行的數(shù)是:1, (2row - 2) - 1, (2row - 2) + 1, (4row - 4) - 1, (4row - 4) + 1;
第三行的數(shù)是:2, (2row - 2) - 2, (2row - 2) + 2, (4row - 4) - 2, (4row - 4) + 2;
第四行的數(shù)是:3, (2row - 2) + 3, (4row - 4) + 3。
可以觀察到,第一行、第四行為差為 2row - 2 的等差數(shù)列;第二行、第三行除了第一個數(shù)取值為行數(shù),每組下標(biāo)為(2n - 1, 2n)的數(shù)圍繞(2row - 2)的倍數(shù)左右取值。
以此規(guī)律,我們可以寫出迭代算法。
代碼實現(xiàn):
class Solution
{
public:string convert(string s, int numRows) {//處理邊界情況if(numRows==1) return s;string ret;int d=2*numRows-2,n=s.size();//1.先處理第一行for(int i=0;i<n;i+=d)ret+=s[i];//2.處理中間行for(int k=1;k<numRows-1;k++)//枚舉每一行{for(int i=k,j=d-k;i<n||j<n;i+=d,j+=d){if(i<n) ret+=s[i];if(j<n) ret+=s[j];}}//3.處理最后一行for(int i=numRows-1;i<n;i+=d)ret+=s[i];return ret;}
};
4.外觀數(shù)列
題目描述:
「外觀數(shù)列」是一個數(shù)位字符串序列,由遞歸公式定義:
countAndSay(1) = "1"
countAndSay(n)
是countAndSay(n-1)
的行程長度編碼。行程長度編碼(RLE)是一種字符串壓縮方法,其工作原理是通過將連續(xù)相同字符(重復(fù)兩次或更多次)替換為字符重復(fù)次數(shù)(運行長度)和字符的串聯(lián)。例如,要壓縮字符串
"3322251"
,我們將"33"
用"23"
替換,將"222"
用"32"
替換,將"5"
用"15"
替換并將"1"
用"11"
替換。因此壓縮后字符串變?yōu)?"23321511"
。給定一個整數(shù)
n
,返回 外觀數(shù)列 的第n
個元素。示例 1:
**輸入:**n = 4
輸出:“1211”
解釋:
countAndSay(1) = “1”
countAndSay(2) = “1” 的行程長度編碼 = “11”
countAndSay(3) = “11” 的行程長度編碼 = “21”
countAndSay(4) = “21” 的行程長度編碼 = “1211”
示例 2:
**輸入:**n = 1
輸出:“1”
解釋:
這是基本情況。
提示:
1 <= n <= 30
算法思路:
所謂外觀數(shù)列,其實只是依次統(tǒng)計字符串中連續(xù)且相同的字符的個數(shù)。依照題意,依次模擬即可。
代碼實現(xiàn):
class Solution
{
public:string countAndSay(int n) {string ret="1";for(int i=1;i<n;i++)//解釋n-1次ret即可。{string tmp;int len=ret.size();for(int left=0,right=0;right<len;){while(right<len&&ret[left]==ret[right])// 找到連續(xù)相同的字符區(qū)間right++;tmp+=to_string(right-left)+ret[left];// 將字符的數(shù)量和字符本身拼接到 tmp 中left=right;// 更新 left 指針到下一個新的字符位置}ret=tmp;// 將生成的新序列賦值給 ret,用于下一次迭代}return ret; // 返回最終生成的序列}
};
5.數(shù)青蛙
題目描述:
給你一個字符串
croakOfFrogs
,它表示不同青蛙發(fā)出的蛙鳴聲(字符串"croak"
)的組合。由于同一時間可以有多只青蛙呱呱作響,所以croakOfFrogs
中會混合多個“croak”
。請你返回模擬字符串中所有蛙鳴所需不同青蛙的最少數(shù)目。
要想發(fā)出蛙鳴 “croak”,青蛙必須 依序 輸出
‘c’, ’r’, ’o’, ’a’, ’k’
這 5 個字母。如果沒有輸出全部五個字母,那么它就不會發(fā)出聲音。如果字符串croakOfFrogs
不是由若干有效的 “croak” 字符混合而成,請返回-1
。示例 1:
輸入:croakOfFrogs = "croakcroak" 輸出:1 解釋:一只青蛙 “呱呱” 兩次
示例 2:
輸入:croakOfFrogs = "crcoakroak" 輸出:2 解釋:最少需要兩只青蛙,“呱呱” 聲用黑體標(biāo)注 第一只青蛙 "crcoakroak" 第二只青蛙 "crcoakroak"
示例 3:
輸入:croakOfFrogs = "croakcrook" 輸出:-1 解釋:給出的字符串不是 "croak" 的有效組合。
提示:
1 <= croakOfFrogs.length <= 105
- 字符串中的字符只有
'c'
,'r'
,'o'
,'a'
或者'k'
算法思路:
模擬青蛙的叫聲。
當(dāng)遇到 ‘r’ ‘o’ ‘a(chǎn)’ ‘k’ 這四個字符的時候,我們要去看看每?個字符對應(yīng)的前驅(qū)字符,有沒有青蛙叫出來。如果有青蛙叫出來,那就讓這個青蛙接下來喊出來這個字符;如果沒有,直接返回 -1 ;
當(dāng)遇到 ‘c’ 這個字符的時候,我們?nèi)タ纯?‘k’ 這個字符有沒有青蛙叫出來。如果有,就讓這個青蛙繼續(xù)去喊 ‘c’ 這個字符;如果沒有的話,就重新搞一個青蛙。
代碼實現(xiàn):
class Solution
{
public:int minNumberOfFrogs(string croakOfFrogs) {string t="croak";int n=t.size();vector<int> hash(n);//用數(shù)組來模擬哈希表unordered_map<char,int> index;//[x,x這個字符對應(yīng)的下標(biāo)]for(int i=0;i<n;i++)index[t[i]]=i;for(auto ch:croakOfFrogs){if(ch=='c'){if(hash[n-1]!=0) hash[n-1]--;hash[index[ch]]++;}else{int i=index[ch];if(hash[i-1]==0) return -1;hash[i-1]--;hash[i]++;}}for(int i=0;i<n-1;i++)if(hash[i]!=0)return -1;return hash[n-1];}
};
最后,筆者要說的是模擬算法雖然在某些情況下可能顯得繁瑣,但它具有極高的通用性和直觀性,能夠解決許多難以直接用數(shù)學(xué)公式或復(fù)雜數(shù)據(jù)結(jié)構(gòu)求解的問題。通過上述問題的分析和實現(xiàn),我們可以看到模擬算法的核心在于理解題目規(guī)則、設(shè)計清晰的模擬流程、處理特殊情況。在實際應(yīng)用中,模擬算法不僅可以幫助我們快速實現(xiàn)解決方案,還能在復(fù)雜問題中找到規(guī)律,為進(jìn)一步優(yōu)化提供思路。
總之,模擬算法是算法設(shè)計中的重要工具,掌握它能夠幫助我們更好地應(yīng)對各種復(fù)雜問題。