Tag Archives: 链表

LeetCode Remove Linked List Elements

203. Remove Linked List Elements

Remove all elements from a linked list of integers that have value val.

Example:

Input:  1->2->6->3->4->5->6, val = 6
Output: 1->2->3->4->5

把链表中等于val的所有节点都删掉。简单题,为了方便,先添加一个0号节点,然后记录prior和tail指针,当tail->val==val时,删掉tail节点,prior节点不动;否则prior和tail都往后移一个位置。直到tail为NULL。完整代码如下:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val)
    {
        ListNode* new_head = new ListNode(0);
        new_head->next = head;
        ListNode *prior = new_head, *tail = prior->next;
        while (tail != NULL) {
            if (tail->val == val) {
                prior->next = prior->next->next;
                tail = prior->next;
            }
            else {
                prior = prior->next;
                tail = prior->next;
            }
        }
        return new_head->next;
    }
};

本代码提交AC,用时29MS。

二刷。简化版本:

class Solution {
public:
	ListNode* removeElements(ListNode* head, int val) {
		ListNode *dummy = new ListNode(0);
		ListNode *tail = dummy;
		while (head != NULL) {
			if (head->val != val) {
				tail->next = head;
				tail = tail->next;
			}
			head = head->next;
		}
		tail->next = NULL;
		return dummy->next;
	}
};

本代码提交AC,用时20MS。

LeetCode Reverse Linked List II

92. Reverse Linked List II

Reverse a linked list from position m to n. Do it in one-pass.

Note: 1 ≤ m ≤ n ≤ length of list.

Example:

Input: 1->2->3->4->5->NULL, m = 2, n = 4
Output: 1->4->3->2->5->NULL

本题需要把链表中[m,n]之间的节点逆序,算是LeetCode Reverse Linked List的升级版,也是链表逆序的通用代码了。
思路简单,首先找到第m个节点,然后和LeetCode Reverse Linked List类似,用头插法把接下来的n-m+1个节点逆序就好了。
因为m可能等于1,为了统一代码,我们先在head前添加一个0号节点,这样操作起来更方便~然后开始找第m个节点,找到之后,把prior和tail切开,从tail开始往后把n-m+1个节点用头插法逆序。
有一个地方需要注意,就像样例一样,当把2~4逆序之后,我们还需要把最后节点5接到逆序之后的链表末尾,为了不再遍历链表,我们在找到第m个节点的时候,事先保存tail节点,也就是先保存2号节点,当逆序完之后,直接2->next=5就好了。
完整代码如下:

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n)
    {
        if (head == NULL || head->next == NULL || m == n)
            return head;
        ListNode* new_head = new ListNode(0);
        new_head->next = head;
        ListNode *prior = new_head, *tail = head, *mid_head, *tmp;
        int offset = 1;
        while (offset < m) {
            prior = prior->next;
            tail = tail->next;
            offset++;
        }
        mid_head = prior; // 需要逆序的前一个节点
        tmp = tail; // 保留已逆序的最后一个节点,也就是需要逆序的第一个节点
        while (offset <= n) {
            prior = tail;
            tail = tail->next;
            prior->next = mid_head->next;
            mid_head->next = prior;
            offset++;
        }
        tmp->next = tail; // 已逆序最后一个节点接上后续节点
        return new_head->next;
    }
};

本代码提交AC,用时3MS。这代码居然一遍写完AC,都没有经过调试,我觉得边界条件处理还是要比较细心。

LeetCode Reverse Linked List

206. Reverse Linked List

Reverse a singly linked list.

Example:

Input: 1->2->3->4->5->NULL
Output: 5->4->3->2->1->NULL

Follow up:

A linked list can be reversed either iteratively or recursively. Could you implement both?


本题要将一个链表反转。简单题,分为迭代法和递归法,迭代法比较容易想到,依次把链表当前的头结点断开,然后用头插法插到新链表的开头。算法如下:

class Solution {
public:
    ListNode* reverseList(ListNode* head)
    {
        if (head == NULL || head->next == NULL)
            return head;
        ListNode* new_head = new ListNode(0);
        while (head != NULL) {
            ListNode* prior = head;
            head = head->next;
            prior->next = new_head->next;
            new_head->next = prior;
        }
        return new_head->next;
    }
};

本代码提交AC,用时9MS。 本题还要求实现递归法。比如链表1->2->3->4,递归的时候把头结点1断开,假设我们能得到2->3->4的反转链表4->3->2,则还需要把1接在之前反转链表的末尾。但是我们得到的4->3->2只返回了头结点,怎么得到尾节点2呢,唯一的方法就是开始我们不断开1,这样我们就可以用1->next得到2,然后把1->next->next=1,最后把1->next置为NULL。算法如下:

class Solution {
public:
    ListNode* reverseList(ListNode* head)
    {
        if (head == NULL || head->next == NULL)
            return head;
        ListNode* reversed_list = reverseList(head->next);
        head->next->next = head; // 重点
        head->next = NULL;
        return reversed_list;
    }
};

本代码提交AC,用时6MS。

二刷。更好理解的递归方法,比如链表1->2->3->4,先把2->3->4逆序变成4->3->2,注意此时1后面还是连着2,也就是说我们可以通过1找到2->3->4逆序的尾巴,然后把1接到尾巴即可。完整代码如下:

class Solution {
public:
	ListNode* reverseList(ListNode* head) {
		if (head == NULL)return NULL;
		if (head->next == NULL)return head;
		ListNode* tail = head->next;
		ListNode *rev_head = reverseList(head->next);
		tail->next = head;
		head->next = NULL;
		return rev_head;
	}
};

本代码提交AC,用时4MS。

LeetCode Remove Duplicates from Sorted List II

82. Remove Duplicates from Sorted List II

Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numbers from the original list.

Return the linked list sorted as well.

Example 1:

Input: 1->2->3->3->4->4->5
Output: 1->2->5

Example 2:

Input: 1->1->1->2->3
Output: 2->3

把已排序链表中有重复元素的节点删掉,只保留出现一次的节点,看样例就可以知道这是怎么回事了。 此题有两个步骤,第一是找到新的头结点,第二是删掉后续的重复节点。 找新头结点时,维护一个head和tail指针,如果tail的val和head的val相同,则更新head和tail, 直到tail为空或者遇到一个distinct的head。 删掉后续重复节点时,维护一个sub_head、prior和tail,如果prior和tail的val相同,则把所有相同的节点都删掉,方法是直接把sub_head的next指向所有相同元素的下一个元素,同时更新prior和tail。 完整代码如下:

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head)
    {
        if (head == NULL || head->next == NULL)
            return head;
        ListNode* tail = head->next;
        while (tail != NULL) { // find new head
            int cnt = 0;
            while (tail != NULL && tail->val == head->val) {
                tail = tail->next;
                cnt++;
            }
            if (tail == NULL)
                return NULL;
            if (cnt == 0)
                break;
            else {
                head = tail;
                tail = head->next;
            }
        }
        ListNode *sub_head = head, *prior = sub_head->next;
        while (prior != NULL) { // delete all duplicate nodes
            tail = prior->next;
            int cnt = 0;
            while (tail != NULL && tail->val == prior->val) {
                tail = tail->next;
                cnt++;
            }
            //if (tail == NULL)break;
            if (cnt == 0) {
                sub_head = prior;
                prior = prior->next;
            }
            else {
                sub_head->next = tail;
                prior = tail;
            }
        }
        return head;
    }
};

本代码提交AC,用时9MS。

二刷。完全不记得一刷的解法了。二刷参考https://discuss.leetcode.com/topic/29849/c-8ms-iterative-naive-but-easy-to-implement,很好理解的一个方法。
设置一个标签to_be_added,为true表明这个节点unique,需要添加到结果链表中,否则重复,需要删除。直接判断head和head->next的值是否相等,如果相等,则需要把所有等于head的节点删掉,比如说样例[1,1,1,2,3],head=1,head->next=1,相等,则把head删掉;接着,head到了第二个1,发现等于第三个1,所以把第二个1删掉;接着head走到第三个1,此时head->next->val != head-val,跳出循环,但是因为to_be_added=false,说明1这个数重复了,所以还是需要把head删掉。只有to_be_added=true才会加入到结果链表中。
最后记得把tail->next=NULL置空,并且所有该删除的节点真的要delete掉。
完整代码如下:

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head)
    {
        if (head == NULL || head->next == NULL)
            return head;
        ListNode* dummy = new ListNode(0 == head->val ? 1 : 0);
        ListNode *tail = dummy, *delete_node;
        bool to_be_added = true;
        while (head) {
            while (head->next && head->val == head->next->val) {
                delete_node = head;
                head = head->next;
                delete delete_node;
                to_be_added = false;
            }
            if (to_be_added) {
                tail->next = head;
                tail = tail->next;
                head = head->next;
            }
            else {
                delete_node = head;
                head = head->next;
                delete delete_node;
            }
            to_be_added = true; // reset
        }
        tail->next = NULL;
        delete_node = dummy;
        dummy = dummy->next;
        delete delete_node;
        return dummy;
    }
};

本代码提交AC,用时6MS。

三刷。简洁版代码,新建一个链表,把原链表中没有重复值的节点接到新链表中,代码如下:

class Solution {
public:
	ListNode* deleteDuplicates(ListNode* head) {
		if (head == NULL || head->next == NULL)return head;
		ListNode *new_head = new ListNode(0);
		ListNode *l0=new_head, *l1 = head, *l2 = head->next;
		while (l2) {
			l2 = l1->next;
			int count = 0;
			while (l2 != NULL && l2->val == l1->val) {
				l2 = l2->next;
				++count;
			}
			if (count == 0) {
				l0->next = l1;
				l0 = l0->next;
			}
			l1 = l2;
		}
		l0->next = NULL;
		return new_head->next;
	}
};

本代码提交AC,用时12MS。

LeetCode Remove Duplicates from Sorted List

83. Remove Duplicates from Sorted List

Given a sorted linked list, delete all duplicates such that each element appear only once.

Example 1:

Input: 1->1->2
Output: 1->2

Example 2:

Input: 1->1->2->3->3
Output: 1->2->3

删除已排序链表中的重复元素。简单,直接遍历,发现后面元素(tail)和前面元素(head)的值相同时,则删除后面元素,直到链表结束。 完整代码如下:

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head)
    {
        if (head == NULL || head->next == NULL)
            return head;
        ListNode* new_head = head;
        ListNode *prior = head, *tail = prior->next;
        while (tail != NULL) {
            while (tail != NULL && tail->val == prior->val) {
                prior->next = tail->next;
                tail = prior->next;
            }
            if (tail == NULL)
                break;
            if (tail->val != prior->val) {
                prior = tail;
                tail = prior->next;
            }
        }
        return new_head;
    }
};

本代码提交AC,用时19MS。

下面提供一个由数组生成链表以及打印链表的函数,方便调试。

// 根据数组生成链表
ListNode* GenList(vector<int>& nums)
{
    if (nums.size() == 0)
        return NULL;
    ListNode* head = new ListNode(nums[0]);
    ListNode* tail = head;
    for (int i = 1; i < nums.size(); i++) {
        ListNode* node = new ListNode(nums[i]);
        tail->next = node;
        tail = tail->next;
    }
    return head;
}
// 打印链表
void PrintList(ListNode* head)
{
    while (head != NULL) {
        cout << head->val << endl;
        head = head->next;
    }
}

二刷。简洁代码:

class Solution {
public:
	ListNode* deleteDuplicates(ListNode* head) {
		if (head == NULL || head->next == NULL)return head;
		ListNode *l1 = head, *l2 = head->next;
		while (l2) {
			while (l2&&l2->val == l1->val) {
				//ListNode *tmp = l2;
				l2 = l2->next;
				//delete tmp;
			}
			l1->next = l2;
			l1 = l2;
		}
		return head;
	}
};

本代码提交AC,用时16MS。

LeetCode Rotate List

61. Rotate List

Given a linked list, rotate the list to the right by k places, where k is non-negative.

Example 1:

Input: 1->2->3->4->5->NULL, k = 2
Output: 4->5->1->2->3->NULL
Explanation:
rotate 1 steps to the right: 5->1->2->3->4->NULL
rotate 2 steps to the right: 4->5->1->2->3->NULL

Example 2:

Input: 0->1->2->NULL, k = 4
Output: 2->0->1->NULL
Explanation:
rotate 1 steps to the right: 2->0->1->NULL
rotate 2 steps to the right: 1->2->0->NULL
rotate 3 steps to the right: 0->1->2->NULL
rotate 4 steps to the right: 2->0->1->NULL

这一题要把链表的后k个右旋到开头,其实就是把后k个节点断开,然后接到开头。思路是先遍历一遍得到链表长度n,然后k=k%n。再从头开始找第n-k-1个,断开然后拼接到开头即可。 完整代码如下:

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k)
    {
        if (head == NULL || head->next == NULL || k == 0)
            return head;
        int n = 1, cut_pos = 0;
        ListNode* tail = head;
        while (tail->next != NULL) {
            n++;
            tail = tail->next;
        }
        //while (k >= n)k -= n;
        k = k % n;
        if (k == 0)
            return head;
        ListNode* new_tail = head;
        while (cut_pos + k + 1 < n) {
            new_tail = new_tail->next;
            cut_pos++;
        }
        ListNode* new_head = new_tail->next;
        new_tail->next = NULL;
        tail->next = head;
        return new_head;
    }
};

本代码提交AC,用时20MS。

还可以其他一些思路,比如把连接头尾相连成一个环,然后找到断开的位置;或者使用快慢指针找到需要断开的位置,快指针先走k步,慢指针再从头开始走,当快指针到末尾时,慢指针就到断开位置了。其实吧,我觉得直接找第n-k-1的位置,应该不比快慢指针慢吧,不知道为什么我这个花了20MS。

二刷。思路跟之前的一样,代码更好看一点。

class Solution {
public:
	int GetListLength(ListNode* head) {
		int len = 0;
		while (head) {
			++len;
			head = head->next;
		}
		return len;
	}
	ListNode* rotateRight(ListNode* head, int k) {
		int len = GetListLength(head);
		if (len == 0 || len == 1)return head;
		k = k % len;
		if (k == 0)return head;
		int l = len - k;
		ListNode *first = head;
		while (--l) { // 注意是--l,不是l--,停在断开前一个位置
			first = first->next;
		}
		ListNode *second = first->next;
		first->next = NULL;
		first = second;
		while (first->next)first = first->next;
		first->next = head;
		return second;
	}
};

本代码提交AC,用时8MS。

LeetCode Swap Nodes in Pairs

24. Swap Nodes in Pairs

Given a linked list, swap every two adjacent nodes and return its head.

You may not modify the values in the list’s nodes, only nodes itself may be changed.

Example:

Given 1->2->3->4, you should return the list as 2->1->4->3.

交换链表的每两个相邻节点。样例不保证是偶数个节点,也可能出现奇数个节点,此时最后一个节点就不要交换了。 交换过程中需要使用3个指针,当前指针ln1,当前指针的下一个指针ln2以及ln1的副本ln3用于更改ln1的next。具体怎么变换的,在纸上画一画就知道了,下面是完整代码。

class Solution {
public:
    ListNode* swapPairs(ListNode* head)
    {
        if (head == NULL || head->next == NULL)
            return head;
        ListNode *ln1 = head, *ln2 = ln1->next, *ln3 = ln1, *new_head = ln2;
        bool first = true;
        while (ln1) {
            ln2 = ln1->next;
            if (ln2 == NULL)
                break;
            ln1->next = ln2->next;
            ln2->next = ln1;
            if (!first) { // 第一次不需要更改ln3的指向
                ln3->next = ln2;
            }
            first = false;
            ln3 = ln1;
            ln1 = ln1->next;
        }
        return new_head;
    }
};

本代码提交AC,用时4MS,只击败了1.83%,郁闷,我这已经是O(n)的时间复杂度,O(1)的空间复杂度,不知道还可以怎样优化。

更漂亮的递归版本:

class Solution {
public:
    ListNode* swapPairs(ListNode* head)
    {
        if (head == NULL || head->next == NULL)
            return head;
        ListNode* second = head->next;
        head->next = swapPairs(head->next->next);
        second->next = head;
        return second;
    }
};

本代码提交AC,用时6MS,无语了。

LeetCode Merge k Sorted Lists

23. Merge k Sorted Lists

Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.

Example:

Input:
[
  1->4->5,
  1->3->4,
  2->6
]
Output: 1->1->2->3->4->4->5->6

归并k个已排序的链表。 之前是归并2个已排序的链表,现在变成多个了。其实就是多路归并,思路之前都知道,不同思路的区别就在于怎样找到所有链表当前位置的最小值,可以直接线性查找,也可以用败者树、胜者树或者堆。我一开始用线性查找,不出意外的TLE了,后来改用建堆的方法,顺利通过。 写代码越来越懒了,直接上STL,需要注意的是STL中的make_heap默认建的是大顶堆,我们需要传自己的比较函数进去以便构建小顶堆。

typedef struct Node {
    ListNode* ln;
    int idx;
    bool operator<(const Node& p) const { return this->ln->val < p.ln->val; }
    bool operator>(const Node& p) const { return this->ln->val > p.ln->val; }
};
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists)
    {
        ListNode *ans = new ListNode(0), *head = ans;
        vector<Node> vn;
        for (int i = 0; i < lists.size(); i++) {
            if (lists[i] != NULL) {
                Node nd;
                nd.ln = lists[i];
                nd.idx = i;
                vn.push_back(nd);
                lists[i] = lists[i]->next;
            }
        }
        if (vn.size() == 0)
            return NULL;
        make_heap(vn.begin(), vn.end(), greater<Node>());
        while (true) {
            Node tmp = vn.front();
            ans->next = tmp.ln;
            ans = ans->next;
            pop_heap(vn.begin(), vn.end(), greater<Node>());
            vn.pop_back();
            if (lists[tmp.idx] != NULL) {
                tmp.ln = lists[tmp.idx];
                vn.push_back(tmp);
                push_heap(vn.begin(), vn.end(), greater<Node>());
                lists[tmp.idx] = lists[tmp.idx]->next;
            }
            if (vn.empty())
                break;
        }
        return head->next;
    }
};

本代码提交AC,用时420MS。

二刷。更简单的是直接使用STL的优先队列,代码如下:

struct LNNode {
	ListNode* ln;
	LNNode() :ln(NULL) {};
	bool operator<(const LNNode& ln_) const{ // 注意两个const都不能少
		return ln->val > ln_.ln->val;
	}
};
class Solution {
public:
	ListNode* mergeKLists(vector<ListNode*>& lists) {
		priority_queue<LNNode> pq;
		for (int i = 0; i < lists.size(); ++i) {
			if (lists[i] != NULL) {
				LNNode cur;
				cur.ln = lists[i];
				pq.push(cur);
			}
		}
		ListNode* root = new ListNode(0);
		ListNode* ans = root;
		while (!pq.empty()) {
			LNNode cur = pq.top();
			pq.pop();
			root->next = cur.ln;
			root = root->next;
			if (cur.ln->next) {
				LNNode nxt;
				nxt.ln = cur.ln->next;
				pq.push(nxt);
			}
		}
		return ans->next;
	}
};

本代码提交AC,用时32MS。

或者不用定义新的类,直接用原来的ListNode*,但新增一个比较函数,代码如下:

struct cmp {
	bool operator()(ListNode* l1, ListNode* l2) {
		return l1->val > l2->val;
	}
};

class Solution {
public:
	ListNode* mergeKLists(vector<ListNode*>& lists) {
		priority_queue<ListNode*, vector<ListNode*>, cmp> pq;
		for (int i = 0; i < lists.size(); ++i) {
			if (lists[i] != NULL) {
				pq.push(lists[i]);
			}
		}
		ListNode* root = new ListNode(0);
		ListNode* ans = root;
		while (!pq.empty()) {
			ListNode* cur = pq.top();
			pq.pop();
			root->next = cur;
			root = root->next;
			if (cur->next) {
				pq.push(cur->next);
			}
		}
		return ans->next;
	}
};

本代码提交AC,用时32MS。

LeetCode Merge Two Sorted Lists

21. Merge Two Sorted Lists

Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.

Example:

Input: 1->2->4, 1->3->4
Output: 1->1->2->3->4->4

归并两个有序链表,要求使用原有链表中的节点,所以只需要依次比较大小,改变原有节点的指向。 代码如下

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
    {
        ListNode *head = new ListNode(0), *l;
        l = head;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                l->next = l1;
                l1 = l1->next;
            }
            else {
                l->next = l2;
                l2 = l2->next;
            }
            l = l->next;
        }
        if (l1)
            l->next = l1;
        else if (l2)
            l->next = l2;
        return head->next;
    }
};

本代码提交AC,用时8MS。

二刷。循环的时候用或运算也可以,代码更简洁。

class Solution {
public:
	ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
		if (l1 == NULL)return l2;
		ListNode* ans = new ListNode(0);
		ListNode* root = ans;
		while (l1 || l2) {
			int v1 = l1 ? l1->val : INT_MAX;
			int v2 = l2 ? l2->val : INT_MAX;
			if (v1 != INT_MAX && v1 <= v2) {
				root->next = l1;
				root = root->next;
				l1 = l1->next;
			}
			else if (v2 != INT_MAX && v2 < v1) {
				root->next = l2;
				root = root->next;
				l2 = l2->next;
			}
		}
		return ans->next;
	}
};

本代码提交AC,用时8MS。

LeetCode Remove Nth Node From End of List

19. Remove Nth Node From End of List

Given a linked list, remove the n-th node from the end of list and return its head.

Example:

Given linked list: 1->2->3->4->5, and n = 2.

After removing the second node from the end, the linked list becomes 1->2->3->5.

Note:

Given n will always be valid.

Follow up:

Could you do this in one pass?


给定一个链表,要求删除倒数第n个元素。我想到的做法是遍历一遍链表,同时把链表每个元素保存到一个数组中,遍历完之后就知道链表长度,这样就可以计算出倒数第n个元素是哪个元素,然后直接删掉即可。 代码如下:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n)
    {
        if (head->next == NULL)
            return NULL;
        vector<ListNode*> nodes;
        ListNode* ln = head;
        while (ln) {
            nodes.push_back(ln);
            ln = ln->next;
        }
        int target = nodes.size() – n;
        if (target == 0) {
            head->next = NULL;
            head = nodes[target + 1];
        }
        else if (target == nodes.size() – 1) {
            nodes[target – 1]->next = NULL;
        }
        else {
            nodes[target – 1]->next = nodes[target + 1];
        }
        return head;
    }
};

本代码提交AC,用时8MS。一看排名倒数,肯定还有更高效的算法,因为这种解法需要保存链表,而且是用vector.push_back的,所以肯定耗时。看题解,原来这是典型的快慢指针题,我这个非ACMer还是反应迟钝呀。

我们维护一个快指针和慢指针,初始的时候快慢都在起点,然后快指针先跑n步,此时快慢指针的距离为n,然后快慢指针同时跑,这样就能保持距离为n,当快指针到达末尾(NULL)的时候,慢指针刚好是在倒数第n的位置,真是巧妙。
但是为了删除倒数第n个节点,需要知道其前一个节点,所以让快指针到达NULL的前面位置停,即faster->next==NULL的时候停下来。
完整代码如下:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n)
    {
        if (head == NULL || head->next == NULL)
            return NULL;
        ListNode *slower = head, *faster = head;
        while (n–) {
            faster = faster->next;
        }
        if (faster == NULL)
            return head->next;
        while (faster->next) {
            faster = faster->next;
            slower = slower->next;
        }
        slower->next = slower->next->next;
        return head;
    }
};

本代码提交AC,用时4MS,果然快了不少。