岛问题
一个矩阵中只有0和1两种值,每个位置都可以和自己的上下左右四个位置相连,如果有一片1连在一起,这个部分叫做一个岛,求一个矩阵上有多少个岛
举例:
001010
111010
100100
000000
这个矩阵有3个岛
进阶:
如何设计一个并行算法解决这个问题
public static int countIslands(int[][] m) {
if (m == null || m[0] == null) {
return 0;
}
int N = m.length;
int M = m[0].length;
int res = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (m[i][j] == 1) {
res++;
infect(m, i, j, N, M);
}
}
}
return res;
}
public static void infect(int[][] m, int i, int j, int N, int M) {
if (i < 0 || i >= N || j < 0 || j >= M || m[i][j] != 1) {
return;
}
//i,j没有越界,且当前位置值为1
m[i][j] = 2;
//修改当前值之后再递归,不会导致无限循环
infect(m, i + 1, j, N, M);
infect(m, i - 1, j, N, M);
infect(m, i, j + 1, N, M);
infect(m, i, j - 1, N, M);
}
时间复杂度O(m * n)
如何设计一个并行算法解决这个问题
先看并查集,再来思考
先把整个二维数组分为两部分,左部分统计有几个岛,右部分统计有几个岛,同时两部分都要统计起始点和结束点,最后通过并查集将相连的岛重新合在一起(即岛的数量减一)
并查集
分析
具体实现
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
/**
* @Author laimouren
* @Date 2021/12/7 19:35
*/
public class UnionFind {
//样本进来会包一层,叫做元素
public static class Element<V>{
public V value;
public Element(V value){
this.value = value;
}
}
public static class UnionFindSet<V>{
public HashMap<V,Element<V>> elementMap;
//key 某个元素 value 该元素的父亲
public HashMap<Element<V>,Element<V>> fatherMap;
//key 某个集合的代表元素,value 该集合的大小
public HashMap<Element<V>,Integer> sizeMap;
public UnionFindSet(List<V> list){
elementMap = new HashMap<>();
fatherMap = new HashMap<>();
sizeMap = new HashMap<>();
for (V value : list) {
Element<V> element = new Element<V>(value);
elementMap.put(value,element);
fatherMap.put(element,element);
sizeMap.put(element,1);
}
}
//给定一个ele,往上一直找,将代表元素返回
private Element<V> findHead(Element<V> element){
Stack<Element<V>> path = new Stack<>();
while (element != fatherMap.get(element)){
path.push(element);
element = fatherMap.get(element);
}
//将遍历的整条链的父都设置为找到的代表元素
//即将其扁平化(向上找优化)
while (!path.isEmpty()){
fatherMap.put(path.pop(),element);
}
return element;
}
public boolean isSameSet(V a,V b){
if(elementMap.containsKey(a) && elementMap.containsKey(b)){
return findHead(elementMap.get(a)) == findHead(elementMap.get(b));
}
return false;
}
public void union(V a,V b){
if(elementMap.containsKey(a) && elementMap.containsKey(b)){
Element<V> aF = findHead(elementMap.get(a));
Element<V> bF = findHead(elementMap.get(b));
if(aF != bF){
Element<V> big = sizeMap.get(aF) >= sizeMap.get(bF) ? aF : bF;
Element<V> small = big == aF ? bF : aF;
fatherMap.put(small,big);
sizeMap.put(big,sizeMap.get(aF) + sizeMap.get(bF));
sizeMap.remove(small);
}
}
}
}
}
KMP算法
解决的问题:字符串str1和str2,str1是否包含str2,如果包含则返回str2在str1中开始的位置。
如何做到时间复杂度为O(N)
前缀和后缀匹配的最长长度K
对str2求前缀和后缀匹配的最长长度K
举例说明
i位置的nextarr
代码实现
/**
* @Author laimouren
* @Date 2021/12/7 21:41
*/
public class KMP {
//N >= M
public static int getIndexOf(String s, String m){
if(s == null || m == null || m.length() < 1 || s.length() < m.length()){
return -1;
}
char [] str1 = s.toCharArray();
char [] str2 = m.toCharArray();
int i1 = 0;
int i2 = 0;
//O(M)
int [] next = getNextArray(str2);
//O(N)
while(i1 < str1.length && i2 < next.length){
//匹配则str1和str2都加一
if(str1[i1] == str2[i2]){
i1++;
i2++;
}
//str2当前位置为0,等同与i2 == 0
else if(next[i2] == -1){
i1++;
}
//此时str1和str2不匹配,通过nextArray往前跳,得到i2应该在的下一个位置
else {
i2 = next[i2];
}
}
//i2越界则匹配成功,结果为i1-i2; 否则i1越界了,此时匹配失败,返回-1
return ((i2 == str2.length) ? (i1 - i2) : -1);
}
/**
* 获取next数组
*/
public static int [] getNextArray(char [] ms){
//人为规定第一个为-1
if(ms.length == 1) {
return new int[]{-1};
}
int [] next = new int[ms.length];
next[0] = -1;
//人为规定第一个为0
next[1] = 0;
//next数组的位置
int i = 2;
//拿cn字符和i-1的字符比
int cn = 0;
while(i < ms.length){
if(ms[i - 1] == ms[cn]) {
//next[i] = cn + 1;
//i++;
//cn++;此时cn值是next[i-1]的值,这个i是更新后的i
next[i++] = ++cn;
}
//当前跳到cn位置的字符,和i-1位置的字符配不上
//cn得往前跳,即它的k值
else if(cn > 0) {
cn = next[cn];
}
//cn <= 0,没办法往前跳了
//此时next[i]为0
else {
next[i++] = 0;
}
}
return next;
}
}