關於鏈表的常見算法題(二)java
鏈表一直是面試最喜歡考的數據結構,靈活而又多變,代碼每每也不是特別長。node
輸入一個鏈表的頭節點,從尾到頭反過來打印出每一個節點的值面試
例如鏈表爲1->2->3,打印出3 2 1算法
有三種思路能夠參考:數據結構
public class _06 {
static class ListNode {
int value;
public ListNode(int value) {
this.value = value;
}
ListNode next;
}
public static void main(String[] args) {
ListNode listNode1 = new ListNode(1);
ListNode listNode2 = new ListNode(2);
ListNode listNode3 = new ListNode(3);
listNode1.next = listNode2;
listNode2.next = listNode3;
_06 test = new _06();
// List<Integer> list = test.printListFromQueue(listNode1);
// List<Integer> list = test.printListFromDG(listNode1);
List<Integer> list = test.printListFromTC(listNode1);
for (Integer i : list) {
System.out.println(i);
}
}
/** * 棧 */
public List<Integer> printListFromQueue(ListNode listNode) {
Stack<Integer> stack = new Stack<>();
while (listNode != null) {
stack.add(listNode.value);
listNode = listNode.next;
}
List<Integer> list = new ArrayList<>();
while (!stack.isEmpty()) {
list.add(stack.pop());
}
return list;
}
/** * 遞歸 */
public List<Integer> printListFromDG(ListNode listNode) {
List<Integer> list = new ArrayList<>();
if (listNode != null) {
list.addAll(printListFromDG(listNode.next));
list.add(listNode.value);
}
return list;
}
/** * 頭插法 */
public List<Integer> printListFromTC(ListNode listNode) {
//頭插法構建逆序鏈表
//新建一個頭部節點,只用於中轉,不存儲值
ListNode head = new ListNode(-1);
//鏈表反轉
while (listNode != null) {
ListNode temp = listNode.next;
listNode.next = head.next;
head.next = listNode;
listNode = temp;
}
List<Integer> list = new ArrayList<>();
//真正的值是從第一個節點開始
head = head.next;
while (head != null) {
list.add(head.value);
head = head.next;
}
return list;
}
}
複製代碼
給定單身鏈表的頭指針和一個節點指針,定義一個函數在O(1)內刪除這個節點。函數
若是這個節點不是尾節點,那麼能夠直接把下一個節點的值賦值給這個節點,再把這個節點指向下下個節點,時間複雜度爲O(1)post
如,1->2->3->4,要刪除的是2,那麼先把2的下一個節點值3,賦值給2的節點,變爲1-3-3-4,而後把第二個節點的位置,指向下下個節點4,這樣,就變爲了1-3-4,2這個節點這刪除了。this
若是是尾指針,那麼找到前一個節點,把前一個節點指向null,時間複雜度爲O(N)spa
若是進行N次操做,這個算法的平均時間複雜度爲O(1).net
/** * 刪除指定節點 */
public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
if (head == null || tobeDelete == null) {
return null;
}
if (tobeDelete.next != null) {
//要刪除的不是尾節點
ListNode next = tobeDelete.next;
tobeDelete.value = next.value;
tobeDelete.next = next.next;
} else {
if (head == tobeDelete) {
head = null;
} else {
ListNode curNode = head;
while (curNode.next != tobeDelete) {
curNode = curNode.next;
}
curNode.next = null;
}
}
return head;
}
複製代碼
在一排序的鏈表中,如何刪除重複的節點
1->2->2->3->3->3->4
1->4
/** * 刪除重複的節點 */
public ListNode deleteDuplication(ListNode pHead) {
if (pHead == null || pHead.next == null) {
return pHead;
}
ListNode next = pHead.next;
//當頭節點有重複時,一直找到沒有重複的爲止
if (pHead.value == next.value) {
while (next != null && pHead.value == next.value) {
next = next.next;
}
return deleteDuplication(next);
} else {
pHead.next = deleteDuplication(pHead.next);
return pHead;
}
}
複製代碼
輸入一個單向鏈表,輸出這個鏈表中倒數第k個節點
1->2->3->4->5->6
倒數第三個點爲4
這是一個單向鏈表,若是咱們使用遍歷,很明顯不方便從尾開始遍歷的,這時候咱們能夠想到的方法是雙指針法。
public ListNode FindKthToTail(ListNode head, int k) {
if (head == null) {
return null;
}
ListNode p1 = head;
while (p1 != null && k > 0) {
p1 = p1.next;
k--;
}
if (k > 0) {
return null;
}
ListNode p2 = head;
while (p1 != null) {
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
複製代碼
頭插法(迭代)
遞歸
分解成最簡單的兩個節點,就next.next = head,這樣就能把1->2反轉成2->1。1的next是2,它的next指向1,就已經完成了2->1
public ListNode reverseList(ListNode head){
if (head==null||head.next==null){
return head;
}
ListNode next = head.next;
head.next = null;
ListNode newHead = reverseList(next);
next.next = head;
return newHead;
}
public ListNode reverseList2(ListNode head){
if (head==null||head.next==null){
return head;
}
ListNode newList = new ListNode(-1);
while (head!=null){
ListNode next = head.next;
head.next = newList.next;
newList.next = head;
head = next;
}
return newList.next;
}
複製代碼
輸入兩個遞增排序的鏈表,合併這兩個鏈表並使新的鏈表中的節點仍然是遞增的。
1-3-5-7
2-4-6-8
1-2-3-4-5-6-7-8
首先咱們要比較兩個鏈表頭,發現1比2小,那麼新鏈表的頭就是1,而後繼續比較,這時仍是比較鏈表頭,咱們想到了,這就是個遞歸啊,把頭繼續比較,依次加進新的鏈表中,因此本題能夠用遞歸來解,固然也能使用迭代。
/** * 遞歸 */
public ListNode merge(ListNode node1, ListNode node2) {
if (node1 == null) {
return node2;
}
if (node2 == null) {
return node1;
}
if (node1.value < node2.value) {
node1.next = merge(node1.next, node2);
return node1;
} else {
node2.next = merge(node1, node2.next);
return node2;
}
}
/** * 迭代 */
public ListNode merge2(ListNode node1, ListNode node2) {
ListNode head = new ListNode(-1);
ListNode cur = head;
while (node1 != null && node2 != null) {
if (node1.value <= node2.value) {
cur.next = node1;
node1 = node1.next;
} else {
cur.next = node2;
node2 = node2.next;
}
cur = cur.next;
}
if (node1 != null) {
cur.next = node1;
}
if (node2 != null) {
cur.next = node2;
}
return head.next;
}
複製代碼
輸入兩個鏈表,找出它們的第一個公共節點
1-2-3-6-7
4-5-6-7
暴力破解法
在第一個鏈表上順序遍歷到一個節點,就到第二個鏈表上遍歷每個節點,若是有一個節點與這個值相等,就找到了。可是時間複雜度爲O(mn)
棧
咱們發現,公共部分必定是在尾部,若是咱們從後面開始找會更快,最後一個相同點就是咱們要找的點,不過這是單向鏈表,因此咱們想到了棧的結構——後進先出
求差法
其實咱們用棧,只是爲了想同時能到達兩個鏈表的尾節點。當兩鏈表長度不一樣時,咱們從頭開始遍歷,到後面的時間就不一致。咱們能夠先遍歷兩個鏈表,獲得它們的長度,而後,鏈表長的先走先走多的幾個節點,接着同時在兩個鏈表遍歷,找到的第一個相同點就是咱們要找的第一個公共點
/** * 用棧取 */
public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
Stack<ListNode> stack1 = new Stack<>();
Stack<ListNode> stack2 = new Stack<>();
//分別放入棧中
while (pHead1 != null) {
stack1.push(pHead1);
pHead1 = pHead1.next;
}
while (pHead2 != null) {
stack2.push(pHead2);
pHead2 = pHead2.next;
}
ListNode result = null;
//從棧頂取值,取到不相等時,上一個就是第一個公共節點
while (!stack1.isEmpty() && !stack2.isEmpty() && stack1.peek() == stack2.peek()) {
stack1.pop();
result = stack2.pop();
}
return result;
}
/** * 差值法 */
public ListNode findFirstCommonNode2(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) {
return null;
}
int count1 = 1;
int count2 = 1;
ListNode p1 = pHead1;
ListNode p2 = pHead2;
//獲取兩個鏈表的長度
while (p1.next != null) {
p1 = p1.next;
count1++;
}
while (p2.next != null) {
p2 = p2.next;
count2++;
}
//哪一個長就先走差值步
if (count1 > count2) {
int dif = count1 - count2;
while (dif != 0) {
pHead1 = pHead1.next;
dif--;
}
} else {
int dif = count2 - count1;
while (dif != 0) {
pHead2 = pHead2.next;
dif--;
}
}
//同時走,當相等時獲得公共節點
while (pHead1 != null && pHead2 != null) {
if (pHead1 == pHead2) {
return pHead1;
}
pHead1 = pHead1.next;
pHead2 = pHead2.next;
}
return null;
}
複製代碼
By《劍指offer》
下面是個人公衆號,歡迎你們關注我