剑指offer是一本很棒的面试宝典,里面讲述了面试官对应聘者的要求与期望,也讲述了面试环节,面试官与应聘者的心理。
但更多的是讲述面试官对应聘者的技术要求,编码要求。
从基础知识、高质量代码、解决问题的思路、优化时空效率与各项能力这几个章节展开叙述,为大量程序员提供了提升能力、查漏补缺的机会,我也从中受益匪浅,为此感谢剑指offer的作者,总结分享编程知识。
牛客网是一个专业IT笔试面试备考平台,我就是在这个平台上做的剑指offer练习,也感谢牛客网,给我编码提供了便利,建议备考面试的程序员到上面去看看,里面整合的试题资源确实丰富,而且还有很多其他在线编程题目,如华为、小米编程题目,ACM,JS等等...
链表是一种重要的数据结构,用来存储数据信息。其特点是本身存储一个单元的信息,然后有一个指针指向下一个节点。如:
这是一个链表
这也是一个链表
原本我以为一个链表就是一串节点,做了剑指offer后发现错了,每一个节点都叫一个链表,如A链表就是有A->B->C三个元素,B链表就是有B->C连个元素,C链表只有C自己一个元素。
剑指offer有一题叫做链表中倒数第K个节点(下面有),若你的输出只是一个链表中的单位(即只有一个节点,指针为空),那你最后的结果就错了。
链表可以存储各种类型的数据,可以是一个字符、一个整形、浮点型等任何基本数据类型,甚至可以是对象(java中是对象的引用)。
题目:输入一个链表的头结点,从尾到头反过来打印出每个节点的值。
链表从头到尾打印比较简单,本来我是想将指针改变方向,这样把链表倒转过来,然后本来末尾的节点变成了头结点,这样就可以了,但是这样就会破坏了链表本来的数据结构,答案就出错了。
所以,最简单的实现方法就是用一个栈去存储链表元素,然后逐个输出就可以了。
import java.util.Stack;
import java.util.ArrayList;
public class Solution {
public ArrayList printListFromTailToHead(ListNode listNode) {
ArrayList list = new ArrayList<>();
Stack stack = new Stack<>();
while( listNode != null ){
stack.push(listNode.val);
listNode = listNode.next;
}
while( !stack.empty() ){
list.add(stack.pop());
}
return list;
}
}
在线编程要我的结果放到一个ArrayList中,所以我还要一个个存进给定的容器中,作为返回值。
问题:输入一个链表,输入该链表中倒数第K个节点。
这一个问题跟上一个链表问题一样,用一个栈作为容器去存储链表节点,然后再弹出K次就可以得到倒数第K个节点了,这是我的做法。
书本上的做法是用两个指针,第一个先走K步,然后第二个再开始走,两个一起走,当第二个走到底,那么第一个指针指向的节点就是我们要的结果,因为它距离第二个指针(也就是链表的末尾)的长度是K。
我的做法需要用一个栈去存储,空间性能没有书本上的好,需要额外的空间为O(n)。
import java.util.Stack;
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
if(k == 0||head == null){
return null;
}
Stack stack = new Stack<>();
ListNode result = null;
while( head.next != null ){
stack.push(head);
head = head.next;
}
stack.push(head);
for(int i=0;i
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
if(k == 0||head == null){
return null;
}
ListNode pre = head;
ListNode result = head;
for(int i = 0; i < k - 1 ; i ++){
if( pre.next == null ){
return null;
}
pre = pre.next;
}
while( pre.next != null ){
pre = pre.next;
result = result.next;
}
return result;
}
}
问题:定义一个函数,输入一个链表头节点,反转该链表并输出反转后的头节点。
定义3个指针,一个指针指向下一个节点(next),一个指针记录当前节点,一个指针指向前一个节点(pre)。
next找到并且记录下一个节点,然后当前节点(head)与前一个节点(pre)反转,即实现反转。然后两个指针向前移动,pre指向head,head指向next。
最后返回pre指向末尾链表。
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode pre = null;
ListNode next = null;
while(head != null){
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
}
问题:输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是按照递增排序的。
我的思路是:考虑两个链表是否为空,然后确定头节点,再循环比较两链表中大小,确定顺序,最后就是续接上未遍历完的链表。
书上是:用递归,考虑是否为空,比较当前链表值,然后返回其中一个链表节点,递归调用,再次比较下一个节点。
我的代码
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null && list2 != null){
return list2;
}
if(list2 == null && list1 != null){
return list1;
}
if(list1 == null && list2 == null){
return null;
}
ListNode thisList = null;
ListNode head = null;
if( list1.val < list2.val ){
thisList = list1;
head = thisList;
list1 = list1.next;
}else{
thisList = list2;
head = thisList;
list2 = list2.next;
}
while(list1 != null && list2 != null){
if(list1.val < list2.val){
thisList.next = list1;
list1 = list1.next;
thisList = thisList.next;
}else{
thisList.next = list2;
list2 = list2.next;
thisList = thisList.next;
}
}
while(list1 != null){
thisList.next = list1;
list1 = list1.next;
thisList = thisList.next;
}
while(list2 != null){
thisList.next = list2;
list2 = list2.next;
thisList = thisList.next;
}
return head;
}
}
问题:复杂链表中,除了一个next指针外,还有一个Random指针,指向该链表中的随机链表节点,要求复制该链表。
我的思路:先用next指针创建一条链表,然后从头节点开始遍历,寻找随机指针指向的节点,时间复杂度是O(n2)。
书上的方法:1.用一个哈希表存放<当前给定的链表节点,我创建好的链表节点>,然后就是一一对应的把random指针指向对应的键值对链表节点就可以了(空间换时间)。2.把创建好的链表链接到给定的链表后面,然后就如下图,再设置指针指向就可以了。
我的代码
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead == null){
return null;
}
RandomListNode head = new RandomListNode(pHead.label);
myClone(head,pHead);
return head;
}
private void myClone(RandomListNode myLink,RandomListNode pHead){
RandomListNode recordmyLink = myLink;
RandomListNode recordpHead = pHead;
while(pHead.next != null){
RandomListNode node = new RandomListNode(pHead.next.label);
myLink.next = node;
myLink = myLink.next;
pHead = pHead.next;
}
myLink = recordmyLink;
pHead = recordpHead;
while(pHead != null){
RandomListNode soon = recordpHead;
RandomListNode changeMyLink = recordmyLink;
while(soon != null){
if(soon == pHead.random){
myLink.random = changeMyLink;
}
changeMyLink = changeMyLink.next;
soon = soon.next;
}
myLink = myLink.next;
pHead = pHead.next;
}
}
}
import java.util.*;
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if( pHead == null )
return pHead;
boolean flag = true;
HashMap hash =
new HashMap();
RandomListNode newHead = new RandomListNode(pHead.label);
RandomListNode soon = newHead;
RandomListNode record = pHead;
while( record != null ){
RandomListNode node = new RandomListNode(record.label);
soon.next = node;
soon = soon.next;
if( flag ){
flag = false;
newHead = node;
}
hash.put(record,node);
record = record.next;
}
for( RandomListNode key : hash.keySet() ){
hash.get(key).random = hash.get(key.random);
}
return newHead;
}
}
问题:输入两个链表,找出它们的第一个公共节点。
我的思路:使用两个辅助栈,然后遍历两个链表,将他们的链表节点都压入栈中,逐个弹出比较(需要额外内存O(m+n))。
书上的最优方法:遍历一遍两链表,知道两个链表的长短,第二次遍历时,较长链表先走上若干步,然后两链表同时开始遍历,知道找到第一个公共节点。
我的代码
import java.util.Stack;
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if( pHead1 == pHead2 ){
return pHead1;
}
Stack stack1 = new Stack<>();
Stack stack2 = new Stack<>();
while( pHead1 != null ){
stack1.push(pHead1);
pHead1 = pHead1.next;
}
while( pHead2 != null ){
stack2.push(pHead2);
pHead2 = pHead2.next;
}
ListNode record = null;
while( !stack1.empty() && !stack2.empty() ){
ListNode thisNode = stack1.pop();
if(stack2.pop() == thisNode){
record = thisNode;
continue;
}else{
return record;
}
}
return record;
}
}
优化后
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if( pHead1 == null || pHead2 == null)
return null;
if( pHead1 == pHead2 )
return pHead1;
int length1 = 0;
int length2 = 0;
ListNode record1 = pHead1;
ListNode record2 = pHead2;
while( pHead1 != null || pHead2 != null ){
if( pHead1 != null ){
length1 ++;
pHead1 = pHead1.next;
}
if( pHead2 != null ){
length2 ++;
pHead2 = pHead2.next;
}
}
if( length1 > length2 ){
int count = length1 - length2;
while( count > 0 ){
record1 = record1.next;
count --;
}
while( record1 != null && record2 != null ){
if( record1 == record2 ){
return record1;
}
record1 = record1.next;
record2 = record2.next;
}
}else{
int count = length2 - length1;
while( count > 0 ){
record2 = record2.next;
count --;
}
while( record1 != null && record2 != null ){
if( record1 == record2 ){
return record1;
}
record1 = record1.next;
record2 = record2.next;
}
}
return null;
}
}
问题:一个链表中包含环,如何找出环的入口节点?
我的思路:遍历链表,每次操作:将当前链表节点指针与容器中的链表节点做判断,若相等,则为环入口,若不相等,将当前链表存入容器。(需要额外的内存O(n)与额外的时间O(n2))
书的方法:找到环点数,然后用双指针移动。(无需额外内存与额外的时间消耗O(1))
我的代码
import java.util.ArrayList;
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead == null){
return null;
}
ArrayList list = new ArrayList<>();
ListNode thisNode = pHead;
list.add(thisNode);
while(thisNode != null){
for(int i=0;i
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if( pHead.next == null )
return null;
ListNode meetingNode = meetingNode(pHead);
int count = 1;
ListNode record = meetingNode;
while( record.next != meetingNode ){
record = record.next;
count ++;
}
ListNode record1 = pHead;
ListNode record2 = pHead;
for( int i = 0; i < count ; i ++ ){
record1 = record1.next;
}
while( record1 != record2 ){
record1 = record1.next;
record2 = record2.next;
}
return record1;
}
private ListNode meetingNode(ListNode pHead){
if( pHead == null )
return null;
ListNode pSlow = pHead.next;
if( pSlow == null )
return null;
ListNode pFast = pSlow.next;
while( pFast != null && pSlow != null ){
if( pFast == pSlow )
return pFast;
pSlow = pSlow.next;
pFast = pFast.next;
if( pFast != null )
pFast = pFast.next;
}
return null;
}
}
问题:在一个排序的链表中,如何删除重复的节点?
我的思路:捣鼓了很久,都没有做出来,终于用了必杀技:位图法。记录每一个节点的特征向量,做好统计,然后再遍历统计结果,重新创建链表。时间复杂度是O(n),空间复杂度也是O(n)。
书本的方法:直接删除。
我的代码
public class Solution {
public ListNode deleteDuplication(ListNode pHead)
{
if(pHead == null||pHead.next == null){
return pHead;
}
ListNode node = pHead;
int length = 0;
while(node.next != null){
length ++;
node = node.next;
}
int[] record = new int[100];
node = pHead;
while(node != null){
record[node.val]++;
node = node.next;
}
ListNode soon = new ListNode(0);
ListNode result = soon;
for(int i=0;i