關於鏈表的常見算法題(一)java
給定一個有序鏈表,請刪除其中的重複元素,使得這個元素僅出現一次node
Input: 1->1->2->3->3
Output: 1->2->3
複製代碼
其實這個題比上篇講的juejin.im/post/5d067f…中刪除重複的節點還要容易一些,由於在此處,有重複的值時,是保留一個節點的,而上一篇中是都刪除。算法
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
return head;
}
head.next = deleteDuplicates(head.next);
return head.value == head.next.value ? head.next : head;
}
複製代碼
給定一個有序鏈表,請刪除其中的重複元素,使得原鏈表中的元素只出現一次數組
Input: 2->3->4->2->null
Output: 2->3->4->null
複製代碼
可使用雙循環暴力破解法,不過期間複雜度就是O(n^2)了,咱們可使用HashMap和存儲結點值,判斷是否有重複,有重複的時候,把當前節點的上一個節點的next指向當前節點的next,這樣,就至關於把當前節點刪除了,當沒有重複時,把這個值加入map中。bash
/** * 雙循環 */
public ListNode deleteDulp(ListNode head) {
if (head==null||head.next==null){
return head;
}
ListNode p = head;
while (p != null) {
ListNode q= p;
while (q.next!=null){
if (p.value==q.next.value){
q.next = q.next.next;
}else {
q = q.next;
}
}
p = p.next;
}
return head;
}
/** * 使用hashmap */
public ListNode deleteDulp2(ListNode head){
HashMap<Integer,Integer> map = new HashMap<>();
ListNode root = head;
ListNode before = head;
while (head!=null){
if (map.containsKey(head.value)) {
before.next = head.next;
head = head.next;
}else {
map.put(head.value,1);
before = head;
head = head.next;
}
}
return root;
}
複製代碼
給定一個單向鏈表,要求刪除從結尾數第n個結點,並返回鏈表數據結構
輸入:5-6-7-8-9 n=2;
輸出:5-6-7-9
複製代碼
核心是找到要刪除的結點的前一個結點位置。咱們可使用雙指針來完成,快指針先走n-1步,而後同時移動快慢雙指針,當快指針的的next爲null時,咱們就找到了當前位置就咱們要找的位置函數
public ListNode removeNthFormEnd(ListNode head, int n) {
ListNode fast = head;
while (n-- > 0) {
fast = fast.next;
}
if (fast == null) {
return head.next;
}
ListNode slow = head;
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return head;
}
複製代碼
給定一個單向鏈表,依次交換每一對相鄰的結點post
1-2-3-4
2-1-4-3
複製代碼
首先咱們要明白,鏈表的兩個結點要如何交換:ui
假若有1-2-3-4-5這個鏈表,咱們要交換的是3和4,那麼spa
咱們想到頭插法反轉,指定一個pre指針,指向頭插的節點,反轉後兩個節點,pre指針指向之前的next,好比增長頭結點0,那麼0-1-2-3-4,先把pre指向0,此時pre.next爲1,把1和2反轉後,變成0-2-1-3-4,再把pre指針移到1的位置,後面同樣的循環就行了。
public ListNode swapPairs(ListNode head) {
//在頭部增長一個節點
ListNode node = new ListNode(-1);
node.next = head;
ListNode pre = node;
while (pre.next != null && pre.next.next != null) {
ListNode node1 = pre.next;
ListNode node2 = pre.next.next;
node1.next = node2.next;
node2.next = node1;
pre.next = node2;
//此時node1已經反轉,這個位置就是咱們要的位置
pre = node1;
}
return node.next;
}
複製代碼
給定一個單向鏈表,每次反轉k個相鄰結點,返回最終修改後的鏈表。k是一個正整數,而且不大於鏈表長度。若是鏈表中的節點個數不是k的整數倍,則最後剩餘的k個結點不作修改
輸入:1-2-3-4-5 k=2
輸出:2-1-4-3-5
複製代碼
這題實際上是上一個題的升級版,因此咱們依然能夠套用上一個題目的方法
使用頭插法
創建另一個鏈表頭,而後要處理的列表遞進處理,一個個的插入新鏈表,最後返回這的next。
核心思想是兩個兩個操做,操做k-1次。操做的內容是用prev與next節點指向新頭節點,而後操做指針關係。
使用遞歸法
設置好分組的間隔,用cnt和curNode分別標識計數和目前的節點,當分組結束後開始遞歸下一組分組,並開始這一組的反轉操做,仍然用cnt和curNode控制組內收縮。
/** * 使用頭插法 */
public ListNode reverseKGroup2(ListNode head, int k) {
if (head == null || head.next == null || k == 1) {
return head;
}
//找到鏈表的長度n
int n = 0;
ListNode root = head;
while (root != null) {
root = root.next;
n++;
}
//使用頭插
ListNode node = new ListNode(-1);
node.next = head;
for (ListNode prev = node, tail = head; n >= k; n -= k) {
for (int i = 1; i < k; i++) {
ListNode next = tail.next.next;
tail.next.next = prev.next;
prev.next = tail.next;
tail.next = next;
}
prev = tail;
tail = tail.next;
}
return node.next;
}
/** * 使用遞歸法 */
public ListNode reverseKGroup(ListNode head, int k) {
ListNode curNode = head;
int cnt = 0;
//把cnt都移動到k位,curNode移動到下一次反轉開始的地方
while (curNode != null && cnt != k) {
curNode = curNode.next;
cnt++;
}
if (cnt == k) {
curNode = reverseKGroup(curNode, k);
while (cnt-- > 0) {
ListNode temp = head.next;
head.next = curNode;
curNode = head;
head = temp;
}
head = curNode;
}
return head;
}
複製代碼
Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 8 -> 0 -> 7
複製代碼
咱們能夠發現,其實就是倒序對應相加,有進位的話要保存到下次相加上,想到了棧這樣的數據結構,後進先出
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Stack<Integer> l1Stack = buildStack(l1);
Stack<Integer> l2Stack = buildStack(l2);
ListNode head = new ListNode(-1);
int carry = 0;
while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carry != 0) {
int x = l1Stack.isEmpty() ? 0 : l1Stack.pop();
int y = l2Stack.isEmpty() ? 0 : l2Stack.pop();
int sum = x + y + carry;
ListNode node = new ListNode(sum % 10);
//頭插法
node.next = head.next;
head.next = node;
carry = sum / 10;
}
return head.next;
}
private Stack<Integer> buildStack(ListNode listNode) {
Stack<Integer> stack = new Stack<>();
while (listNode != null) {
stack.push(listNode.value);
listNode = listNode.next;
}
return stack;
}
複製代碼
給定一個單鏈表,判斷它是不是迴文鏈表,即正着遍歷和反着遍歷獲得的序列是相同的。
要求,0(1)空間複雜度
由於要求的空間複雜度爲O(1),因此棧啊什麼的數據結構來存儲的就不能用了,這時候須要用到分治的思想
咱們能夠把鏈切成兩半,把後半段反轉,再比較這兩半是否相等就能夠了。
具體能夠這麼來實現,使用快慢兩個指針,快指針每次兩步,慢指針每次一步,當快指針的next或者next.next爲空時,慢指針就在鏈表的中間(偶數個時,慢指針是靠近頭那一側的節點)。而後從慢指針的下一個點開始把後面的鏈表反轉,而後分別從頭和尾(這個時候尾已經轉到了中間)前進,這能夠判斷是不是一個迴文
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}
ListNode fast = head;
ListNode slow = head;
//找到中點
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//後面一段的開始點
ListNode secondHead = slow.next;
ListNode p1 = secondHead;
ListNode p2 = p1.next;
slow.next = null;//把原來的置空
while (p1 != null && p2 != null) {
ListNode temp = p2.next;
p2.next = p1;
p1 = p2;
p2 = temp;
}
//將第二段鏈表的尾巴置空,已經轉過來了,頭就是尾
secondHead.next = null;
while (p1 != null) {
if (head.value != p1.value) {
return false;
}
head = head.next;
p1 = p1.next;
}
return true;
}
複製代碼
給定一個頭結點爲 root 的鏈表, 編寫一個函數以將鏈表分隔爲 k 個連續的部分。
每部分的長度應該儘量的相等: 任意兩部分的長度差距不能超過 1,也就是說可能有些部分爲 null。
這k個部分應該按照在鏈表中出現的順序進行輸出,而且排在前面的部分的長度應該大於或等於後面的長度。
返回一個符合上述規則的鏈表的列表。
輸入: root = [1, 2, 3], k = 5
輸出: [[1],[2],[3],[],[]]
複製代碼
計算出鏈表的長度,而後根據n/k求出數組長度,n%k不爲0時說明不能均分,因此從第一個開始,鏈表長度加1,mod--,直到0
public ListNode[] splitListToParts(ListNode root, int k) {
int n = 0;
ListNode cur = root;
//計算鏈表長度
while (cur != null) {
n++;
cur = cur.next;
}
int mod = n % k;
int size = n / k;
ListNode[] ret = new ListNode[k];
//從新把root賦值給cur
cur = root;
for (int i = 0; cur != null && i < k; i++) {
ret[i] = cur;
int curSize = size + (mod-- > 0 ? 1 : 0);
for (int j = 0; j < curSize - 1; j++) {
cur = cur.next;
}
ListNode next = cur.next;
cur.next = null;
cur = next;
}
return ret;
}
複製代碼
給定一個單鏈表,請將全部的偶數節點拿出來,放在奇數節點後面,注意,偶數指的是節點的位置編號,而不是值。
你須要使用O(N)的時間和O(N)的空間
這個仍是能夠經過雙指針來解決,用兩個指針一塊兒遍歷,每次遍歷兩個節點,而後再將偶數鏈表頭接在奇數尾上。
public ListNode oddEvenList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode odd = head;//放奇數
ListNode even = head.next;//放偶數
ListNode evenHead = even;
while (even != null && even.next != null) {
//先把odd的next指向下一個奇數,而後把odd的指針切換到這個奇數上
odd.next = odd.next.next;
odd = odd.next;
even.next = even.next.next;
even = even.next;
}
odd.next = evenHead;
return head;
}
複製代碼
By LeetCode
下面是個人公衆號,歡迎你們關注我