Leetcode 18 java

?????1???????141. 環形鏈表1 題目

?????1???????141. 環形鏈表

給你一個鏈表的頭節點?head?,判斷鏈表中是否有環。

如果鏈表中有某個節點,可以通過連續跟蹤?next?指針再次到達,則鏈表中存在環。 為了表示給定鏈表中的環,評測系統內部使用整數?pos?來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。注意:pos?不作為參數進行傳遞?。僅僅是為了標識鏈表的實際情況。

如果鏈表中存在環?,則返回?true?。 否則,返回?false?。

示例 1:

輸入:head = [3,2,0,-4], pos = 1
輸出:true
解釋:鏈表中有一個環,其尾部連接到第二個節點。

示例?2:

輸入:head = [1,2], pos = 0
輸出:true
解釋:鏈表中有一個環,其尾部連接到第一個節點。

示例 3:

輸入:head = [1], pos = -1
輸出:false
解釋:鏈表中沒有環。

提示:

  • 鏈表中節點的數目范圍是?[0, 104]
  • -105 <= Node.val <= 105
  • pos?為?-1?或者鏈表中的一個?有效索引?。

進階:你能用?O(1)(即,常量)內存解決此問題嗎?

2 我的想法

判斷環的思想就是,用一個追蹤指針從頭遍歷到尾指針,存在環的意思是:如果在pos所指示的位置有追蹤指針,那么追蹤指針和尾指針會交匯。

也就是說:

1.如果有環,尾指針指向pos所在節點

2.如果沒有環,第一種情況是單個元素,第二種情況是尾指針一定不指向pos所在節點

/*** Definition for singly-linked list.* class ListNode {*     int val;*     ListNode next;*     ListNode(int x) {*         val = x;*         next = null;*     }* }*/
public class Solution {public boolean hasCycle(ListNode head) {}
}

難道寫一個尾指針.next是否與pos節點一致的判斷就好了嗎?

具體要怎么實現呢,其實鏈表對應節點所在val值要是一樣的呢?我怎么表示pos節點,我這樣的想法可行嗎,如何寫出合適的java代碼?如果是用遍歷的話,空間復雜度是O(1),時間復雜度是O(n)對嗎?寫一個for循環加斷?

3 實現

關于你的想法的分析

你提到 "寫一個尾指針.next 是否與 pos 節點一致的判斷",這個思路存在幾個問題:

  1. pos 不可用:題目明確說明 pos 是系統內部使用的,不會作為參數傳遞給我們的函數,所以我們無法直接獲取和使用 pos 值
  2. 尾指針不存在:在有環的鏈表中,不存在傳統意義上的 "尾指針"(因為鏈表會循環,沒有終點)
  3. val 值可能重復:如你所擔心的,不同節點可能有相同的 val 值,無法通過 val 來判斷是否是同一個節點

所以我們需要一種不依賴 pos、不依賴節點值、且空間效率高的方法。

詳細解題思路

最經典的解法是 "快慢指針法",也叫 "龜兔賽跑算法",思路如下:

  1. 指針初始化

    • 定義兩個指針,都從鏈表頭部出發
    • 慢指針 (slow) 每次只移動 1 步
    • 快指針 (fast) 每次移動 2 步
  2. 核心邏輯

    • 如果鏈表中沒有環:快指針會先到達鏈表末尾(指向 null)
    • 如果鏈表中有環:兩個指針最終一定會在環中相遇
      • 因為快指針比慢指針移動得快,進入環后會逐漸追上慢指針
  3. 終止條件

    • 若快指針到達 null,返回 false(無環)
    • 若快慢指針相遇,返回 true(有環)

算法為什么有效?

假設鏈表存在環,當慢指針剛進入環時,快指針已經在環內某個位置。此時可以看作快指針在追趕慢指針:

  • 快指針每次比慢指針多走 1 步
  • 相當于慢指針靜止,快指針以每秒 1 步的速度追趕
  • 只要在環內,快指針最終一定會追上慢指針
/*** Definition for singly-linked list.* class ListNode {*     int val;*     ListNode next;*     ListNode(int x) {*         val = x;*         next = null;*     }* }*/
public class Solution {public boolean hasCycle(ListNode head) {// 處理邊界情況:空鏈表一定沒有環if (head == null) {return false;}// 初始化快慢指針ListNode slow = head;      // 慢指針,每次走1步ListNode fast = head.next; // 快指針,每次走2步// 當快慢指針不相等時繼續循環while (slow != fast) {// 快指針到達末尾,說明沒有環// 注意要先判斷fast是否為null,再判斷fast.nextif (fast == null || fast.next == null) {return false;}// 慢指針移動1步slow = slow.next;// 快指針移動2步fast = fast.next.next;}// 循環結束,說明快慢指針相遇,存在環return true;}
}

復雜度分析

  • 時間復雜度:O (n),其中 n 是鏈表中的節點數

    • 無環情況:快指針會遍歷 n/2 個節點就到達末尾
    • 有環情況:最壞情況下,兩個指針在環中最多遍歷 n 個節點就會相遇
  • 空間復雜度:O (1),只使用了兩個指針,不隨鏈表長度變化,滿足題目進階要求

其他可能的解法(但不滿足 O (1) 空間)

  1. 哈希表法

    • 遍歷鏈表,將每個節點存入哈希表
    • 每次訪問節點前先檢查哈希表,如果已存在則有環
    • 空間復雜度 O (n),不滿足進階要求
  2. 標記法

    • 遍歷鏈表時給每個訪問過的節點做標記
    • 如果遇到已標記的節點則有環
    • 需要修改節點結構,實際中不常用

綜上,快慢指針法是解決這個問題的最優解,既高效又節省空間。

4 兩種解法

先回顧核心問題:為什么判斷鏈表有環不能靠 “節點值” 或 “pos”?

在理解方法前,先明確兩個關鍵前提(也是你之前擔心的點):

  1. pos 是 “系統內部標記”,我們拿不到:題目里的 pos 只是用來給我們看 “環接在哪”,實際寫代碼時,函數參數只有head,根本沒有 pos 這個值,所以沒法用 “尾指針.next == pos 節點” 這種思路。
  2. 節點值可能重復,不能作為判斷依據:比如鏈表是1→2→1→2(有環),兩個 “1” 節點值相同,但卻是不同節點,靠值判斷會誤判;反之,不同節點值也可能組成環,所以 “值” 完全不可靠。

這也是為什么我們需要「哈希表」和「快慢指針」這兩種不依賴 “值” 或 “pos” 的方法 —— 它們判斷的是 “節點本身是否被重復訪問”。

方法一:哈希表法 ——“給走過的節點拍個照,再遇到就是有環”

核心思路:用 “哈希表” 當 “相冊”,記錄所有走過的節點

就像你逛公園時,每經過一個景點就拍張照存手機里。如果逛著逛著,發現眼前的景點 “手機里早就有照片了”,說明你繞回了之前的路(有環);如果一直走到公園出口(鏈表末尾,head == null),都沒重復照片,說明沒環。

步驟拆解(對應代碼)

我們用 Java 的HashSet(哈希表的一種)來實現 “相冊”,因為HashSet有個特性:添加重復元素時會返回false,這正好幫我們判斷 “是否見過這個節點”。

public class Solution {public boolean hasCycle(ListNode head) {// 1. 初始化哈希表(相冊),存的是“節點本身”,不是節點值!Set<ListNode> seen = new HashSet<ListNode>();// 2. 遍歷鏈表:只要當前節點不為null(沒走到出口),就繼續走while (head != null) {// 3. 嘗試把當前節點加入哈希表://    - 如果添加失敗(返回false),說明之前見過這個節點→有環,返回true//    - 如果添加成功,說明是第一次見,繼續往下走if (!seen.add(head)) {return true;}// 4. 移動到下一個節點(逛下一個景點)head = head.next;}// 5. 走出循環說明head == null(走到出口),沒環,返回falsereturn false;}
}
關鍵細節:為什么存 “ListNode” 而不是 “val”?
  • ListNode(節點對象):每個節點對象在內存中都有唯一的 “地址”,哈希表判斷重復時,比較的是 “地址”,能確保 “同一個節點才會被判定為重復”。
  • val(節點值):如之前說的,值可能重復,會導致 “不同節點被誤判為重復”,比如1→2→1(無環),第二個 “1” 會被誤判為重復,返回錯誤的true
復雜度理解
  • 時間復雜度 O (N):最壞情況是 “鏈表無環”,我們要把所有 N 個節點都遍歷一遍,每個節點的 “添加” 和 “判斷” 操作在哈希表中是 O (1),所以總時間是 O (N)。
  • 空間復雜度 O (N):最壞情況是 “鏈表無環”,我們要把 N 個節點都存進哈希表,所以空間是 O (N)—— 這也是它的缺點,不如快慢指針省空間。

方法二:快慢指針法(Floyd 判圈算法)——“讓兔子和烏龜賽跑,追上就是有環”

核心思路:用兩個速度不同的指針,模擬 “兔子(快)” 和 “烏龜(慢)” 在鏈表上跑
  • 如果鏈表沒環:兔子跑得比烏龜快,會先跑到鏈表末尾(fast == nullfast.next == null),永遠追不上烏龜。
  • 如果鏈表有環:兔子會先進入環,然后在環里繞圈;等烏龜也進入環后,兔子因為速度快,總會在某個時刻追上烏龜(兩個指針指向同一個節點)。
步驟拆解(對應代碼)

先明確指針規則:

  • 慢指針(烏龜):每次走 1 步(slow = slow.next
  • 快指針(兔子):每次走 2 步(fast = fast.next.next
public class Solution {public boolean hasCycle(ListNode head) {// 1. 處理邊界:空鏈表(head==null)或只有1個節點(head.next==null),肯定沒環if (head == null || head.next == null) {return false;}// 2. 初始化指針:慢指針從head出發,快指針從head.next出發(關鍵細節,后面解釋)ListNode slow = head;ListNode fast = head.next;// 3. 循環:只要快慢指針沒相遇,就繼續跑while (slow != fast) {// 4. 檢查快指針是否到末尾:如果fast或fast.next是null,說明沒環if (fast == null || fast.next == null) {return false;}// 5. 烏龜走1步,兔子走2步slow = slow.next;fast = fast.next.next;}// 6. 跳出循環→快慢指針相遇,說明有環,返回truereturn true;}
}
關鍵細節 1:為什么初始時快指針要從head.next出發,而不是和慢指針一起從head出發?

這是為了適配while循環的 “先判斷,后執行” 邏輯:

  • 如果兩個指針都從head出發:初始時slow == fastwhile (slow != fast)的條件直接不滿足,循環不執行,直接返回false—— 但此時如果鏈表有環(比如head自己指向自己),就會誤判。
  • 快指針從head.next出發:初始時slow = headfast = head.nextslow != fast,循環能正常開始;即使鏈表是head自環(head.next = head),第一次循環時:
    • slow會走到head.next = head
    • fast會走到head.next.next = head.next = head
    • 此時slow == fast,跳出循環返回true,判斷正確。

如果想用 “兩個指針都從head出發”,可以把while改成do-while(先執行,后判斷),代碼如下(邏輯完全等價,只是循環方式不同):

public boolean hasCycle(ListNode head) {if (head == null) return false;ListNode slow = head, fast = head;// do-while:先移動,再判斷是否相遇do {// 快指針到末尾,沒環if (fast == null || fast.next == null) return false;slow = slow.next;fast = fast.next.next;} while (slow != fast);// 相遇,有環return true;
}
關鍵細節 2:為什么快指針走 2 步,不是 3 步、4 步?

核心是 “快指針速度比慢指針快”,走 2 步是最簡潔的選擇:

  • 走 2 步時,每次循環快慢指針的距離會減少 1(比如初始距離 1,下次距離 0;初始距離 2,下次距離 1,再下次距離 0),必然會相遇。
  • 走 3 步、4 步也能實現,但代碼會更復雜(比如要多判斷fast.next.next是否為 null),且沒有性能優勢,所以 2 步是最優選擇。
復雜度理解
  • 時間復雜度 O (N)
    • 無環情況:快指針走 N/2 步就到末尾(因為每次走 2 步),時間 O (N)。
    • 有環情況:最壞是 “環很小,烏龜進環前要走很多步”,但總體來看,快指針最多繞環 1 圈就能追上烏龜,總步數還是 O (N)。
  • 空間復雜度 O (1):只用到slowfast兩個指針,不管鏈表多長,都只占 2 個指針的空間,完全符合題目 “O (1) 內存” 的進階要求。

兩種方法的對比:怎么選?

對比維度哈希表法快慢指針法
核心邏輯記錄已訪問節點,看是否重復速度差導致相遇
時間復雜度O(N)O(N)
空間復雜度O (N)(需要存節點)O (1)(只需要兩個指針)
代碼簡潔度較簡潔(依賴哈希表 API)稍復雜(需處理指針初始化)
適用場景除了判斷環,還想記錄節點只需要判斷環,追求省空間

實際面試中,快慢指針法是更優的選擇—— 因為它滿足 O (1) 空間,也是面試官更想考察的思路(能體現對鏈表特性的深入理解);哈希表法更偏向 “直觀思路”,適合作為輔助理解的方法。

通過這兩個方法的拆解,你應該能徹底明白 “判斷鏈表有環” 的核心邏輯了~ 下次遇到類似問題,不管是用哈希表還是快慢指針,都能清晰地寫出代碼啦!

【解釋:if (!seen.add(head))】

我們來詳細解釋一下你看不懂的這行代碼:if (!seen.add(head))

這句話的作用

這句話的意思是:嘗試把當前節點head添加到哈希表seen中,如果添加失敗(說明這個節點已經在哈希表中存在了),就返回true(表示有環)

逐步拆解

  1. seen是一個HashSet<ListNode>類型的集合,用來存儲我們已經訪問過的節點

  2. seen.add(head)這個方法的特性:

    • 當我們往 HashSet 中添加元素時,如果這個元素之前不存在,就會添加成功,返回true
    • 如果這個元素之前已經存在,就會添加失敗,返回false
  3. !是邏輯非運算符,用來取反:

    • 如果seen.add(head)返回true(添加成功,是新節點),那么!true就是false,不會進入 if 語句
    • 如果seen.add(head)返回false(添加失敗,節點已存在),那么!false就是true,會進入 if 語句,執行return true

舉個生活化的例子

這就像我們去游樂園玩,每個景點門口有個登記簿:

  • 第一次到某個景點,我們在登記簿上寫下名字(添加成功),繼續游玩
  • 如果走到一個景點,發現登記簿上已經有我們的名字了(添加失敗),說明我們繞了一圈又回到了曾經去過的地方,這就證明游樂園的路線是環形的

整個代碼的邏輯流程

  1. 從頭節點開始遍歷鏈表
  2. 每到一個節點,就嘗試把它加入哈希表
  3. 如果添加失敗(節點已存在),說明有環,返回true
  4. 如果添加成功,就繼續訪問下一個節點
  5. 直到遍歷完所有節點(head == null),說明沒有環,返回false???????

這樣通過哈希表記錄訪問過的節點,就能判斷鏈表是否存在環了,是不是很容易理解呢?

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

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

相關文章

Linux 正則表達式詳解(基礎 + 擴展 + 實操)

Linux 正則表達式詳解&#xff08;基礎 擴展 實操&#xff09; 正則表達式&#xff08;Regular Expression&#xff0c;簡稱 RE&#xff09;是 Linux 文本處理的核心工具&#xff0c;用于定義字符匹配模式&#xff0c;配合 grep、sed、awk 等工具可實現文本過濾、查找、替換等…

Json-rpc通信項目(基于C++ Jsoncpp muduo庫)

一、介紹RPC RPC&#xff08;Remote Procedure Call&#xff09;遠程過程調用&#xff0c;一種通過網絡從遠程計算器上請求服務&#xff0c;而不需要了解底層網絡通信細節&#xff0c;RPC可以使用多種網絡協議進行通信&#xff0c;并且在TCP/IP網絡四層模型中跨越了傳輸層和應…

RL【9】:Policy Gradient

系列文章目錄 Fundamental Tools RL【1】&#xff1a;Basic Concepts RL【2】&#xff1a;Bellman Equation RL【3】&#xff1a;Bellman Optimality Equation Algorithm RL【4】&#xff1a;Value Iteration and Policy Iteration RL【5】&#xff1a;Monte Carlo Learnin…

Redis是什么?一篇講透它的定位、特點與應用場景

Redis是什么&#xff1f;一篇講透它的定位、特點與應用場景 1. Redis的定義與核心概念 1.1 什么是Redis&#xff1f; Redis&#xff08;Remote Dictionary Server&#xff09; 是一個開源的、基于內存的數據結構存儲系統&#xff0c;可以用作數據庫、緩存和消息代理。Redis由…

一款免費開源輕量的漏洞情報系統 | 漏洞情報包含:組件漏洞 + 軟件漏洞 + 系統漏洞

工具介紹 bug_search一款免費開源輕量的漏洞情報系統 基于python3 Amis2.9 開發&#xff0c;僅依賴Flask,requests&#xff0c;無需數據庫&#xff0c;Amis是百度開源的低代碼前端框架漏洞情報包含&#xff1a;組件漏洞 軟件漏洞 系統漏洞 增加郵件發送消息報警功能增加釘釘…

詳解在Windows系統中生成ssl證書,實現nginx配置https的方法

目錄一、下載安裝OpenSSL二、證書生成三、修改nginx配置總結Nginx 是一個高性能的HTTP和反向代理web服務器&#xff0c;在進行web項目開發時&#xff0c;大多都是使用nginx對外提供web服務。HTTPS &#xff08;全稱&#xff1a;Hypertext Transfer Protocol Secure [5]&#xf…

AI視覺算法中的OpenCV API (二)

視頻寫入 (FourCC, VideoWriter)? 1. VideoWriter_fourcc - 視頻編碼器四字符代碼 # OpenCV 3.x, 4.x fourcc cv2.VideoWriter_fourcc(M,J,P,G)fourcc cv2.VideoWriter_fourcc(*H264)fourcc cv2.VideoWriter_fourcc(*MJPG) ?FourCC?&#xff1a; 代表 ?Four ?Charac…

分享| 2025年版AIGC數字人實驗室解決方案教學資源解析

AIGC數字人實驗室解決方案構建了涵蓋基礎層、平臺環境層與資源層的多層次教學架構&#xff0c;依托150平方米的實體空間與60人并行授課的規模化支持&#xff0c;為學生提供了技術實踐與創新的高效平臺。其教學資源體系覆蓋AIGC文本生成、圖像生成、數字人應用與智能體開發四大核…

內存大(巨)頁

一、大&#xff08;巨&#xff09;頁 大&#xff08;巨&#xff09;頁&#xff0c;很好理解&#xff0c;就是的大的頁。說這個大頁前&#xff0c;得先把計算機中內存的管理簡單說明一下&#xff0c;否則可能對于一些新手或者把操作系統中內存管理的方法的開發者不太友好。最早的…

langgraph astream使用詳解

langgraph中graph的astream&#xff08;stream&#xff09;方法分別實現異步&#xff08;同步&#xff09;流式應答&#xff0c;在langgraph-api服務也是核心方法&#xff0c;實現與前端的對接&#xff0c;必須要把這個方法弄明白。該方法中最重要的參數是stream_mode&#xff…

【C++】模板進階:非類型參數、模板特化與分離編譯

目錄 1. 非類型模板參數 2. 模板的特化 3. 分離編譯 1. 非類型模板參數 模板參數分類類型形參與非類型形參。 類型形參即&#xff1a;出現在模板參數列表中&#xff0c;跟在class或者typename之類的參數類型名稱。 非類型形參&#xff0c;就是用一個常量作為類(函數)模板…

棧-1047.刪除字符串中的所有相鄰重復項-力扣(LeetCode)

一、題目解析 1、反復執行重復項刪除操作 2、s僅由小寫英文字母組成 二、算法原理 該題并不難&#xff0c;難的是能不能想到用棧這個數據結構解題 解法&#xff1a;棧模擬 橫著看起來不好理解&#xff0c;我們把它豎起來&#xff0c;是不是和消消樂很類似&#xff0c;兩兩消…

【每日算法】移除元素 LeetCode

雙指針方法是解決數組或鏈表問題中非常高效的技巧之一&#xff0c;尤其適用于原地修改數組或減少時間復雜度的場景。以下是對雙指針方法的詳細講解。1. 雙指針方法的核心思想雙指針方法通常使用兩個指針&#xff08;或索引&#xff09;在數組或鏈表中協同工作&#xff0c;通過一…

Android 項目:畫圖白板APP開發(六)——分頁展示

本篇將介紹如何為我們的畫板應用添加分頁展示功能&#xff0c;讓用戶可以創建多個畫布并在它們之間輕松切換。這章沒有啥知識點的講解&#xff0c;主要介紹一下每頁保存的數據結構是什么樣的。 一、ListView 多頁數據的管理我們使用ListView。之前有文章講過ListView這里就不多…

智能眼鏡產品成熟度分析框架與評估

引言 當前(2025年9月12日),智能眼鏡(Smart Glasses)市場正處于快速演進階段,從早期的新奇設備向主流消費電子轉型。AI整合、AR顯示和多模態交互的進步推動了這一轉變。根據最新數據,2025年AI眼鏡發貨量預計達686萬臺,同比增長265%,全球市場規模從2024年的約19.3億美元…

(網絡編程)網絡編程套接字 UDP的socket API 代碼解析

網絡編程基礎 為什么需要網絡編程?--豐富的網絡資源 用戶在瀏覽器中,打開在線視頻網站,如優酷看視頻,實質是通過網絡,獲取到網絡上的一個視頻資源。與本地打開視頻文件類似,只是視頻文件這個資源的來源是網絡。 相比本地資源來說,網絡提供了更為豐富的網絡資源:所謂的網絡資源…

【STM32】狀態機(State Machine)

這篇博客介紹 狀態機&#xff08;State Machine&#xff09;&#xff0c;適合用于嵌入式開發、驅動開發、協議解析、按鍵識別等多種場景。 一、什么是狀態機&#xff08;State Machine&#xff09;&#xff1f; 狀態機&#xff08;State Machine&#xff09;是一種用于描述系統…

深度學習在離崗檢測中的應用

離崗檢測技術正逐步成為現代企業精細化管理和安全生產的重要工具。這項基于計算機視覺和人工智能的應用&#xff0c;通過自動化、實時化的監測方式&#xff0c;有效提升了工作紀律性和運營效率&#xff0c;為項目管理者和企業提供了創新的監管解決方案。在許多工作場景中&#…

Spring緩存(二):解決緩存雪崩、擊穿、穿透問題

1. 緩存穿透問題與解決方案 1.1 什么是緩存穿透 緩存穿透是指查詢一個不存在的數據&#xff0c;由于緩存中沒有這個數據&#xff0c;每次請求都會直接打到數據庫。 如果有惡意用戶不斷請求不存在的數據&#xff0c;就會給數據庫帶來巨大壓力。 這種情況下&#xff0c;緩存失去了…

PHP 與 WebAssembly 的 “天然隔閡”

WebAssembly&#xff08;簡稱 WASM&#xff09;是一種低級二進制指令格式&#xff0c;旨在為高級語言提供高性能的編譯目標&#xff0c;尤其在瀏覽器環境中實現接近原生的執行效率。它主要用于前端性能密集型場景&#xff08;如游戲引擎、視頻編解碼、3D 渲染等&#xff09;&am…