算法 1.5 鏈表 + 快慢指針:環(huán)形鏈表【leetcode 141】

題目描述

給定一個(gè)鏈表,判斷鏈表中是否有環(huán),存在環(huán)返回 true爽室,否則返回 false

  • 連續(xù)跟蹤 next 指針再次到達(dá)某個(gè)節(jié)點(diǎn),則鏈表中有環(huán)
  • 你能用 O(1)(即淆攻,常量)內(nèi)存解決此問(wèn)題嗎阔墩?

提示:
鏈表中節(jié)點(diǎn)的數(shù)目范圍是 [0, 10^4]
-10^5 <= Node.val <= 10^5
pos 為環(huán)開始節(jié)點(diǎn)的索引,若 pos = -1瓶珊,則沒(méi)有環(huán)
pos 為 -1 或者鏈表中的一個(gè) 有效索引
pos 不作為參數(shù)進(jìn)行傳遞啸箫,僅僅是為了標(biāo)識(shí)鏈表的實(shí)際情況

注意:pos 只是幫助理解測(cè)試用例,并不是真正的輸入值

數(shù)據(jù)結(jié)構(gòu)

  • 數(shù)組伞芹、指針變量

算法思維

  • 遍歷忘苛、雙指針、快慢指針唱较、追擊

解題要點(diǎn)

  • 鏈表的特點(diǎn)
  • 快慢指針的思想以及使用
  • 模型的應(yīng)用:涉及"環(huán)"的問(wèn)題 --> 追擊問(wèn)題 --> 快慢指針

解題步驟

一. Comprehend 理解題意

可以理解為"重復(fù)訪問(wèn)"問(wèn)題扎唾,檢測(cè)鏈表的某節(jié)點(diǎn)能否二次到達(dá)

  • 需要一個(gè)容器記錄已經(jīng)訪問(wèn)過(guò)的節(jié)點(diǎn)
  • 每次訪問(wèn)到新的節(jié)點(diǎn),都與容器中的記錄進(jìn)行匹配南缓,若相同則存在環(huán)
  • 若匹配之后沒(méi)有相同節(jié)點(diǎn)胸遇,則存入容器,繼續(xù)訪問(wèn)新的節(jié)點(diǎn)
  • 直到訪問(wèn)節(jié)點(diǎn)的next指針?lè)祷豱ull汉形,或者當(dāng)前節(jié)點(diǎn)與容器的某個(gè)記錄相同狐榔,操作結(jié)束

也可以理解為“追擊”問(wèn)題,如果存在環(huán)获雕,跑得快的一定能追上跑得慢的

  • 就像一快一慢兩個(gè)運(yùn)動(dòng)員薄腻,如果在直道賽跑,不存在追擊問(wèn)題届案;
    如果是在環(huán)道賽跑庵楷,快的繞了一圈肯定可以追上慢的
  • 定義快慢兩個(gè)運(yùn)動(dòng)員,指向鏈表的第一楣颠、第二個(gè)節(jié)點(diǎn):slow = head; fast = head.next;
  • 快運(yùn)動(dòng)員的步長(zhǎng)為2尽纽,慢的為1,即fast每次移動(dòng)兩個(gè)節(jié)點(diǎn)童漩,slow每次移動(dòng)一個(gè)節(jié)點(diǎn)
  • 若最終 fast == slow 弄贿,說(shuō)明存在環(huán);若 fast == null || fast.next == null矫膨,操作結(jié)束
二. Choose 選擇數(shù)據(jù)結(jié)構(gòu)與算法
解題方法
  • 解法一:二次到達(dá)法
  • 解法二:追擊問(wèn)題法
解法一:二次到達(dá)法

數(shù)據(jù)結(jié)構(gòu):數(shù)組(或者:鏈表差凹、棧期奔、隊(duì)列)
算法思維:遍歷

解法二:追擊問(wèn)題法

數(shù)據(jù)結(jié)構(gòu):指針變量 x 2
算法思維:遍歷、雙指針(快慢指針)

三. Code 編碼實(shí)現(xiàn)基本解法
解法一:二次到達(dá)法 -- 思路分析
  1. 定義數(shù)組記錄已訪問(wèn)節(jié)點(diǎn):new ListNode[10000];
  2. 遍歷鏈表的每個(gè)節(jié)點(diǎn)危尿,并與容器中已存放的節(jié)點(diǎn)依次比較:
    ? 相同則方法結(jié)束呐萌,返回 true
    ? 不同則存入最新位置,繼續(xù)遍歷下個(gè)節(jié)點(diǎn)
  3. 若 next 指針為 null谊娇,則方法結(jié)束肺孤,返回 false
邊界問(wèn)題
  • 數(shù)組越界:
    鏈表最多有一萬(wàn)個(gè)節(jié)點(diǎn),容器不會(huì)越界济欢;
    與容器中節(jié)點(diǎn)進(jìn)行比對(duì)赠堵,正向遍歷容器,元素為 null 時(shí)終止法褥,后續(xù)都是未使用的空間
細(xì)節(jié)問(wèn)題
  • 遍歷鏈表茫叭,通過(guò) head=head.next 進(jìn)行迭代
    當(dāng)且僅當(dāng)此節(jié)點(diǎn)與容器某個(gè)節(jié)點(diǎn)相同時(shí)返回 true,其它情況都返回 false
    比較相等時(shí)可以用 "=="(比較地址)
class Solution {
    public boolean hasCycle(ListNode head) {
        // 1.定義數(shù)組記錄已訪問(wèn)節(jié)點(diǎn) 
        ListNode[] array = new ListNode[10000];
        // 2.遍歷鏈表的每個(gè)節(jié)點(diǎn)挖胃, 
        while(head != null) {
            // 并與容器鐘已存放的節(jié)點(diǎn)依次比較 
            for(int i = 0; i < array.length; i++) {
                if(array[i] == head) {
                    return true;
                }
                if(array[i] == null) {
                    array[i] = head; // 將當(dāng)前節(jié)點(diǎn)存放到最新位置 
                    break; // 結(jié)束容器的遍歷 
                }
            }
            head = head.next;
        }
        // 3.若next指針為null杂靶,則方法結(jié)束梆惯,返回false
        return false;
    }
}

時(shí)間復(fù)雜度:O(n2) -- 遍歷數(shù)組 O(n)酱鸭,每個(gè)節(jié)點(diǎn)都需要額外再遍歷一次數(shù)組 O(n2)
空間復(fù)雜度:O(1) -- 固定長(zhǎng)度 10000 的數(shù)組 O(1)
執(zhí)行耗時(shí):108 ms,擊敗了 5.26% 的Java用戶
內(nèi)存消耗:38.3 MB垛吗,擊敗了 99.89% 的Java用戶

四. Consider 思考更優(yōu)解
剔除無(wú)效代碼凹髓,優(yōu)化空間消耗
  • 每個(gè)節(jié)點(diǎn)都需要遍歷容器查找,比較耗時(shí)
  • 按最大測(cè)試數(shù)據(jù)量創(chuàng)建容器怯屉,空間消耗巨大
尋找更好的算法思維
  • 要證明某個(gè)節(jié)點(diǎn)是否第二次到達(dá)蔚舀,可否將已遍歷節(jié)點(diǎn)進(jìn)行標(biāo)記?
  • 環(huán)形結(jié)構(gòu)對(duì)應(yīng)生活中的追擊問(wèn)題锨络,可否使用“追擊問(wèn)題”模擬實(shí)現(xiàn)赌躺?
  • 借鑒其它算法
五. Code 編碼實(shí)現(xiàn)最優(yōu)解
解法二:追擊問(wèn)題法 -- 思路分析
  1. 定義快慢兩個(gè)指針:slow = head; fast = head.next;
  2. 遍歷鏈表:
    ? 快指針步長(zhǎng)為 2:fast = fast.next.next;
    ? 慢指針步長(zhǎng)為 1:slow = slow.next;
  3. 當(dāng)且僅當(dāng)快慢指針重合,有環(huán)羡儿,返回 true
  4. 快指針為 null礼患,或其 next 指向 null,沒(méi)有環(huán)掠归,返回 false缅叠,操作結(jié)束
邊界問(wèn)題
  • fast 和 fast.next 指針的非空判斷
  • slow.next 指針不需要非空判斷
    若有環(huán),則始終有:slow.next != null
    若無(wú)環(huán)虏冻,則 fast 或 fast.next 先為空
細(xì)節(jié)問(wèn)題
  • 需要注意:由于 fast 的步長(zhǎng)為 2肤粱,因此 fast == nullfast.next == null 都需要判斷
class Solution {
    public boolean hasCycle(ListNode head) {

        //0.非空判斷
        if (head == null) return false;

        //1.聲明快慢兩個(gè)指針
        ListNode slow = head;
        ListNode fast = head.next;

        //2.利用短路特性,先判斷 fast 是否為空厨相,再判斷 fast.next 是否為空
        while (slow != fast && fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        return slow == fast;
    }
}

時(shí)間復(fù)雜度:O(n) -- 遍歷鏈表 O(n)
空間復(fù)雜度:O(1) -- 兩個(gè)指針變量 O(1)
執(zhí)行耗時(shí):0 ms领曼,擊敗了 100% 的Java用戶
內(nèi)存消耗:38.8 MB鸥鹉,擊敗了 88.78% 的Java用戶

六. Change 變形與延伸
題目變形
  • (練習(xí))分別使用 head 和 head.next 作為鏈表的 快/慢 指針
  • (練習(xí))標(biāo)記值法:對(duì)節(jié)點(diǎn)的 val 屬性進(jìn)行標(biāo)記,賦一個(gè)超出合法范圍的值
  • (練習(xí))hash 表法
延伸擴(kuò)展
  • 龜兔賽跑問(wèn)題悯森,追擊問(wèn)題
  • 地球宋舷,火星與太陽(yáng)連為一線的問(wèn)題
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瓢姻,隨后出現(xiàn)的幾起案子祝蝠,更是在濱河造成了極大的恐慌,老刑警劉巖幻碱,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绎狭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡褥傍,警方通過(guò)查閱死者的電腦和手機(jī)儡嘶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)恍风,“玉大人蹦狂,你說(shuō)我怎么就攤上這事∨蟊幔” “怎么了凯楔?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)锦募。 經(jīng)常有香客問(wèn)我摆屯,道長(zhǎng),這世上最難降的妖魔是什么糠亩? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任虐骑,我火速辦了婚禮,結(jié)果婚禮上赎线,老公的妹妹穿的比我還像新娘廷没。我一直安慰自己,他們只是感情好垂寥,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布颠黎。 她就那樣靜靜地躺著,像睡著了一般矫废。 火紅的嫁衣襯著肌膚如雪盏缤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天蓖扑,我揣著相機(jī)與錄音唉铜,去河邊找鬼。 笑死律杠,一個(gè)胖子當(dāng)著我的面吹牛潭流,可吹牛的內(nèi)容都是我干的竞惋。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼灰嫉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼拆宛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起讼撒,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤浑厚,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后根盒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钳幅,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年炎滞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了敢艰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡册赛,死狀恐怖钠导,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情森瘪,我是刑警寧澤牡属,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站柜砾,受9級(jí)特大地震影響湃望,放射性物質(zhì)發(fā)生泄漏换衬。R本人自食惡果不足惜痰驱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瞳浦。 院中可真熱鬧担映,春花似錦、人聲如沸叫潦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)矗蕊。三九已至短蜕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間傻咖,已是汗流浹背朋魔。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卿操,地道東北人警检。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓孙援,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親扇雕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拓售,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容