【燒腦算法】定長滑動窗口:算法題中的“窗口”智慧

目錄

引言

定長滑動窗口習題剖析

3439. 重新安排會議得到最多空余時間 I

2134. 最少交換次數來組合所有的 1 II

1297. 子串的最大出現次數

2653. 滑動子數組的美麗值

567. 字符串的排列

438. 找到字符串中所有字母異位詞

30. 串聯所有單詞的子串

220. 存在重復元素 III

總結?


引言

本篇博客是繼上一篇的續寫,上一篇博客【入門算法】定長滑動窗口:算法題中的“窗口”智慧-CSDN博客介紹了定長滑動窗口的使用場景及方法,以及一些常見的面試題型,本篇博客將對定長滑動窗口題型進行進一步深入,本章的題目有難度需要有一定的滑動窗口思想。

PS:本篇博客中的所有題目均來自于靈茶山艾府 - 力扣(LeetCode)分享的題單。

定長滑動窗口習題剖析

3439. 重新安排會議得到最多空余時間 I

題解:重新安排k個會議,得到最大空余時間,不能調整會議的先后順序。通過分析得到最大空余時間的方法是將k+1個會議移動到一起就能讓空余最大,這也就能夠轉化為在一個只有k個會議的區間內最大空余時間問題。

class Solution {
public:int maxFreeTime(int eventTime, int k, vector<int>& startTime, vector<int>& endTime) {//本題通過k次移動可以實現將k+1個會議進行整合,//這也就實現了讓k個會議左右兩邊的空余時間進行整合//所以本題就轉化為了求k個會議兩邊空余時間的最大值//使用滑動窗口實現int n=startTime.size();int left=0,right=startTime[0];    //此處right要跳著走,不能使用right++,否則會超時int ret=0,tmp=k,intime=0,i=0;   //intime用來統計區間內會議的時長,i表示是第幾個會議while(i<n){//入窗口while(i<n&&tmp>=0){if(startTime[i]==right&&tmp==0) break;  if(startTime[i]==right) {intime+=endTime[i]-startTime[i];  //統計區間內會議的時長tmp--,i++;   //對left和right區間內的會議個數進行統計if(i==n) return max(ret,eventTime-left-intime);right=startTime[i];}}//更新答案ret=max(ret,right-left-intime);//出窗口left = endTime[i - k];  //讓left移動到區間內第一個會議結尾intime -= (endTime[i - k] - startTime[i - k]);   //此處減去區間內第一個會議時長tmp++ ; }return ret;}
};

2134. 最少交換次數來組合所有的 1 II

題解:將數組中的所有1聚集起來,通過交換讓一個區間內全部存儲1,求最小的交換次數。找一個區間能夠包含所有的1,且這一個區間內0的個數最少,這樣交換的此處也就最少了。這一區間長度就是1的個數。通過滑動窗口進行處理,注意此題是環形數組要特別處理。

class Solution {
public:int minSwaps(vector<int>& nums) {//此題要求將數組中1都聚集起來,也就是說有一部分區域內都是1//這也就使得只需要保持該區域中0的個數最小即可,這一個區域大小不難看出就是1的總個數int n=nums.size(),one=0;for(auto e:nums)if(e==1) one++;    //統計數組中1的個數int left=0,right=0;int ret=INT_MAX,tmp=0;while(right<n+one)  //right<n+one來處理泛型數組的要求{//入窗口while(right-left<one)if(nums[(right++)%n]==0) tmp++;//更新答案ret=min(ret,tmp);//出窗口if(nums[(left++)%n]==0) tmp--;}return ret;}
};

1297. 子串的最大出現次數

題解:字符串的范圍是minSize---maxSize之間,出現最多的一定是子字符串,即最小長度的字符串,所以此處的最大值可以忽略。還是通過滑動窗口來解決。

class Solution {
public:int maxFreq(string s, int maxLetters, int minSize, int maxSize) {//通過map來統計子字符串出現的次數unordered_map<string, int> m;int ch[26] = { 0 };   //統計字符的個數int right = 0, left = 0, n = s.size();int differ = 0;   //用來統計不同字符的個數int ret=0;       //返回值while (right < n){//入窗口if (ch[s[right] - 'a'] == 0) differ++;ch[s[right++] - 'a']++;if(right - left == minSize)  //長度滿足條件進一步判斷是否滿足條件{    //調整答案if(differ<=maxLetters) {string tmp=s.substr(left,right-left);m[tmp]++;ret=max(ret,m[tmp]);}//出窗口ch[s[left] - 'a']--;if (ch[s[left] - 'a'] == 0) differ--;left++;}}return ret;}
};

2653. 滑動子數組的美麗值

題解:此題分輕松的看出是一個滑動窗口問題,所以此題的關鍵在于怎么求第x小的數。采用優先級隊列??刪除的時候不好操作;每次k個排一次序??時間復雜度太高了。通過分析數據看到nums[i]大小在-50到50以內,所以可以采用計數排序的方法來解決。

class Solution {
public:#define N 50//求第x小的數int GetMinK(int* count,int x){for(int i=0;i<2*N+1;i++){if(count[i]!=0) x-=count[i];if(x<=0) return i;}return 0;}vector<int> getSubarrayBeauty(vector<int>& nums, int k, int x) {//此題的nums[i]的數據范圍較小,所以可以通過計數排序的方法進行求第小的數int count[2*N+1]={0};//進行計數排序int left=0,right=0,n=nums.size();vector<int> ret;while(right<n){//入窗口while(right-left<k)count[nums[right++]+N]++;//調整答案int a=GetMinK(count,x)-50;if(a<0) ret.push_back(a);else ret.push_back(0);//出窗口count[nums[left++]+N]--;}return ret;}
};

567. 字符串的排列

題解:通過先遍歷s1來確定s1中字符的種類及個數,若s2中有s1的子串,其區間長度必定是s1的長度,所以其窗口的長度確定,可直接使用滑動窗口。

class Solution {
public:bool checkInclusion(string s1, string s2) {int k=s1.size(),n=s2.size();if(k>n) return false;  //s1的長度小于s2,必定不成立 //通過一個長度為k的滑動窗口來實現,需要對s1的字符種類及個數進行計數來決定其是否是子串int ch[26]={0};   //通過ch來記錄s1字符的種類及個數int num=0;      //用num來記錄s1有多少個不同的字符,方便判斷s2的區間中是否是字串for(auto e:s1) {if(ch[e-'a']==0) num++;ch[e-'a']++;}int left=0,right=0;while(right<n){//入窗口while(right<n&&right-left<k){ch[s2[right]-'a']--;if(ch[s2[right]-'a']==0) num--;if(num==0) return true;    //此時區間內所有的字符都能在s1中找到,返回trueright++;}//出窗口if(ch[s2[left]-'a']==0) num++;ch[s2[left]-'a']++;left++;}return false;}
};

438. 找到字符串中所有字母異位詞

題解:與上題相同,只需要將每次找到后將下標放入到數組中即可。

class Solution {
public:vector<int> findAnagrams(string s2, string s1) {int k=s1.size(),n=s2.size();if(k>n) return {};  //s1的長度小于s2,必定沒有滿足條件的//通過一個長度為k的滑動窗口來實現,需要對s1的字符種類及個數進行計數來決定其是否是子串int ch[26]={0};   //通過ch來記錄s1字符的種類及個數int num=0;      //用num來記錄s1有多少個不同的字符,方便判斷s2的區間中是否是字串for(auto e:s1) {if(ch[e-'a']==0) num++;ch[e-'a']++;}int left=0,right=0;vector<int> ret;while(right<n){//入窗口while(right<n&&right-left<k){ch[s2[right]-'a']--;if(ch[s2[right]-'a']==0) num--;if(num==0) ret.push_back(left);    //此時區間內所有的字符都能在s1中找到,將第一個位置的下標插入right++;}//出窗口if(ch[s2[left]-'a']==0) num++;ch[s2[left]-'a']++;left++;}return ret;}
};

30. 串聯所有單詞的子串

題解:與上一題類似,只不過將字符換位了判斷字符串。此題依舊可以采用滑動窗口只不過從滑動字符變成了滑動字符串,用一個窗口維護每次需要統計的單詞總數,每一次進---出都對單詞進行操作即可。但是需要注意:第一個單詞的起始位置不止有一個。

class Solution {
public://words中字符串給長度相同是重要信息//先將words放入到set中去,方便判斷子字符串是否滿足條件vector<int> findSubstring(string s, vector<string>& words) {int num=words.size(),len=words[0].size();  //用num來表示有多少個子字符串,len表示每一個字符串長度int n=s.size();if(n<num*len) return {}; //s長度比words總長小直接返回vector<int> ret;for(int k=0;k<len;k++)  //采用滑動窗口的方式進行,進單詞--判斷--出單詞,要特別注意的是的第一個單詞開始的位置{unordered_map<string,int> all;     //int記錄區間內單詞與目標的單詞個數差for(auto e:words)all[e]--;  int left=k,right=k;//先將num-1個單詞入窗口,因為在后面循環中每次入單詞和出單詞都是對一個單詞進行操作for(int i=0;i<num-1;i++){if(right+len>n) break;   //防止結尾處不能滿足一個單詞string tmp=s.substr(right,len);if(++all[tmp]==0) all.erase(tmp);  //差為0,就從map中刪除right+=len;}while(right<n){//進行入窗口,再入一個單詞if(right+len>n) break;string tmp=s.substr(right,len);if(++all[tmp]==0) all.erase(tmp); right+=len;//更新答案if(all.empty()) ret.push_back(left);//出窗口tmp=s.substr(left,len);if(--all[tmp]==0) all.erase(tmp); left+=len;}}return ret;}
};

220. 存在重復元素 III

題解:滑動窗口+二分查找。根據題目:兩個下標i和j,abs(i - j) < indexDiff 就不難想到要使用滑動窗口解決。但是對于abs( nums[i] - nums[j] )<=valueDiff(可以拆分為 nums[i] - valueDiff<= nums[j] <=nums[i] +?valueDiff)應該如何進行處理,可以遍歷nums[i]將其前面的indexDiff個中找是否存在滿足abs( nums[i] - nums[j] )<=valueDiff 的數據即可,可以使用set存放前面indexDiff個數據,然后找第一個 >=nums[i] - valueDiff的數據記為l,再判斷abs(l+nums[i])<=value是否滿足。

class Solution {
public:bool containsNearbyAlmostDuplicate(vector<int>&  nums, int indexDiff, int valueDiff) {//使用 滑動窗口+二分//控制一段長度小于indexDiff的區間,以nums[i]為基點看理他最近的數是否滿足第二個條件int left = 0, n = nums.size();set<int> s;for (int right = 0; right < n; right++){auto l = s.lower_bound((long)nums[right]-valueDiff);if ((l != s.end() && abs(*l - (long)nums[right]) <= valueDiff))return true;if (right - left == indexDiff)s.erase(nums[left++]);s.insert(nums[right]);}return false;}
};

?能否對上面代碼進行優化???

此處采用一種新方法:桶排序

上述方法使用了一個循環+二分查找時間復雜度是O(N*logN)),查找時使用的時二分查找logN,能否對查找進行優化,優化為O(1);O(1)的查找就要使用哈希桶了。

當前位置值如果時num,使用哈希桶去找[ num-valueDiff,num+valueDiff ]中的是否存在值,那么查找的時間復雜度就是O(valueDiff)了,當valueDiff很大的時候肯定不是O(1)的查找;

那就不能直接將一個數據放在一個桶中來實現,對數據進行分類,將一組數據放入桶中。abs(nums[i]-nums[j] )<=valueDiff所以可以將valueDiff看成一組,比如 valueDiff=3時 0 1 2 | 3 ?4 5 | 6 ?7 ?8 | 9 10 11將每3個數據看作一組,放入到一個桶中,當一個桶中有兩個數據時這兩個數據就滿足條件,當該位置的桶沒有數據但是旁邊桶中有數據的時候,判斷旁邊桶的數據時候滿足條件。但是valueDiff可能是0,在進行除法的時會出現匯報發錯,所以不直接將valueDiff看作一組,而是將valueDiff+1看作一組。

計算哈希桶的key:對于>=0的數,實際 val/(valueDiff+1) 即可,但是對于負數來說-4 -3 -2 -1,如果直接對負數/(valueDiff+1)就會導致,-4和-3 -2 -1分成兩組,所以對于負數要將數據+1再除,+1后-4 -3 -2 -1都映射到0上面,而0 1 2 3 也映射到0上面,所以還需要對結果 -1

非負數的key:index= val / (valueDiff+1);

負數的key:index= (val+1) / (valueDiff+1) -1。

class Solution {#define LL long longLL size;   //用于計算當前數據放在哪一個桶里面
public:bool containsNearbyAlmostDuplicate(vector<int>& nums, int indexDiff, int valueDiff) {//使用哈希桶進行處理size=valueDiff+1L;  //1L指的是將1轉化為long long類型unordered_map<LL,LL> m; //作為存儲數據的哈希桶int left=0,n=nums.size();for(int right=0;right<n;right++){LL val=nums[right];LL index=getIdx(val);  //記錄放在哪一個桶中if(m.count(index)) return true;LL l=index-1,r=index+1;  //判斷左右兩個桶中的數據是否滿足條件if((m.count(l)&&abs(val-m[l])<=valueDiff)||(m.count(r)&&abs(val-m[r])<=valueDiff)) return true;if(right-left>=indexDiff)m.erase(getIdx(nums[left++]));m.insert({index,val});}return false;}LL getIdx(LL u){return u>=0?u/size:(u+1)/size-1;} 
};

總結?

此篇博客中的題目不再僅僅是簡單的定長滑動窗口題目,更多的是需要進行一定的分析搭配其他的算法進行處理,此篇題目更注重一題多解,讓我們再面試的時候可以靈活應變,找到最優解。題目不少,難度較高,感謝閱讀!!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/907266.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/907266.shtml
英文地址,請注明出處:http://en.pswp.cn/news/907266.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JWT安全:接收無簽名令牌.【簽名算法設置為none繞過驗證】

JWT安全&#xff1a;假密鑰【簽名隨便寫實現越權繞過.】 JSON Web 令牌 (JWT)是一種在系統之間發送加密簽名 JSON 數據的標準化格式。理論上&#xff0c;它們可以包含任何類型的數據&#xff0c;但最常用于在身份驗證、會話處理和訪問控制機制中發送有關用戶的信息(“聲明”)。…

XGBoost與SHAP深度解析:從算法原理到實戰價值

在機器學習領域&#xff0c;XGBoost以其卓越的性能長期占據Kaggle競賽和工業界的主流地位&#xff0c;而SHAP&#xff08;SHapley Additive exPlanations&#xff09;則成為模型可解釋性的標桿工具。本文將深度解析兩者的技術內核&#xff0c;并通過實戰案例揭示其結合應用的實…

Java SE Cloneable接口和深/淺拷貝

Java為我們提供了各種各樣功能的接口&#xff0c;Clonable接口就是其中之一。 它通常配合Object類的 clone方法使用。這個方法可以為我們創建一個對象的拷貝&#xff0c;即復制一個對象。在進入本文的主要內容之前&#xff0c;先來對訪問限定符 protected進行一個解剖。 1.再…

Python學習(3) ----- Python的函數定義及其使用

Python 中函數是組織好的、可重復使用的代碼塊&#xff0c;用于實現單一或相關聯的功能。下面是函數定義和使用的完整說明&#xff1a; &#x1f4cc; 一、函數定義語法 def 函數名(參數1, 參數2默認值, *args, **kwargs):"""函數說明文檔"""函…

vue2使用el-tree實現兩棵樹間節點的拖拽復制

原文鏈接&#xff1a;兩棵el-tree的節點跨樹拖拽實現 參照這篇文章&#xff0c;把它做成組件&#xff0c;新增左側樹&#xff08;可拖出&#xff09;被拖節點變灰提示&#xff1b; 拖拽中&#xff1a; 拖拽后&#xff1a; TreeDragComponent.vue <template><!-- …

智變與重構:AI 賦能基礎教育教學的范式轉型研究報告

一、研究背景與核心價值 &#xff08;一&#xff09;技術驅動下的教育轉型浪潮 在全球數字化轉型加速的背景下&#xff0c;人工智能作為核心技術力量&#xff0c;正重塑基礎教育生態。據《人工智能賦能未來教育研究報告》指出&#xff0c;我國教育數字化戰略行動已推動超 70…

Go語言中Print、Printf和Println的區別及使用場景詳解

在Go語言的fmt包中&#xff0c;Print、Printf和Println是三個基礎但功能各異的輸出函數。本文將從多個維度進行詳細對比分析&#xff0c;并給出具體的使用建議。 1. 核心區別深度解析 1.1. 函數簽名與基本行為 func Print(a ...interface{}) (n int, err error) func Printf…

高端制造行業 VMware 替代案例合集:10+ 頭部新能源、汽車、半導體制造商以國產虛擬化支持 MES、PLM 等核心應用系統

在“中國制造 2025”政策的推動下&#xff0c;國內的新能源、汽車制造、半導體、高端裝備等高端制造產業迎來了蓬勃發展&#xff0c;成為全球制造業版圖中舉足輕重的力量。訂單數量的激增與國產化轉型的趨勢&#xff0c;也為高端制造企業的 IT 基礎設施帶來了新的挑戰&#xff…

Spring Ai | 從零帶你一起走進AI項目(中英)

目錄 Thinking Study question pox.xml Maven Gradle Configure API Key Use the AI Client Question Thinking 讓數據變得更加貼近用戶的想法 Study question null pox.xml 添加依賴 Maven <dependencies><dependency><groupId>org.springfram…

LiveGBS作為下級平臺GB28181國標級聯2016|2022對接海康大華宇視華為政務公安內網等GB28181國標平臺查看級聯狀態及會話

LiveGBS作為下級平臺GB28181國標級聯2016|2022對接海康大華宇視華為政務公安內網等GB28181國標平臺查看級聯狀態及會話 1、GB/T28181級聯概述2、搭建GB28181國標流媒體平臺3、獲取上級平臺接入信息3.1、向下級提供信息3.2、上級國標平臺添加下級域3.3、接入LiveGBS示例 4、配置…

卸載 Office PLUS

Office PLUS作為微軟官方推出的智能辦公提效工具&#xff0c;自2015年問世以來&#xff0c;憑借其豐富的模板資源和便捷的智能功能&#xff0c;迅速贏得了廣大職場人士和學生的青睞。本文將全面介紹Office PLUS的發展歷程、核心功能、可能帶來的使用問題&#xff0c;以及如何徹…

影響沉金價格的因素如何體現在多層電路板制造上?

隨著科技的不斷發展&#xff0c;電子產品越來越普及&#xff0c;對電路板的需求也越來越大。多層電路板作為電子產品的核心部件&#xff0c;其性能和質量直接影響到整個產品的穩定性和可靠性。在多層電路板的生產過程中&#xff0c;沉金工藝是一種常用的表面處理方法&#xff0…

擴展摩爾投票法:找出出現次數超過 n/3 的元素

文章目錄 問題描述關鍵洞察算法原理Java 實現算法演示投票階段驗證階段 復雜度分析算法關鍵點通用化公式實際應用場景邊界情況處理總結 標簽&#xff1a;LeetCode 169, 摩爾投票法, 多數元素, 算法擴展, 數組處理 在解決多數元素問題時&#xff0c;我們學習了經典的摩爾投票法處…

Git:現代軟件開發的基石——原理、實踐與行業智慧·優雅草卓伊凡

Git&#xff1a;現代軟件開發的基石——原理、實踐與行業智慧優雅草卓伊凡 一、Git的本質與核心原理 1. 技術定義 Git是一個分布式版本控制系統&#xff08;DVCS&#xff09;&#xff0c;由Linus Torvalds在2005年為管理Linux內核開發而創建。其核心是通過快照&#xff08;Sna…

程序人生-hello’s P2P

計算機系統 大作業 題 目 程序人生-hello’s P2P 專 業 計算機與電子通信類 學   號 2023111990 班   級 23L0514 學 生 袁騁 指 導 教 師 史…

Java設計模式之設計原則

Java設計模式 Java設計模式主要原則是開閉原則&#xff0c;即對擴展開放&#xff0c;對修改關閉。由此衍生出5大原則&#xff1a;單一職責原則&#xff0c;里式替換原則&#xff0c;迪米特原則&#xff0c;接口隔離職責&#xff0c;依賴倒置原則。1、開閉原則 開閉原則&#x…

使用 ssld 提取CMS 簽名并重簽名

拿SpringBoard的cms簽名和entitlements.xml&#xff0c;對tihook.dylib進行重簽名 工具來源&#xff1a;https://github.com/eksenior/ssld

WebFuture:測試郵件發送失敗

問題描述&#xff1a;測試郵件發送失敗 問題分析&#xff1a; 查看報錯是模擬發送郵件請將systemsettings.json中的EnabledMail設為false&#xff01; 解決方案&#xff1a; 網站根目錄找到Configuration&#xff0c;如下圖所示&#xff0c;將systemsettings.json中的Enabled…

LiveNVR 直播流拉轉:Onvif/RTSP/RTMP/FLV/HLS 支持海康宇視天地 SDK 接入-視頻廣場頁面集成與視頻播放說明

LiveNVR直播流拉轉&#xff1a;Onvif/RTSP/RTMP/FLV/HLS支持海康宇視天地SDK接入-視頻廣場頁面集成與視頻播放說明 一、視頻頁面集成1.1 關閉接口鑒權1.2 視頻廣場頁面集成1.2.1 隱藏菜單欄1.2.2 隱藏播放頁面分享鏈接 1.3 其它頁面集成 二、播放分享頁面集成2.1 獲取 iframe 代…

12. CSS 布局與樣式技巧

在前端開發中&#xff0c;CSS 是控制頁面樣式和布局的核心技術。本文總結了 CSS 布局中的關鍵概念和實用技巧&#xff0c;包括 overflow 屬性、背景圖片處理、精靈圖技術、display 屬性、浮動布局以及清除浮動的方法。 一、overflow 屬性詳解 overflow 屬性用于控制當元素內容…