排列是整個數組遍歷,組合是從該下標開始遍歷後續數據,去重:先排序,然後有相同元素,若前面的元素未使用則給元素也不用,否則會產生重複解,可以藉助used數組記錄該下標對應的元素是否使用過了。
目錄
216. 組合總和 III
https://leetcode-cn.com/problems/combination-sum-iii/
找出所有相加之和爲 n 的 k 個數的組合。組合中只允許含有 1 - 9 的正整數,並且每種組合中不存在重複的數字。
說明:所有數字都是正整數。解集不能包含重複的組合。
示例 1:輸入: k = 3, n = 7,輸出: [[1,2,4]]
示例 2:輸入: k = 3, n = 9,輸出: [[1,2,6], [1,3,5], [2,3,4]]
題解
一:與77題的組合思路一樣,不取同樣的值所以start+1。
class Solution(object):
def combinationSum3(self, k, n):
"""
:type k: int
:type n: int
:rtype: List[List[int]]
"""
def DFS(start, tmp_res, k, n):
if len(tmp_res) == k and n == 0:
res.append(tmp_res[:])
return
start += 1
if n < 0 or start > 9 or len(tmp_res) >= k:
return
for i in xrange(start, 10):
residue = n - i
if residue < 0:
break
tmp_res.append(i)
DFS(i, tmp_res, k, residue)
tmp_res.pop()
res = []
if k > 9 or n > 45 or n < k or k <= 0 or n <= 0:
return res
DFS(0, [], k, n)
return res
78. 子集
https://leetcode-cn.com/problems/subsets/
給定一組不含重複元素的整數數組 nums,返回該數組所有可能的子集(冪集)。
說明:解集不能包含重複的子集。
示例:輸入: nums = [1,2,3]
輸出:
[[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]]
題解
一:這道題和組合的思路一樣,區別在與將tmp_res添加入答案。組合添加進答案是達到某一個條件,例如tmp_res的長度到達一個值或者tmp_res滿足其他條件。這邊的tmp_res總會添加進答案。還要注意空集是任何集合的子集也要加入。
class Solution(object):
def subsets(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
def DFS(start, tmp_res):
start += 1
if start >= len(nums):
return
for i in xrange(start, len(nums)):
tmp_res.append(nums[i])
res.append(tmp_res[:])
DFS(i, tmp_res)
tmp_res.pop()
res = [[]]
if not nums:
return res
DFS(-1, [])
return res
90. 子集 II
https://leetcode-cn.com/problems/subsets-ii/
給定一個可能包含重複元素的整數數組 nums,返回該數組所有可能的子集(冪集)。
說明:解集不能包含重複的子集。
示例:輸入: [1,2,2]
輸出:
[[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]]
題解
一:這題遇上題思路幾乎一樣,只不過原數組有重複元素,但是解集不能含重複解,去重還是可以藉助排序。
class Solution(object):
def subsetsWithDup(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
def DFS(start, used, tmp_res, nums):
start = start + 1
if start >= len(nums):
return
for i in xrange(start, len(nums)):
if i > 0 and nums[i] == nums[i - 1] and not used[i - 1]:
continue
tmp_res.append(nums[i])
res.append(tmp_res[:])
used[i] = True
DFS(i, used, tmp_res, nums)
tmp_res.pop()
used[i] = False
res = [[]]
if not nums:
return res
used = [False] * len(nums)
nums = sorted(nums)
DFS(-1, used, [], nums)
return res
401. 二進制手錶
https://leetcode-cn.com/problems/binary-watch/
二進制手錶頂部有 4 個 LED 代表 小時(0-11),底部的 6 個 LED 代表 分鐘(0-59)。每個 LED 代表一個 0 或 1,最低位在右側。
例如,上面的二進制手錶讀取 “3:25”。給定一個非負整數 n 代表當前 LED 亮着的數量,返回所有可能的時間。
示例:輸入: n = 1,返回: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"]
提示:輸出的順序沒有要求。小時不會以零開頭,比如 “01:00” 是不允許的,應爲 “1:00”。分鐘必須由兩位數組成,可能會以零開頭,比如 “10:2” 是無效的,應爲 “10:02”。超過表示範圍(小時 0-11,分鐘 0-59)的數據將會被捨棄,也就是說不會出現 "13:00", "0:61" 等時間。
題解
一:一道經典的組合題,添加進答案必須滿足亮的燈數爲num。小trick,將小時和分鐘拼在一起組成時間數組,前四位是hour,後面的是分鐘,每個下標上的數值是手錶表示數的數值。因爲超出表示範圍的數據會被捨棄,所以不會存在進位的問題,還可以提前終止,例如若分鐘數超出表示範圍,則可以break掉,若小時數超出範圍,由於後面還可以考慮分鐘數,所以僅continue掉。
class Solution(object):
def readBinaryWatch(self, num):
"""
:type num: int
:rtype: List[str]
"""
def DFS(start, hour, minute, num):
if num == 0:
hour_str = str(hour)
if minute < 10:
minute_str = '0' + str(minute)
else:
minute_str = str(minute)
time_str = hour_str + ":" + minute_str
res.append(time_str)
return
start += 1
if start >= len(time):
return
for i in xrange(start, len(time)):
if i < 4 and hour + time[i] >= 12:
continue
if i >= 4 and minute + time[i] >= 60:
break
if i < 4:
DFS(i, hour + time[i], minute, num - 1)
else:
DFS(i, hour, minute + time[i], num - 1)
if num == 0:
return ["0:00"]
res =[]
time = [1, 2, 4, 8, 1, 2, 4, 8, 16, 32]
DFS(-1, 0, 0, num)
return res
79. 單詞搜索
https://leetcode-cn.com/problems/word-search/
給定一個二維網格和一個單詞,找出該單詞是否存在於網格中。單詞必須按照字母順序,通過相鄰的單元格內的字母構成,其中“相鄰”單元格是那些水平相鄰或垂直相鄰的單元格。同一個單元格內的字母不允許被重複使用。
示例:
board =
[['A','B','C','E'],
['S','F','C','S'],
['A','D','E','E']]
給定 word = "ABCCED", 返回 true
給定 word = "SEE", 返回 true
給定 word = "ABCB", 返回 false
提示:board 和 word 中只包含大寫和小寫英文字母。1 <= board.length <= 200,1 <= board[i].length <= 200,1 <= word.length <= 10^3
題解
一:一道經典的回溯題。遞歸的終止條件,要麼已經找到了,要麼這一次通過,即看完了word的最後一個位置,即idx>=len(word)。之前沒有及時停止,時間上沒通過,所以
if idx >= len(word) or self.flag:
self.flag = True
return
通過self.flag來看是否需要及時停止也很重要。
class Solution(object):
def __init__(self):
self.flag = False
def exist(self, board, word):
"""
:type board: List[List[str]]
:type word: str
:rtype: bool
"""
def DFS(x, y, idx, used):
if idx >= len(word) or self.flag:
self.flag = True
return
if (0 <= x < len(board) and 0 <= y < len(board[0])
and not used[x][y] and board[x][y] == word[idx]):
used[x][y] = True
for i in xrange(4):
DFS(x + dx[i], y + dy[i], idx + 1, used)
used[x][y] = False
m, n = len(board), len(board[0])
used = [[False] * n for _ in xrange(m)]
dx = [0, 1, 0, -1]
dy = [1, 0, -1, 0]
for i in xrange(m):
for j in xrange(n):
if self.flag:
return True
if board[i][j] == word[0]:
DFS(i, j, 0, used)
return self.flag
200. 島嶼數量
https://leetcode-cn.com/problems/number-of-islands/
給你一個由 '1'(陸地)和 '0'(水)組成的的二維網格,請你計算網格中島嶼的數量。島嶼總是被水包圍,並且每座島嶼只能由水平方向或豎直方向上相鄰的陸地連接形成。此外,你可以假設該網格的四條邊均被水包圍。
示例 1:
輸入:
11110
11010
11000
00000
輸出: 1
示例 2:
輸入:
11000
11000
00100
00011
輸出: 3
題解
一:floodfill算法,經典的回溯算法,這邊注意used並不需要恢復,因爲我們只要遍歷過的島嶼在後面的循環中都不會再遍歷了,同樣的。
class Solution(object):
def numIslands(self, grid):
"""
:type grid: List[List[str]]
:rtype: int
"""
def DFS(x, y):
if (x < 0 or x >= len(grid) or y < 0 or y >= len(grid[0])
or used[x][y] or grid[x][y] != '1'):
return
used[x][y] = True
for p in pos:
t_x = x + p[0]
t_y = y + p[1]
DFS(t_x, t_y)
res = 0
if not grid or not grid[0]:
return res
m, n = len(grid), len(grid[0])
used = [[False] * n for _ in xrange(m)]
pos = [[0, 1], [1, 0], [0, -1], [-1, 0]]
for i in xrange(m):
for j in xrange(n):
if grid[i][j] == '1' and not used[i][j]:
DFS(i, j)
res += 1
return res
130. 被圍繞的區域
https://leetcode-cn.com/problems/surrounded-regions/
給定一個二維的矩陣,包含 'X' 和 'O'(字母 O)。找到所有被 'X' 圍繞的區域,並將這些區域裏所有的 'O' 用 'X' 填充。
示例:
X X X X
X O O X
X X O X
X O X X
運行你的函數後,矩陣變爲:
X X X X
X X X X
X X X X
X O X X
解釋:
被圍繞的區間不會存在於邊界上,換句話說,任何邊界上的 'O' 都不會被填充爲 'X'。 任何不在邊界上,或不與邊界上的 'O' 相連的 'O' 最終都會被填充爲 'X'。如果兩個元素在水平或垂直方向相鄰,則稱它們是“相連”的。
題解
一:與上題一樣, 只不過這邊多了一個記號self.flag,當遇到邊界的“O”時,標記self.flag標爲False,不需要改變原數組,因爲與邊界相連的“O”不需要改變,當進行了DFS後,只要標記爲True,即表示沒有邊界“O”,需要將這些“O”改爲“X”。
class Solution(object):
def __init__(self):
self.flag = True
def solve(self, board):
"""
:type board: List[List[str]]
:rtype: None Do not return anything, modify board in-place instead.
"""
def DFS(x, y, used):
if (x < 0 or x >= len(board) or y < 0 or y >= len(board[0])
or used[x][y] or board[x][y] == 'X' or not self.flag):
return
if x == 0 or x == len(board) - 1 or y == 0 or y == len(board[0]) - 1:
self.flag = False
return
used[x][y] = True
for p in pos:
t_x = x + p[0]
t_y = y + p[1]
DFS(t_x, t_y, used)
if len(board) <= 2 or len(board[0]) <= 2:
return board
m, n = len(board), len(board[0])
pos = [[0, 1], [1, 0], [0, -1], [-1, 0]]
for i in xrange(1, m - 1):
for j in xrange(1, n - 1):
if board[i][j] == 'O':
self.flag = True
used = [[False] * n for _ in xrange(m)]
DFS(i, j, used)
if self.flag:
for row in xrange(1, m - 1):
for col in xrange(1, n- 1):
if used[row][col]:
board[row][col] = "X"
return
二:對一的優化,從邊界開始,一需要對每一個“O”進行DFS,這邊只需要對邊界的“O”進行DFS。將與邊界的O相鄰的那些O記爲中間標記“P”,那麼剩下來的那些“O”必定不與邊界相連,將這些“O”改成“X”,同時由於與邊界相連的不需要改變,所以將“P”改回“O”。
class Solution(object):
def solve(self, board):
"""
:type board: List[List[str]]
:rtype: None Do not return anything, modify board in-place instead.
"""
def DFS(x, y):
if (x < 0 or x >= len(board) or y < 0 or y >= len(board[0])
or board[x][y] != 'O'):
return
board[x][y] = 'P'
for p in pos:
t_x = x + p[0]
t_y = y + p[1]
DFS(t_x, t_y)
if len(board) <= 2 or len(board[0]) <= 2:
return board
m, n = len(board), len(board[0])
pos = [[0, 1], [1, 0], [0, -1], [-1, 0]]
boundary = [0, n - 1]
for i in xrange(m):
for j in boundary:
if board[i][j] == 'O':
DFS(i, j)
boundary = [0, m - 1]
for i in boundary:
for j in xrange(n):
if board[i][j] == 'O':
DFS(i, j)
for i in xrange(m):
for j in xrange(n):
if board[i][j] == "O":
board[i][j] = "X"
elif board[i][j] == "P":
board[i][j] = "O"
695. 島嶼的最大面積
https://leetcode-cn.com/problems/max-area-of-island/
給定一個包含了一些 0 和 1 的非空二維數組 grid 。一個 島嶼 是由一些相鄰的 1 (代表土地) 構成的組合,這裏的「相鄰」要求兩個 1 必須在水平或者豎直方向上相鄰。你可以假設 grid 的四個邊緣都被 0(代表水)包圍着。找到給定的二維數組中最大的島嶼面積。(如果沒有島嶼,則返回面積爲 0 。)
示例 1:
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
對於上面這個給定矩陣應返回 6。注意答案不應該是 11 ,因爲島嶼只能包含水平或垂直的四個方向的 1 。
示例 2:
[[0,0,0,0,0,0,0,0]]
對於上面這個給定的矩陣, 返回 0。
注意: 給定的矩陣grid 的長度和寬度都不超過 50。
題解
一:這幾題思路都差不多。
class Solution(object):
def maxAreaOfIsland(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
def DFS(i, j, tmp_res):
if (i < 0 or i >= len(grid) or j < 0 or j >= len(grid[0])
or used[i][j] or grid[i][j] == 0):
return
used[i][j] = True
tmp_res[0] += 1
for p in pos:
t_x = i + p[0]
t_y = j + p[1]
DFS(t_x, t_y, tmp_res)
res = 0
if not grid or not grid[0]:
return res
m, n = len(grid), len(grid[0])
used = [[False] * n for _ in xrange(m)]
pos = [[0, 1], [1, 0], [0, -1], [-1, 0]]
for i in xrange(m):
for j in xrange(n):
if grid[i][j] == 1 and not used[i][j]:
tmp_res = [0]
DFS(i, j, tmp_res)
res = max(res, tmp_res[0])
return res
二:其實和法一一模一樣。
class Solution(object):
def maxAreaOfIsland(self, grid):
def DFS(i, j):
tmp_res = 0
if (i < 0 or i >= len(grid) or j < 0 or j >= len(grid[0])
or used[i][j] or grid[i][j] == 0):
return 0
used[i][j] = True
tmp_res += 1
for p in pos:
t_x = i + p[0]
t_y = j + p[1]
tmp_res += DFS(t_x, t_y)
return tmp_res
res = 0
if not grid or not grid[0]:
return res
m, n = len(grid), len(grid[0])
used = [[False] * n for _ in xrange(m)]
pos = [[0, 1], [1, 0], [0, -1], [-1, 0]]
for i in xrange(m):
for j in xrange(n):
if grid[i][j] == 1 and not used[i][j]:
tmp_res = DFS(i, j)
res = max(res, tmp_res)
return res
463. 島嶼的周長
https://leetcode-cn.com/problems/island-perimeter/
給定一個包含 0 和 1 的二維網格地圖,其中 1 表示陸地 0 表示水域。網格中的格子水平和垂直方向相連(對角線方向不相連)。整個網格被水完全包圍,但其中恰好有一個島嶼(或者說,一個或多個表示陸地的格子相連組成的島嶼)。島嶼中沒有“湖”(“湖” 指水域在島嶼內部且不和島嶼周圍的水相連)。格子是邊長爲 1 的正方形。網格爲長方形,且寬度和高度均不超過 100 。計算這個島嶼的周長。
示例 :
輸入:
[[0,1,0,0],
[1,1,1,0],
[0,1,0,0],
[1,1,0,0]]
輸出: 16
解釋: 它的周長是下面圖片中的 16 個黃色的邊:
題解
一:因爲只有一個島嶼,所以我們可以用rec直接統計與每個島嶼相鄰的島嶼個數。沒有相鄰島嶼,結合題意意味着只有一個單位即rec[0]=1,邊長4;若有一個相鄰島嶼,則貢獻3的周長;2個相鄰,則貢獻2的周長;3個相鄰,則貢獻1的周長;4個相鄰,則貢獻0的周長。
class Solution(object):
def islandPerimeter(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
def cnt(i, j):
res = 0
for p in pos:
t_x = i + p[0]
t_y = j + p[1]
if (0 <= t_x < len(grid) and 0 <= t_y < len(grid[0])
and grid[t_x][t_y] == 1):
res += 1
return res
res = 0
if not grid or not grid[0]:
return res
m, n = len(grid), len(grid[0])
pos = [[0, 1], [1, 0], [0, -1], [-1, 0]]
rec = {0: 0, 1:0, 2:0, 3: 0, 4:0}
for i in xrange(m):
for j in xrange(n):
if grid[i][j] == 1:
num = cnt(i, j)
rec[num] += 1
for k, v in rec.items():
res += (4 - k) * v
return res
二:每遇到一個單位的島嶼,假設貢獻4的周長,但若上邊有島嶼相連,即有相鄰邊,周長的貢獻減少2;若左邊有島嶼相連,同有相鄰邊,周長的貢獻減少2。
class Solution(object):
def islandPerimeter(self, grid):
res = 0
if not grid or not grid[0]:
return res
m, n = len(grid), len(grid[0])
for i in xrange(m):
for j in xrange(n):
if grid[i][j] == 1:
res += 4
if i >= 1 and grid[i - 1][j] == 1:
res -= 2
if j >= 1 and grid[i][j - 1] == 1:
res -= 2
return res
三:借鑑大神的DFS方法,描述部分轉自https://leetcode-cn.com/problems/island-perimeter/solution/tu-jie-jian-ji-er-qiao-miao-de-dfs-fang-fa-java-by/,
求島嶼的周長其實有很多種方法,如果用 DFS 遍歷來求的話,有一種很簡單的思路:島嶼的周長就是島嶼方格和非島嶼方格相鄰的邊的數量。注意,這裏的非島嶼方格,既包括水域方格,也包括網格的邊界。我們可以畫一張圖,看得更清晰:
將這個“相鄰關係”對應到 DFS 遍歷中,就是:每當在 DFS 遍歷中,從一個島嶼方格走向一個非島嶼方格,就將周長加 1。代碼如下:
class Solution(object):
def islandPerimeter(self, grid):
def DFS(x, y):
res = 0
# 與邊界相鄰,加1
if (x < 0 or x >= len(grid) or y < 0 or y >= len(grid[0])):
return 1
# 與水域相鄰,加1
if grid[x][y] == 0:
return 1
# 遍歷過,直接返回
if used[x][y]:
return 0
used[x][y] = True
# 考察四個方向
for p in pos:
t_x = x + p[0]
t_y = y + p[1]
res += DFS(t_x, t_y)
return res
if not grid or not grid[0]:
return 0
m, n = len(grid), len(grid[0])
used = [[False] * n for _ in xrange(m)]
pos = [[0, 1], [1, 0], [0, -1], [-1, 0]]
for i in xrange(m):
for j in xrange(n):
if grid[i][j] == 1:
res = DFS(i, j)
return res
733. 圖像渲染
https://leetcode-cn.com/problems/flood-fill/
有一幅以二維整數數組表示的圖畫,每一個整數表示該圖畫的像素值大小,數值在 0 到 65535 之間。給你一個座標 (sr, sc) 表示圖像渲染開始的像素值(行 ,列)和一個新的顏色值 newColor,讓你重新上色這幅圖像。爲了完成上色工作,從初始座標開始,記錄初始座標的上下左右四個方向上像素值與初始座標相同的相連像素點,接着再記錄這四個方向上符合條件的像素點與他們對應四個方向上像素值與初始座標相同的相連像素點,……,重複該過程。將所有有記錄的像素點的顏色值改爲新的顏色值。最後返回經過上色渲染後的圖像。
示例 1:
輸入:
image = [[1,1,1],[1,1,0],[1,0,1]]
sr = 1, sc = 1, newColor = 2
輸出: [[2,2,2],[2,2,0],[2,0,1]]
解析: 在圖像的正中間,(座標(sr,sc)=(1,1)),在路徑上所有符合條件的像素點的顏色都被更改成2。注意,右下角的像素沒有更改爲2,因爲它不是在上下左右四個方向上與初始點相連的像素點。
注意:
image 和 image[0] 的長度在範圍 [1, 50] 內。
給出的初始點將滿足 0 <= sr < image.length 和 0 <= sc < image[0].length。
image[i][j] 和 newColor 表示的顏色值在範圍 [0, 65535]內。
題解
一:經典的DFS。這邊用used來標記那些與sr和sc同色的單元,退出後,將used爲True的置爲新值,其餘的賦上原圖像對應位置的值。
class Solution(object):
def floodFill(self, image, sr, sc, newColor):
"""
:type image: List[List[int]]
:type sr: int
:type sc: int
:type newColor: int
:rtype: List[List[int]]
"""
def DFS(x, y, color):
if (x < 0 or x>= len(image) or y < 0 or y >= len(image[0])
or image[x][y] != color or used[x][y]):
return
used[x][y] = True
for p in pos:
t_x = x + p[0]
t_y = y + p[1]
DFS(t_x, t_y, color)
m, n = len(image), len(image[0])
used = [[False] * n for _ in xrange(m)]
pos = [[0, 1], [1, 0], [0, -1], [-1, 0]]
DFS(sr, sc, image[sr][sc])
new_image =[[0] * n for _ in range(m)]
for i in xrange(m):
for j in xrange(n):
new_image[i][j] = newColor if used[i][j] else image[i][j]
return new_image
二:不借助額外空間,遇到與sr和sc同色的單元直接置爲新值,但是這樣有一種情況會退不出遞歸,那就是原值與新值相等的情況,其實這邊我們省略了used數組,而是用是否是原值來判斷是否遍歷過,因爲每次遍歷都會將原值改爲新值,但是二者相等,無法分辨是否遍歷過,所以要單拎出來。同時易知二者相等,其實無需改變,可直接返回。
if color == newColor:
return new_image
class Solution(object):
def floodFill(self, image, sr, sc, newColor):
def DFS(x, y, color, newColor):
if (x < 0 or x>= len(image) or y < 0 or y >= len(image[0])
or new_image[x][y] != color):
return
new_image[x][y] = newColor
for p in pos:
t_x = x + p[0]
t_y = y + p[1]
DFS(t_x, t_y, color, newColor)
m, n = len(image), len(image[0])
used = [[False] * n for _ in xrange(m)]
pos = [[0, 1], [1, 0], [0, -1], [-1, 0]]
new_image =[[image[i][j] for j in xrange(n)] for i in range(m)]
color = image[sr][sc]
if color == newColor:
return new_image
DFS(sr, sc, color, newColor)
return new_image