面試高頻題 力扣 283.移動零 雙指針技巧 原地修改 順序保持 C++解題思路 每日一題

目錄

  • 零、題目描述
  • 一、為什么這道題值得你花幾分鐘看懂?
  • 二、題目拆解:提取其中的關鍵點
  • 三、明確思路:雙指針的巧妙配合
  • 四、算法實現:雙指針的代碼演繹
  • 五、C++代碼實現:一步步拆解
    • 代碼拆解
    • 時間復雜度和空間復雜度
  • 六、實現過程中的坑點總結
  • 七、舉一反三

零、題目描述

題目鏈接:移動零

題目描述:
在這里插入圖片描述

示例 1:
輸入: nums = [0,1,0,3,12]
輸出: [1,3,12,0,0]

示例 2:
輸入: nums = [0]
輸出: [0]

提示:
1 <= nums.length <= 10^4
-2^31 <= nums[i] <= 2^31 - 1

一、為什么這道題值得你花幾分鐘看懂?

如果你正在打磨自己的算法基礎,那「移動零」這道題你一定不能錯過——它是 LeetCode 第 283 題,更是雙指針技巧的經典入門題,幾乎所有算法入門教程都會拿它舉例。

從算法能力提升來講,這道題能幫你深刻理解雙指針在原地修改數組中的核心作用,掌握「快慢指針配合」的精髓。這種技巧不僅能解決數組中的元素移動問題,還能延伸到鏈表操作、滑動窗口等多種場景,是很多高效算法的基礎。

甚至在實際開發中,當需要對數據進行過濾、整理,又希望節省空間時,雙指針的思路能幫你寫出更高效、更優雅的代碼。

二、題目拆解:提取其中的關鍵點

先看原題:

給定一個數組 nums,編寫一個函數將所有 0 移動到數組的末尾,同時保持非零元素的相對順序。必須在不復制數組的情況下原地對數組進行操作。

再結合所給的代碼框架和提示:

class Solution {
public:void moveZeroes(vector<int>& nums) {}
};

核心要素提煉:

  1. 輸入是一個整數類型的向量nums,需要對其進行原地修改。
  2. 操作目標是將數組中的所有0移到末尾,同時保證非零元素的相對順序不變。
  3. 不能使用額外的數組來輔助,必須在原數組上進行操作。

關鍵點:

  1. 雙指針應用:用兩個指針分別追蹤非零元素的位置和當前遍歷的位置。
  2. 元素交換:將非零元素移動到前面合適的位置。
  3. 保持順序:確保非零元素的相對位置和原來一致。
  4. 原地操作:不使用額外的數組空間,只在原數組上進行修改。

三、明確思路:雙指針的巧妙配合

1. 最直觀的想法:先移非零,再補零

拿到題的第一反應:把所有非零元素按順序移到數組前面,然后把剩下的位置都填上0。比如對于數組[0,1,0,3,12],先把非零元素1312依次移到前面,得到[1,3,12,...],再把后面的位置補成0,結果就是[1,3,12,0,0]

這種思路的關鍵是如何高效地找到非零元素應該放置的位置,這就需要用到雙指針了。

2. 雙指針的分工

在這里,我們可以用兩個指針:

  • cur指針:負責遍歷整個數組,尋找非零元素,相當于“探索者”。
  • dest指針:負責記錄下一個非零元素應該放置的位置,相當于“占位者”。

初始時,cur從數組開頭開始遍歷,dest指向-1(表示還沒有確定非零元素的位置)。當cur遇到非零元素時,就把dest向前移動一位,然后交換curdest指向的元素。這樣,dest始終指向已經處理好的非零元素的最后一個位置。

舉個例子(示例1):

初始數組:[0,1,0,3,12],cur=0,dest=-1
cur=0,元素是0,不做操作,cur++
cur=1,元素是1(非零),dest++變為0,交換nums[0]和nums[1],數組變為[1,0,0,3,12],cur++
cur=2,元素是0,不做操作,cur++
cur=3,元素是3(非零),dest++變為1,交換nums[1]和nums[3],數組變為[1,3,0,0,12],cur++
cur=4,元素是12(非零),dest++變為2,交換nums[2]和nums[4],數組變為[1,3,12,0,0],cur++
遍歷結束,得到結果

3. 為什么這樣可行?

因為cur指針一直在dest指針前面或者和它同步,dest指針前面的元素都是已經處理好的非零元素,并且保持著原來的相對順序。當cur遇到非零元素時,把它交換到dest+1的位置,就保證了非零元素按原來的順序排列在前面,而后面剩下的位置自然都是0了。

這種方法只需要遍歷一次數組,并且是原地操作,時間復雜度是O(n),空間復雜度是O(1),非常高效。

四、算法實現:雙指針的代碼演繹

這道題我用雙指針的方法來實現,下面講解具體的算法思路。

核心邏輯:
cur指針遍歷數組,當遇到非零元素時,將dest指針向前移動一位,然后交換curdest指向的元素,這樣就能把非零元素逐步移到前面,零自然就到后面去了。

步驟拆解:

  1. 初始化cur為0,dest為-1。
  2. cur遍歷數組中的每個元素:
    • nums[cur]是非零元素:
      • dest向前移動一位(dest++)。
      • 交換nums[cur]nums[dest]
    • cur向前移動一位(cur++)。
  3. 遍歷結束后,數組中的所有零都被移到了末尾,非零元素保持相對順序不變。

雙指針細節:

  1. cur指針:從0開始,依次遍歷數組的每個元素,直到數組末尾。它的作用是發現非零元素。
  2. dest指針:初始值為-1,表示還沒有非零元素被放置。每當cur找到一個非零元素,dest就向前移動一位,指向這個非零元素應該放置的位置。
  3. 交換操作:當cur找到非零元素時,交換nums[cur]nums[dest],這樣非零元素就被放到了前面合適的位置。
  4. 遍歷順序cur始終從左到右遍歷,保證了非零元素的相對順序不變。

五、C++代碼實現:一步步拆解

完整代碼(附詳細注釋):

#include <vector>
using namespace std;class Solution {
public:void moveZeroes(vector<int>& nums) {int n = nums.size();// cur用于遍歷數組,dest用于記錄非零元素應放的位置for (int cur = 0, dest = -1; cur < nums.size(); cur++) {if (nums[cur] != 0) {  // 遇到非零元素dest++;  // dest移動到下一個位置swap(nums[dest], nums[cur]);  // 交換元素,將非零元素放到前面}}}
};

代碼拆解

1. 變量初始化

int n = nums.size();  // 獲取數組長度,雖然這里沒有直接使用,但可以用于后續擴展
int cur = 0;  // 遍歷指針,從數組開頭開始
int dest = -1;  // 目標指針,初始為-1,表示還沒有非零元素被放置

作用cur負責遍歷整個數組,dest負責跟蹤非零元素應該放置的位置,初始狀態下還沒有非零元素被處理,所以dest為-1。

2. 主循環邏輯

for (int cur = 0, dest = -1; cur < nums.size(); cur++) {// 循環條件:cur遍歷完數組的所有元素if (nums[cur] != 0) {  // 當遇到非零元素時dest++;  // 目標位置向前移動一位swap(nums[dest], nums[cur]);  // 交換元素,將非零元素放到目標位置}// 如果是零元素,則不做處理,cur繼續向前移動
}

核心設計

  • 遍歷機制cur從0開始,每次循環后自增1,直到遍歷完整個數組,確保每個元素都被檢查。
  • 非零元素處理:當nums[cur]不是0時,說明找到了一個需要放到前面的元素。此時dest先自增1,指向當前應該放置非零元素的位置,然后交換nums[cur]nums[dest],這樣非零元素就被放到了前面。
  • 零元素處理:當nums[cur]是0時,不做任何操作,cur繼續向前移動,零元素就被留在了原地,最終會被后面的非零元素交換到后面去。

示例走讀(示例1:nums = [0,1,0,3,12])

步驟curdestnums[cur]操作數組狀態
初始0-10不操作,cur++[0,1,0,3,12]
11-11dest++變為0,交換nums[1]和nums[0],cur++[1,0,0,3,12]
2200不操作,cur++[1,0,0,3,12]
3303dest++變為1,交換nums[3]和nums[1],cur++[1,3,0,0,12]
44112dest++變為2,交換nums[4]和nums[2],cur++[1,3,12,0,0]
結束5(退出循環)2--[1,3,12,0,0]

通過這個過程可以清晰地看到,非零元素1、3、12依次被放到了數組前面,零元素被移到了后面,并且非零元素的相對順序保持不變。

時間復雜度和空間復雜度

復雜度類型具體值說明
時間復雜度O(n)n是數組的長度,cur指針遍歷整個數組一次,每個元素最多被交換一次
空間復雜度O(1)只使用了兩個額外的指針變量,沒有使用額外的數組或其他數據結構

這種算法在時間和空間上都非常高效,符合題目的要求。

六、實現過程中的坑點總結

  1. dest指針的初始值
    錯誤寫法

    for (int cur = 0, dest = 0; cur < nums.size(); cur++) {// ...
    }
    

    問題:如果dest初始化為0,當數組的第一個元素是非零元素時,會把它和自己交換,雖然結果正確,但做了無用功。更重要的是,如果數組全是零元素,可能會出現不必要的操作。
    正確做法:初始化為-1,讓第一個非零元素能正確地放到索引0的位置。

  2. 忘記交換元素
    錯誤寫法

    if (nums[cur] != 0) {dest++;nums[dest] = nums[cur];  // 直接賦值,沒有處理原來的位置
    }
    

    問題:這樣會導致原來dest位置的元素被覆蓋,而cur位置的非零元素沒有被清除,可能會留下重復的值。
    正確做法:使用swap函數交換兩個位置的元素,確保非零元素移動的同時,零元素被換到后面。

  3. 遍歷順序錯誤
    錯誤寫法

    for (int cur = nums.size() - 1; cur >= 0; cur--) {// ...
    }
    

    問題:從后往前遍歷會打亂非零元素的相對順序,無法保證移動后非零元素的順序和原來一致。
    正確做法cur指針必須從左到右遍歷數組,才能保證非零元素的相對順序不變。

  4. 處理空數組或只有一個元素的情況
    雖然代碼中沒有專門處理,但當前的代碼邏輯對這些情況是兼容的:

    • 如果數組為空,循環不會執行,直接返回。
    • 如果數組只有一個元素,無論是0還是非零,循環執行一次后都會得到正確的結果。
  5. 不必要的交換
    curdest相等時(比如數組開頭都是非零元素),交換操作是不必要的。可以添加一個判斷來優化:

    if (nums[cur] != 0) {dest++;if (cur != dest) {  // 只有當位置不同時才交換swap(nums[dest], nums[cur]);}
    }
    

    這樣可以減少一些無用的交換操作,提高代碼效率。

七、舉一反三

學會這道題的雙指針技巧,你能解決很多類似的數組操作問題:

  • LeetCode 27. 移除元素:給定一個值,移除數組中所有等于這個值的元素,思路是用雙指針將不等于該值的元素移到前面。
  • LeetCode 26. 移除重復元素:刪除有序數組中的重復項,讓每個元素只出現一次,用雙指針可以高效地實現。
  • LeetCode 80. 刪除有序數組中的重復項 II:允許元素最多出現兩次,雙指針的思路可以擴展應用。

明天我們一起討論LeetCode 1089. 復寫零這道題有興趣的朋友可以提前思考下

雙指針技巧在數組和鏈表操作中非常常見,掌握它能讓你在處理元素移動、刪除、查找等問題時更加得心應手。

最后,歡迎大家在評論區分享自己的代碼和思路,一起交流學習~ 如果你有更巧妙的方法,也請不吝賜教,我會認真學習并回復的!
在這里插入圖片描述

這是今天的封面原圖:
在這里插入圖片描述

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

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

相關文章

arrch64架構下調用pyvista報錯

arrch64架構下調用pyvista報錯 問題 python編程使用到了pyvista&#xff0c;使用conda新建了環境&#xff0c;但是使用的時候報錯 Traceback (most recent call last):File "/home/ztl/MGGBSAR/src/trans_las_3D.py", line 16, in <module>import pyvista as p…

功能強大編輯器

時間限制&#xff1a;1秒 內存限制&#xff1a;128M題目描述你要幫助小可創造一個超級數字編輯器&#xff01;編輯器依舊運行在Linux下&#xff0c;因此你只能通過指令去操控他。指令有五種&#xff1a; In X 表示在光標左側插入一個數字 Del 表示刪除光標左側一個數字 …

【力扣】面試經典150題總結01-數組/字符串

1.合并兩個有序數組&#xff08;簡單&#xff09;要求直接在num1上操作&#xff0c;已經預留了空間&#xff0c;所以直接倒著從大到小插入。當其中一個數組遍歷完&#xff0c;就把另一個數組剩余的部分插入。2.移除元素&#xff08;簡單&#xff09;要求原地移除數組中所有val元…

基于 Hadoop 生態圈的數據倉庫實踐 —— OLAP 與數據可視化(一)

目錄 一、OLAP 與 Impala 簡介 1. OLAP 簡介 2. Impala 簡介 &#xff08;1&#xff09;Impala 是什么 &#xff08;2&#xff09;為什么要使用 Impala &#xff08;3&#xff09;適合 Impala 的使用場景 &#xff08;4&#xff09;Impala 架構 &#xff08;5&#xff…

PyTorch L2范數詳解與應用

torch.norm 是什么 torch.norm(dot_product, p=2, dim=-1) 是 PyTorch 中用于計算張量 L2 范數的函數, 1. 各參數解析 dot_product:輸入張量,在代碼中形狀為 [batch_size, seq_len](每個元素是 token 隱藏狀態與關注向量的點積)。 p=2:指定計算L2 范數(歐幾里得范數)…

循環神經網絡RNN原理精講,詳細舉例!

第一部分&#xff1a;為什么需要RNN&#xff1f;在了解RNN是什么之前&#xff0c;我們先要明白它解決了什么問題。傳統的神經網絡&#xff0c;比如我們常見的前饋神經網絡&#xff08;Feedforward Neural Network&#xff09;或者卷積神經網絡&#xff08;CNN&#xff09;&…

如何用USRP捕獲手機信號波形(中)手機/基站通信

目錄&#xff1a; 如何用USRP捕獲手機信號波形&#xff08;上&#xff09;系統及知識準備 如何用USRP捕獲手機信號波形&#xff08;中&#xff09;手機/基站通信 如何用USRP捕獲手機信號波形&#xff08;下&#xff09;協議分析 四、信號捕獲結果 4.1 時域波形 我懷疑下面…

(LeetCode 面試經典 150 題 ) 155. 最小棧 (棧)

題目&#xff1a;155. 最小棧 思路&#xff1a;棧&#xff0c;時間復雜度0(n)。 在插入棧元素val時&#xff0c;同時加入一個字段&#xff0c;維護插入當前元素val時的最小值即可。 C版本&#xff1a; class MinStack { public:stack<pair<int,int>> st;MinStac…

算法:動態規劃 洛谷 線性狀態動態規劃 P1439【模板】最長公共子序列

思路&#xff1a;因為n<1e5,所以不能O&#xff08;n方&#xff09;的復雜度&#xff0c;所以常規的計算最長公共子序列的方法就不行&#xff0c;不過這題有個特點&#xff0c;就是a&#xff0c;b都是排列&#xff0c;那么a有的數b也有&#xff0c;并且數量還一樣&#xff0c…

Linux跑后臺服務

vi /usr/lib/systemd/system/my_service.service文件配置內容&#xff1a;[Unit] Descriptionmyprogram Afternetwork.target[Service] Userroot Typesimple ExecStart/home/userabc/programs/myprogram/myprogram.out Restarton-failure WorkingDirectory/home/userabc/progra…

Linux基礎練習題1

1、配置網絡地址 請為此虛擬機配置以下網絡參數&#xff1a; 1&#xff09;主機名&#xff1a;chenyu.example.com &#xff08;將chenyu改成自己名字的全拼&#xff09; 2&#xff09;IP 地址&#xff1a;192.168.100.100/24 3&#xff09;默認網關&#xff1a;192.168.100.25…

# 前端開發規范基礎匯總

前端開發規范基礎匯總 命名規范 常用的命名規范 camelCase&#xff08;小駝峰式命名法 —— 首字小寫&#xff09;PascalCase&#xff08;大駝峰式命名法 —— 首字大寫&#xff09;snake_case&#xff08;下劃線命名法&#xff09;kebab-case&#xff08;短橫線命名法&…

jQuery UI Tabs切換功能實例

jQuery UI Tabs切換功能使用jQuery UI實現Tabs切換功能的方法。代碼示例創建了一個包含四個標簽頁&#xff08;按鈕A-D&#xff09;的界面&#xff0c;每個標簽對應不同的內容區域。通過引入jQuery UI庫并調用tabs()方法實現基本切換功能。文章還提到可以通過配置選項修改默認行…

關于為什么stm32的開漏輸出可以讀取引腳的數值

在使用軟件模擬iic通信時&#xff0c;要將SDA線配置為開漏輸出&#xff0c;既然配置為開漏輸出&#xff0c;為什么程序還可以通過SDA線讀取數據&#xff1f;查閱手冊&#xff1a;只說了結論&#xff1a;在開樓模式下&#xff0c;對輸入數據寄存器的讀訪問可以得到IO狀態來看輸出…

墨者:SQL手工注入漏洞測試(SQLite數據庫)

1. 墨者學院&#xff1a;SQL手工注入漏洞測試(SQLite數據庫)&#x1f680; 2. SQLite數據庫注入特點&#x1f50d; SQLite數據庫和MySQL數據庫語法不同&#xff0c;不能直接套用MySQL的注入方式。但SQLite有個特殊的數據庫sqlite_master&#xff0c;它存儲了所有表結構信息&…

【Apache Tomcat】

目錄Tomcat 基本簡介Tomcat 架構組成Tomcat 的目錄結構Tomcat 的工作原理Tomcat 的配置文件Tomcat 與其他服務器對比Tomcat 使用場景Tomcat 與 Spring Boot常見問題與優化Tomcat&#xff08;全稱 Apache Tomcat&#xff09;是由 Apache 軟件基金會開發和維護的一款 開源的 Web …

Nginx參數proxy_set_header 與 add_header 核心區別

proxy_set_header 與 add_header 是 Nginx 中兩個用于操作 HTTP 頭部信息的指令&#xff0c;但作用方向和使用場景完全不同。以下是兩者的核心區別&#xff1a;核心區別概述特性proxy_set_headeradd_header作用方向? 請求頭&#xff08;Request Headers&#xff09; → 后端服…

若依框架-前端二次開發快速入門簡述

1.目錄如左圖所示&#xff0c;主要分為bin,build,node_modules,public,src幾個部分&#xff0c;我們從gitee上使用bash將項目克隆到本地后&#xff0c;進入項目目錄&#xff0c;并安裝好依賴后可以直接使用命令啟動服務&#xff0c;具體命令見README.md&#xff0c;安裝好依賴后…

day 41 類和方法

day 28 類是對屬性和方法的封裝&#xff0c;可以理解為模版&#xff0c;通過對模型實例化可以實現調用這個類的屬性和方法。比如創建一個隨機森林類&#xff0c;然后就可以調用它的訓練和預測方法。 一個常見的類的定義包括了&#xff1a; 1、關鍵字class 2、類名 3、語法固定…

Docker學習日志-Docker容器配置、Nginx 配置與文件映射

Docker學習日志-Docker容器配置、Nginx 配置與文件映射 docker run 之后能否再次修改卷映射或端口映射&#xff1f; 不能直接修改已創建容器的卷映射或端口映射。 Docker 的設計原則是 **容器是不可變的 **&#xff0c;也就是說&#xff1a; 一旦容器通過 docker run 創建完成&…