BAT 经典算法笔试题 —— 逆转单向链表

栏目: 编程工具 · 发布时间: 5年前

内容简介:不善言谈的优秀程序员在面试中往往是要吃巨亏的,你没有办法通过说话来轻易证明自己的实力。不论是大厂还是小厂,大部分面试官都不具备优秀的面试能力,它们也只能通过三言两语观察一下面试者的表面工夫。有很多这样吃了亏的程序员,不喜欢准备面试,不喜欢吹嘘虚假的不存在的经验和能力,甚至连网上的笔试题都懒得做,因为在实际工作中这些鸟题根本一点都用不上。但是这并不是什么值得骄傲的真诚,面试不做准备是对目标企业的不尊重,也是个人性格上自大的一种表现。虽然所有的面试官都希望面试者是真实的无虚假的不做表面文章的,但是这样的人真的站

不善言谈的优秀 程序员 在面试中往往是要吃巨亏的,你没有办法通过说话来轻易证明自己的实力。不论是大厂还是小厂,大部分面试官都不具备优秀的面试能力,它们也只能通过三言两语观察一下面试者的表面工夫。有很多这样吃了亏的程序员,不喜欢准备面试,不喜欢吹嘘虚假的不存在的经验和能力,甚至连网上的笔试题都懒得做,因为在实际工作中这些鸟题根本一点都用不上。

但是这并不是什么值得骄傲的真诚,面试不做准备是对目标企业的不尊重,也是个人性格上自大的一种表现。虽然所有的面试官都希望面试者是真实的无虚假的不做表面文章的,但是这样的人真的站在你面前时,几乎所有的面试官往往又都看不上。

那如何在尽量少做表面功夫的基础上让面试官能够看上你?其中的方法之一就是展现平时个人优秀的作品和实现代码,但是这需要时间需要实打实的功夫,能够有机会作出优秀个人作品的人并不多见,大多数人都在忙碌的工作中耗尽了所有的时间。那还有一个方法就是在笔试阶段大显身手,让自己优秀的代码能力跃然纸上,让面试官瞬间对你产生不一样的感觉。

不要被笔试阶段那些算法题给吼到了,在你眼里似乎它们是为程序天才们准备的。其实大多数公司的笔试题也是来源于网上,那些被无数人做烂的题目。你被题目搞晕了而别人没有,那是因为别人已经做过这道题了,而不是智商所致。

就拿今天我要讲的算法题 —— 逆转单向链表,它是一个非常简单的题目,但是如果你是第一次见到这道题,将它完完整整没有 bug 的写出来是着实需要费一番功夫的。期望在那短短的笔试题环节就轻松搞定这道题,那真是非常有算法天赋的人才能做到的事。难道大厂里面的程序员个个都是天才,鬼才相信。天才总是极少数的,多数都是像老钱这样的庸才。

好,言归正传,下面我们开始讲解今天的算法题 —— 逆转单向链表。首先这是一个单向的链表,不同于 Java 里面的 LinkedList,它是双向的链表。链表中每个节点之间通过 next 指针串接起来,会有一个链表头和链表尾指针 hold 住整个链表。逆转的任务就是将 head -> a -> b -> c -> d <- tail 变成 head -> d -> c -> b -> a <- tail。

BAT 经典算法笔试题 —— 逆转单向链表

链表结构表示

BAT 经典算法笔试题 —— 逆转单向链表
class Node<T> {
	T value;
	Node<T> next;

	Node(T value) {
		this.value = value;
	}
}

class ReverseLinkedList<T> {
  Node<T> head, tail;
}
复制代码

链表构造器

我们需要将所有的元素一个一个的使用 next 指针串接起来,链表的头尾节点要用 head 和 tail 变量把持住。加入新元素时,需要调整尾部节点的 next 指针,指向新加入的元素节点。

BAT 经典算法笔试题 —— 逆转单向链表
class ReverseLinkedList<T> {
  private Node<T> head, tail;
  
  public ReverseLinkedList(T... values) {
	for (T value : values) {
		if (tail == null) {
            // 第一个节点
			head = tail = new Node<>(value);
		} else {
            // 后面的节点往链表尾部追加
			Node<T> oldTail = tail;
			oldTail.next = tail = new Node<>(value);
		}
	}
  }
}

ReverseLinkedList<Integer> l = new ReverseLinkedList<>(1,2,3,4,5,6);
复制代码

链表内容呈现

我们需要提供一个链表的输出方法,以便快速对比逆转后链表的内容是否正确

BAT 经典算法笔试题 —— 逆转单向链表
class ReverseLinkedList<T> {
  private Node<T> head, tail;

  public String toString() {
	StringBuilder sb = new StringBuilder();
	sb.append('[');
	Node<T> cur = head;
	while (cur != null) {
		sb.append(cur.value);
		cur = cur.next;
		if (cur != null) {
			sb.append(',');
		}
	}
	sb.append(']');
	return sb.toString();
  }
}
复制代码

迭代逆转算法

循环调整 next 指针是最容易想到的方法,但是要将指针精确地逆转正确其实并不容易。下面代码中的循环部分很短,但是却很精致,使用了三个临时局部变量 cur、next 和 nextnext,稍有不慎就会出错。

BAT 经典算法笔试题 —— 逆转单向链表

当我写完下面这段代码时,虽然可以正常运行出期望的结果,但是总当心哪里会有遗漏,是不是什么地方少了个 if else,这就是编写算法代码时常见的心理状态。

class ReverseLinkedList<T> {
  private Node<T> head, tail
  
  public ReverseLinkedList<T> reverseByLoop() {
    // 空链表或者单元素都无需处理
	if (head == tail) {
		return this;
	}
	Node<T> cur = head;
	Node<T> next = cur.next;
	while (next != null) {
		Node<T> nextnext = next.next;
		next.next = cur;
		cur = next;
		next = nextnext;
	}
	tail = head;
	tail.next = null;
	head = cur;
	return this;
  }
}
复制代码

递归逆转算法

使用递归的思想来解决这个问题也是一个很好的主意,只不过当链表特别长时,调用栈会很深,链表长到一定程度就会抛出臭名昭著的异常 StackOverflowException。

BAT 经典算法笔试题 —— 逆转单向链表
class ReverseLinkedList<T> {
  private Node<T> head, tail

  public ReverseLinkedList<T> reverseByRecursive() {
	Node<T> oldTail = tail;
	tail = reverseFrom(head);
	tail.next = null;
	head = oldTail;
	return this;
  }

  private Node<T> reverseFrom(Node<T> from) {
  	if (from == tail) {
	  return from;
	}
	Node<T> end = reverseFrom(from.next);
	end.next = from;
	return from;
  }
}
复制代码

完整代码

package leetcode;

public class ReverseLinkedList<T> {

	static class Node<T> {
		T value;
		Node<T> next;

		Node(T value) {
			this.value = value;
		}
	}

	Node<T> head;
	Node<T> tail;

	@SafeVarargs
	public ReverseLinkedList(T... values) {
		for (T value : values) {
			if (tail == null) {
				head = tail = forNode(value);
			} else {
				Node<T> oldTail = tail;
				oldTail.next = tail = forNode(value);
			}
		}
	}

	public ReverseLinkedList<T> reverseByLoop() {
		if (head == tail) {
			return this;
		}
		Node<T> cur = head;
		Node<T> next = cur.next;
		while (next != null) {
			Node<T> nextnext = next.next;
			next.next = cur;
			cur = next;
			next = nextnext;
		}
		tail = head;
		tail.next = null;
		head = cur;
		return this;
	}

	public ReverseLinkedList<T> reverseByRecursive() {
		Node<T> oldTail = tail;
		tail = reverseFrom(head);
		tail.next = null;
		head = oldTail;
		return this;
	}

	private Node<T> reverseFrom(Node<T> from) {
		if (from == tail) {
			return from;
		}
		Node<T> end = reverseFrom(from.next);
		end.next = from;
		return from;
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append('[');
		Node<T> cur = head;
		while (cur != null) {
			sb.append(cur.value);
			cur = cur.next;
			if (cur != null) {
				sb.append(',');
			}
		}
		sb.append(']');
		return sb.toString();
	}

	private static <T> Node<T> forNode(T value) {
		return new Node<>(value);
	}

	public static void main(String[] args) {
		ReverseLinkedList<Integer> rl = new ReverseLinkedList<>(1, 2, 3, 4, 5, 6);
		System.out.println(rl.reverseByRecursive().reverseByRecursive());
	}
}
复制代码
BAT 经典算法笔试题 —— 逆转单向链表

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入理解计算机系统

深入理解计算机系统

Randal E.Bryant、David O'Hallaron / 龚奕利、雷迎春 / 中国电力出版社 / 2004-5-1 / 85.00元

从程序员的视角,看计算机系统! 本书适用于那些想要写出更快、更可靠程序的程序员。通过掌握程序是如何映射到系统上,以及程序是如何执行的,读者能够更好的理解程序的行为为什么是这样的,以及效率低下是如何造成的。粗略来看,计算机系统包括处理器和存储器硬件、编译器、操作系统和网络互连环境。而通过程序员的视角,读者可以清晰地明白学习计算机系统的内部工作原理会对他们今后作为计算机科学研究者和工程师的工作有......一起来看看 《深入理解计算机系统》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试