在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

DFS算法秒殺五道島嶼系列問題

jf_78858299 ? 來源:labuladong ? 作者:labuladong ? 2023-04-19 10:39 ? 次閱讀

島嶼問題是經典的面試高頻題,雖然基本的島嶼問題并不難,但是島嶼問題有一些有意思的擴展,比如求子島嶼數量,求形狀不同的島嶼數量等等,本文就來把這些問題一網打盡。

島嶼系列問題的核心考點就是用 DFS/BFS 算法遍歷二維數組

本文主要來講解如何用 DFS 算法來秒殺島嶼系列問題,不過用 BFS 算法的核心思路是完全一樣的,無非就是把 DFS 改寫成 BFS 而已。

那么如何在二維矩陣中使用 DFS 搜索呢?如果你把二維矩陣中的每一個位置看做一個節點,這個節點的上下左右四個位置就是相鄰節點,那么整個矩陣就可以抽象成一幅網狀的「圖」結構。

根據 [學習數據結構和算法的框架思維],完全可以根據二叉樹的遍歷框架改寫出二維矩陣的 DFS 代碼框架:

// 二叉樹遍歷框架
void traverse(TreeNode root) {
    traverse(root.left);
    traverse(root.right);
}

// 二維矩陣遍歷框架
void dfs(int[][] grid, int i, int j, boolean[] visited) {
    int m = grid.length, n = grid[0].length;
    if (i < 0 || j < 0 || i >= m || j >= n) {
        // 超出索引邊界
        return;
    }
    if (visited[i][j]) {
        // 已遍歷過 (i, j)
        return;
    }
    // 前序:進入節點 (i, j)
    visited[i][j] = true;
    dfs(grid, i - 1, j); // 上
    dfs(grid, i + 1, j); // 下
    dfs(grid, i, j - 1); // 左
    dfs(grid, i, j + 1); // 右
    // 后序:離開節點 (i, j)
    // visited[i][j] = true;
}

因為二維矩陣本質上是一幅「圖」,所以遍歷的過程中需要一個visited布爾數組防止走回頭路,如果你能理解上面這段代碼,那么搞定所有島嶼問題都很簡單。

這里額外說一個處理二維數組的常用小技巧,你有時會看到使用「方向數組」來處理上下左右的遍歷,和前文 [圖遍歷框架]的代碼很類似:

// 方向數組,分別代表上、下、左、右
int[][] dirs = new int[][]{{-1,0}, {1,0}, {0,-1}, {0,1}};

void dfs(int[][] grid, int i, int j, boolean[] visited) {
    int m = grid.length, n = grid[0].length;
    if (i < 0 || j < 0 || i >= m || j >= n) {
        // 超出索引邊界
        return;
    }
    if (visited[i][j]) {
        // 已遍歷過 (i, j)
        return;
    }

    // 進入節點 (i, j)
    visited[i][j] = true;
    // 遞歸遍歷上下左右的節點
    for (int[] d : dirs) {
        int next_i = i + d[0];
        int next_j = j + d[1];
        dfs(grid, next_i, next_j);
    }
    // 離開節點 (i, j)
    // visited[i][j] = true;
}

這種寫法無非就是用 for 循環處理上下左右的遍歷罷了,你可以按照個人喜好選擇寫法。

島嶼數量

這是力扣第 200 題「島嶼數量」,最簡單也是最經典的一道島嶼問題,題目會輸入一個二維數組grid,其中只包含0或者10代表海水,1代表陸地,且假設該矩陣四周都是被海水包圍著的。

我們說連成片的陸地形成島嶼,那么請你寫一個算法,計算這個矩陣grid中島嶼的個數,函數簽名如下:

int numIslands(char[][] grid);

比如說題目給你輸入下面這個grid有四片島嶼,算法應該返回 4:

圖片

思路很簡單,關鍵在于如何尋找并標記「島嶼」,這就要 DFS 算法發揮作用了,我們直接看解法代碼:

// 主函數,計算島嶼數量
int numIslands(char[][] grid) {
    int res = 0;
    int m = grid.length, n = grid[0].length;
    // 遍歷 grid
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (grid[i][j] == '1') {
                // 每發現一個島嶼,島嶼數量加一
                res++;
                // 然后使用 DFS 將島嶼淹了
                dfs(grid, i, j);
            }
        }
    }
    return res;
}

// 從 (i, j) 開始,將與之相鄰的陸地都變成海水
void dfs(char[][] grid, int i, int j) {
    int m = grid.length, n = grid[0].length;
    if (i < 0 || j < 0 || i >= m || j >= n) {
        // 超出索引邊界
        return;
    }
    if (grid[i][j] == '0') {
        // 已經是海水了
        return;
    }
    // 將 (i, j) 變成海水
    grid[i][j] = '0';
    // 淹沒上下左右的陸地
    dfs(grid, i + 1, j);
    dfs(grid, i, j + 1);
    dfs(grid, i - 1, j);
    dfs(grid, i, j - 1);
}

為什么每次遇到島嶼,都要用 DFS 算法把島嶼「淹了」呢?主要是為了省事,避免維護visited數組

因為dfs函數遍歷到值為0的位置會直接返回,所以只要把經過的位置都設置為0,就可以起到不走回頭路的作用。

PS:這類 DFS 算法還有個別名叫做 [FloodFill 算法],現在有沒有覺得 FloodFill 這個名字還挺貼切的~

這個最最基本的島嶼問題就說到這,我們來看看后面的題目有什么花樣。

封閉島嶼的數量

上一題說二維矩陣四周可以認為也是被海水包圍的,所以靠邊的陸地也算作島嶼。

力扣第 1254 題「統計封閉島嶼的數目」和上一題有兩點不同:

1、用0表示陸地,用1表示海水。

2、讓你計算「封閉島嶼」的數目。所謂「封閉島嶼」就是上下左右全部被1包圍的0,也就是說 靠邊的陸地不算作「封閉島嶼」

函數簽名如下:

int closedIsland(int[][] grid)

比如題目給你輸入如下這個二維矩陣:

圖片

算法返回 2,只有圖中灰色部分的0是四周全都被海水包圍著的「封閉島嶼」。

那么如何判斷「封閉島嶼」呢?其實很簡單,把上一題中那些靠邊的島嶼排除掉,剩下的不就是「封閉島嶼」了嗎

有了這個思路,就可以直接看代碼了,注意這題規定0表示陸地,用1表示海水:

// 主函數:計算封閉島嶼的數量
int closedIsland(int[][] grid) {
    int m = grid.length, n = grid[0].length;
    for (int j = 0; j < n; j++) {
        // 把靠上邊的島嶼淹掉
        dfs(grid, 0, j);
        // 把靠下邊的島嶼淹掉
        dfs(grid, m - 1, j);
    }
    for (int i = 0; i < m; i++) {
        // 把靠左邊的島嶼淹掉
        dfs(grid, i, 0);
        // 把靠右邊的島嶼淹掉
        dfs(grid, i, n - 1);
    }
    // 遍歷 grid,剩下的島嶼都是封閉島嶼
    int res = 0;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (grid[i][j] == 0) {
                res++;
                dfs(grid, i, j);
            }
        }
    }
    return res;
}

// 從 (i, j) 開始,將與之相鄰的陸地都變成海水
void dfs(int[][] grid, int i, int j) {
    int m = grid.length, n = grid[0].length;
    if (i < 0 || j < 0 || i >= m || j >= n) {
        return;
    }
    if (grid[i][j] == 1) {
        // 已經是海水了
        return;
    }
    // 將 (i, j) 變成海水
    grid[i][j] = 1;
    // 淹沒上下左右的陸地
    dfs(grid, i + 1, j);
    dfs(grid, i, j + 1);
    dfs(grid, i - 1, j);
    dfs(grid, i, j - 1);
}

只要提前把靠邊的陸地都淹掉,然后算出來的就是封閉島嶼了。

PS:處理這類島嶼問題除了 DFS/BFS 算法之外,Union Find 并查集算法也是一種可選的方法,前文 [Union Find 算法運用] 就用 Union Find 算法解決了一道類似的問題。

這道島嶼題目的解法稍微改改就可以解決力扣第 1020 題「飛地的數量」,這題不讓你求封閉島嶼的數量,而是求封閉島嶼的面積總和。

其實思路都是一樣的,先把靠邊的陸地淹掉,然后去數剩下的陸地數量就行了,注意第 1020 題中1代表陸地,0代表海水:

int numEnclaves(int[][] grid) {
    int m = grid.length, n = grid[0].length;
    // 淹掉靠邊的陸地
    for (int i = 0; i < m; i++) {
        dfs(grid, i, 0);
        dfs(grid, i, n - 1);
    }
    for (int j = 0; j < n; j++) {
        dfs(grid, 0, j);
        dfs(grid, m - 1, j);
    }

    // 數一數剩下的陸地
    int res = 0;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (grid[i][j] == 1) {
                res += 1;
            }
        }
    }

    return res;
}

// 和之前的實現類似
void dfs(int[][] grid, int i, int j) {
    // ...
}

篇幅所限,具體代碼我就不寫了,我們繼續看其他的島嶼問題。

島嶼的最大面積

這是力扣第 695 題「島嶼的最大面積」,0表示海水,1表示陸地,現在不讓你計算島嶼的個數了,而是讓你計算最大的那個島嶼的面積,函數簽名如下:

int maxAreaOfIsland(int[][] grid)

比如題目給你輸入如下一個二維矩陣:

圖片

其中面積最大的是橘紅色的島嶼,算法返回它的面積 6。

這題的大體思路和之前完全一樣,只不過dfs函數淹沒島嶼的同時,還應該想辦法記錄這個島嶼的面積

我們可以給dfs函數設置返回值,記錄每次淹沒的陸地的個數,直接看解法吧:

int maxAreaOfIsland(int[][] grid) {
    // 記錄島嶼的最大面積
    int res = 0;
    int m = grid.length, n = grid[0].length;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (grid[i][j] == 1) {
                // 淹沒島嶼,并更新最大島嶼面積
                res = Math.max(res, dfs(grid, i, j));
            }
        }
    }
    return res;
}

// 淹沒與 (i, j) 相鄰的陸地,并返回淹沒的陸地面積
int dfs(int[][] grid, int i, int j) {
    int m = grid.length, n = grid[0].length;
    if (i < 0 || j < 0 || i >= m || j >= n) {
        // 超出索引邊界
        return 0;
    }
    if (grid[i][j] == 0) {
        // 已經是海水了
        return 0;
    }
    // 將 (i, j) 變成海水
    grid[i][j] = 0;

    return dfs(grid, i + 1, j)
         + dfs(grid, i, j + 1)
         + dfs(grid, i - 1, j)
         + dfs(grid, i, j - 1) + 1;
}

解法和之前相比差不多,我也不多說了,接下來的兩道島嶼問題是比較有技巧性的,我們重點來看一下。

子島嶼數量

如果說前面的題目都是模板題,那么力扣第 1905 題「統計子島嶼」可能得動動腦子了:

圖片

這道題的關鍵在于,如何快速判斷子島嶼 ?肯定可以借助 [Union Find 并查集算法] 來判斷,不過本文重點在 DFS 算法,就不展開并查集算法了。

什么情況下grid2中的一個島嶼Bgrid1中的一個島嶼A的子島?

當島嶼B中所有陸地在島嶼A中也是陸地的時候,島嶼B是島嶼A的子島。

反過來說,如果島嶼B中存在一片陸地,在島嶼A的對應位置是海水,那么島嶼B就不是島嶼A的子島

那么,我們只要遍歷grid2中的所有島嶼,把那些不可能是子島的島嶼排除掉,剩下的就是子島。

依據這個思路,可以直接寫出下面的代碼:

int countSubIslands(int[][] grid1, int[][] grid2) {
    int m = grid1.length, n = grid1[0].length;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (grid1[i][j] == 0 && grid2[i][j] == 1) {
                // 這個島嶼肯定不是子島,淹掉
                dfs(grid2, i, j);
            }
        }
    }
    // 現在 grid2 中剩下的島嶼都是子島,計算島嶼數量
    int res = 0;
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (grid2[i][j] == 1) {
                res++;
                dfs(grid2, i, j);
            }
        }
    }
    return res;
}

// 從 (i, j) 開始,將與之相鄰的陸地都變成海水
void dfs(int[][] grid, int i, int j) {
    int m = grid.length, n = grid[0].length;
    if (i < 0 || j < 0 || i >= m || j >= n) {
        return;
    }
    if (grid[i][j] == 0) {
        return;
    }

    grid[i][j] = 0;
    dfs(grid, i + 1, j);
    dfs(grid, i, j + 1);
    dfs(grid, i - 1, j);
    dfs(grid, i, j - 1);
}

這道題的思路和計算「封閉島嶼」數量的思路有些類似,只不過后者排除那些靠邊的島嶼,前者排除那些不可能是子島的島嶼。

不同的島嶼數量

這是本文的最后一道島嶼題目,作為壓軸題,當然是最有意思的。

力扣第 694 題「不同的島嶼數量」,題目還是輸入一個二維矩陣,0表示海水,1表示陸地,這次讓你計算 不同的 (distinct) 島嶼數量,函數簽名如下:

int numDistinctIslands(int[][] grid)

比如題目輸入下面這個二維矩陣:

圖片

其中有四個島嶼,但是左下角和右上角的島嶼形狀相同,所以不同的島嶼共有三個,算法返回 3。

很顯然我們得想辦法把二維矩陣中的「島嶼」進行轉化,變成比如字符串這樣的類型,然后利用 HashSet 這樣的數據結構去重,最終得到不同的島嶼的個數。

如果想把島嶼轉化成字符串,說白了就是序列化,序列化說白了遍歷嘛,前文 [二叉樹的序列化和反序列化]講了二叉樹和字符串互轉,這里也是類似的。

首先,對于形狀相同的島嶼,如果從同一起點出發,dfs函數遍歷的順序肯定是一樣的

因為遍歷順序是寫死在你的遞歸函數里面的,不會動態改變:

void dfs(int[][] grid, int i, int j) {
    // 遞歸順序:
    dfs(grid, i - 1, j); // 上
    dfs(grid, i + 1, j); // 下
    dfs(grid, i, j - 1); // 左
    dfs(grid, i, j + 1); // 右
}

所以,遍歷順序從某種意義上說就可以用來描述島嶼的形狀,比如下圖這兩個島嶼:

圖片

假設它們的遍歷順序是:

下,右,上,撤銷上,撤銷右,撤銷下

如果我用分別用1, 2, 3, 4代表上下左右,用-1, -2, -3, -4代表上下左右的撤銷,那么可以這樣表示它們的遍歷順序:

2, 4, 1, -1, -4, -2

你看,這就相當于是島嶼序列化的結果,只要每次使用dfs遍歷島嶼的時候生成這串數字進行比較,就可以計算到底有多少個不同的島嶼了

要想生成這段數字,需要稍微改造dfs函數,添加一些函數參數以便記錄遍歷順序:

void dfs(int[][] grid, int i, int j, StringBuilder sb, int dir) {
    int m = grid.length, n = grid[0].length;
    if (i < 0 || j < 0 || i >= m || j >= n 
        || grid[i][j] == 0) {
        return;
    }
    // 前序遍歷位置:進入 (i, j)
    grid[i][j] = 0;
    sb.append(dir).append(',');

    dfs(grid, i - 1, j, sb, 1); // 上
    dfs(grid, i + 1, j, sb, 2); // 下
    dfs(grid, i, j - 1, sb, 3); // 左
    dfs(grid, i, j + 1, sb, 4); // 右

    // 后序遍歷位置:離開 (i, j)
    sb.append(-dir).append(',');
}

dir記錄方向,dfs函數遞歸結束后,sb記錄著整個遍歷順序,其實這就是前文 [回溯算法核心套路]說到的回溯算法框架,你看到頭來這些算法都是相通的。

有了這個dfs函數就好辦了,我們可以直接寫出最后的解法代碼:

int numDistinctIslands(int[][] grid) {
    int m = grid.length, n = grid[0].length;
    // 記錄所有島嶼的序列化結果
    HashSet

這樣,這道題就解決了,至于為什么初始調用dfs函數時的dir參數可以隨意寫,這里涉及 DFS 和回溯算法的一個細微差別,前文 [圖算法基礎]有寫,這里就不展開了。

以上就是全部島嶼系列問題的解題思路,也許前面的題目大部分人會做,但是最后兩題還是比較巧妙的,希望本文對你有幫助。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 數據結構
    +關注

    關注

    3

    文章

    573

    瀏覽量

    40132
  • DFS
    DFS
    +關注

    關注

    0

    文章

    26

    瀏覽量

    9165
  • BFS
    BFS
    +關注

    關注

    0

    文章

    9

    瀏覽量

    2170
收藏 人收藏

    評論

    相關推薦

    智能硬件欲起飛?必須跨越這五道門檻

    9月20日,筆者受邀參加一個智能硬件線下沙龍,來自智能硬件生產、研發、銷售領域的幾位資深人士分享了自己對行業、產品的看法。筆者結合沙龍分享及自身了解總結出,智能硬件從熱捧到熱賣還差五道坎。##行業發展是有規律的、循序漸進的。大到產業發展,小到一項技術創新、產品創新被用戶廣泛接受,都需要一定的時間。
    發表于 09-23 09:59 ?671次閱讀

    單片機8031三題:三、四、。一題10元

    單片機8031三題:三、四、。一題10元,直接發我qq840921270 ,會給第一個采用的人直接發支付寶,決不食言,食言剁屌!求助大神,小弟謝過了
    發表于 04-16 17:02

    經典算法大全(51個C語言算法+單片機常用算法+機器學十大算法

    、dynamic programming  四、BFS和DFS優先搜索算法  、紅黑樹算法的實現與剖析  (續)、教你透徹了解紅黑樹  
    發表于 10-23 14:31

    PCB生產工藝 | 第五道主流程之圖形轉移

    銜接上文,繼續為朋友們分享普通單雙面板的生產工藝流程。如圖,第五道主流程為圖形轉移。圖形轉移的目的為:利用光化學原理,將圖形線路的形狀轉移到印制板上,再利用化學原理,將圖形線路在印制板上制作
    發表于 02-17 11:46

    安防企業轉型升級過程中還需要攻克這五道關口

    。在這個過程中,對于眾多安防企業來說,選擇成為最重要的事情,企業家眼光既要向外,看到市場變化及趨勢;又要向內,審視企業自身優勢和競爭力。在轉型升級過程中,安防企業需要攻克五道關口。
    發表于 08-27 16:44 ?845次閱讀

    RT-Thread DFS 組件的主要功能特點

    DFS 的層次架構如下圖所示,主要分為 POSIX 接口層、虛擬文件系統層和設備抽象層。
    發表于 07-08 15:29 ?4665次閱讀

    一篇文章秒殺區間相關的問題

    經常有讀者問區間相關的問題,今天寫一篇文章,秒殺區間相關的問題。 所謂區間問題,就是線段問題,讓你合并所有線段、找出線段的交集等等。主要有兩個技巧: 1、排序。常見的排序方法就是按照區間起點排序
    的頭像 發表于 10-12 14:54 ?1900次閱讀
    一篇文章<b class='flag-5'>秒殺</b>三<b class='flag-5'>道</b>區間相關的問題

    網絡通訊廠商:深圳市和芯潤德科技有限公司簡介

    地址:深圳市南山區粵興五道北理工創新大廈三樓A單元
    的頭像 發表于 04-16 11:01 ?1860次閱讀

    秒殺幾道運用Dijkstra算法的題目

    ,變得看起來好像特別復雜,特別牛逼。 但如果你看過歷史文章,應該可以對算法形成自己的理解,就會發現很多算法都是換湯不換藥,毫無新意,非常枯燥。 比如,我們說二叉樹非常重要,你把這個結構掌握了,就會發現 動態規劃,分治算法,回溯(
    的頭像 發表于 09-24 10:59 ?2961次閱讀
    <b class='flag-5'>秒殺</b>幾道運用Dijkstra<b class='flag-5'>算法</b>的題目

    如何用DFS算法秒殺島嶼系列問題

    DFS/BFS 算法遍歷二維數組 。 本文主要來講解如何用 DFS 算法秒殺島嶼
    的頭像 發表于 11-16 17:13 ?1756次閱讀
    如何用<b class='flag-5'>DFS</b><b class='flag-5'>算法</b>來<b class='flag-5'>秒殺</b><b class='flag-5'>島嶼</b><b class='flag-5'>系列</b>問題

    DFS發布全球首個奢侈品旅游零售元宇宙,系列數字藏品限量發行

    中國時間2022年8月1日,世界知名奢侈品旅游零售商DFS集團正式發布全球首個奢華旅行零售元宇宙,牽手國內領先的數字藏品發行、推廣及交易平臺淘派發行系列限量數字藏品。 DFS集團始終以敏銳的潮流意識
    的頭像 發表于 09-06 10:44 ?1506次閱讀

    PCB生產工藝 | 第五道主流程之圖形轉移

    銜接上文,繼續為朋友們分享普通單雙面板的生產工藝流程。 如圖,第五道主流程為圖形轉移。 圖形轉移的目的為: 利用光化學原理,將圖形線路的形狀轉移到印制板上,再利用化學原理,將圖形線路在印制板上制作
    的頭像 發表于 02-16 21:00 ?2219次閱讀

    【生產工藝】第五道主流程之圖形轉移

    銜接上文,繼續為朋友們分享普通單雙面板的生產工藝流程。 如圖,第五道主流程為圖形轉移。 圖形轉移的目的為: 利用光化學原理,將圖形線路的形狀轉移到印制板上,再利用化學原理,將圖形線路在印制板上制作
    的頭像 發表于 04-06 09:20 ?1125次閱讀

    滑動窗口算法解決子串問題教程

    本文詳解「滑動窗口」這種高級雙指針技巧的算法框架,帶你秒殺幾道高難度的子字符串匹配問題。 LeetCode 上至少有 9 題目可以用此方法高效解決。但是有幾道是 VIP 題目,有幾道題目雖不
    的頭像 發表于 04-19 11:06 ?749次閱讀
    滑動窗口<b class='flag-5'>算法</b>解決子串問題教程

    清華五道口全球科技與金融發展學者團來本源量子參觀交流

    9月2日,清華五道口全球科技與金融發展學者們來到本源量子參觀交流。各位學員來到本源量子計算體驗中心,在云中心徐老師的陪同下進行了參觀,不僅看到了國內首批工程化的量子計算機,還通過介紹了解到本源量子在
    的頭像 發表于 09-07 09:48 ?581次閱讀
    清華<b class='flag-5'>五道</b>口全球科技與金融發展學者團來本源量子參觀交流
    主站蜘蛛池模板: 夜福利视频| 免费观看老外特级毛片| 国产精品影视| www.亚洲日本| 欧美成人三级伦在线观看| 夜夜爱夜夜爽| 在线精品国产成人综合第一页| 涩久久| 永久看日本大片免费| 麻豆国产一区二区在线观看| 天天干天天干天天色| 亚洲免费网站在线观看| 日产乱码免费一卡二卡在线| 夜夜骚视频| 亚洲欧美一区二区三区另类| 拍拍拍拍拍拍拍无挡大全免费| 午夜精品久久久久久久2023| 亚洲一区二区三区中文字幕| 日本黄色小视频网站| 五月天亚洲综合| 日本不卡视频一区二区| 亚洲欧洲综合网| 俄罗斯毛片基地| 欧美极品第1页专区| 日本三级在线播放线观看2021| 午夜香蕉视频| 美女下面小内内的沟| 激情五月婷婷网| 1024国产欧美日韩精品| 色综合久久一区二区三区| 午夜国产高清精品一区免费| 日本一区二区三区不卡在线视频| 美女被免费视频网站九色| 黄色视网站| 色香视频首页| 欧美特黄特色aaa大片免费看| 中文字幕第一区| 免费在线黄网站| 港台无码| 日本免费黄色大片| 九九国产在线|