https://leetcode.com/problems/find-the-duplicate-number/
https://leetcode.com/problems/linked-list-cycle/

这篇文章通过实际刷题过程中遇到的两个问题对Floyd Cycle Detection Algorithm进行复习总结。

Floyd Cycle Detection Algorithm

这个算法的核心思想就是用快慢指针在链表上移动,如果有环,快指针就必然会追上慢指针。不仅在探测链表是否存在环问题上有应用,还有不少的延伸应用。

Python代码如下(via Wikipedia):

def brent(f, x0):
    # main phase: search successive powers of two
    power = lam = 1
    tortoise = x0
    hare = f(x0)  # f(x0) is the element/node next to x0.
    while tortoise != hare:
        if power == lam:  # time to start a new power of two?
            tortoise = hare
            power *= 2
            lam = 0
        hare = f(hare)
        lam += 1

    # Find the position of the first repetition of length λ
    mu = 0
    tortoise = hare = x0
    for i in range(lam):
    # range(lam) produces a list with the values 0, 1, ... , lam-1
        hare = f(hare)
    # The distance between the hare and tortoise is now λ.

    # Next, the hare and tortoise move at same speed until they agree
    while tortoise != hare:
        tortoise = f(tortoise)
        hare = f(hare)
        mu += 1
 
    return lam, mu

141. Linked List Cycle

描述:

Given a linked list, determine if it has a cycle in it.

Follow up:

Can you solve it without using extra space?

分析:

这道题想用恒定空间解决,就必须用FLoyd算法,思路比较简单。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(!head)
            return false;
        struct ListNode * pmove=head,* qmove=head;
        while(pmove&&qmove&&qmove->next){
            pmove=pmove->next;
            qmove=qmove->next->next;
            if(pmove==qmove)
                return true;
        }
        return false;
    }
};

142. Linked List Cycle II

描述:

Given a linked list, return the node where the cycle begins. If there is no cycle, return null.

Note: Do not modify the linked list.

Follow up:

Can you solve it without using extra space?

分析:

这道题的要求高一些,要求找出环的入口,那么我们知道当相遇时,快指针一定比慢指针多跑了环长的整数倍。所以我们可以在相遇时从起点释放一个慢指针,当其与原来的慢指针相遇时,即为环的入口。

这个证明在以前的文章中写过,不再赘述。

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(!head||!head->next)
            return NULL;
        struct ListNode * fast=head,*slow=head,*entry=head;
        while(fast&&fast->next){
            fast=fast->next->next;
            slow=slow->next;
            if(fast==slow){
                while(entry!=slow){
                    entry=entry->next;
                    slow=slow->next;
                }
                return entry;
            }
        }
        return NULL;
    }
};

287. Find the Duplicate Number

描述:

Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.

Note:

1.You must not modify the array (assume the array is read only).

2.You must use only constant, O(1) extra space.

3.Your runtime complexity should be less than O(n2).

4.There is only one duplicate number in the array, but it could be repeated more than once.

分析:

这道题需要借鉴上面的思想。

一个数组中有且只有一个重复数字,数组索引和元素存在映射关系,所以也可以用FLoyd算法,与快慢指针不同的是,此处的快指针是做两次映射,而慢指针只做一次映射。最后让快指针从0开始找环的入口即为重复元素。代码如下:

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        if(nums.empty())
            return 0;
        int slow=nums[0],fast=nums[nums[0]];
        while(slow!=fast){
            slow=nums[slow];
            fast=nums[nums[fast]];
        }
        fast=0;
        while(fast!=slow){
            slow=nums[slow];
            fast=nums[fast];
        }
        return fast;
    }
};

206. Reverse Linked List

描述:

Reverse a singly linked list.

Hint:

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

分析;

经典的一道链表习题,要求逆置链表,方法有迭代和递归两种

1.迭代:迭代的方法就是遍历整个链表,将指向下一结点的指针改变方向,要注意的是因为单链表无法直接找到某个结点的前驱,所以我们需要三个指针,用来记录当前结点,当前节点的后继和其后继的后继,代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(!head||!head->next)
            return head;
        struct ListNode *cur=head,*pnext=cur->next,*prev=NULL;
        cur->next=NULL;
        while(pnext){
            prev=pnext->next;
            pnext->next=cur;
            cur=pnext;
            pnext=prev;
        }
        head=cur;
        return head;
    }
};

2.递归:

递归的思路比较巧妙,它假设链表此时除了第k个结点之前的结点,其余结点已经全部置逆,即如下的情况:

n1 → … → nk-1 → nk → nk+1 ← … ← nm

那么此时我们需要的操作就是让第k个结点也呈逆置的状态:

即:

head->next-next=head;
head->next=NULL;

递归代码如下:

/*Recursion*/
struct ListNode* reverseList(struct ListNode* head) {
    if(!head)
    	return head;
    struct ListNode * p=reverselist(head->next);
    head->next-next=head;
    head->next=NULL;
	return p;
}

143. Reorder List

描述:

Given a singly linked list L: L0→L1→…→Ln-1→Ln, reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→…

You must do this in-place without altering the nodes’ values.

For example, Given {1,2,3,4}, reorder it to {1,4,2,3}.

分析:

要求对链表进行重新排序,排序的规则见题干。

由于要求了in-place,所以时间复杂度只能是O(1),那么我们可以先对后半段链表进行逆序,之后与前半段链表进行合并即可,代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
private:
    struct ListNode * reverseList(struct ListNode *head){
        if(!head||!head->next)
            return head;
        struct ListNode *cur=head,*pnext=cur->next,*prev=NULL;
        cur->next=NULL;
        while(pnext){
            prev=pnext->next;
            pnext->next=cur;
            cur=pnext;
            pnext=prev;
        }
        head=cur;
        return head;
    }
public:
    void reorderList(ListNode* head) {
        if(!head||!head->next)
        return;
        struct ListNode *fast=head,*slow=head,*right=NULL,*pmove=head,*qmove=NULL;
        while(fast&&fast->next){
            slow=slow->next;
            fast=fast->next->next;
        }
        
        right=slow->next;
        slow->next=NULL;/*从中间截断*/
        
        right=reverseList(right);
        qmove=right;
        pmove=head;
        int index=0;
        /*对于奇数个和偶数个要分两种情况*/
        while(pmove&&qmove){
            if(index%2==0){
                struct ListNode * next=pmove->next;
                pmove->next=qmove;
                pmove=next;
            }
            else{
                struct ListNode * next=qmove->next;
                qmove->next=pmove;
                qmove=next;
            }
            index++;
        }
    }
};

61. Rotate List

描述:

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

For example:

Given 1->2->3->4->5->NULL and k = 2, return 4->5->1->2->3->NULL.

分析:

这道题在发现规律之后也很简单,思路与上面的题有些类似,但我们根据链表的一些性质可以降低空间复杂度到常数级。

我们首先遍历整个链表,同时计算长度len并将最后一个结点的next指针指向head,形成一个循环链表,考虑到k值可能大于len,所以我们令k%=len,之后指针再向前移动len-k步,从这里断开整个循环链表即可,代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* rotateRight(struct ListNode* head, int k) {
    if(!head||!head->next||k<=0)
        return head;
    struct ListNode *pmove=head;
    int len=1;
    while(pmove->next){
        pmove=pmove->next;
        len++;
    }
    pmove->next=head;
    k=len-k%len;
    while(k){
        pmove=pmove->next;
        k--;
    }
    head=pmove->next;
    pmove->next=NULL;
    return head;
}

328. Odd Even Linked List

Given a singly linked list, group all odd nodes together followed by the even nodes. Please note here we are talking about the node number and not the value in the nodes.

You should try to do it in place. The program should run in O(1) space complexity and O(nodes) time complexity.

Example:

Given 1->2->3->4->5->NULL,

return 1->3->5->2->4->NULL.

Note:

The relative order inside both the even and odd groups should remain as it was in the input.

The first node is considered odd, the second node even and so on …

这道题要求我们调整链表次序,使其满足位序为奇数的结点相连之后与位序为偶数的结点相连。

我们可以使用三个指针,oddeveneven_head,其中even_head用于记录位序为偶数结点组成的的链表的头部,便于之后与奇数结点组成的链表相连。

我们最开始可以让odd指向第一个结点,even指向第二个,因为even之后又是odd,所以odd之后应该接even的后继结点,并且将odd后移,如此反复,最后将oddeven_head相连接。

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(!head||!head->next)
            return head;
        struct ListNode *odd=head,*even_head=head->next,*even=even_head;
        while(even&&even->next){
            odd->next=even->next;
            odd=odd->next;
            even->next=odd->next;
            even=even->next;
        }
        odd->next=even_head;
        return head;
    }
};

86. Partition List

描述:

Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x.

You should preserve the original relative order of the nodes in each of the two partitions.

For example,

Given 1->4->3->2->5->2and x = 3,

return 1->2->2->4->3->5.

分析:

这道题考察快速排序的思想,但没有要求sort只要求了partition(还要求partition前后元素相对位置不能改变),所以用一个指针遍历原链表,分两种情况:

1. 结点值小于val,放入链表1 2. 其余情况放入链表2

之后连接两个链表即可。

可以在纸上模拟这两个过程,会发现最终是不会改变相对位置的。

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        struct ListNode left(0),right(0);
        struct ListNode *pLeft=&left,*pRight=&right;
        if(!head)
            return head;
        while(head){
            if(head->val<x){
                pLeft->next=head;
                pLeft=pLeft->next;
            }
            else{
                pRight->next=head;
                pRight=pRight->next;
            }
            head=head->next;
        }
        pRight->next=NULL;
        pLeft->next=right.next;
        return left.next;
    }
};

24. Swap Nodes in Pairs

描述:

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

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

Your algorithm should use only constant space. You may not modify the values in the list, only nodes itself can be changed.

分析:

这道题让我们成对交换链表中的结点,只能改变指针指向不能改变节点的值。

那么我们需要维护三个指针,一个用来记录一对结点的后继结点的指针nextstart,另外两个记录一对结点的两个指针ptr1,ptr2,之后交换结点并将交换后的一对结点重新接回链表即可。

需要讨论的地方在于,最终状态下,当链表为奇数个结点时,ptr2->next==NULL,为偶数个结点时,ptr2==NULL,在循环时需要注意。代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(!head||!head->next)
            return head;
        struct ListNode dummy(0);
        dummy.next = head;
        struct ListNode *ptr1=&dummy,*ptr2=head;
        while(ptr2){
            if(!ptr2->next)
                break;
            struct ListNode * nextstart=ptr2->next->next;
            ptr2->next->next=ptr2;
            ptr1->next=ptr2->next;
            ptr2->next=nextstart;
            ptr1=ptr2;
            ptr2=ptr2->next;
        }
        return dummy.next;
    }
};

27. Remove Element

描述:

Given an array and a value, remove all instances of that value in place and return the new length.

Do not allocate extra space for another array, you must do this in place with constant memory.

The order of elements can be changed. It doesn’t matter what you leave beyond the new length.

Example: Given input array nums = [3,2,2,3], val = 3

Your function should return length = 2, with the first two elements of nums being 2.

Hint:

1.Try two pointers.

2.Did you use the property of “the order of elements can be changed”?

3.What happens when the elements to remove are rare?

分析:

因为题目中明确要求in-place完成删除操作,所以我们不能申请额外的存储空间,那么方法就是从头到尾遍历并对数组重新赋值,代码如下:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int new_len=0,i=0,size=nums.size();
        for(i=0;i<nums.size();i++){
            if(nums[i]!=val){
                nums[new_len++]=nums[i];
            }
        }
        return new_len;
    }
};

另一种one-line的解法是使用STL中的remove函数

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        return distance(nums.begin(),remove(nums.begin(),nums.end(),val));
    }
};

203. Remove Linked List Elements

描述:

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

Example

Given: 1 –> 2 –> 6 –> 3 –> 4 –> 5 –> 6, val = 6

Return: 1 –> 2 –> 3 –> 4 –> 5

分析:

此题要求在链表中删除指定值,那么链表与数组的区别在于链表不能随机访问,所以我们必须要遍历整个链表并且用于遍历链表的指针记录的应该是指定值的前驱元素。代码如下:

考虑到第一个结点有可能被删除,所以我们使用了dummy node这个编程技巧。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        struct ListNode dummy(0);
        dummy.next=head;
        struct ListNode *pmove=&dummy;
        while(pmove->next){
            if(pmove->next->val==val)
                pmove->next=pmove->next->next;
            else
                pmove=pmove->next;
        }
        return dummy.next;
    }
};

19. Remove Nth Node From End of List

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

For 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.

Try to do this in one pass.

这道题要求我们删除链表倒数第n个元素,快慢指针法即可,注意循环条件。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        struct ListNode dummy(0);
        dummy.next=head;
        struct ListNode * fast=&dummy,*slow=&dummy,*tmp=NULL;
        for(int i=n;i>0;i--)
            fast=fast->next;
        while(fast->next){
            fast=fast->next;
            slow=slow->next;
        }
        tmp=slow->next;
        slow->next=tmp->next;
        delete tmp;
        return dummy.next;
    }
};

26.Remove Duplicates from Sorted Array

描述:

Given a sorted array, remove the duplicates in place such that each element appear only once and return the new length.

Do not allocate extra space for another array, you must do this in place with constant memory.

For example, Given input array nums = [1,1,2],

Your function should return length = 2, with the first two elements of nums being 1 and 2 respectively. It doesn’t matter what you leave beyond the new length.

分析:

遍历数组即可

代码:

使用数组

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if(nums.empty())
            return 0;
        int i=0,new_len=0;
        for(i=1;i<nums.size();i++){
            if(nums[i]!=nums[new_len])
                nums[++new_len]=nums[i];
        }
        return new_len+1;
    }
};

使用STL

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        return distance(nums.begin(),unique(nums.begin(),nums.end()));
    }
};

看的出来使用STL可以大大地简化我们的代码。

80.Remove Duplicates from Sorted Array II

描述:

Follow up for “Remove Duplicates”: What if duplicates are allowed at most twice?

For example, Given sorted array nums = [1,1,1,2,2,3],

Your function should return length = 5, with the first five elements of nums being 1, 1, 2, 2 and 3. It doesn’t matter what you leave beyond the new length.

分析:这两道题的区别在于第二道题中的去除重复时不能只留一个,而是要留两个。那么我们在循环变量的初始值和逻辑判断的条件上做一些变化。

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if(nums.size()<3)
            return nums.size();
        int new_len=1,size=nums.size(),i=0;
        for(i=2;i<size;i++){
            if(nums[i]!=nums[new_len-1])
                nums[++new_len]=nums[i];
        }
        return new_len+1;
    }
};

83. Remove Duplicates from Sorted List

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

For example,

Given 1->1->2, return 1->2.

Given 1->1->2->3->3, return 1->2->3.

双指针法,让后指针指向的元素的值与前指针比较即可,注意循环终止条件

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(!head||!head->next)
            return head;
        struct ListNode * pmove=head,*qmove=NULL,*tmp=NULL;
        while(pmove){
            qmove=pmove;
            while(qmove->next){
                if(pmove->val==qmove->next->val){
                        tmp=qmove->next;
                        qmove->next=tmp->next;
                        free(tmp);
                }
                else
                    qmove=qmove->next;
            }
            pmove=pmove->next;
        }
        return head;
    }
};

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.

For example,

Given 1->2->3->3->4->4->5, return 1->2->5.

Given 1->1->1->2->3, return 2->3.

分析:

这道题的要求比前面要高一些,不仅要删除重复元素,而且要全部删除一个不留。这就需要我们维护一个pre指针和一个布尔变量,前者指向当前元素的前驱元素,后者标记是否出现了重复,另外,还要对三种情况进行判断:

1.发现当前元素和后继元素相等: 此时我们只需要按删除链表中某一元素的方法,删去后一元素即可,同时令标记值为true

2.标记值为true 此时就需要用我们的pre指针,借助其完成删除重复元素集合的首元素,删除之后,令cur指向pre的后继元素,同时令标记值为false

3.其余情况 pre,cur指针后移即可

此外,这道题应用了一个编程技巧,即哑节点(dummy node),作用相当于链表头结点,因为这道题有可能在表头进行删除操作,所以为了统一操作,我们设置了一个哑节点。

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *deleteDuplicates(ListNode *head) {
        if(!head||!head->next)
            return head;
        struct ListNode dummy(0);
        struct ListNode * pre=&dummy,*cur=head,*tmp=NULL;
        bool duplicate=false;
        dummy.next=head;
        while(cur){
            if(cur->next&&cur->val==cur->next->val){
                tmp=cur->next;
                cur->next=tmp->next;
                free(tmp);
                duplicate=true;
            }
            else if(duplicate){
                pre->next=cur->next;
                free(cur);
                cur=pre->next;
                duplicate=false;
            }
            else{
                pre=cur;
                cur=cur->next;
            }
        }
        head=dummy.next;
        return head;
    }
};

27.Remove Element

Given an array and a value, remove all instances of that value in place and return the new length.

Do not allocate extra space for another array, you must do this in place with constant memory.

The order of elements can be changed. It doesn’t matter what you leave beyond the new length.

Example: Given input array nums = [3,2,2,3], val = 3

Your function should return length = 2, with the first two elements of nums being 2.

删除数组中和给定值相等的元素,返回删除后的数组和数组长度。

这道题用遍历即可,见代码:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int new_len=0,i=0,size=nums.size();
        for(i=0;i<nums.size();i++){
            if(nums[i]!=val){
                nums[new_len++]=nums[i];
            }
        }
        return new_len;
    }
};