排列是整个数组遍历,组合是从该下标开始遍历后续数据,去重:先排序,然后有相同元素,若前面的元素未使用则给元素也不用,否则会产生重复解,可以借助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