算法原理与实践(表、栈和队列)

  链表

  大纲
  1. 链表介绍
  2. 基本操作
  3. Dummy Node
  4. 追赶指针技巧
  5. 例题分析

  链表介绍
  单向链表(singly linked list),每个节点有一个 next 指针指向后一个节点,还有一个成员变量用以储存数值;
  双向链表(Doubly Linked List),还有一个 prev 指针指向前一个节点。
  Search: O(n), Del, Add: O(1)

  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.

#include <list>
#include <iostream>
#include <iterator>

using namespace std;

template<typename T>
void removeDuplicate(list<T>& l)
{
    list<T>::iterator chaser = l.begin();
    list<T>::iterator runner = ++l.begin();

    while (runner != l.end())
    {
        if (*chaser == *runner)
        {
            runner = l.erase(runner);
        }
        else
        {
            ++chaser;
            ++runner;
        }
    }
}

template<typename T>
void dump(list<T> l)
{
    for (auto &e : l)
    {
        cout << e << " ";
    }
    cout << endl;
}

int main()
{
    list<int> l = { 1, 1, 2 };
    removeDuplicate(l);
    dump(l);

    list<int> l2 = { 1, 1, 2, 3, 3 };
    removeDuplicate(l2);
    dump(l2);
}
View Code

 

  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.

#include <iostream>
#include <list>
#include <unordered_map>

using namespace std;

template<typename T>
void removeDuplicate2(list<T>& l)
{
    unordered_map<T, unsigned> m;

    for (auto &e : l)
        m[e]++;

    list<T>::iterator it = l.begin();

    while (it != l.end())
    {
        if (m[*it] > 1)
            it = l.erase(it);
        else
            it++;
    }
}

template<typename T>
void dump(const list<T>& l)
{
    for (auto &e : l)
    {
        cout << e << " ";
    }
    cout << endl;
}

int main()
{
    list<int> l = { 1, 2, 3, 3, 4, 4, 5 };

    removeDuplicate2(l);

    dump(l);


    list<int> l2 = { 1, 1, 1, 2, 3 };

    removeDuplicate2(l2);

    dump(l2);
}
View Code

 

  Partition List
  Given a linked list and a value x, write a function to reorder this list such that all nodes less than x come before the nodes greater than or equal to x.
  解题分析:将list分成两部分,但两部分的head节点连是不是null都不确定。但总是可以创建两个dummy节点然后在此基础上append,这样就不用处理边界条件了。

#include <iostream>
#include <list>

using namespace std;

template<typename T> 
list<T>* partitionList(const list<T>& l, const T& val)
{
    list<T> rightList;
    list<T>* leftList = new list<T>;

    for (auto &e : l)
    {
        if (e > val)
            rightList.push_back(e);
        else
            leftList->push_back(e);
    }

    leftList->insert(leftList->end(), rightList.cbegin(), rightList.cend());

    return leftList;
}

template<typename T>
void dump(const list<T>& l)
{
    for (auto &e : l)
        cout << e;

    cout << endl;
}

int main()
{
    list<int> l = { 6, 5, 4, 3, 2, 1 };

    list<int>* pL = partitionList(l, 3);

    dump(*pL);

    return 0;
}
View Code

  

  追赶指针技巧

  对于寻找list某个特定位置的问题,不妨用两个变量 chaser 与 runner,以不同的速度遍历 list,找到目标位置: ListNode *chaser = head, *runner = head。并且可以用一个简单的小 test case 来验证(例如长度为4和5的list)

  Middle Point

  Find the middle point of linked list.
  解题分析: 寻找特定位置,runner以两倍速前进,chaser 一倍速,当runner到达tail时,chaser即为所求解。

#include <list>
#include <iostream>

using namespace std;

template<typename T>
typename list<T>::iterator findMiddlePoint(list<T> &l)
{
    list<T>::iterator itRunner = l.begin();
    list<T>::iterator itChaser = l.begin();

    while (itRunner != l.end())
    {
        itRunner++;

        if (itRunner != l.end())
        {
            itRunner++;
            itChaser++;
        }
    }

    return itChaser;
}

int main()
{
    list<int> l = { 1, 2, 3, 4, 5 };

    cout << *findMiddlePoint(l) << endl;

    return 0;
}
View Code

 

  kth to Last element
  Find the kth to last element of a singly linked list
  解题分析:之前类似。只是runner与chaser以相同倍速前进,但runner提前k步出发

#include <list>
#include <iostream>

using namespace std;

template<typename T>
typename list<T>::iterator findKthLast(list<T> &l,const size_t k)
{
    list<T>::iterator itRunner = l.begin();
    list<T>::iterator itChaser = l.begin();

    for (size_t i = 0; i < k; i++)
        itRunner++;

    while (itRunner != l.end())
    {
        itRunner++;
        itChaser++;
    }

    return itChaser;
}

int main()
{
    list<int> l = { 1, 2, 3, 4, 5 };

    cout << *findKthLast(l, 1) << endl;

    return 0;
}
View Code

  

  如何判断一个单链表中有环?
  Given a linked list, determine if it has a cycle in it.

算法原理与实践(表、栈和队列)_第1张图片

/*返回nullptr说明没有环,否则返回指向环节点的指针*/
struct Node* findLoop(struct Node* head) {
    struct Node* runner = head;
    struct Node* chaser = head;

    while (runner != nullptr) {
        runner = runner->next;

        if (runner->next != nullptr)
            runner = runner->next;
        else
            return nullptr;

        if (runner->next == chaser)
            return chaser;
        else
            chaser = chaser->next;
    }

    return nullptr;
}
View Code

 

  Circular List Node
  Given a circular linked list, return the node at the beginning of the loop
  解题分析:寻找某个特定位置,用 runner technique。Runner 以两倍速度遍历,假定有 loop,那么 runner 与 chaser 一定能在某点相遇。相遇后,再让 chaser 从 head 出发再次追赶 runner,第二次相遇的节点为 loop 开始的位置。(我对 chaser 第二次再从 head 出发追赶这里不赞同,因为第一次就已经找到了环节点,而且第二次再出发时,找到的还是环节点)。

  代码是只相遇一次,并且相遇的节点就是 loop node。

#include <iostream>

using namespace std;

const unsigned SIZE = 5;
typedef int DateType;

struct Node {
    DateType val;
    struct Node *next;
};

void initList(struct Node **head) {
    struct Node* curr = (*head);

    for (int i = 0; i < SIZE; i++) {
        curr->next = new struct Node();
        curr = curr->next;
        curr->val = i;
        curr->next = nullptr;
    }
}

void print(struct Node* head) {
    unsigned loop = 0;
    struct Node* curr = head;
    while (curr != nullptr) {
        cout << curr->val << " ";
        curr = curr->next;
        if (++loop > SIZE * 2)
            break;
    }
}

/*返回nullptr说明没有环,否则返回指向环节点的指针*/
struct Node* findLoop(struct Node* head) {
    struct Node* runner = head;
    struct Node* chaser = head;

    while (runner != nullptr) {
        runner = runner->next;

        if (runner->next != nullptr)
            runner = runner->next;
        else
            return nullptr;

        if (runner->next == chaser)
            return chaser;
        else
            chaser = chaser->next;
    }

    return nullptr;
}

void relesseSingleLine(struct Node **head) {
    struct Node* curr = *head;
    struct Node* runner = *head;

    while (curr != nullptr) {
        runner = runner->next;
        delete curr;
        curr = runner;
    }
}

void releaseLoopLine(struct Node **head) {
    while ( (*head)->next != *head) {
        struct Node *runner = (*head)->next->next;
        delete (*head)->next;
        (*head)->next = runner;
    }
    (*head)->next = nullptr;
}

void release(struct Node **head) {
    struct Node *loop = findLoop(*head);
    if (loop != nullptr) {
        //has a loop
        releaseLoopLine(&loop);
    }

    relesseSingleLine(head);
}

/*返回第index个位置上的节点指针*/
struct Node* getPtr(struct Node* head, unsigned index) {
    struct Node* curr = head;
    for (unsigned i = 0; i < index; i++)
        curr = curr->next;

    return curr;
}

int main() {
    struct Node* head = new struct Node();
    initList(&head);

    getPtr(head, SIZE)->next = getPtr(head, 3);

    cout << findLoop(head)->val << endl;

    print(head);
    release(&head);

    return 0;
}
View Code

 

  判断两个单链表是否有交点?
  先判断两个链表是否有环,如果一个有环一个没环,肯定不相交;如果两个都没有环,判断两个列表的尾部是否相等;如果两个都有环,判断一个链表上的 Z 点是否在另一个链表上。
  如何找到第一个相交的节点?
  求出两个链表的长度 L1, L2(如果有环,则将 Y 点当做尾节点来算),假设 L1 < L2,用两个指针分别从两个链表的头部开始走,长度为 L2 的链表先走 L2 - L1,然后两个一起走,直到二者相遇。

 

  Rotate List

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

  e.g. 

  example k = 4 and list = 10->20->30->40->50->60.
  change to => 50->60->10->20->30->40

#include <list>
#include <iostream>

using namespace std;

template<typename T>
list<T> rotateList(const list<T> &l, const size_t k)
{
    list<T>::const_iterator itK = l.cbegin();

    for (size_t i = 0; i < k; i++)
        itK++;

    list<T> l2;

    for (list<T>::const_iterator itRight = itK; itRight != l.cend(); itRight++)
        l2.push_back(*itRight);

    for (list<T>::const_iterator itLeft = l.cbegin(); itLeft != itK; itLeft++)
        l2.push_back(*itLeft);

    return l2;
}

int main()
{
    list<int> l1 = { 1, 2, 3, 4, 5 ,6 };

    list<int> l2 = rotateList(l1, 4);

    for (auto &e : l2)
        cout << e << " ";

    return 0;
}
View Code

 

  模式识别
  1.在遍历 Linked list 时,注意每次循环内只处理一个或一对节点。核心的节点只处理当前这一个,否则很容易出现重复处理的问题。

  Reverse Linked List
  Reverse the linked list and return the new head.
  循环遍历linked-list, 每次只处理当前指针的next 变量。
  非递归 vs 递归

#include <list>
#include <iostream>
#include <algorithm>

using namespace std;

template<typename T>
list<T> reverseList(list<T> &l)
{
    list<T> l2;

    for (auto &e : l)
        l2.push_front(e);
    
    return l2;
}

int main()
{
    list<int> l = { 1, 2, 3, 4, 5, 6, 7, 8 };

    list<int> l2 = reverseList(l);

    for (auto &e : l2)
        cout << e << " ";

    return 0;
}
View Code   

  模式识别
  2. Swap Node 问题
  交换两个节点,不存在删除的话,两个节点的prev节点的next指针,以及这两个节点的next指针,会受到影响。总是可以
    a. 先交换两个prev节点的next指针的值;
    b. 再交换这两个节点的next指针的值。
  无论这两个节点的相对位置和绝对位置如何,以上的处理方式总是成立。

  Swap Adjacent(邻近的) Nodes
  Given a linked list, swap every two adjacent nodes and return its head.

#include <list>
#include <iostream>
#include <algorithm>

using namespace std;

template<typename T>
void swapAdjancentNode(list<T> &l)
{
    int tick = 2;

    list<T>::iterator itRunner = l.begin();
    list<T>::iterator itChaser = l.begin();

    itRunner++;

    while (itRunner != l.end())
    {
        if (tick == 2)
        {
            swap(*itRunner, *itChaser);
            tick = 0;
        }

        itRunner++;
        itChaser++;
        tick++;
    }
}

int main()
{
    list<int> l = { 1, 2, 3, 4, 5, 6, 7};

    swapAdjancentNode(l);

    for (auto &e : l)
        cout << e << " ";

    return 0;
}
View Code

  模式识别
  3. 同时处理两个 linked list 的问题,循环的条件一般可以用 while( l1 && l2 ) ,再处理剩下非 NULL 的 list。这样的话,边界情况特殊处理,常规情况常规处理。

 

  Add List Sum
  Given two linked lists, each element of the lists is a integer. Write a function to return a new list, which is the “sum” of the given two lists.
    Part a. Given input (7->1->6) + (5->9->2), output 2->1->9.
    Part b. Given input (6->1->7) + (2->9->5), output 9->1->2.
  解题分析:对于 a,靠前节点的解不依赖靠后节点,因此顺序遍历求解即可。对于 b,靠前节点的解依赖于靠后节点(进位),因此必须用递归或栈处理。并且,subproblem 返回的结果,可以是一个自定义的结构(进位 + sub-list)。当然,也可以 reverse List 之后再用 a 的解法求解。

#include <list>
#include <iostream>

using namespace std;

list<int> addListSum(const list<int> l1, const list<int> l2)
{
    list<int> l;

    int carry = 0;

    list<int>::const_iterator it1 = l1.cbegin();
    list<int>::const_iterator it2 = l2.cbegin();

    while (it1 != l1.cend() || it2 != l2.cend())
    {
        int sum = carry + (it1 != l1.cend() ? *it1 : 0) + (it2 != l2.cend() ? *it2 : 0);

        carry = sum / 10;

        sum = sum % 10;

        l.push_back(sum);

        if (it1 != l1.cend())
            it1++;

        if (it2 != l2.cend())
            it2++;
    }

    if (carry)
        l.push_back(carry);

    return l;
}

int main()
{
    list<int> l1 = { 7, 1, 6 };
    list<int> l2 = { 5, 9, 2, 1 };


    list<int> l = addListSum(l1, l2);

    for (auto e : l)
        cout << e << " ";

    return 0;
}
View Code

 

  Merge Two Sorted List
  Merge two sorted linked lists and return it as a new list.

 

  

 

你可能感兴趣的:(算法原理与实践(表、栈和队列))