排列的数目
给定一个由 不同 正整数组成的数组 nums ,和一个目标整数 target 。请从 nums 中找出并返回总和为 target 的元素组合的个数。数组中的数字可以在一次排列中出现任意次,但是顺序不同的序列被视作不同的组合。
题目数据保证答案符合 32 位整数范围。
分析:
动态规划问题。定义大小为target+1的一维数组dp,dp[i]表示目标整数为i时,nums中数字可以产生的组合个数,初始化dp[0] = 1。递推关系为:遍历数组中的数字num,如果当前的num<=i,则有dp[i] += dp[i-num]。
class Solution {
public int combinationSum4(int[] nums, int target) {
int n = nums.length;
int[] dp = new int[target+1];
dp[0] = 1;
for (int i = 1; i <= target ; i++) {
for (int num : nums){
if (num<=i){
dp[i] += dp[i-num];
}
}
}
return dp[target];
}
}
岛屿的最大面积
给定一个由 0 和 1 组成的非空二维数组 grid ,用来表示海洋岛屿地图。一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。找到给定的二维数组中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
分析:
采用dfs来解决。首先对grid进行遍历,如果当前grid[i][j] = 1,则从该点开始进行dfs。定义int dfs(grid, x, y),x和y分别代表着当前的横纵坐标。如果当前grid[x][y] = 0,则返回0;如果grid[x][y]=1,ans=1,然后将grid[x][y]置位0表示已经搜索过了。然后向上下左右四个方向进行搜索,搜索的时候要注意进行越界的判断,最后返回的ans = dfs(grid, x+1, y)+dfs(grid, x-1, y)+dfs(grid, x, y+1)+dfs(grid, x, y-1)。遍历完grid之后返回dfs过程中的最大值。
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int ans = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
ans = Math.max(ans, dfs(grid, i, j));
}
}
return ans;
}
public int dfs(int[][] grid, int x, int y){
if (x<0||y<0||x>=grid.length||y>=grid[0].length||grid[x][y] == 0){
return 0;
}
grid[x][y] = 0;
int ans = 1;
ans += dfs(grid, x+1, y);
ans += dfs(grid, x-1, y);
ans += dfs(grid, x, y+1);
ans += dfs(grid, x, y-1);
return ans;
}
}
二分图
存在一个 无向图 ,图中有 n 个节点。其中每个节点都有一个介于 0 到 n - 1 之间的唯一编号。给定一个二维数组 graph ,表示图,其中 graph[u] 是一个节点数组,由节点 u 的邻接节点组成。形式上,对于 graph[u] 中的每个 v ,都存在一条位于节点 u 和节点 v 之间的无向边。该无向图同时具有以下属性:
- 不存在自环(graph[u] 不包含 u)。
- 不存在平行边(graph[u] 不包含重复值)。
- 如果 v 在 graph[u] 内,那么 u 也应该在 graph[v] 内(该图是无向图)
- 这个图可能不是连通图,也就是说两个节点 u 和 v 之间可能不存在一条连通彼此的路径。
二分图 定义:如果能将一个图的节点集合分割成两个独立的子集 A 和 B ,并使图中的每一条边的两个节点一个来自 A 集合,一个来自 B 集合,就将这个图称为 二分图 。如果图是二分图,返回 true ;否则,返回 false 。
分析:
染色问题。假设现将一个起点v染成红色,然后把graph[u]中的点全部染成绿色,接着把graph[u]中所有点的邻接点全部染成红色…不断重复知道所有点都染完,则表明该图是二分图;但如果在某一步骤中想要将点染成红色或者绿色时,该点已经是绿色或者红色,则代表该图不是二分图,直接返回false。
class Solution {
private static final int UNCOLORED = 0;
private static final int RED = 1;
private static final int GREEN = 2;
private int[] color;
private boolean valid;
public boolean isBipartite(int[][] graph) {
int n = graph.length;
valid = true;
color = new int[n];
Arrays.fill(color, UNCOLORED);
for (int i = 0; i < n && valid; ++i) {
if (color[i] == UNCOLORED) {
dfs(i, RED, graph);
}
}
return valid;
}
public void dfs(int node, int c, int[][] graph) {
color[node] = c;
int cNei = c == RED ? GREEN : RED;
for (int neighbor : graph[node]) {
if (color[neighbor] == UNCOLORED) {
dfs(neighbor, cNei, graph);
if (!valid) {
return;
}
} else if (color[neighbor] != cNei) {
valid = false;
return;
}
}
}
}
矩阵中的距离
给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。
输入:mat = [[0,0,0],[0,1,0],[1,1,1]]
输出:[[0,0,0],[0,1,0],[1,2,1]]
分析:
采用dfs,与上面的岛屿类问题相似,不过这里需要额外在定义一个数组visited来表示当前节点有没有被访问过。
class Solution {
int min;
boolean[][] visited;
public int[][] updateMatrix(int[][] mat) {
int[][] res = new int[mat.length][mat[0].length];
visited = new boolean[mat.length][mat[0].length];
for (int i = 0; i < mat.length; i++) {
for (int j = 0; j < mat[0].length; j++) {
if (mat[i][j] == 1){
min = Integer.MAX_VALUE;
dfs(mat, i, j,0);
res[i][j] = min;
}
}
}
return res;
}
public void dfs(int[][] mat, int x, int y, int dis){
if (x<0||y<0||x>= mat.length||y>=mat[0].length||visited[x][y]) return;
if (mat[x][y] == 0){
min = Math.min(min, dis);
return;
}
if (dis>min) return;
visited[x][y] = true;
dfs(mat, x+1, y, dis+1);
dfs(mat, x-1, y, dis+1);
dfs(mat, x, y+1, dis+1);
dfs(mat, x, y-1, dis+1);
visited[x][y] = false;
}
}
开密码锁
一个密码锁由 4 个环形拨轮组成,每个拨轮都有 10 个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。字符串 target 代表可以解锁的数字,请给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。
分析:
使用bfs来解决。首先利用一个哈希表来存储deadends中的值,如果0000或者target存在于deadends中直接返回false。然后在定义一个队列q和哈希表seen,q用来进行bfs而seen是用来记录已经访问的值,防止进行重复操作。首先把0000入队并且定义一个变量step记录旋转次数,因为每次旋转都只能旋转一个拨轮的一位数字,所以会出现四种情况0001、0010、0100、1000;然后判断这四种情况产生的字符串是否存在于deadends与seen中,如果不存在则入队;如果入队的字符串有target则直接返回step。
class Solution {
public int openLock(String[] deadends, String target) {
HashSet<String> set = new HashSet<>();
for (String s : deadends) set.add(s);
String start = "0000";
if (start.equals(target)) return 0;
if (set.contains(target)||set.contains(start)) return -1;
Queue<String> queue = new LinkedList<>();
Set<String> seen = new HashSet<>();
int step = 0;
queue.offer(start);
seen.add(start);
while (!queue.isEmpty()){
step++;
int size = queue.size();
for (int i = 0; i<size; i++) {
String status = queue.poll();
for (String s : getNext(status)){
if (!seen.contains(s) && !set.contains(s)){
if (s.equals(target)) return step;
queue.offer(s);
seen.add(s);
}
}
}
}
return -1;
}
private List<String> getNext(String status) {
List<String> list = new ArrayList<>();
char[] chars = status.toCharArray();
for (int i = 0; i < 4; i++) {
char num = chars[i];
chars[i] = getPreNum(num);
list.add(new String(chars));
chars[i] = getNextNum(num);
list.add(new String(chars));
chars[i] = num;
}
return list;
}
private char getNextNum(char num) {
return num == '9'?'0':(char) (num+1);
}
private char getPreNum(char num) {
return num == '0'?'9':(char) (num-1);
}
}