举例法简单来讲就是数学中的归纳推理和演绎推理,根据特征找到通解,最常见的是在数列运算过程中,大家熟知的斐波那契数列,1+....+100,等等,都可以使用举例法解答。
模式匹配法是指将现有问题与相似问题作类比,看看能否通过修改相关问题的解法来解决新问题。
采用简化推广法,具体做法对于问题可以分步进行处理。首先,我们会修改某个约束条件,比如数据类型或数据量,从而简化这个问题。接着,转而处理这个问题的简化版本。最后,一旦找到解决简化版问题的算法,我们就可以基于这个问题进行推广,并调整最终的解决方案,找到最优解。
对于某些问题,简单构造法非常高效。使用简单构造法,我们会从最基本的情况(比如n=1)来解决问题,还是拿斐波那契数列来举例子,最后可以发现,这是一个递归方法可以解决的问题。所以,简单构造法最后往往会演变成递归法。
数据结构头脑风暴法过程往往会相对来说较复杂。运用数据结构中的链表,数组,二叉树,堆来解决问题,根据具体情况选择最优解。
散列表是一种将键(key)映射为值(value)从而实现快速查找的数据结构。散列表包含一个底层数组和一个散列函数(hash function)。插入一个对象及对应的键时,散列函数会将键映射为数组的一个索引。这个对象就回储存到数组中该索引的位置。
public HashMap buildMap(Student[] students){
HashMap map = new HashMap<>();
for (student s : students) {
map.put(s.getId(),s);
}
return map;
}
ArrayList,即动态数组,是一种按需调整大小的数组,数据访问时间为O(1)。一种典型的实现是在数组存满时扩容,每次扩容时O(n),均摊下来还是O(1)。
public ArrayList merges(String[] words,String[] more){
ArrayList sentence = new ArrayList<>();
for (String w : words) {
sentence.add(w);
}
for (String m : more) {
sentence.add(m);
}
return sentence;
}
在Java编程中把一组字符串拼接起来,可以使用String str = "hello" + "字符";
但是这样操作相当于每次都创建一个string对象的字符串,运行效率低下而且还特别占用内存,为了简化这种状况可以使用StringBuffer
。
public String mergeStr(String[] string){
StringBuffer sb = new StringBuffer();
for (String str : string) {
sb.append(str);
}
return sb.toString();
}
链表问题往往涉及递归操作,非常依赖概念。
以下代码为创建一个基本的单向链表。
class Node{
Node next = null;
int data;
public Node(int d){
data = d;
}
void appendToTail(int d){
Node end = new Node(d);
Node n = this;
while(n.next != null){
n = n.next;
}
n.next = end;
}
}
删除单向链表,给定一个节点n,我们先找到它的前趋节点prev,并将prev.next设置为n.next。如果是双向链表,还要更新n.next,将n.next.prev置为n.prev.
Node deleteNode(Node head,int d){
Node n = head;
if (n.data == d) {
return head.next;//表头指向下一个节点
}
while(n.next ! = null){
if (n.next.data == d) {
n.next = n.next.next;
return head; //表头不变
}
n = n.next;
}
return head;
}
在处理链表问题时,“快行指针”(runner,或者称为第二个指针)是一种很常见的技巧,“快行指针”指的是同时用两个指针来迭代访问链表,只不过其中一个比另一个超前一些。“快”指针往往先行几步,或与“慢”指针相差固定的步数。
许多链表问题都要用到递归。当然,还需注意递归算法至少要占用O(n)空间,其中n为递归调用层数。
栈采用后进先出(LIFO)顺序。实际上,栈和链表本质上是一样的,用户只能看到栈顶元素。
class Stacks{
Node top;
Object pop(){
if (top != null) {
Object item = top.data;
top = top.next;
return item;
}
return null;
}
void push(Object item){
Node t = new Node(item);
t.next = top;
top = t;
}
Object peek(){
return top.data;
}
}
以下是一个具体栈的实例:
public class Stacks {
static String[] months = {"Jan","Feb","Mar","Apr","May",
"June","Aug","Sep","Oct","Nov","Dec"};
public static void main(String[] args) {
Stack stack = new Stack();
//进栈,先进元素压入栈底,最后的元素在栈顶
for (int i = 0; i < months.length; i++) {
stack.push(months[i]+"");
}
System.out.println("stk = " + stack);
stack.addElement("************");
System.out.println("element 5 = " + stack.elementAt(5));
System.out.println("popping elements: ");
//出栈后进先出,栈顶元素最先被输出
while (!stack.empty()) {
System.out.println(stack.pop());
}
}
}
队列采用先进先出(FIFO)顺序。队列也可以用链表实现,新增元素追加至表尾。
class Queue{
Node first,last;
void enqueue(Object item){
if (first == null) {
last = new Node(item);
first = last;
}else{
last.next = new Node(item);
last = last.next;
}
}
Object dequeue(){
if (first != null) {
Object item = first.data;
first = first.next;
return item;
}
}
}
二叉树遍历一般分为中序、后序和前序遍历。
trie树是n层树的一种变体,其中每个节点存储有字符。整棵树的每条路径自上而下表示一个单词。
void search(Node root){
if(root == null) return;
visit(root);
root.visited = true;
foreach(Node n in root.adjacent){
if(n.visited == false) search(n);
}
}
void search(Node root){
Queue queue = new Queue();
root.visited = true;
visit(root);
queue.enqueue(root);//加至队列尾部
while(!queue.isEmpty()){
Node r = queue.dequeue();//从队列头部移除
foreach(Node n in r.adjacent){
if(n.visited ==false){
visit(n);
n.visited = true;
queue.enqueue(n);
}
}
}
}