【发布时间】:2019-03-19 21:29:24
【问题描述】:
第一次发帖想我会试试这个社区。
我已经研究了几个小时,但似乎找不到足够接近的示例来从中获取想法。我不在乎答案是什么语言,但更喜欢 java、c/c++ 或伪代码。
我希望在网格中找到长度为 n 的连续路径。
我找到了一个递归解决方案,我认为该解决方案很干净且始终有效,但如果路径数量太大,则运行时间很差。我意识到我可以迭代地实现它,但我想先找到一个递归解决方案。
我不在乎答案是什么语言,但更喜欢 java、c/c++。
问题是这样的—— 对于 String[] 和 int pathLength 有多少条具有该长度的路径。
{ "ABC", "CBZ", "CZC", "BZZ", 长度为 3 的“ZAA”}
A B C A . C A B . A . . A . . A . . . . .
. . . . B . C . . C B . . B . . B . . . .
. . . . . . . . . . . . C . . . . C C . .
. . . . . . . . . . . . . . . . . . B . .
. . . . . . . . . . . . . . . . . . . A .
(spaces are for clarity only)
返回 7 条长度为 3 (A-B-C) 的可能路径
这是原来的递归解决方案
public class SimpleRecursive {
private int ofLength;
private int paths = 0;
private String[] grid;
public int count(String[] grid, int ofLength) {
this.grid = grid;
this.ofLength = ofLength;
paths = 0;
long startTime = System.currentTimeMillis();
for (int j = 0; j < grid.length; j++) {
for (int index = grid[j].indexOf('A'); index >= 0; index = grid[j].indexOf('A', index + 1)) {
recursiveFind(1, index, j);
}
}
System.out.println(System.currentTimeMillis() - startTime);
return paths;
}
private void recursiveFind(int layer, int x, int y) {
if (paths >= 1_000_000_000) {
}
else if (layer == ofLength) {
paths++;
}
else {
int xBound = grid[0].length();
int yBound = grid.length;
for (int dx = -1; dx <= 1; ++dx) {
for (int dy = -1; dy <= 1; ++dy) {
if (dx != 0 || dy != 0) {
if ((x + dx < xBound && y + dy < yBound) && (x + dx >= 0 && y + dy >= 0)) {
if (grid[y].charAt(x) + 1 == grid[y + dy].charAt(x + dx)) {
recursiveFind(layer + 1, x + dx, y + dy);
}
}
}
}
}
}
}
}
这非常慢,因为每个新字母都可能衍生出 8 次递归,因此复杂度猛增。
我决定使用记忆来提高性能。
这就是我想出的。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class AlphabetCount {
private int ofLength;
private int paths = 0;
private String[] grid;
// This was an optimization that helped a little. It would store possible next paths
// private HashMap<Integer, ArrayList<int[]>> memoStack = new HashMap<Integer, ArrayList<int[]>>();
//hashmap of indices that are part of a complete path(memoization saves)
private HashMap<Integer, int[]> completedPath = new HashMap<Integer, int[]>();
//entry point
public int count(String[] grid, int ofLength) {
this.grid = grid;
//Since i find the starting point ('A') by brute force then i just need the next n-1 letters
this.ofLength = ofLength - 1;
//variable to hold number of completed runs
paths = 0;
//holds the path that was taken to get to current place. determined that i dont really need to memoize 'Z' hence ofLength -1 again
List<int[]> fullPath = new ArrayList<int[]>(ofLength - 1);
//just a timer to compare optimizations
long startTime = System.currentTimeMillis();
//this just loops around finding the next 'A'
for (int j = 0; j < grid.length; j++) {
for (int index = grid[j].indexOf('A'); index >= 0; index = grid[j].indexOf('A', index + 1)) {
//into recursive function. fullPath needs to be kept in this call so that it maintains state relevant to call stack? also the 0 here is technically 'B' because we already found 'A'
recursiveFind(fullPath, 0, index, j);
}
}
System.out.println(System.currentTimeMillis() - startTime);
return paths;
}
private void recursiveFind(List<int[]> fullPath, int layer, int x, int y) {
//hashing key. mimics strings tohash. should not have any duplicates to my knowledge
int key = 31 * (x) + 62 * (y) + 93 * layer;
//if there is more than 1000000000 paths then just stop counting and tell me its over 1000000000
if (paths >= 1_000_000_000) {
//this if statement never returns true unfortunately.. this is the optimization that would actually help me.
} else if (completedPath.containsKey(key)) {
paths++;
for (int i = 0; i < fullPath.size() - 1; i++) {
int mkey = 31 * fullPath.get(i)[0] + 62 * fullPath.get(i)[1] + 93 * (i);
if (!completedPath.containsKey(mkey)) {
completedPath.put(mkey, fullPath.get(i));
}
}
}
//if we have a full run then save the path we took into the memoization hashmap and then increase paths
else if (layer == ofLength) {
for (int i = 0; i < fullPath.size() - 1; i++) {
int mkey = 31 * fullPath.get(i)[0] + 62 * fullPath.get(i)[1] + 93 * (i);
if (!completedPath.containsKey(mkey)) {
completedPath.put(mkey, fullPath.get(i));
}
}
paths++;
}
//everything with memoStack is an optimization that i used that increased performance marginally.
// else if (memoStack.containsKey(key)) {
// for (int[] path : memoStack.get(key)) {
// recursiveFind(fullPath,layer + 1, path[0], path[1]);
// }
// }
else {
int xBound = grid[0].length();
int yBound = grid.length;
// ArrayList<int[]> newPaths = new ArrayList<int[]>();
int[] pair = new int[2];
//this loop checks indices adjacent in all 8 directions ignoring index you are in then checks to see if you are out of bounds then checks to see if one of those directions has the next character
for (int dx = -1; dx <= 1; ++dx) {
for (int dy = -1; dy <= 1; ++dy) {
if (dx != 0 || dy != 0) {
if ((x + dx < xBound && y + dy < yBound) && (x + dx >= 0 && y + dy >= 0)) {
if (grid[y].charAt(x) + 1 == grid[y + dy].charAt(x + dx)) {
pair[0] = x + dx;
pair[1] = y + dy;
// newPaths.add(pair.clone());
//not sure about this... i wanted to save space by not allocating everything but i needed fullPath to only have the path up to the current call
fullPath.subList(layer, fullPath.size()).clear();
//i reuse the int[] pair so it needs to be cloned
fullPath.add(pair.clone());
//recursive call
recursiveFind(fullPath, layer + 1, x + dx, y + dy);
}
}
}
}
}
// memoStack.putIfAbsent(key, newPaths);
// memo thought! if layer, x and y are the same as a successful runs then you can use a
// previous run
}
}
}
问题是我的记忆从未真正被使用过。递归调用有点模仿深度优先搜索。前-
1
/ | \
2 5 8
/\ |\ |\
3 4 6 7 9 10
因此,保存一次运行不会以任何节省性能的方式与另一次运行重叠,因为它在返回调用堆栈之前在树的底部进行搜索。所以问题是......我如何记住这个?或者一旦我完全运行,我如何递归回树的开头,以便我编写的记忆工作。
真正扼杀性能的测试字符串是 { "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "ABCDEFGHIJKLMNOPQRSTUVWXYZ" }; 对于长度为 26 的所有路径 (应该返回 1000000000)
PS。作为第一次发布任何关于一般代码改进或不良编码习惯的 cmet 将不胜感激。此外,由于我之前没有发布过,请让我知道这个问题是否不清楚或格式不正确或太长等。
【问题讨论】:
-
我无法从您的描述中理解问题所在。你说输入是一个字符串数组和一个路径长度,然后使用第一个字符串显示几个(但绝不是全部)路径......数组中剩余的字符串如何进入它?路径长度如何进入它?各个字母是什么意思?
-
问题是在二维数组中找到从“A”到长度的所有路径。前任。如果长度为 3,那么您正在寻找从 A-B-C 引出的所有路径。
-
这回答了我 3 个问题中的 1 个,谢谢。
-
我更新了这个问题,希望能为您提供更清晰的信息。将 string[] 视为 2d 字符网格。在那个网格中,我们想要遍历从一个字母到下一个字母的路径,从“A”开始,并以输入参数的任何长度结束。我们可以垂直水平或对角线。字母本身没有任何意义。路径长度是每个字母路径应该有多长。数组中剩余的字符串都是相同的二维字符数组的一部分,不应该被认为是单独的字符串。如果您仍然迷路,请告诉我
-
谢谢,现在清楚多了!
标签: java algorithm recursion search memoization