您的位置:首页 > 编程语言 > Java开发

Cracking the Code Interview Chapter Two-- TaylorZhangYuxin's Solusion

2016-06-29 15:18 411 查看
This article is my own thinking and analysis when reading the cracking the code interview 6th edition.



Chapter 2 Linked Lists

The basic singly linked list is of the following code:

public 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;
}
}


Abstract:

This Chapter mainly discussed how to access a precise node in the linked list and how to trace the special node in a special structure, such as loop and intersection. It seems no difficulties in the chapter to understand. However, the most difficult thing in this chapter is recursion design. How to design a recursion, how do decompose the recursion structure to keep track the data among the nodes? More practice is required in recursion.

2.1 Write code to remove duplicates from an unsorted linked list.

Analysis:

There is no data base for this linked list, so a buffer to store scanned data is required. The basic thought now is to iterate through the list and check every element. Once the duplicate date was found, remove this node from this list.

Solution description:

iterate through the list and use Hash Set to store every scanned data. Use the way in chapter one to check duplicate data.

(comment: Because the language I use is Java which is different from the book. Here I just represent the code I wrote. All the code from the book will not be shown out.)

public LinkedList<E> ChapterTwo01(LinkedList<E> linklist) {
// TODO Auto-generated constructor stub
HashSet<E> buffer = new HashSet<E>();
int i = 0;
for( E data : linklist){
if(buffer.contains(data)){
linklist.remove(i);
}
else{
buffer.add(data);
}
i++;
}
return linklist;
}


FOLLOW UP:

How would you solve this problem if a temporary buffer is not allowed?

Analysis:

If there is no buffer to hold the data, the only thing we can do is to compare each data with the rest of the list. This solution wastes time but saves spaces.

Solution description:

Use loop within a loop to check the duplicate data.

public LinkedList<E> ChapterTwo01FollowUP(LinkedList<E> linklist){
for(int i = 0; i< linklist.size(); i++){
for(int j = i; j<linklist.size(); j++){
if(linklist.get(i) == linklist.get(j)){
linklist.remove(j);
}
}
}
return linklist;
}


What I got:

Save memory sometimes will cause a huge waste of processing time.

2.2 Implement an algorithm to find the kth to last element of a singly linked list.

Analysis:

Kth to last element is the Kth element that counts from the backwards to the front. Here the thing is worth to notice that singly linked list can only move forward.

If the length is known to us, then the answer is simple – just count the data position based on the length.

If the length is not known to us, we need to first get the length then count the Kth element from the end.

Solution description:

(1) Use counter to count the length while iterate through the list. Then calculate the position to get the data.

public int ChapterTwo02V1(Node listhead, int k) {
// TODO Auto-generated constructor stub
Node tracer = listhead;
int length = 0;
//count the length of the list
while(tracer.next != null){
length ++;
tracer = tracer.next;
}
//fetch the data
for(int i = 0; i< length - k; i++){
listhead = listhead.next;
}
return listhead.data;
}


(2) We could use the recursion to trace the data back.

public void ChapterTwo02V2(Node listhead, int k){
int i = 0;
recursion(listhead, k, i);
}

private int recursion(Node listhead, int k, int i) {
// TODO Auto-generated method stub
int length = 0;
if(listhead.next != null){
i++;
length = recursion(listhead.next, k, i);
if(length - i == k){
System.out.println(listhead.data);
}
}
else{
//the final element, now i is the length of this list
if(k == 0) System.out.println(listhead.data);
i++;
return i;
}
return length;
}

/**
* This is the recursion solution from the book.
* @param head
* @param k
* @return
*/
public int ChapterTwo02V3(Node head,int k){
if(head == null){
return 0;
}
int index = ChapterTwo02V3(head.next,k)+1;
if(k == 0 && index == 1) System.out.println(head.data);
if(index == k){
System.out.println(head.data);
}
return index;
}


(3) Use running technique to set two pointer move together. Create two head as p1 and p2. Move p1 k steps into the list. Then move p1 and p2 together till p1 hit the end of the list. Then at this time, p2 is in the position of Kth to the last.

/**
* the solution from the book that uses runner technique.
* @param head
* @param k
* @return
*/
public Node ChapterTwo02V4(Node head, int k){
Node p1 = head;
Node p2 = head;

//move p1 k nodes to the list
for(int i = 0; i<k; i++){
if(p1 == null) return null;
p1 = p1.next;
}

//move them at the same pace. when p1 hits the end, p2 will be at the right element
while(p1 != null){
p1 = p1.next;
p2 = p2.next;
}
return p2;
}


What I got:

If we want to get two data from a child function/method, one way to solve it is to create a global variant. Another way is to calculate one data from another, when the two data was somehow in some relation. However, in this circumstances, the two data were one.

The runner technique performs well in finding a data in a given relative position. More exercises are needed in this.

2.3 Implement an algorithm to delete a node in the middle of a singly linked list, given only access to that node.

Example:

input: the node c from the linked list a-> b-> c-> d-> e-> f

output: nothing is returned, but the new linked list looks like a-> b-> d-> e-> f

Analysis:

In this case, the important key here is to understand how a linked list work. When b jump to c, the b.next points to c. If we delete c, then now b.next will point to d rather than c. If we can’t change b.next, how about we change c’s value and c.next’s value? Let it to d and e. Then this solution is solved easily.

public ChapterTwo03(Node middle) {
// TODO Auto-generated constructor stub
middle.data = middle.next.data;
middle.next = middle.next.next;
}


One Step Ahead:

Though in the question, it says the given node is not the first or the last node. However, what if it is? The last node works like when access node.next, a NULL will be returned. We can’t access the former node so we can’t change the former note to set its next to NULL. However, we could set this node to NULL to indicate that this node is dummy.

What I got:

When interview, read carefully about the question. However, never stop thinking about the special situation. It shows that you are active and has the eager to challenge the rear but hard situation.

2.4 Write code t partition a linked list around a value x, such that all nodes less than x come before all node greater than or equal to x. if x is contained within the list, the values of x only need to be after the elements less than x. The partition element x can appear anywhere in the “right partition”; it does not need to appear between the left and right partitions.

Analysis:

This question has two approach.

Since the main process is to switch the access order, we can choose whether change the value of node, or change the node order. All the smaller values must be moved to the left, so a iteration is required to find out which values at which position need to move. Then switch the value of node.

(1) Switch the value. This approach changes the value for the node. Did not change the node order. Here we only need two temp nodes, one for iteration, one for switch.

/**
* The solution that chance the value of node rather chance the node order
* @param nodehead
* @param partition
*/
public void ChapterTwo04V1(Node nodehead, int partition) {
// TODO Auto-generated constructor stub
Node n1 = nodehead;//node ready for data switch

c933
Node n2 = nodehead.next;//node goes into the list to check target node

int tempdata = 0;
while(n2.next != null){
if(n2.data < partition){
//switch datas
tempdata = n1.data;
n1.data = n2.data;
n2.data = tempdata;

n1 = n1.next;
}
n2 = n2.next;
}
}


(2) Switch the node itself. Sometimes, when the node content is too large or the value is private that is not allowed to access, we need to manipulate the node rather than value. For the linked list, every node is linked, when we change the node, we need to change the lines between them. Here 4 temp node are required.

Another thing I notice is that when we change the list, the head of the list it not the given one. We need to store the new list head so that someone else can check the list or do the further use.

/**
* change nodes in the list
* @param nodehead
* @param partition
* @return the new list head
*/
public Node ChapterTwo04V2(Node nodehead, int partition){
Node n11 = null;//the node that marks the former node of switch node
Node n12 = nodehead;//the node that need to be switch back
Node n21 = nodehead;// The node that marks the former node of the target node
Node n22 = nodehead.next;// The node that marks the target node
int exchangecounter = 0;

while(n22.next != null){
if(n22.data < partition){
Node temp = n12;
n21.next = n12;
n12.next = n22.next;
if(exchangecounter != 0){
n11.next = n22;
}
n22.next = temp.next;

//exchange n22 and n12 to reset it's position
temp = n22;
n22 = n12;
n12 = temp;
if(exchangecounter == 0){
n11 = n12;
nodehead = n11;
}
else{
n11 = n11.next;
}
n12 = n12.next;
exchangecounter ++;
}
n21 = n21.next;
n22 = n22.next;
}

return nodehead;
}


(3) The book argued that there is a stable solution that create two new lists to hold smaller elements and greater elements. There we need four nodes to keep track the lists. At the end, merge the two lists into one and return.

public Node ChapterTwo04V3(Node nodehead, int partition){
Node n11 = null;
Node n12 = null;
Node n21 = null;
Node n22 = null;

while(nodehead.next != null){
if(nodehead.data < partition){
//save to list1
if(n11 == null){
n11 = nodehead;
n12 = nodehead;
}
else{
n12.next = nodehead;
n12 = nodehead;
}
}
else{
//save to lsit2
if(n21 == null){
n21 = nodehead;
n22 = nodehead;
}
else{
n22.next = nodehead;
n22 = nodehead;
}
}
nodehead = nodehead.next;
}
n12.next = n21;

return n11;
}


What I got:

There are many approaches to a question. However, the most optimal one does not exist. The matters is what the thought of yours. So the truth proves that write down your thought before read the “standard” solution is really important.

2.5 You have two numbers represented by a linked list, where each node contains a single digit. The digits are stored in reverse order, such that the 1’s digit is at the head of the list. Write a function that adds the two numbers and returns the sum as a linked list.

EXAMPLE:

Input: (7 -> 1 -> 6) + (5 -> 9 -> 2). This is 617 + 295

Output: 2 -> 1 -> 9. That is 912.

Analysis:

This question looks simple. Read the two lists and form the number. After submission, create a new list. All about it is extract the correct number in the precise position.

Solution description

(1) The first element in the list is of 10 powers 0. The second is of 10 powers 1. Then it is easy to form the number out. After get the right number, deform the number by divide it by 10. Form a new output list based on the result.

/**
* solution 1
*/
public Node ChapterTwo05(Node num1, Node num2) {
int number1 = 0;
int number2 = 0;

number1 = extractNum(num1);
number2 = extractNum(num2);

return formOutput(number1 + number2);
}

/**
* form the list based on number
* input 41234
* output 4 -> 3 -> 2 -> 1 -> 4
* @param i
* @return
*/
private Node formOutput(int i) {
Node head = new Node(i%10);
Node end = head;
while(i > 10){
i = i/10;
end.next = new Node(i%10);
end = end.next;
}
end.next = new Node(i%10);

return head;
}
/**
* extract number from the list
* 7 -> 1 -> 5 = 517
* @param num1
* @return
*/
private int extractNum(Node num1) {
int number = 0;
int power = 0;
while(num1 != null){
number = number + num1.data * (int)Math.pow(10, power);
num1 = num1.next;
}
return number;
}


(2) Since the list is stored from back to head, we could calculate them one by one to get each digit. So the list can be formed one by one.

/**
* solution 2
* no calculation and reform
*/
public Node ChapterTwo05V2(Node num1, Node num2) {
int carry = (num1.data + num2.data)/10;
Node head = new Node((num1.data + num2.data)%10);
Node end = head;

while(num1.next != null && num2.next != null){
num1 = num1.next;
num2 = num2.next;

end.next = new Node((num1.data + num2.data)%10 + carry);
carry = (num1.data + num2.data)/10;
end = end.next;
}
if(num1.next != null){
num1.data += carry;
while(num1.next != null){
end.next = num1;
end = end.next;
num1 = num1.next;
}
}else if(num2.next != null){
num2.data += carry;
while(num2.next != null){
end.next = num2;
end = end.next;
num2 = num2.next;
}
}
return head;
}


Follow Up

Suppose the digits are stored in forward order. Repeat the above problem.

EXAMPLE:

Input: (6 -> 1 -> 7) + (2 -> 9 -> 5). This is 617 + 295

Output: 9 -> 1 -> 2. That is 912.

Analysis:

As always, first form the number then get the sum. Based on the sum, create the list. Now, we cannot form the list from the head to the end. However, we are able to from it from end to the head. It also works.

However, the book argues that it is better to process node by node. Which means use recursive to add each node to form the final list. Not calculate two number out.

/**
* FOLLOW UP
* the digit that stroes in forward order
* 4 -> 1 -> 2 -> 3 = 4123
* @param num1
* @param num2
* @return
*/
public Node ChapterTwo05FOLUP(Node num1, Node num2) {
// TODO Auto-generated constructor stub
int number1 = 0;
int number2 = 0;

number1 = extractReverseNum(num1);
number2 = extractReverseNum(num2);

return formReverseOutput(number1 + number2);
}

private Node formReverseOutput(int i) {
// TODO Auto-generated method stub
Node head = new Node(i%10);
i /= 10;
while(i > 0){
Node newHead = new Node(i%10);
newHead.next = head;
head = newHead;
i /= 10;
}
return head;
}

private int extractReverseNum(Node num1) {
// TODO Auto-generated method stub
int number = 0;
while (num1 != null){
number = number*10 + num1.data;
num1 = num1.next;
}
return number;
}


2.6 implement a function to check if a linked list is a palindrome.

Analysis:

Palindrome defines as that it reads the same forward or backward. To do this, we have to go through the whole list to check every element.

(1) use recursion. Use global variables to trace the nodes.

(2) create a new list that has a reverse order of the original one. When creating it, the length can be figured out. Then check through the two list step by step till the middle, which gets from the length.

public boolean ChapterTwo06V2(Node listhead) {
if(listhead == null) return false;
Node reverselist = listhead;
int length = 1;
while (listhead.next != null){
listhead = listhead.next;
Node temp = listhead;
temp.next = reverselist;
reverselist = temp;
}
for(int i = 0; i < length/2; i++){
if(reverselist.data != listhead.data) return false;
reverselist = reverselist.next;
listhead = listhead.next;
}
return false;
}


What I got:

The recursion solution is not discussed here. The reason is that I’m lazy. And the recursion solution is too complicated. I can’t figure it out. Wait the second time to figure that out.

2.7 Give two linked lists, determine if the two lists intersect/ return the intersecting node. Note that the intersection is defined based on reference, not value. That is, if the kth node of the first linked list is the exact same node as the jth node of the second linked list, then they are intersecting.

Analysis:

By definition, if two lists intersect with each other, they must end in the same node.

(1) Reaches the end of each list, compare the node’s intersection and trace back to the head to find the very first same node.

public Node ChapterTwo07(Node list1, Node list2) {
// TODO Auto-generated constructor stub
int length1 = getLength(list1);
int length2 = getLength(list2);

if(length1 > length2){
for(int i = 0; i < length1 - length2; i++){
list1 = list1.next;
}
}
else{
for(int i = 0; i< length2 - length1; i++){
list2 = list2.next;
}
}
while(list1 != list2 ){
if(list1.next == null || list2.next == null) return null;
list1 = list1.next;
list2 = list2.next;
}
return list1;
}

private int getLength(Node list1) {
// TODO Auto-generated method stub
if(list1 == null) return 0;
int len = 1;
while (list1.next != null){
list1 = list1.next;
len++;
}
return len;
}


What I got:

Basically, the key here is to clear find the structure of the list and figure out the position. From the theory in the front, if runtime is in the same level, it doesn’t matter to do an extra process to get a key data. Now, we run a O(n) time to fetch the length. Then run a constant time, O(k) which is the position of intersecting node to get the data. This basically is O(n) time.

2.8 Given a circular linked list, implement an algorithm that returns the node at the beginning of the loop.

DEFINATION

Circular linked list: a linked list in which a node’s next pointer points to an earlier node, so as to make a loop in the linked list.

Input: A -> B -> C -> D ->E ->F ->C

Output: C

Analysis:

(1) Use hash set to store the node and check the duplicate node.

public Node ChapterTwo08V1(Node listhead) {
// TODO Auto-generated constructor stub
HashSet<Node> nodeset = new HashSet<Node>();
while(!nodeset.contains(listhead)){
if(listhead.next == null){
return null;
}
nodeset.add(listhead);
listhead = listhead.next;
}
return listhead;
}


(2) The book stress a tricky solution. Use faster mover and slow mover, check the node collision point to find out the loop beginner. Assume the length that before the loop is k steps. The length of the loop is LOOPLEN steps. Of course, before processing the loop beginning point, we need to check whether there is loop.

i. First set two movers at the beginner. One fast mover moves two steps per time and slow mover moves one step per time.

ii. When the slow mover reaches the beginning of loop, the fast mover stretch into the loop k steps. The distance between slow mover and fast mover is LOOPLEN – k.

iii. Move them till they are colliding. Now the collision point is away from the loop beginner k steps.

iv. Now move slow mover to the beginner of the list. Move both slow mover and fast mover one step every time. When they collide again, the point is the loop beginner.

public Node ChapterTwo08V2(Node listhead) {
// TODO Auto-generated constructor stub
if(listhead == null) return null;
Node fastmover = listhead;
Node slowmover = listhead;

do{
if(fastmover.next == null || fastmover.next == null)
return null;    //no loop existed
fastmover = fastmover.next;
fastmover = fastmover.next;
slowmover = slowmover.next;
}while(fastmover != slowmover);

//now the two mover collide
slowmover = listhead;
while(slowmover == fastmover){
slowmover = slowmover.next;
fastmover = fastmover.next;
}
return fastmover;
}


What I got:

I know that in this question the book’s solution may not be very optimal. However, the book’s solution takes a deep analysis about the structure of the loop. Use the feature of loop and the runner technique to find out desired data. The thought need us to carefully think about. More practice is desired in this area.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息