面試算法2---鏈表問題(1)


0、節點定義

//單鏈表
class Node {
	public int value;
	public Node next;
	public Node(int value) {
		this.value=value;
	}
}

//雙鏈表
class DoubleNode{
	public int value;
	public DoubleNode last;
	public DoubleNode next;
	
	public DoubleNode(int data) {
		this.value=data;
	}
}

一、打印兩個有序鏈表的公共部分

給定兩個有序鏈表的頭指針head1和head2,打印兩個鏈表的公共部分。

//打印兩個有序鏈表的公共部分
public void printCommonPart(Node head1, Node head2) 
{
	System.out.println("Common Part:");
	if(head1==null || head2==null) {
		return ;
	}
	while(head1!=null && head2!=null) {
		if (head1.value==head2.value) {
			System.out.print(head2.value + " ");
			head1=head1.next;
			head2=head2.next;
		}else if (head1.value>head2.value){
			head2=head2.next;
		}else {
			head1=head1.next;
		}
	}
	System.out.println();
}

二、在單鏈表和雙鏈表中刪除倒數第K個節點

如果鏈表的長度爲N,時間複雜度達到O(N),額外空間複雜度達到O(1)。

//單鏈表中刪除倒數第K個節點
public Node deletelastKthNode(Node head,int lastKth) {
	// 若不需要刪除,則返回頭結點
	if (head==null || lastKth<1) {
		return head;
	}
	Node cur=head;
	while(cur!=null) {
		lastKth--;
	}
	if (lastKth==0) {
		return head.next;
	}

	if (lastKth==0) {
		return head.next;
	}
	if (lastKth<0) {
		cur=head;
		while(++lastKth!=0) {
			cur=cur.next;
		}
		cur=cur.next;
	}
	return head;
}

//雙鏈表中刪除倒數第K個節點
public DoubleNode deleteLastkthNode(DoubleNode head, int lastKth) {
	if(head==null || lastKth<1) {
		return head;
	}
	DoubleNode cur=head;
	while(cur!=null) {
		cur=cur.next;
		lastKth--;
	}
	if (lastKth==0) {
		head = head.next;
		head.last=null;
	}
	
	if (lastKth<0) {
		cur=head;
		while(++lastKth!=0) {
			cur=cur.next;
		}
		DoubleNode next = cur.next.next;
		cur.next=next;
		//next.last=cur;
		if(next!=null) {
			next.last=cur;
		}
	}
	
	return head;
}

三、刪除鏈表的中間節點

給定鏈表的頭結點head,實現刪除鏈表的中間節點的函數。
如:
不刪除任何節點;
1->2,刪除節點1;
1->2->3,刪除節點2
1->2->3->4,刪除節點2;
1->2->3->4->5,刪除節點3;

// 刪單鏈表的中間節點
public Node removeMidNode(Node head) {
	//無節點和只有一個節點時,不刪
	if (head==null || head.next==null) {
		return head;
	}
	// 2個節點
	if(head.next!=null && head.next.next==null) {
		return head.next;
	}
	Node pre=head;
	Node cur=head.next.next;
	while(cur.next!=null && cur.next.next!=null) {
		pre=pre.next;
		cur=cur.next.next;
	}
	pre.next=pre.next.next;
	return head;
}

四、反轉單向和雙向鏈表

最好能做到代碼一次成型,運行不出錯。

// 反轉單鏈表
public Node reverseNode(Node head) {
	if (head==null) {
		return head;
	}
	Node pre=null;
	Node next=null;
	while(head!=null) {
		next=head.next;
		head.next=pre;
		pre=head;
		head=next;
	}
	return pre;
}

// 反轉雙鏈表
public DoubleNode reverseDoubleNode(DoubleNode head) {
	if (head==null) {
		return head;
	}
	DoubleNode pre=null;
	DoubleNode next=null;
	
	while(head!=null) {
		next=head.next;
		head.next=pre;
		head.last=next;
		pre=head;
		head=next;
	}
	return pre;
}

五、反轉部分單向鏈表

給定一個單向鏈表的頭節點head,以及兩個整數from和to,在單向鏈表上把第from個節點到第to個節點這一部分進行反轉。
例如:
1->2->3->4->5->null,from=2,to=4
調整結果爲:1->4->3->2->5->null
再如:
1->2->3->null, from=1, to=3
調整結果爲:3->2->1->null

// 部分反轉單鏈表
public Node reversePart(Node head,int from,int to) {
	if (head==null) {
		return head;
	}
	Node node1=head;
	int len=0;
	Node fPre=null;
	Node toPos=null;
	while(node1!=null) {
		len++;
		fPre=(len==from-1?node1:fPre);
		toPos=(len==to+1?node1:toPos);
		node1=node1.next;
	}
	if(from>=to || from<1 || to>len) {
		return head;
	}
	node1 =(fPre==null?head:fPre.next);
	Node node2=node1.next;
	node1.next=toPos;
	Node next=null;
	while(node2!=toPos) {
		next=node2.next;
		node2.next=node1;
		node1=node2;
		node2=next;
	}
	if(fPre!=null) {
		fPre.next=node1;
		return head;
	}
	return node1;
}

六、判斷一個鏈表是否爲迴文結構

給定一個鏈表的頭節點head,請判斷該鏈表是否爲迴文結構。
例如:
1->2->1->null,返回true;
1->2->2->1->null,返回true;
1->2->3->null,返回false;

// 利用棧結構,判斷鏈表是否爲迴文結構
public boolean isPalindromel(Node head) {
	Stack<Node> stack = new Stack<Node>();
	Node cur =head;
	while(cur!=null) {
		stack.push(cur);
		cur=cur.next;
	}
	cur=head;
	while(cur!=null) {
		if(stack.pop().value != cur.value) {
			return false;
		}
		cur=cur.next;
		
	}
	return true;
}

七、將單向鏈表按某值劃分成左邊小、中間相等、右邊大的形式

給定一個單向鏈表的頭節點head,節點的值類型是整型,再給定一個整數pivot。實現一個調整鏈表的函數,將鏈表調整爲左部分都是小於pivot的節點,中間部分都是值等於pivot的節點,右部分都是值大於pivot的節點。除了這個要求外,對調整後的節點順序沒有更多的要求。

例如:鏈表9->0->4->5->1,pivot=3.
調整後鏈表可以是1->0->4->9->5,也可以是:0->1->9->5->4。

//單向鏈表按某值劃分
public Node listPartition1(Node head, int pivot) {
	if (head==null) {
		return head;
	}
	int k=0;
	Node cur=head;
	while(cur!=null) {
		k++;
		cur=cur.next;
	}
	Node[] nodeArr = new Node[k];
	cur=head;
	k=0;
	while(cur!=null) {
		nodeArr[k++]=cur;
		cur=cur.next;
	}
	// 數組內元素調整
	arrPartition(nodeArr, pivot);
	// 數組拼接成鏈表
	for(k=1; k < nodeArr.length; k++) {
		nodeArr[k-1].next=nodeArr[k];
	}
	nodeArr[k-1].next = null;
	
	return nodeArr[0];
}

public void swapNode(Node[] nodeArr, int a,int b) {
	Node temp = nodeArr[a];
	nodeArr[a]=nodeArr[b];
	nodeArr[b]=temp;
}

public void arrPartition(Node[] nodeArr, int pivot) {
	int small=-1;
	int len=nodeArr.length;
	int index=0;
	while(index!=len) {
		if(nodeArr[index].value<pivot) {
			swapNode(nodeArr, ++small, index++);
		}else if (nodeArr[index].value == pivot) {
			index++;
		}else {
			swapNode(nodeArr, --len, index);
		}
	}
}

八、兩個單鏈表生成相加鏈表

假設鏈表中每個節點的值都在0~9之間,那麼鏈表整體就可以代表一個整數。例如:9->3->7,可以代表整數937。
例如:鏈表1位9->3->7,鏈表2爲6->3,最後生成新的結果鏈表爲1->0->0->0。

思路:先將鏈表轉換成整數,求和,再轉換成鏈表。這種方式轉int類型時可能會溢出,所以不建議這種方法。

//利用棧結構求解。該方法空間複雜度較高,可以利用逆序鏈表求解
public  Node addList(Node head1,Node head2) {
	Stack<Integer> s1 = new Stack<Integer>();
	Stack<Integer> s2 = new Stack<Integer>();
	Node cur=head1;
	while(cur!=null) {
		s1.push(cur.value);
		cur=cur.next;
	}
	cur=head2;
	while(cur!=null) {
		s2.push(cur.value);
		cur=cur.next;
	}
	Node node=null;
	Node pre=null;
	int ca=0;
	int n=0;
	int n1=0;
	int n2=0;
	while(!s1.isEmpty() || !s2.isEmpty()) {
		n1= (s1.isEmpty())?0:s1.pop();
		n2= (s2.isEmpty())?0:s2.pop();
		n=n1+n2+ca;
		pre=node;
		node = new Node(n%10);
		node.next=pre;
		ca=n/10;
	}
	if(ca==1) {
		pre=node;
		node=new Node(1);
		node.next=pre;
	}
	return node;
}

九、將單鏈表的每K個節點逆序

給定一個單鏈表的頭節點head,實現一個調整單鏈表的函數,使得每K個節點之間逆序,如果最後不夠K個節點一組,則不調整最後幾個節點。
例如:
1->2->3->4->5->6->7->8->null,K=3。
調整後:3->2->1->6->5->4->7->8->null。其中7/8不調整,因爲不夠一組。

//將單鏈表的每K個節點之間逆序
public Node reverseKNodes(Node head, int K) {
	if (K<2) {
		return head;
	}
	Stack<Node> stack = new Stack<Node>();
	
	Node cur = head;
	Node pre = null;
	Node next = null;
	Node newHead = head;
	while (cur!=null) {
		next = cur.next;
		stack.push(cur);
		if (stack.size() == K) {
			pre = reverse(stack, pre, next);
			newHead = newHead==head?cur:newHead;
		}
		cur = next;
	}
	return newHead;
}

public Node reverse(Stack<Node> stack, Node left, Node right) {
	Node cur = stack.pop();
	if(left != null) {
		left.next=cur;
	}
	Node next = null;
	while (!stack.isEmpty()) {
		next = stack.pop();
		cur.next = next;
		cur = next;
	}
	cur.next = right;
	return cur;
}

十、刪除無序單鏈表中值重複出現的節點

給定一個無序單鏈表的頭節點head,刪除其中值重複出現的節點。
例如:1->2->3->3->4->4->2->1->1->null,刪除值重複的節點之後爲1->2->3->4->null。
方法一:時間複雜度爲O(1)。
方法二:額外空間複雜度爲O(1)。

//方法一:刪除無序單鏈表中值重複出現的節點
public void removeRep1(Node head) {
	if(head == null) {
		return;
	}
	Node pre = head;
	Node cur = pre.next;
	HashSet<Integer> set = new HashSet<>();
	set.add(head.value);
	while(cur!=null) {
		if(set.contains(cur.value)) {
			pre.next = cur.next;
		} else {
			set.add(cur.value);
			pre = cur;
		}
		cur = cur.next;
	}
}
//方法二:刪除無序單鏈表中值重複出現的節點
public void removeRep2(Node head) {
	Node cur = head;
	Node pre = null;
	Node next = null;
	while(cur!=null) {
		pre = cur;
		next = pre.next;
		while(next!=null) {
			if(cur.value == next.value) {
				pre.next = next.next;
			}else {
				pre = next;
			}
			next = next.next;
		}
		cur = cur.next;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章