!!!排列組合問題 古典概型現在面試的主趨勢
單向鏈表中最重要的是節點,真正有用的值是value,next是維持結構用的
- 1 第一個節點有特殊性,1)沒有前驅2)代表整個鏈表
- 2 插入刪除節點時需要前驅節點,除非是第一個節點
- 3 凡是解引用的地方,需要考慮引用是否爲null
- 4 方便斷開方便接上
public class ListNode {
/**
* @Description: 刪除鏈表中的指定元素{1.考慮指定元素包含了head的情況}
* 1.
* 定義新鏈表:Node head表示原鏈表的head;Node result 結果鏈表;
* Node last 記錄最後一個節點,可以提升尾插的性能
* 一次遍歷鏈表中的每一個節點,如果節點的值不是要刪除的值,把節點尾插到新鏈表中
* 2.遍歷鏈表,若果是指定指就刪除
*/
public ListNode next;
public int val;
public ListNode (int val) {
this.val= val;
}
public ListNode removeElements(ListNode head, int val) {
ListNode result=null;
ListNode cur=head;
ListNode last=null;
while(cur!=null){
if(cur.val==val){
cur=cur.next;
}else{
ListNode next=cur.next;
cur.next=null;
if(result==null){
//將節點尾插進入新鏈表
cur.next=result;
result=cur;
last=result;
}else{
last.next=cur;
last=cur;
}
cur=next;
}
}
return result;
}
/**
* @Description: 反轉單鏈表:定義一個新鏈表,取源鏈表中的每一個節點,頭插到新鏈表的節點中
* @Param:
* @return:
*/
public LinkedNode reversList(LinkedNode head) {
//定義結果鏈表
LinkedNode result = null;
LinkedNode cur = head;
//進行頭插操作
// 1-2-3-4
//1-null;2-1-null;
while (cur != null) {
LinkedNode next = cur.next;
//前插操作:
// 1.將當前取到的節點作爲結果鏈表的頭元素,頭插進去(node.next=head;this.head=node;)
// 2.當前元素就成爲了結果鏈表
cur.next = result;
result = cur;
//使循環繼續,利用cur取下一個節點
cur = next;
}
return result;
}
/**
* @Description: 反轉鏈表2:利用三引用反轉
* @Param: LinkedNode pre=null;LinkedNode cur=head;LinkedNode next=cur.next;
* @return:
*/
public LinkedNode reverseList2(LinkedNode head) {
if (head == null) {
return null;
}
LinkedNode pre = null;
LinkedNode cur = head;
//1-2-3-4-2-3
// p c n
//3-2-1-null
while (cur != null) {
LinkedNode next = cur.next;
//逆置的過程
cur.next = pre;
pre = cur;
//循環向後走的條件
cur = next;
}
return pre;
}
/**
* @Description: 合併兩個有序鏈表:定義一個新鏈表:分別遍歷兩個鏈表,選擇一個值較小的尾插進新鏈表
* @Param: LinkedNode next=cur.next; LinkedNode result=null;LinkedNode last=null;
* @return:
*/
public LinkedNode mergeTwoList(LinkedNode l1, LinkedNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
LinkedNode cur1 = l1;
LinkedNode cur2 = l2;
LinkedNode last = null;
LinkedNode result = null;
while (cur1 != null && cur2 != null) {
if (cur1.value >= cur2.value) {
LinkedNode next = cur2.next;
cur2.next = null;
//取值小的(cur2)尾插操作
if (result == null) {
result = cur2;
} else {
last.next = cur2;
}
last = result;
cur2 = next;
} else {
LinkedNode next = cur1.next;
cur1.next = null;
if (result == null) {
result = cur1;
} else {
last.next = cur1;
}
last = result;
cur1 = next;
}
}
return result;
}
/**
* @Description: 以給定x爲基準將鏈表分割爲兩部分,
* 所有小於x的節點排在大於或=x的節點前,節點的排列順序不改變
* <p>
* 解決思路:定義兩個鏈表:遍歷原鏈表,小於x的節點就放在小的新鏈表中,
* 大於=x的就放在大的鏈表中,再把兩個鏈表鏈接即可
*/
public LinkedNode partation(LinkedNode pHead, int x) {
LinkedNode small = null;
LinkedNode smallLast = null;
LinkedNode big = null;
LinkedNode bigLast = null;
LinkedNode cur = pHead;
while (cur != null) {
LinkedNode next = cur.next;
if (cur.value < x) {
//保證尾插的最後一個節點的next爲空
cur.next = null;
//如果小鏈表沒有頭節點,當前就是小鏈表的頭節點
if (small == null) {
small = cur;
} else {
//否則就就最後一個節點的下一個,即尾插
smallLast.next = cur;
}
smallLast = cur;
cur = next;
} else {
cur.next = null;
if (big == null) {
big = cur;
} else {
bigLast.next = cur;
}
bigLast = cur;
cur = next;
}
}
//將大鏈表尾插到小鏈表中{ 1.small爲null || 2.big爲null}
LinkedNode result = null;
if (small == null) {
return big;
} else {
smallLast.next = big;
return small;
}
}
/**
* @Description: 給定一個帶有節點head的鏈表,返回鏈表的之間節點,如果有兩個節點,取第二個節點
* 解決:雙指針遍歷:一個指針跑兩步,另一個指針只跑一步,
* 因此快的指針走到null節點(末尾)了,慢的指針正好在鏈表之間
*/
public LinkedNode middleNode(LinkedNode head) {
LinkedNode fast = head;
LinkedNode slow = head;
while (fast != null) {
fast = fast.next;
if (fast == null) {
break;
}
slow = slow.next;
//fast走了兩步,slow只走了一步,
// 並且保證若是偶數個節點,慢指針值向的是中間的後一個節點
fast = fast.next;
}
return slow;
}
/**
* @Description: 在非空單鏈表中找指定倒數第K個節點
* 解決:定義一前以後兩個節點,先讓前一個節點先走K步,然後一起前進,
* 當前面的節點走到null,則就是倒數第K個節點
* 考慮特殊情況{鏈表正好有m個節點,1.指定k大於m: 2.k等於m}
* 1-2-3-4-5
* @return:
*/
public LinkedNode findKthNode(LinkedNode head, int k) {
LinkedNode front = head;
LinkedNode back = head;
int i = 0;
for (i = 0; i < k; i++) {
front = front.next;
}
//如果鏈表有5個節點,要找倒數第六個
if (front == null && i < k) {
return null;
//如果鏈表有5個節點,要找倒數第五個
} else if (front == null) {
return head;
}
while (front != null) {
front = front.next;
back = back.next;
}
return back;
}
/**
* @Description: 對於一個鏈表,設計一個時間複雜度爲O(N),額外空間複雜度爲O(1)的算法,
* 判斷其是否爲迴文結構 (ABCCBA)
* 解決:遍歷鏈表,得到鏈表長度,然後找到中間節點,將中間節點以後的鏈表逆置,
* 再與新鏈表逐個比較,一旦有不同的節點就說明不是迴文
*/
public boolean chkPalindrome(LinkedNode A) {
LinkedNode middle = A;
int len = getLenght(A);
for (int i = 0; i < len / 2; i++) {
//得到之間節點
middle = middle.next;
}
middle = reversList(middle);
while (A != null && middle != null) {
if (A.value != middle.value) {
return false;
}
A = A.next;
middle = middle.next;
}
return true;
}
private int getLenght(LinkedNode a) {
int len = 0;
while (a != null) {
len++;
a = a.next;
}
return len;
}
/**
* @Description: 在一個排序鏈表中,存在重複的節點,刪除鏈表中重複的節點,
* 重複的節點不保留,返回鏈表頭指針
* 解決:兩個指針一前一後,若不相等則一起向前走,
* 若相等,則前面的節點繼續向前走,走到不相等或爲空爲止,將第二個節點尾插到第一個節點
* 還需引入一個prev=null,申請一個假節點dummy,作爲處理後的鏈表的頭節點
* @return: dummy.next
*/
public LinkedNode deleteDuplication(LinkedNode head) {
//p1,p2是進行比較的兩個節點
LinkedNode p1 = head;
LinkedNode p2 = head.next;
//假節點用來消除第一個節點沒有前驅的特殊性(如果第一個節點開始就重複便無法處理)
LinkedNode dummy = new LinkedNode(0);
dummy.next = head;
//pre永遠是p1的前一個,用來刪除節點
LinkedNode pre = dummy;
//1-2-3-4-4-4-5-6
//如果值不相等則往前走
if (head == null) {
return null;
}
while (p2 != null) {
//因爲有序,p1和p2不相等,和p2的next更不會相等,因此比較可以向前繼續
if (p1.value != p2.value) {
pre = pre.next;
p1 = p1.next;
p2 = p2.next;
} else {
//若果相等則p2向前走,走到不相等爲止
while (p2.value == p1.value) {
p2 = p2.next;
}
//將p2指向的節點尾插到pre上
pre.next = p2;
p1 = p2;
p2 = p2.next;
}
}
return dummy.next;
}