我今天在Java采访中得到了这个问题。
我必须实现一个集合,它是一个数组,并且只能在数组的末尾执行add
和delete
方法。
除此之外,我还需要实现另外两种方法,即undo
和redo
方法,只能执行一次。
例如:
设x为包含{1,5,7,9}.
现在我在其中添加{44,66,77}
并将其设为{1,5,7,9,44,66,77}
。
现在当我撤消它时,数组应该删除{44,66,77}
。如果我之后重做,它应该回到{1,5,7,9,44,66,77}
。
同样对于删除。
在复杂性和记忆方面实现这一目标的最佳方法是什么?
我对面试官的解决方案:
根据采访者的说法,这是一个完全不正确的解决方案。
答案 0 :(得分:12)
我会像这样解决它。基本上,将有三个列表。
Command
interface 的实例Command
接口实例的重做列表(可选,如下所述)
public interface Command {
void do();
void undo();
}
将有两个Command
实施:AddCommand
和RemoveCommand
。
add()
上,AddCommand
的新实例已创建并添加到撤消列表。然后我们调用此命令的do()
,该命令修改实际值并存储添加项目的index
。remove()
上,RemoveCommand
的新实例已创建并添加到撤消列表。然后我们调用此命令的do()
,它修改实际值并存储index
和已删除项的值。undo()
上,我们从撤消列表中提取最后一个命令,并执行此命令的undo()
方法。该命令被推送到重做列表。撤消AddCommand
和RemoveCommand
的方法可以还原更改。redo()
上,我们从重做列表中提取最后一个命令,然后再次执行do()
方法。已拉出的命令将添加到撤消列表。另一个优化是删除重做列表并使用撤消索引。在这种情况下,当您undo()
时,您不需要从列表中删除/添加值,而只需将撤消索引减少一个。同样,redo()
会将其增加一个。
因此,最终解决方案将包含值列表,撤消列表和索引值。
更新:
对于最简单的情况,只需要一个undo()
/ redo()
操作,解决方案看起来会更简单。它没有撤消列表和索引,而是足以存储最新的命令实例。因此,我们将有值列表和最后撤消命令的实例。
答案 1 :(得分:7)
要么我误解了这个问题,要么是人们过分思考这个问题。这个问题有很多限制,所以:
然后在每个不同的操作上更新这些。不需要堆栈或任何东西,因为不需要多次撤消/重做。
答案 2 :(得分:3)
我要说的一件事是面试官可能不赞同你的回答是因为它不是很优雅。不是因为它很慢或占用了很多空间。
有basically two ways to do undo and redo,其中一个是每次都保存整个数组的副本。这种方式在这里可能是不可取的(并且很容易为数组实现)所以我将谈论另一种方式。
现在,这里的一个方面是重做实际上也只是一个撤销。如果undo是逆,则redo是undo的逆的倒数。这实际上意味着每个撤消操作应该能够做到这两点。撤消应该导致操作自行翻转,以便下次撤消时执行原始操作。
undo和redo方法,只能执行一次
如果你必须存储它们的长历史(它变得非常方便),Undos和redos是同一件事更重要但它仍然适用于此。
这样做意味着撤消是一个具有一点状态的对象。由于可能存在多个不同的操作,因此界面在此处很有用(策略模式,如注释中所述):
interface UndoAction {
public void undo();
}
对于set
操作,一个非常简单的撤消对象如下所示:
class SetUndoAction implements UndoAction {
int index;
Object oldValue;
SetUndoAction(int index, Object oldValue) {
this.index = index;
this.oldValue = oldValue;
}
public void undo() {
Object newValue = get(index);
set(index, oldValue);
oldValue = newValue;
}
}
如果您对此致电undo
,则会执行撤消操作,下次您致电undo
时会执行重做。
添加和删除有点不同,因为它们不只是交换值。反转添加或删除的Undo
的状态也将涉及方法调用。
添加:
删除:
这些是相反的,因此我们可以描述与add
操作类似的简单remove
或set
操作:
class AddRemoveUndoAction implements UndoAction {
int index;
Object theValue;
boolean wasAdd;
AddRemoveUndoAction(int index, Object theValue, boolean wasAdd) {
this.index = index;
this.theValue = theValue;
this.wasAdd = wasAdd;
}
public void undo() {
if(wasAdd) {
remove(index);
} else {
add(index, theValue);
}
wasAdd = !wasAdd;
}
}
add
创建new AddRemoveUndoAction(..., ..., true)
,remove
创建new AddRemoveUndoAction(..., ..., false)
。
现在,从具有数组的类的角度来看,它只需要适当地创建和存储这些对象。
如果您有多个undos的历史记录,您可以使用某种类型的数据结构(可能是堆栈或队列)来存储它们,尽管您需要一些特殊功能:
您可以使用java.util.Stack
并从外部管理这些内容(通过ListIterator
),但我发现这是一个令人头痛的问题,而且为此专门编写单独的数据结构要好得多。< / p>
对于仅存储单个项目的撤消和重做,它更容易,您可以只有两个字段。
在数组保持类上调用undo
时:
undo
。在数组保持类上调用redo
时:
undo
。调用可以撤消的数组保持类的方法应该在undo字段中添加一个新的UndoAction
对象,并将重做字段设置为null。
这种设计对于多重撤销情况最有利,因为抽象的每一层只对自己负责:
答案 3 :(得分:2)
我一直在做一些研究,正如我的建议,你可以注册&#34;事物的数量&#34;进入数组 - 然后存储在某个地方。因此,当您进行撤消时 - 它会删除最后插入的对象。
所以以这种方式思考:
容器保存输入数组的项目数= 1,2,4,2
数组= {[x] [x,x] [x,x,x,x] [x,x]}
如果你想删除最后一个动作,你从容器2中取出并删除数组中的最后一个条目,这样你就可以了:
容器中包含输入数组的项目数= 1,2,4
Array = {[x] [x,x] [x,x,x,x]}
依此类推。
此外,您可以查看this有趣的模式,该模式可以将对象恢复到以前的状态。
答案 4 :(得分:2)
我的解决方案是使用ArrayLists Map来保存Array的历史值。 这是代码:
public class UndoRedoArray {
private List<Integer> currentValues;
private int version = 0;
private int highestVersion = 0;
private Map<Integer, List<Integer>> history = new HashMap<Integer,List<Integer>>();
public Integer[] getValues() {
return (Integer[]) getCurrentValues().toArray();
}
private List<Integer> getCurrentValues() {
if (currentValues == null) {
currentValues = new ArrayList<Integer>();
}
return currentValues;
}
private void add(Integer[] newValues) {
incrementHistory();
getCurrentValues().addAll(Arrays.asList(newValues));
}
private void incrementHistory() {
if (history.get(version) != null) {
throw new IllegalArgumentException("Cannot change history");
}
history.put(version,getCurrentValues());
if (version > 2) {
history.remove(version - 2);
}
version++;
if (version > highestVersion) {
highestVersion = version;
}
}
private void delete(Integer[] endValues) {
incrementHistory();
int currentLength = getCurrentValues().size();
int i = endValues.length-1;
for (int deleteIndex = currentLength - 1; deleteIndex > currentLength - endValues.length; deleteIndex--) {
if (!endValues[i].equals(getCurrentValues().get(deleteIndex))) {
throw new IllegalArgumentException("Cannot delete element(" + endValues[i] + ") that isn't there");
}
getCurrentValues().remove(deleteIndex);
}
}
public void undo() {
version--;
if (history.get(version) == null) {
throw new RuntimeException("Undo operation only supports 2 undos");
}
this.currentValues = history.get(version);
}
public void redo() {
version++;
if (history.get(version) == null) {
throw new RuntimeException("Redo operation only supported after undo");
}
this.currentValues = history.get(version);
}
}
答案 5 :(得分:1)
如果您想使用最小内存:
答案 6 :(得分:1)
我能想到的一件事就是拥有一组数组。像这样:
Actual Data = []
Bitmap History = [[]]
End Index = 0
添加[4,5]
Actual Data = [[4, 5]]
Bitmap History = [ [00], [11] ]
End Index= 1
添加[6,7]
Actual Data = [[4, 5], [6, 7]]
Bitmap History = [ [0000], [1100], [1111] ]
End Index = 2
添加[8,9]
Actual Data = [[4, 5], [6, 7], [8, 9]]
Bitmap History = [ [000000], [110000], [111100], [111111] ]
End Index = 3
在索引0处添加[1,2]
Actual Data = [[1,2], [4,5], [6,7], [8,9]]
Bitmap History = [ [00000000], [00110000], [00111100], [00111111], [11111111] ]
End Index = 4
删除9
Actual Data = [[1,2], [4,5], [6,7], [8,9]]
Bitmap History = [ [00000000], [00110000], [00111100], [00111111], [11111111], [11111110] ]
删除8
Actual Data = [[1,2], [4,5], [6,7], [8,9]]
Bitmap History = [ [00000000], [00110000], [00111100], [00111111], [11111111], [11111110], [11111100] ]
最后添加10
Actual Data = [[1,2], [4,5], [6,7], [8,9], [10]]
Bitmap History = [ [000000000], [001100000], [001111000], [001111110], [111111110], [111111100], [111111000], [111111001] ]
End Index = 7
撤消三次应该→结束索引= 4 →使用位图历史记录[111111110]来展平
Flattened Data = [1,2,4,5,6,7,8,9]
有一个名为flatten()的方法,它使用位图历史记录中的数据来获取数组的内容并从中创建一个数组。
现在,当您要撤消时,您所做的就是减少结束指数值。要重做,只需增加结束指数值即可。 flatten方法将负责显示Actual Data数组中的正确元素。
删除操作会在位图历史记录中插入一个新条目,并关闭该号码的标记。
答案 7 :(得分:1)
我会像这样实现它:
执行的操作可以作为字符串保存在数组/向量中。在阵列上的每个操作上,可以存储关于操作的信息。在数组上的每次撤销/重做时,将从操作数组中获取一个值,如果是撤消然后计数器操作,并且在重做的情况下将执行相同的操作,并且在执行操作数组或操作数组中的指针后将更新。 / p>
假设你有一个阵列 arr ,操作数组说 optArr 和 ptr 将指向 optArr <中的最后一个元素/强>
arr {5} 和 optArr {&#34;添加5&#34;} 和 ptr = 0
arr {5,9} 和 optArr {&#34;添加5&#34;,&#34;添加9&#34;} 和 ptr = 1
arr {5,9,7} 和 optArr {&#34;添加5&#34;,&#34;添加9&#34;,&#34;添加7& #34; } 和 ptr = 2
arr {5,7} 和 optArr {&#34;添加5&#34;,&#34;添加9&#34;,&#34;添加7&#34 ;,&#34;删除9&#34; } 和 ptr = 3
value = optArr[ptr--]
值&#34;删除9&#34; 现在计数器操作(&#34;添加9&#34;)将执行此操作。
value = optArr[++ptr]
值为&#34;将执行删除9&#34; 。
答案 8 :(得分:1)
使用:
// Create three lists and a string variable that stores the last operation name.
List<Integer> sizeIndex = new ArrayList<Integer>();
List<Integer> finalArray = new ArrayList<Integer>();
List<Integer> lastDelete = new ArrayList<Integer>();
String lastOperation;
// Add the first size of the finalArray.
sizeIndex.add(finalArray.size());
public void add(List<Integer> someArray){
lastOperation = "add";
finalArray.addAll(someArray);
sizeIndex.add(finalArray.size());
}
public void delete(){
lastOperation = "delete";
lastDelete = finalArray.subList(sizeIndex.size()-1, sizeIndex.size());
finalArray = finalArray.subList(0, sizeIndex.size()-1);
sizeIndex.remove(sizeIndex.size() - 1);
}
public undo(){
if("add".equals(lastOperation))
delete();
else
add(lastDelete);
}
public redo(){
if("add".equals(lastOperation))
add(lastDelete);
else
delete();
}
<强>逻辑:强>
当列表添加到最后时,新列表将添加为最后一个 列表中的元素。因此可以从中提取最后一个列表 索引sizeBefore添加列表和sizeAfterAddding。所以保持 一个大小的轨道;我将更新的大小存储在
sizeIndex
上。<强>运营强>
加(listToBeAppended); //将列表添加到当前列表中。
删除(); //将删除最后添加的列表。
撤消(); //将重做最后一次操作,即如果添加则删除,如果删除则添加。
重做(); //再次执行最后一次操作,即如果添加然后删除,则重做将再次添加。
答案 9 :(得分:1)
我不是Java人,但我认为这个解决方案可以很容易地转换为Java:
首先对问题进行分析:我们需要一个设计用于执行一组操作的类,此操作只能执行一次,并且每次都撤消和重做一次。所以过程是:
add
或delete
)。所以它是一个设计有一个action / s来完成和撤消的系统,而可以认为do / redo命令会频繁执行并以这种方式设计系统。强>
考虑到这一点,我建议实施add
/ delete
操作,只需不以任何方式修改基础数据,只需提供不同的数据代理,具体取决于行动被取消或重做。